Sound in Flash 10 (beta). Generating Waveforms, Timbre and Pitch.

September 12, 2008. Posted by Andy Hulstkamp under Actionscript 3 Astro (Flash 10 beta) Flash Sound
Keywords: , , , ,

It’s been quite a while (back in 2002, flash 5) since I did some serious work using oscillators and waveforms. I’m writing this down while trying to get back on track. The stuff described here is definitely no secret. It’s basic, but relevant to create some tunes in flash. Maybe you’ll find some of the stuff useful.

Through the introduction of the SampleDataEvent it is now easy to write data back to the audiostream:

//const AMP_MULTIPLIER:Number = 0.15; //keep this low while testing
private function noiseWave(event:SampleDataEvent):void {   
    var sample:Number;
    for (var i:int=0; i<8192; i++ ) {
        sample = Math.random() -.5; 
        event.data.writeFloat(sample * AMP_MULTIPLIER);
        event.data.writeFloat(sample * AMP_MULTIPLIER);
    }    
}

This will simply generate some noise. Sounds rather boring, doesn’t it? Don’t neglect the noise though, it’s a great source for percussive sounds, when treated accordingly. Anyway, to get something more exciting we need to introduce pitch, timber (quality of sound) and loudness.

Tones & Pitch

To produce a sound we perceive as a tone at a certain pitch, we need to introduce a bunch of data that is repeated over time. This gives us a repeating pattern. Let’s call a cycle of that pattern the waveform. Aha, so let’s simply write 1,-1,1,-1,1,-1… to the stream, right? Yes, we could do that, but no, you and I wouldn’t hear a bleep. It would simply be out of our hearing range. To get the tones into the hearing range, the pattern needs to be repeated a certain amount of times per second. Remember the note, your music teacher in school was desperately hammering on his piano, to get everyone in tune? That was probably an A above middle C. This note has a frequency of 440 Hz, meaning the waveform (or data in our case) cycles – or oscillates - at 440 times per second. The next lower A is at 220 Hz, the next higher 880 Hz. Basically, the more cycles per sec, the higher the frequency, the higher the pitch.

Flash works at a sampling rate of 44100 Hz. It uses 44100 samples/sec to produce sound. If we want to generate an A (440 Hz) in flash, we need to repeatedly write a bunch of 44100/440 ≈ 100 samples to the audiostream. The 100 samples are actually the wavelength represented in samples.

//const AMP_MULTIPLIER:Number = 0.15;
//const BASE_FREQ:int = 440;
//const SAMPLING_RATE:int = 44100;
//const TWO_PI:Number = 2*Math.PI;
//const TWO_PI_OVER_SR:Number = TWO_PI/SAMPLING_RATE;
 
private function sineWave1(event:SampleDataEvent):void {
    var sample:Number
    for (var i:int=0; i<8192; i++) {
        sample = Math.sin((i+event.position) * TWO_PI_OVER_SR * BASE_FREQ);
        event.data.writeFloat(sample * AMP_MULTIPLIER);
        event.data.writeFloat(sample * AMP_MULTIPLIER);
    }
}

This will generate a pure sine A440Hz. By layering multiple sines of different frequencies a vast number of waveforms can be created. A square wave could be generated using multiple sines, but that would put heavy loads on the CPU. Luckily we can cheat:

//the square wave implemented via sine, duty cycle for a square is 1:2
private function squareWave1(event:SampleDataEvent):void {
    //lets be nice to our equipment and keep the amplitude low
    var amp:Number = 0.075;
    var sample:Number;
    for (var i:int=0; i<8192; i++) {
        sample = Math.sin((i + event.position) * TWO_PI_OVER_SR * BASE_FREQ) > 0 ? amp : -amp;
        event.data.writeFloat(sample);
        event.data.writeFloat(sample);
    }
}

This gives us a nice square wave, but there’s quite an expensive Math.sin() in it. Another approach might be better regarding performance:

//keeps track of current phase
var phase:Number;
 
private function squareWave2(event:SampleDataEvent):void {
    var amp:Number = 0.075;
    var sample:Number;
    for (var i:int; i < 8192; i++) {
        sample = phase < Math.PI ? amp : -amp;
        phase = phase + (TWO_PI_OVER_SR * BASE_FREQ);
        phase = phase > TWO_PI ? phase-TWO_PI : phase;
        event.data.writeFloat(sample);
        event.data.writeFloat(sample);
    }
}

The square wave has the same pitch as the sine wave above but sounds different (different quality of sound or timbre). Why? Overtones.

Timbre (quality of sound)

The perceived timbre of a sound is determined by its spectrum and loudness over time. The spectrum is basically the sum of different frequencies in a sound. Based on the fundamental frequency (in our example 440 Hz) there could be other frequencies (overtones) on top of that. These overtones can either be harmonic, when they are multiple octaves above the fundamental frequency or inharmonic, when somewhere in-between. We don’t need to deal with overtones, let’s just state that the waveform and timbre of a sound are coupled. HOW a waveform sounds is mainly a question of perception and association. The other factor that defines the timbre is the loudness of a sound over time.

Loudness

Loudness of a sound over time is another factor that defines the timbre. If we change

-this:
event.data.writeFloat(sample);
event.data.writeFloat(sample);
-to that:
event.data.writeFloat(sample * (8192-c)/8192);
event.data.writeFloat(sample * (8192-c)/8192);

we get a sound with a strong attack and a linear decay. However, the sounds we got so far are a little static. To get more interesting results, we need to introduce modulation. Modulation can affect all kinds of parameters (like pitch, loudness, envelope, shifts in overtones, anything) to slightly or drastically change the waveform over time. Here’s an example that modulates the pulse width and the amplitude of a pulse wave:

//modulated pulse, one LFO modulates pulse-width, another one the amplitude
private function pulseWaveMod1(event:SampleDataEvent):void {
         // get those out
        var amp:Number = 0.075;
	var pwr:Number = Math.PI/1.05;
	var dpw:Number;
	var am:Number;
	var pos:Number;
	var sample:Number;
	for (var i:int=0; i<8192; i++) {
		pos = i + event.position;
		dpw = Math.sin (pos/0x4800) * pwr; //LFO -> PW
		sample = phase < Math.PI - dpw ? amp : -amp;
		phase = phase + (TWO_PI_OVER_SR * BASE_FREQ);
		phase = phase > TWO_PI ? phase-TWO_PI : phase;
		am = Math.sin (pos/0x1000); //LFO -> AM
	        event.data.writeFloat(sample * am);
		event.data.writeFloat(sample * am);
	}
}

By modulating different parameters the sound gets more interesting. Now, nobody stops you from modulating the modulators, giving more drastic results.

Here’s a little test of basic waveforms up to rather heavily modulated:

WaveformTester

Click here for the demo. Turn down volume first. Needs the latest Flash-Player 10 (rc 091508).

Here’s the source. UPDATE: Adobe changed the sound api. Use SampleDataEvent.SAMPLE_DATA instead of Event.SAMPLE_DATA for the listener.

Yeah, sounds still thin, though

You’re right. To get those fat bastard sounds we need more voices, effects (the holy reverb), filters, compressors etc. but that’s a whole different story. In the end, at the basis of it all are the waveforms.



Share/Save/Bookmark

19 Responses to “Sound in Flash 10 (beta). Generating Waveforms, Timbre and Pitch.”

  1. memory dump. » Blog Archive » Sound in Flash 10 (beta). Generating Waveforms, Timbre and Pitch Says:

    [...] http://www.hulstkamp.com/2008/09/12/sound-in-flash-10-beta-generating-waveforms-timbre-and-pitch/175 [...]

  2. Etan Says:

    Hi there. Thank you for publishing this interesting flash demo. I was trying to play around with it in CS4 by creating a .fla file (there were only .as files in the zipped source code) in the same folder as the WaveformTester.as file but when I attempt to instantiate a WaveformTester var like this (it’s the only line of code in the only frame of the only layer):

    var wf:WaveformTester = new WaveformTester();

    I receive this error:

    TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at WaveformTester/buildCrapGUI()
    at WaveformTester/init()
    at WaveformTester()
    at waveform_fla::MainTimeline/frame1()

    I don’t have a strong background in flash so I guess I’m overlooking something silly. Any insight?

    Thanks,
    Etan

  3. Andy Hulstkamp Says:

    Etan,

    The code has been developed using Flex Builder and the Flash 10 beta api. Adobe changed parts of the api for the final release. Check:

    Issues with the Vector-Class (import)
    Line #93 stage might be null, since WaveformTester hasn’t been added to the stage when the init is called. Try commenting this out.

    Did you use the debugger in Flash and trace through WaveformTester to see where the RTE happens?

  4. Etan Says:

    Yea, I was using the debugger to try and figure it out but the null stage error didn’t mean a whole lot to me since the stage object is sort of new to me (been learning in Actionscript 2.0).

    With some web research I managed to get it working by using this in the .fla file:


    var mc:MovieClip = new MovieClip();
    addChild(mc);
    var wf:WaveformTester = new WaveformTester();
    mc.addChild(wf);

    I also still had to comment out or change line 93 in WaveformTester.as from

    btn.y = stage.stageHeight - btn.height - yp;
    to
    btn.y = yp + 200;

    because the stage null RTE was still being produced, and using no value for btn.y placed the buttons over the plotter.

    Thank you for the help,
    Etan

  5. Andy Hulstkamp Says:

    Etan,

    you still get the NPE because WaveformTester() has not been added to the stage when it calls the init().

    Try removing init() from the constructor WaveformTester() and then call it manually

    use

    var wf:WaveformTester = new WaveformTester();
    //add DisplayObject to stage
    mc.addChild(wf);
    //now call init()
    wf.init();

    or try using Stage.stageHeight instead of the stage-member.

    hth
    andy

  6. Claudio Tellez Says:

    My english is a little bad :d. here i go…

    is there any way to save those sounds generated with Flash on my computer?

    Ex: im working on a virtual dj n am needing to save the mixes in a mp3 file from flash.

    Thnks :D

    CT

  7. Edward Padsfraussy Says:

    First of all congratulation for such a great site. I learned a lot reading article here today. I will make sure i visit this site once a day so i can learn more.

  8. Andy Hulstkamp Says:

    thanks Edward, glad you find some stuff usefull.

  9. Andy Hulstkamp Says:

    Claudio,

    Better use Adobe AIR for this. Check the FileStream class. This class gives you access to the File-System. You could simply write out the sample data but that won’t be of much help. You need to write the data in a format that you can use in a Sound-Application. MP3 is an compressed format so you’ll need to encode the sample data in mp3-format. You might want to google for an actionscript mp3-encoder that makes all the work for you or check the mp3-format an then create the encoder.

    Hope this gets you started.

  10. maru Says:

    Really really cool post !
    “Sound in Flash for dummies” (like me). I think it’s a must-read :)
    thx for all, can’t wait to read your other stuff !

  11. rob dick Says:

    Andy,

    This was exactly the type of blog entry I was searching for. Very well explained and certainly inspires the tech behind the site currently being built by myself.

    Claudio’s need also raised some thoughts as I also ran into the same problem of how to manage the persistence of generated audio data in the least obstructive way. Agree however that AIR is probably the way to go, especially when certain more polished scenarios require things like autosave and caching of remotely downloadable sample files for audio production/mixing tools etc.

    The other inspiration was the introduction of Abobe Pixel Bender, leveraging the GPU in a non-conventional way and applied to areas where the number crunching does present a performance issue in more intensive applications.

    Thanks for this, delightful read, I look forward to your further posts.

    Rob

  12. Andy Hulstkamp Says:

    Rob,

    Thanks!

    I think Claudio wanted to save the generated audio-data locally, so that’s why I suggested to use AIR. I haven’t looked into this but I don’t think there should be any problems of writing the data to a blob from flash.

    The use of Pixel Bender to do some number crunching is very tempting, although it is currently not leveraging the GPU in flash in a consistent way.

  13. Free Readings Online » Blog Archive » Generating sound in Flash 10 Says:

    [...] Player 10 Dynamic Sound Generating Waveforms, Timbre and Pitch Read more | Permalink | Comments | Read more articles in hacks | Digg this! Posted by slashman [...]

  14. Generating sound in Flash 10 » Developages - Development and Technology Blog Says:

    [...] Flash Playe&#114 10 Dynam&#105&#99 S&#111&#117nd &#71enerat&#105ng Waveforms, T&#105m&#98re and P&#105tch [...]

  15. Generating sound in Flash 10 - machine quotidien Says:

    [...] Flash Player 10 Dynamic Sound Generating Waveforms, Timbre and Pitch [...]

  16. Jon Sapp Says:

    I’ve been looking for this info! Nice work!

  17. jun :: realeyes » Blog Archive » Make Some Noise! Creating an analog audio controller with Merapi and AIR Says:

    [...] http://www.hulstkamp.com/2008/09/12/sound-in-flash-10-beta-generating-waveforms-timbre-and-pitch/175 (Info and the reference I used when playing with Flash Player 10 Sound API.) [...]

  18. James Says:

    Hi, Very nice write up. Is it possible to work out from the sound spectrum what the pitch of a certain sound is. I have been playing around with working out peaks and troughs and allowing for noise by passing in a tolerance. Is there another way?

  19. Andy Hulstkamp Says:

    James

    thanks.

    Maybe this could be of interest.

Leave a Reply