In Part I and Part II of this series, we learned how to utilize the Sound object to synthesize sound, and how to create sounds of various frequencies. This post will just be a quick detour onto a couple of tricks you can implement.
The first one is visualizing the wave you are playing. In the SAMPLE_DATA event handler, you are already generating 2048 samples to create a wave form. While you’re creating these, it’s a piece of cake to go ahead and draw some lines based on their values. Look here:
[as3]import flash.media.Sound;
import flash.events.SampleDataEvent;
import flash.events.MouseEvent;
import flash.utils.Timer;
import flash.events.TimerEvent;
var position:int = 0;
var n:Number = 0;
var sound:Sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
sound.play();
function onSampleData(event:SampleDataEvent):void
{
graphics.clear();
graphics.lineStyle(0, 0x999999);
graphics.moveTo(0, stage.stageHeight / 2);
for(var i:int = 0; i < 2048; i++)
{
var phase:Number = position / 44100 * Math.PI * 2;
position ++;
var sample:Number = Math.sin(phase * 440 * Math.pow(2, n / 12));
event.data.writeFloat(sample); // left
event.data.writeFloat(sample); // right
graphics.lineTo(i / 2048 * stage.stageWidth, stage.stageHeight / 2 - sample * stage.stageHeight / 8);
}
}
var timer:Timer = new Timer(500);
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start();
function onTimer(event:TimerEvent):void
{
n = Math.floor(Math.random() * 20 - 5);
timer.delay = 125 * (1 + Math.floor(Math.random() * 7));
}[/as3]
All I've done here is clear the graphics, set a line style, and move to the center left of the screen. Then with each sample, move across the screen a bit and up or down depending on the value of the sample. This gives you something looking like this:
You can see the wave change its frequency with each new note.
The next trick is something I learned from Andre Michelle a very short while ago. You notice that the sine wave as is feels very flat and bland. Quite obviously computer generated. That’s because the amplitude, or height, of the wave is always constant: -1.0 to 1.0. That’s just not natural for real world things that make sounds. If you strike a piano keyboard, you’ll notice that it goes very loud at first, then settles down to a steady value as you hold the key, then when you release it, it fades out. These changes in volume are known as the envelope of a sound. It generally has an four phases, known as ADSR. From Wikipedia:
Attack time is the time taken for initial run-up of level from nil to peak.
Decay time is the time taken for the subsequent run down from the attack level to the designated sustain level.
Sustain level is the amplitude of the sound during the main sequence of its duration.
Release time is the time taken for the sound to decay from the sustain level to zero after the key is released.
Many of Andre Michelle’s sound experiments and toys have a very nice, pleasing bell sound to them, so I knew he was using some kind of envelope, but I know that envelopes can be pretty complex to code. So I asked him about it. He gave me a one or two sentence answer which just made me say, “OH! Of course!” Basically, all you need to do is start the sound at full amplitude and reduce it over time. So simple. Essentially, you are throwing away the attack, decay, and sustain and just programming in a release.
In this version of the project, we just set up an amp variable and set it to 1.0. On each SAMPLE_DATA event, reduce the amplitude by a fraction. And multiply the sample value by that amplitude. When a new note begins, reset amp to 1.0.
[as3]import flash.media.Sound;
import flash.events.SampleDataEvent;
import flash.events.MouseEvent;
import flash.utils.Timer;
import flash.events.TimerEvent;
var position:int = 0;
var n:Number = 0;
var amp:Number = 1.0;
var sound:Sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
sound.play();
function onSampleData(event:SampleDataEvent):void
{
graphics.clear();
graphics.lineStyle(0, 0x999999);
graphics.moveTo(0, stage.stageHeight / 2);
for(var i:int = 0; i < 2048; i++)
{
var phase:Number = position / 44100 * Math.PI * 2;
position ++;
var sample:Number = Math.sin(phase * 440 * Math.pow(2, n / 12)) * amp;
event.data.writeFloat(sample); // left
event.data.writeFloat(sample); // right
graphics.lineTo(i / 2048 * stage.stageWidth, stage.stageHeight / 2 - sample * stage.stageHeight / 8);
}
amp *= 0.7;
}
var timer:Timer = new Timer(500);
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start();
function onTimer(event:TimerEvent):void
{
amp = 1.0;
n = Math.floor(Math.random() * 20 - 5);
timer.delay = 125 * (1 + Math.floor(Math.random() * 7));
}[/as3]
Here, I'm multiplying amp by 0.7 on each event. This gives a pretty pleasing bell sound. Change that value around to get different characters. Or you could even do some kind of funky vibrato thing like this:
[as3]amp = 0.5 + Math.cos(position * 0.001) * 0.5;[/as3]
OK, that's all for this time.
Excellent write-up! Very clean and concise! I think the Sound object in Flash is still very neglected and needs much more support! For example a loadBytes() method to load a ByteArray into it like the Loader can do. So far if we want to load sound data from a file we have to use Sound.load() or something more involved like this: http://www.flexiblefactory.co.uk/flexible/?p=46#more-46
thanks wow!
Great posts Keith. I’ve been working with dynamic sound for the first time recently, and this would have helped me no end! I made an Audio Air app loosely based on Conway’s Game of Life – http://www.lawriecape.co.uk/theblog/?p=735
Hey Keith, take a look at ALF (http://music.ece.drexel.edu/ALF)
ALF looks cool. My point here is to explain the process at its lowest level for those who want to do it themselves.
Hi Keith,
I’ve also been working w/ sound programming in AS3 recently. Your components have come in very handy! Thanks 🙂
http://labs.makemachine.net/category/audio/
so cool ! thanks
That’s not vibrato, that’s tremolo.
Yup, you’re right. Tremolo. http://en.wikipedia.org/wiki/Vibrato#Vibrato_and_tremolo
I love these posts! I just used this to make a “sound game” so my kid can play with it using the mouse. I had one problem though, I cant get this import to work
import flashx.textLayout.elements.InlineGraphicElement;
I’m using Flash CS4. Thanks
Paul, ignore that. I fixed the code. It was an inadvertent auto-import. I probably started typing “int” and that came up and I hit enter. Didn’t notice it when I copied and pasted into the blog.
How do I Load an external sound file and apply the wave to it . I tryed this
var n:Number = 0;
var amp:Number = 1.0;
var mySound:Sound = new Sound();
var myChannel:SoundChannel = new SoundChannel();
var lastPosition:Number = 0;
mySound.load(new URLRequest(“song2.mp3”));
mySound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
myChannel = mySound.play();
You article give me many knowledge about sound! thanks!!