AS3 Sound Synthesis III – Visualization and Envelopes

Jul 21 2010 Published by under ActionScript, Flash

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:


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.


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.

17 responses so far. Comments will be closed after post is one year old.