After seeing what some speakers are doing at the FITC in San Francisco, I decided it was time to use flash to play with sound.

After some research around internet, ( Andre Anaya, bit 101 and Andre Michelle ), I finally created my own sound class, now lets see how it works...

First of all, lets understand how does sound works taking one of the simplest sound forms, a note, represented by a sine wave.

A note frequency, with the help of wikipedia, can be described physically as:

Frec = 440 × 2octave/12 Hz

here is a smooth analog version:

But, a real note amplitude, changes with time, has what it's call ADSR envelope, ( attack, decay, sustain and release ), so it will look like this.

Again from wikipedia:

Attack time: Time taken for initial run-up of level from null to peak.
Decay time: Time taken for the subsequent run down from the attack level to the designated sustain level.
Sustain level: Level during the main sequence of the sound's duration.
Release time: Time taken for the level to decay from the sustain level to zero.

Flash, can then send this values to the sound card, and play our note.
For doing this with actionScript 3, we need to create a sound object

var sound:Sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
sound.play();

and then, populate our sound object with samples.

The SampleDataEvent is call every time when the sound buffer is empty, and we need to fill it again with some more samples that represent the sound we want to play.

This is the important part of generating sound with as3.
We need to add a number of samples to the sound object, generally between 2048 and 8192.

We do this using the ByteArray.writeFloat method.
Generally you want to write values from -1.0 to 1.0 in there.
Each float value you write in there is what we called a sample.
As sound is stereo, we need to do this twice, one for each channel.

event.data.writeFloat(sampleValue); // left
event.data.writeFloat(sampleValue); // right

When the sound object run out of samples, the sampleDataEvent will be called again, and we need to populate it with more samples.
For doing this, I'll calculate the frequency between samples for my tone or phase, and then generate and store the values on the sound buffer.

Lets see an example of how to generate different octaves of the our note:

Click here to start the example ( requires flash ).

import flash.media.Sound;
import flash.events.SampleDataEvent;
import flash.utils.Timer;
import flash.events.TimerEvent;

var _octave:uint    = 25;
var _frec:Number    = 440 * Math.pow(2, _octave / 12);
var _numSamples:uint  = 2048;
var _amp:Number      = 0.1;
var _phase:Number    = 0;
var _dphase:Number    = 1 / 44100 * Math.PI * 2;
var _status:uint    = 1;

var sound:Sound    = new Sound();

sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
sound.play();

var _draw = false;

function onSampleData(event:SampleDataEvent):void
  {
  graphics.clear();
  graphics.lineStyle(0, 0x999999);
  graphics.moveTo(0, stage.stageHeight / 2);
  
  for(var sample:int = 0; sample < 2048; sample++)
    {
    // get actual sample
    var sampleValue:Number = 0.5*Math.sin(_phase * _frec) * _amp;

    // add the samples to the sound object
    event.data.writeFloat(sampleValue); // left
    event.data.writeFloat(sampleValue); // right
    
    // next phase
    _phase += _dphase;
    
    // reduce the amplitud
    if (_status==1)
      {
      // tone creation > amplitud from 0 to 1
      _amp += (1.0 - _amp)/10;
      
      if (_amp>0.9) _status = 2;
      }
    else          
      {
      // tone loosing volume > amplitud from 1 to 0
      _amp *= 0.9998;
      }


    // draw the sound
    var sw:uint = stage.stageWidth;
    var sh:uint = stage.stageHeight;
    
    graphics.lineTo(sample / 2048 * sw, sh / 2 - sampleValue * sh / 2);
    }
  _draw = true;
  }

// Add a new note with random octaves from 10 to 20
var timer:Timer = new Timer(500);
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start();

function onTimer(event:TimerEvent):void
  {
  _amp    = 0.1;
  _octave = 10+10*Math.random();
  _frec   = 440 * Math.pow(2, _octave / 12);
  _phase  = 0;
  _status = 1;
  
  timer.delay  = 1000;
  }

In the next example we add 2048 samples to our sound object every time that the sound object runs out of samples.
In order to send the right samples, we use the _phase variable as pointer.

To simplify our sound, we will only focus on attack and decay or the ADSR envelope.
The amplitude jump from 0 to 1 when the sound is created, and then is always reduce by a 0.9998 faktor, so our note loose volume with the time.

Each time that we create a new tone, the amplitude is reset to 0, and we get a random value for the octave.

If instead of just one note, we want to play more at the same time, we just need to add all values to our sample:

Click here to start the example ( requires flash ).

var _notes:Array = new Array();
_notes[0]	= 96;
_notes[1]	= 80;
_notes[2]	= 70;
_notes[3]	= 60;

var sample:Number = 0;

for (var n:int=0;n<4;n++)
  sample += Math.sin(phase * 440 * Math.pow(2, _notes[n] / 12 - 6)) * amp;

sample /= 4;

We can add as much values as we want to each sample, but we need to keep all sample values between -1 and 1 or our sound will be distorted.

The good in this is that we can read the sound data before giving it to the sound card, and we can process the audio with complex filters and DSP analysis, use the information for visual display, etc... This is why it is handled in chunks of audio, so we can be able to process this sound in real time.


Download all example files