Eu passei as duas ultimas semanas trabalhando num projeto envolvendo som. Infelizmente não posso mostra-lo ainda, mas meu objetivo era tocar um monte de notas musicais em um intervalo especifico. A primeira coisa que eu tentei fazer foi carregar um monte de samples e toca-los quando precisasse, mas o desempenho ficou muito baixo com isso. Então minha solução foi procurar no Google e no lab do Andre Michelle para entender como trabalhar com sons dinamicos.
Bom, ainda estou aprendendo, mas eu acho que muitas pessoas gostariam de trabalhar com isso e não tem a menor ideia por onde começar, por isso espero que esse post seja util para essas pessoas.
A primeira coisa que precisamos entender é que o som é uma onda, e essa onde pode ter diferentes formatos dependendo do som que você está ouvindo.

Agora imagine que essa onda é formada por pontos conectados. Cada ponto representa uma posição na onda em um determinado intante.
*mexa seu mouse horizontalmente para mudar o formato da onde
Certo, agora vamos chamar esses pontos de samples, e dizer que eles podem mudar seu valor de -1 a 1.
Basicamente é assim que o som funciona no flash. Mas existem algumas coisas que devemos saber sobre a qualidade. Um som com qualidade boa depende do numero de samples tocados durante um segundo, e a precisão de cada ponto. O numero de samples tocados num segundo é chamado de sample rate, o flash trabalha com um sample rate de 44100 samples por segundo. Isso significa que cada segundo contém 44100 pontos. Cada sample, ou ponto, tem a precisao de 64 bits, sendo 32 bits para o canal esquerdo e 32 bits pro canal direito.
Agora vamos trazer isso para o flash. Lembre-se que estamos usando o player 10.
Primeiro nos criamos uma instancia de Sound, uma instancia de SoundChannel, adicionamos um listener ao som e tocamos ele no sound channel.
package { import flash.display.Sprite; import flash.events.SampleDataEvent; import flash.media.Sound; import flash.media.SoundChannel; public class Main extends Sprite { private var sound:Sound; private var channel:SoundChannel; public function Main():void{ sound = new Sound(); sound.addEventListener(SampleDataEvent.SAMPLE_DATA, writeData); channel = sound.play(); } private function writeData(e:SampleDataEvent):void{ } } }
O evento que sera ouvido é disparado antes do som ser executado e pede os dados de audio que compõe aquele fragmento de som. Você pode prover entre 2048 e 8192 samples. Quanto maior esse numero é, melhor é o desempenho, porém a latencia é maior. Se você provê menos de 2048 samples (por chamada dolistener de sampleData), a aplicação termina depois de tocar os samples restantes.
Agora vamor preencher os dados de audio com samples randomicos. Cada sample com um valor entre -1 e 1.
private function writeData(e:SampleDataEvent):void{ for(var i:Number = 0; i<8192; i++){ //Left Channel e.data.writeFloat(Math.random()*2-1); //RightChannel e.data.writeFloat(Math.random()*2-1); } }
Não ouvimos nada além de um ruido. Para criar algum som decente precisamos criar um formato de onda que faça algum sentido. Existem alguns formatos basicos que podemos utilizar para criar algum som.

Vamos utilizar uma senoide para criar uma nota musical A4. Se a frequência dessa nota é 4400.00 Hz, isso significa que nossa onda completa 440 ciclos por segundo.

Essa é uma onda de 1Hz
E essa é uma onda de 2Hz
*Humans can hear between 20 to 20,000 hertz
Se nos sabemos que cada segundo contém 44100 samples e uma nota A4 completa 440 ciclos por segundo, cada sample será um peço de 440/44100 de um ciclo. Um ciclo são 360 graus, ou Math.PI*2 radianos pois o flash trabalha com radianos quando calcuca senos. Convertendo isso para o flash temos o seguinte:
private var phase:Number = 0; private var step:Number = Math.PI*2 * 440/44100; private function writeData(e:SampleDataEvent):void{ for(var i:Number = 0; i<8192; i++){ //Left Channel e.data.writeFloat(Math.sin(phase)); //RightChannel e.data.writeFloat(Math.sin(phase)); phase += step; } }
Agora conseguimos ouvir alguma coisa.
Veja o resultado aqui
Se você quiser tocar notas diferentes você pode tentar essa formula onde a nota mais baixa é um A2.
phaseStep = 110.0*Math.pow( 2, octave + semiTone / 12 )/44100;
Vamos tentar criar um acorde maior A4.
Precisamos de 3 notas para isso, um A4, um C#4 e um E4.
private var A4:Number = 110.0*Math.pow( 2, 4 + 0 / 12 )/44100; private var Csharp4:Number = 110.0*Math.pow( 2, 4 + 4 / 12 )/44100; private var E4:Number = 110.0*Math.pow( 2, 4 + 7 / 12 )/44100;
e precisamos de uma fase para cada nota
private var phaseA4:Number = 0; private var phaseCsharp4:Number = 0; private var phaseE4:Number = 0;
Depois adicionar os valores para compor o acorde
private function writeData(e:SampleDataEvent):void{ for(var i:Number = 0; i<8192; i++){ var amplitudeA4:Number = Math.sin(phaseA4); var amplitudeCsharp4:Number = Math.sin(phaseCsharp4); var amplitudeE4:Number = Math.sin(phaseE4); phaseA4 += A4; phaseCsharp4 += Csharp4; phaseE4 += E4; e.data.writeFloat(amplitudeA4+amplitudeCsharp4+amplitudeE4); e.data.writeFloat(amplitudeA4+amplitudeCsharp4+amplitudeE4); } }
Quando precisamos unir dois ou mais sons, precisamos apenas somar cada valor para ter a amplitude final do sample, o problema é que quando escrevemos um valor maior que 1 ou menor que -1, nos distorcemos o som.
Veja isso
Nesse caso podemos resolver o problema dividindo o valor final por 3, uma vez que temos 3 notas
private function writeData(e:SampleDataEvent):void{ for(var i:Number = 0; i<8192; i++){ var amplitudeA4:Number = Math.sin(phaseA4); var amplitudeCsharp4:Number = Math.sin(phaseCsharp4); var amplitudeE4:Number = Math.sin(phaseE4); phaseA4 += A4; phaseCsharp4 += Csharp4; phaseE4 += E4; e.data.writeFloat((amplitudeA4+amplitudeCsharp4+amplitudeE4)/3); e.data.writeFloat((amplitudeA4+amplitudeCsharp4+amplitudeE4)/3); } }
Agora ouvimos um som muito mais agradavel.
Baixe os arquivos aqui
Nós podemos fazer muitas coisas com isso, mas podemos fazer muito mais usando samples pré-gravados e manipulando os samples para criar efeitos.
Wow man, awesome post. congrats!!
[]s!
great post, now i think i understand. thanks very much!
But please could you explain one more thing:
phaseStep = 110.0*Math.pow( 2, octave + semiTone / 12 )/44100;
would be nice, thanks.
This is a formula to get the phaseStep of different notes. Every musical note is part of an Octave and every octave is compound by 12 semi tunes (A, A#, B, C, C#, D, D#, E, F, F#, G and G#).
Check this reference
Frequencies of Musical Notes
Each row represent a semi tune frequency. When you set the base frenquency as 110.00 Hz, it means that the fisrt octave is 2 and the first semiTune will be an A, so if you passe octave = 0 and semiTune = 0, you will get an A2. If you pass octave = 2 and semiTune = 4, you will get a C4 because it is 2 octaves above and 4 semi tunes above A2.
If you change the base frequency, every note will follow it. For instance, if you set the base frequency as 261.63 the first octave will be 4 and the first semi tune will be C, you will get a middle C4:
phaseStep = 261.63*Math.pow( 2, octave + semiTone / 12 )/44100;
phaseStep = 261.63*Math.pow( 2, 0 + 0 / 12 )/44100; //C4
phaseStep = 261.63*Math.pow( 2, 0 + 1 / 12 )/44100; //C#4
phaseStep = 261.63*Math.pow( 2, 0 + 2 / 12 )/44100; //D4
phaseStep = 261.63*Math.pow( 2, 1 + 0 / 12 )/44100; //C5
phaseStep = 261.63*Math.pow( 2, 1 + 0 / 12 )/44100; //C6
I don’t know the logic behind this formula, but I know that it works ;)
Hope this is more clear to you.
Thanks for the comment
Ok, very interesting. thank you!
good one…
Great tutorial. Exactly what I was looking for. Thanks!
Very nice tutorial… :)
script work nice on my machine, thanx a lot
greetings form germany
this might help on using power^n to shift notes – http://www.phy.mtu.edu/~suits/NoteFreqCalcs.html
a = 2^(n/12) which means there are “12 half steps” between octaves. (example, you will count 12 white and black keys between middle C and high C, etc)