Making Waves

In a previous post, “Digging into the Microphone in Flash Player 10.1″, reader David Law asked in the comments how it would be possible to save .wav files to the server. I wasn’t sure right offhand, but thought I’d spend my lunch hour yesterday looking into it. Well, after reading this quick tutorial on Adobe Developer Connection, it turns out it’s actually quite simple. Almost embarrassingly simple, but still I thought I’d post it in case anyone else was looking to do the same thing.

The trick is to just convert your sound bytes into a wav formatted ByteArray object which can then be saved to the server with a quick bit of AMFPHP. You’ll notice in that Adobe article that there is included a WAVWriter class – that is all that’s necessary to convert your uncompressed ByteArray to a .wav file. Once you have that, the below AMFPHP class can easily save that .wav file to your server:

<?php
 
class Wave {
 
    /**
     * save wav files to server
     */
    function saveWav($wavByteArray) {
		// create a writable 'wavs' directory in the same directory as this service - probably want to use a different path
		$dir = "wavs";
 
		$fileName = "/sound" . time() . ".wav";
 
		$data = $wavByteArray->data;
		$result = file_put_contents($dir . $fileName, $data);
 
		return $result;
    }
}
 
?>

You may notice that the above class just saves the file to a directory within your AMFPHP services directory. You’ll more than likely not want to do that, so you can just create a different path. Or you may want to save the .wav directly in a database as a blob, in which case this old post may help you out. You may also want to name the .wav file on the client side and pass that along as an additional parameter rather than giving it a random name on the server side. In any case, this was just a quick example. You can do what you want from here.

Below is a quick .swf example (requires the 10.1 Flash Player) that will allow you to save the .wav file directly to your local machine to try it out (once you begin recording, you only have 3 seconds to say something into your mic, so make it quick. Didn’t want to go crazy on bandwidth).

Get Adobe Flash player

Again, this saves the file locally rather than to a server, but if you look through the code, you’ll see a commented area that would send the file to the AMFPHP class above rather than to the user’s machine via FileReference. Also, if you want to compile this yourself, you’ll need the WAVWriter class from the Adobe tutorial as well as the great MinimalComps from Bit-101 (of course you could just use your own buttons easily enough, but those components are freakin’ awesome for quick prototyping and if you don’t already have them, I recommend you get them).

package {
 
	import com.bit101.components.PushButton;
	import com.bit101.components.Style;
	import flash.display.Sprite;
	import flash.events.Event;
	import com.adobe.audio.format.WAVWriter;
	import flash.events.MouseEvent;
	import flash.events.StatusEvent;
	import flash.events.TimerEvent;
	import flash.net.FileReference;
	import flash.net.NetConnection;
	import flash.net.Responder;
	import flash.text.AntiAliasType;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	import flash.utils.Timer;
 
	import flash.events.SampleDataEvent;
	import flash.media.Microphone;
	import flash.utils.ByteArray;
 
	/**
	 * Record .wav file through mic then send it to users machine or server
	 * @author Devon O.
	 */
	[SWF(width='400', height='200', backgroundColor='#FFFFFF', frameRate='31')]
	public class Main extends Sprite {
 
		public static const GATEWAY_URL:String = "path/to/gateway.php";
 
		private var _startButton:PushButton;
		private var _stopButton:PushButton;
		private var _saveButton:PushButton;
 
		private var _recordingMessage:TextField;
 
		private var _timer:Timer;
		private var _mic:Microphone;
		private var _recordingComplete:Boolean = false;
		private var _soundRecording:ByteArray;
 
		private var _fileRef:FileReference = new FileReference();
 
		public function Main():void {
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
 
		private function init(e:Event = null):void {
			removeEventListener(Event.ADDED_TO_STAGE, init);
			// entry point
 
			initMicrophone();
			initMessage();
			initUI();
		}
 
		private function initMicrophone():void {
			_mic = Microphone.getMicrophone();
			_mic.rate = 44;
			_mic.gain = 60;
			_mic.addEventListener(StatusEvent.STATUS, onmicStatus);
		}
 
		private function onmicStatus(event:StatusEvent):void {
			_mic.removeEventListener(StatusEvent.STATUS, onmicStatus);
 
			if (event.code == "Microphone.Muted") {
				_recordingMessage.text = "You must allow access\nto Mic to record.";
				_mic.removeEventListener(SampleDataEvent.SAMPLE_DATA, gotMicData);
			} else {
				_recordingMessage.text = "Recording...";
				_timer = new Timer(3000, 1);
				_timer.addEventListener(TimerEvent.TIMER_COMPLETE, timesUp);
				_timer.start();
			}
		}
 
		private function timesUp(event:TimerEvent):void {
			_timer.removeEventListener(TimerEvent.TIMER_COMPLETE, timesUp);
			stopRecording();
		}
 
		private function startRecording(event:MouseEvent):void {			
			_startButton.enabled = false;
			_recordingComplete = true;
			_soundRecording = new ByteArray();
			_mic.addEventListener(SampleDataEvent.SAMPLE_DATA, gotMicData);
		}
 
		private function stopRecording():void{
			_recordingMessage.text = "Recording complete.\nClick 'Save' to export .wav.";
			_saveButton.enabled = true;
			_mic.removeEventListener(SampleDataEvent.SAMPLE_DATA, gotMicData);
		}
 
		private function gotMicData(micData:SampleDataEvent):void {
			_soundRecording.writeBytes(micData.data);
		}
 
		private function saveRecording(event:MouseEvent):void {
			if (_soundRecording == null || _soundRecording.length <= 0) return;
 
			_saveButton.enabled = false;
 
			var wavWriter:WAVWriter = new WAVWriter();
 
			_soundRecording.position = 0;
 
			wavWriter.numOfChannels = 1;
			wavWriter.sampleBitRate = 16;
			wavWriter.samplingRate = 44100;
 
			var wavBytes:ByteArray = new ByteArray();
 
			wavWriter.processSamples(wavBytes, _soundRecording, 44100, 1); // convert our ByteArray to a WAV file.
 
			/* Use something like this to send the .wav to the server rather than the local machine:
 
			var resp:Responder = new Responder(successHandler, errorHandler);
			var gateway:NetConnection = new NetConnection();
			gateway.connect(GATEWAY_URL);
			gateway.call("Wave.saveWav", resp, wavBytes);
 
			*/
 
			// or use this to save it locally
			_fileRef.save(wavBytes, "sound.wav");
		}
 
		private function successHandler(o:Object):void {
			trace(o);
			if (!o) trace("wav file not saved");
		}
 
		private function errorHandler(o:Object):void {
			for (var prop:String in o) trace(prop + " = " + o[prop]);
		}
 
		private function initMessage():void {
			_recordingMessage = new TextField();
			_recordingMessage.selectable = false;
			_recordingMessage.mouseEnabled = false;
			_recordingMessage.autoSize = TextFieldAutoSize.LEFT;
			_recordingMessage.antiAliasType = AntiAliasType.ADVANCED;
			_recordingMessage.defaultTextFormat = new TextFormat("_sans", 24, 0x660000);
			_recordingMessage.text = "Click 'Record' to begin...";
			_recordingMessage.x = 10;
			_recordingMessage.y = 100;
			_recordingMessage.multiline = true;
			addChild(_recordingMessage);
		}
 
		private function initUI():void {
			Style.BUTTON_FACE = 0x000000;
			_startButton = new PushButton(this, 10, 10, "Record", startRecording);
			_saveButton = new PushButton(this, _startButton.x + _startButton.width + 5, 10, "Save", saveRecording);
			_saveButton.enabled = false;
		}
	}
}

Of course, saving .wav files is all good and well, but you’ll probably want to actually be able to play them back. As you know (or should), Flash doesn’t natively allow for dynamically loading and playing .wav files. Needless to say, there is a way around that though. The general idea is this: the .wav file must be loaded in as a stream of bytes. This byte stream is then added to the library of a dynamically generated .swf file. The .wav can then be extracted from the library of that .swf and played as a Sound object using ApplicationDomain. Thankfully, I didn’t have to bother doing all this myself. A bit of Googling turned up this perfectly working example. I rearranged the code a little bit (added a real package, added some events, and added a playWav() method) which you can download in zipped format here. All credit goes to the original author though. Here’s a quick usage example:

wp = new WavPlayer();
wp.addEventListener(ProgressEvent.PROGRESS, onProgress);
wp.addEventListener(Event.COMPLETE, onComplete);
wp.Load("mysound.wav");
 
private function onProgress(event:ProgressEvent):void {
	trace((event.bytesLoaded / event.bytesTotal) * 100);
}
 
private function onComplete(event:Event):void {
	wp.playWav();
}

Bear in mind that this WavPlayer works by loading in raw ones and zeroes which can be extremely hazardous if you don’t know exactly where those ones and zeroes came from. I strongly suggest you use this only on .wav files you’ve created yourself or know for a fact don’t have any monkey business going on inside.

Hope all that helps some folks out. Have fun…

9 Comments »

  1. thanks for the great article,

    i modified ur code and uploaded the byteArray of recorded sound data from microphone to J2EE server and saved in wave file.
    i used blazeDS’s remote object to communicate flex and java

    thanks a lot once again.

  2. Devon O. says:

    Sounds like a great modification, Ganesh. I’m glad the post could help out.

  3. Foufou says:

    U can give me th source please :) ??????

  4. Devon O. says:

    Hey Foufou – the entire source is actually there in the post (no hidden files anywhere :) )

  5. Rob says:

    Great post! I’m wondering if you would absolutely suggest using AMFPHP to send the wav file to the server. Can this be done just using the HTTPService or the URLRequest classes and a simple PHP script? Any thoughts on how to do this? I’ve seen examples for .jpegs, but none for wavs and I’m very confused by the base64 encoding/decoding process. Thanks for your time!!!

  6. Rob says:

    I answered my own question. If you’re like me, and don’t want to set up AMFPHP just yet, you can send your bytearray to the server using a URLRequest and a couple lines of PHP code. See: http://blog.joa-ebert.com/2006/05/01/save-bytearray-to-file-with-php/

  7. Devon O. says:

    Cool, Rob. Glad you got it sorted..

  8. ADZMIR says:

    hye can you give more details on load wav in flash
    i still cant get it.
    I able to record to server.. but i cant see where to start to load back the wav file into flas

RSS feed for comments on this post. / TrackBack URI

Leave a Reply

Devon O. Wolfgang

Technical Reviewer of “The Essential Guide to Flash CS4 AIR Development”

Contributing Author of “Flash AS3 for Interactive Agencies”

Senior Flash Engineer PopCap Games, International Ltd.

Portfolio

UV Scrolling in Starling


Obviously this could come in pretty darned handy for space games, side scrollers, etc, etc[...]

Drawing on Stuff in Away3D 4.0

So, Easter Day, I thought I’d sit down and make a little ‘Paint on an Egg and Send it to Your Friend’ app.

Santabot: A Unity3D Flash Game


All right, so a Christmas game like “Santabot vs. The Flying Saucers from Mars” may be a day late[...]

Magnify – a jQuery Plugin


Let me begin by saying right up front, I have not given up on Flash[...]

It’s a Starling Halloween


Getting some practice for the upcoming Zombie Apocalypse[...]

Getting Started with Proscenium

So I had a chance this weekend to sit down and play around with Adobe’s new 3D framework for Stage3D, Proscenium, and thought I’d share a few of the results (a word of caution, there are no preloaders for any of the examples and may load a bit slowly). The first shows some reflections and [...]

Particle Editor for Starling Framework

An in-browser particle editor for the Starling 2D Framework for Flash Player 11.

So Long and Thanks for all the Flash on the Beach

So, another Flash on the Beach has just has just drawn to a close[...]

Game Development Tips from the Trenches of PopCap

Well, this is a post that’s a bit overdue, but, thanks to a well timed bank holiday, I finally had the opportunity to sit down and type up what I’ve been meaning to for some time now…

Old Skool Demoscene FX as 3D Textures

Many moons ago, I got the idea to create some demoscene plane deformation effects in Flash based on the formulas found here: http://www.iquilezles.org/www/articles/deform/deform.htm. I posted my less than desired results up on wonderfl. Thankfully, fellow wonderfl user, Hasufel, forked my attempt and optimized the hell out of it coming up with this. Well, today, for no [...]

Making The Gaming Scene (A Change in Careers)

And for my second blog post of the day, a much more personal note. After about three years of working with, what I would consider as objectively as possible, the best digital agency in Ireland, vStream Digital Media, I have made the immense career decision to leave the agency world and enter the arena of [...]

Feeling Lucky?

Images to dice, kick ascii style [...]

Adventures in Playbook Land

Adventures in Playbook Land


Now that the ordeal is over, I thought I’d take the time to sit down and share my account of what it was like to develop a Blackberry Playbook application using the Adobe Flex SDK[...]

Flash

Draw it for Me

So many ideas – so little time….

Kinect Application Running in Dublin

So, on Friday I just wrapped up our latest project at vStream Digital Media, a Kinect powered flipbook that lets users flip through the hand written notebooks of Philip Lynott of Thin Lizzy. The app uses OpenNI, runs in Adobe AIR and is currently on display at a pretty bitchin’ Phil Lynott exhibition running in [...]

Beach Ball Kinect Party

So we finally got a Kinect camera hooked up to a pc at work and, while it doesn’t seem to be legal to use it for commercial projects (but, hey, I’m no lawyer), the boss asked me to get it figured out and come up with some ideas just in case it would be feasible [...]