Dynamic Sound – Part 1

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.

sound_wave

Agora imagine que essa onda é formada por pontos conectados. Cada ponto representa uma posição na onda em um determinado intante.

This movie requires Flash Player 9

*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.

This movie requires Flash Player 9

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.

wave_shape

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.

wave_cycle

Essa é uma onda de 1Hz

This movie requires Flash Player 9

E essa é uma onda de 2Hz

This movie requires Flash Player 9

*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.

Veja

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.

This entry was posted in as3 and tagged , , , . Bookmark the permalink.

16 Responses to Dynamic Sound – Part 1

  1. FabioTNT says:

    Wow man, awesome post. congrats!!
    []s!

  2. Obi says:

    great post, now i think i understand. thanks very much!

  3. Obi says:

    But please could you explain one more thing:
    phaseStep = 110.0*Math.pow( 2, octave + semiTone / 12 )/44100;
    would be nice, thanks.

  4. admin says:

    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

  5. Obi says:

    Ok, very interesting. thank you!

  6. bechar says:

    good one…

  7. jonah says:

    Great tutorial. Exactly what I was looking for. Thanks!

  8. lostchild says:

    Very nice tutorial… :)

  9. psybermoon says:

    script work nice on my machine, thanx a lot
    greetings form germany

  10. ina says:

    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)

  11. Ciekawa stronka i dobre publikacje. Gratuluje ;]

  12. Mikael says:

    Something got wrong here. It should be
    A4 = 110*Math.power(2,2+0/12) * 2pi/44100 to get a A4=440hz tone.

  13. Pingback: NightFlyer Games » Turn down that noise!

  14. I love looking through and I believe this website got some genuinely utilitarian stuff on it! .

  15. 3rlend says:

    Thanks! Awesome! Makes it much easier to understand!

  16. Pingback: hostgator black friday

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">