Browse Source

wami-recorder Revision: c5b24bbc70e9

Juan Carlos Raña 13 years ago
parent
commit
b9f2ae2bff
32 changed files with 3107 additions and 0 deletions
  1. BIN
      main/inc/lib/wami-recorder/example/client/Wami.swf
  2. 44 0
      main/inc/lib/wami-recorder/example/client/basic.html
  3. BIN
      main/inc/lib/wami-recorder/example/client/buttons.png
  4. 349 0
      main/inc/lib/wami-recorder/example/client/gui.js
  5. 52 0
      main/inc/lib/wami-recorder/example/client/index.html
  6. 273 0
      main/inc/lib/wami-recorder/example/client/recorder.js
  7. 12 0
      main/inc/lib/wami-recorder/example/server/README.txt
  8. 9 0
      main/inc/lib/wami-recorder/example/server/php/record.php
  9. 53 0
      main/inc/lib/wami-recorder/example/server/python/server.py
  10. 9 0
      main/inc/lib/wami-recorder/example/wami-recorder.xml
  11. 171 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/audio/AuContainer.as
  12. 158 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/audio/AudioFormat.as
  13. 125 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/audio/DecodePipe.as
  14. 126 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/audio/EncodePipe.as
  15. 54 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/audio/IAudioContainer.as
  16. 131 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/audio/WaveContainer.as
  17. 1 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/client/FlashSettings.as
  18. 104 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/client/Wami.mxml
  19. 109 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/client/WamiAudio.as
  20. 61 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/client/WamiListener.as
  21. 180 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/client/WamiParams.as
  22. 39 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/play/IPlayer.as
  23. 179 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/play/WamiPlayer.as
  24. 5 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/record/ChunkPipe.as
  25. 49 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/record/IRecorder.as
  26. 111 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/record/MultiPost.as
  27. 159 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/record/SinglePost.as
  28. 269 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/record/WamiRecorder.as
  29. 93 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/utils/BytePipe.as
  30. 144 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/utils/External.as
  31. 3 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/utils/Pipe.as
  32. 35 0
      main/inc/lib/wami-recorder/src/edu/mit/csail/wami/utils/StateListener.as

BIN
main/inc/lib/wami-recorder/example/client/Wami.swf


+ 44 - 0
main/inc/lib/wami-recorder/example/client/basic.html

@@ -0,0 +1,44 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html>
+<head>
+<!-- swfobject is a commonly used library to embed Flash content -->
+<script type="text/javascript"
+	src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+
+<!-- Setup the recorder interface -->
+<script type="text/javascript" src="recorder.js"></script>
+
+<script>
+	function setup() {
+		Wami.setup("wami");
+	}
+	
+	function record() {
+		status("Recording...");
+		Wami.startRecording("https://wami-recorder.appspot.com/audio");
+	}
+
+	function play() {
+		Wami.startPlaying("https://wami-recorder.appspot.com/audio");
+	}
+
+	function stop() {
+		status("");
+		Wami.stopRecording();
+		Wami.stopPlaying();
+	}
+
+	function status(msg) {
+		document.getElementById('status').innerHTML = msg;
+	}
+</script>
+</head>
+
+<body onload="setup()">
+	<input type="button" value="Record" onclick="record()"></input>
+	<input type="button" value="Stop" onclick="stop()"></input>
+	<input type="button" value="Play" onclick="play()"></input>
+	<div id="status"></div>
+	<div id="wami"></div>
+</body>
+</html>

BIN
main/inc/lib/wami-recorder/example/client/buttons.png


+ 349 - 0
main/inc/lib/wami-recorder/example/client/gui.js

@@ -0,0 +1,349 @@
+var Wami = window.Wami || {};
+
+// Upon a creation of a new Wami.GUI(options), we assume that a WAMI recorder
+// has been initialized.
+Wami.GUI = function(options) {
+	var RECORD_BUTTON = 1;
+	var PLAY_BUTTON = 2;
+
+	setOptions(options);
+	setupDOM();
+
+	var recordButton, playButton;
+	var recordInterval, playInterval;
+
+	function createDiv(id, style) {
+		var div = document.createElement("div");
+		if (id) {
+			div.setAttribute('id', id);
+		}
+		if (style) {
+			div.style.cssText = style;
+		}
+		return div;
+	}
+
+	function setOptions(options) {
+		if (!options.buttonUrl) {
+			options.buttonUrl = "buttons.png";
+		}
+
+		if (typeof options.listen == 'undefined' || options.listen) {
+			listen();
+		}
+	}
+
+	function setupDOM() {
+		var guidiv = createDiv(null,
+				"position: absolute; width: 214px; height: 137px;");
+		document.getElementById(options.id).appendChild(guidiv);
+
+		var rid = Wami.createID();
+		var recordDiv = createDiv(rid,
+				"position: absolute; left: 40px; top: 25px");
+		guidiv.appendChild(recordDiv);
+
+		recordButton = new Button(rid, RECORD_BUTTON, options.buttonUrl);
+		recordButton.onstart = startRecording;
+		recordButton.onstop = stopRecording;
+
+		recordButton.setEnabled(true);
+
+		if (!options.singleButton) {
+			var pid = Wami.createID();
+			var playDiv = createDiv(pid,
+					"position: absolute; right: 40px; top: 25px");
+			guidiv.appendChild(playDiv);
+
+			playButton = new Button(pid, PLAY_BUTTON, options.buttonUrl);
+			playButton.onstart = startPlaying;
+			playButton.onstop = stopPlaying;
+		}
+	}
+
+	/**
+	 * These methods are called on clicks from the GUI.
+	 */
+	function startRecording() {
+		if (!options.recordUrl) {
+			alert("No record Url specified!");
+		}
+		recordButton.setActivity(0);
+		playButton.setEnabled(false);
+		Wami.startRecording(options.recordUrl,
+				Wami.nameCallback(onRecordStart), Wami
+						.nameCallback(onRecordFinish), Wami
+						.nameCallback(onError));
+	}
+
+	function stopRecording() {
+		Wami.stopRecording();
+		clearInterval(recordInterval);
+		recordButton.setEnabled(true);
+	}
+
+	function startPlaying() {
+		if (!options.playUrl) {
+			alert('No play URL specified!');
+		}
+
+		playButton.setActivity(0);
+		recordButton.setEnabled(false);
+
+		Wami.startPlaying(options.playUrl, Wami.nameCallback(onPlayStart), Wami
+				.nameCallback(onPlayFinish), Wami.nameCallback(onError));
+	}
+
+	function stopPlaying() {
+		Wami.stopPlaying();
+	}
+
+	this.setPlayUrl = function(url) {
+		options.playUrl = url;
+	}
+
+	this.setRecordUrl = function(url) {
+		options.recordUrl = url;
+	}
+
+	this.setPlayEnabled = function(val) {
+		playButton.setEnabled(val);
+	}
+
+	this.setRecordEnabled = function(val) {
+		recordButton.setEnabled(val);
+	}
+
+	/**
+	 * Callbacks from the flash indicating certain events
+	 */
+
+	function onError(e) {
+		alert(e);
+	}
+
+	function onRecordStart() {
+		recordInterval = setInterval(function() {
+			if (recordButton.isActive()) {
+				var level = Wami.getRecordingLevel();
+				recordButton.setActivity(level);
+			}
+		}, 200);
+		if (options.onRecordStart) {
+			options.onRecordStart();
+		}
+	}
+
+	function onRecordFinish() {
+		playButton.setEnabled(true);
+		if (options.onRecordFinish) {
+			options.onRecordFinish();
+		}
+	}
+
+	function onPlayStart() {
+		playInterval = setInterval(function() {
+			if (playButton.isActive()) {
+				var level = Wami.getPlayingLevel();
+				playButton.setActivity(level);
+			}
+		}, 200);
+		if (options.onPlayStart) {
+			options.onPlayStart();
+		}
+	}
+
+	function onPlayFinish() {
+		clearInterval(playInterval);
+		recordButton.setEnabled(true);
+		playButton.setEnabled(true);
+		if (options.onPlayFinish) {
+			options.onPlayFinish();
+		}
+	}
+
+	function listen() {
+		Wami.startListening();
+		// Continually listening when the window is in focus allows us to
+		// buffer a little audio before the users clicks, since sometimes
+		// people talk too soon. Without "listening", the audio would record
+		// exactly when startRecording() is called.
+		window.onfocus = function() {
+			Wami.startListening();
+		};
+
+		// Note that the use of onfocus and onblur should probably be replaced
+		// with a more robust solution (e.g. jQuery's $(window).focus(...)
+		window.onblur = function() {
+			Wami.stopListening();
+		};
+	}
+
+	function Button(buttonid, type, url) {
+		var self = this;
+		self.active = false;
+		self.type = type;
+
+		init();
+
+		// Get the background button image position
+		// Index: 1) normal 2) pressed 3) mouse-over
+		function background(index) {
+			if (index == 1)
+				return "-56px 0px";
+			if (index == 2)
+				return "0px 0px";
+			if (index == 3)
+				return "-112px 0";
+			alert("Background not found: " + index);
+		}
+
+		// Get the type of meter and its state
+		// Index: 1) enabled 2) meter 3) disabled
+		function meter(index, offset) {
+			var top = 5;
+			if (offset)
+				top += offset;
+			if (self.type == RECORD_BUTTON) {
+				if (index == 1)
+					return "-169px " + top + "px";
+				if (index == 2)
+					return "-189px " + top + "px";
+				if (index == 3)
+					return "-249px " + top + "px";
+			} else {
+				if (index == 1)
+					return "-269px " + top + "px";
+				if (index == 2)
+					return "-298px " + top + "px";
+				if (index == 3)
+					return "-327px " + top + "px";
+			}
+			alert("Meter not found: " + self.type + " " + index);
+		}
+
+		function silhouetteWidth() {
+			if (self.type == RECORD_BUTTON) {
+				return "20px";
+			} else {
+				return "29px";
+			}
+		}
+
+		function mouseHandler(e) {
+			var rightclick;
+			if (!e)
+				var e = window.event;
+			if (e.which)
+				rightclick = (e.which == 3);
+			else if (e.button)
+				rightclick = (e.button == 2);
+
+			if (!rightclick) {
+				if (self.active && self.onstop) {
+					self.active = false;
+					self.onstop();
+				} else if (!self.active && self.onstart) {
+					self.active = true;
+					self.onstart();
+				}
+			}
+		}
+
+		function init() {
+			var div = document.createElement("div");
+			var elem = document.getElementById(buttonid);
+			if (elem) {
+				elem.appendChild(div);
+			} else {
+				alert('Could not find element on page named ' + buttonid);
+			}
+
+			self.guidiv = document.createElement("div");
+			self.guidiv.style.width = '56px';
+			self.guidiv.style.height = '63px';
+			self.guidiv.style.cursor = 'pointer';
+			self.guidiv.style.background = "url(" + url + ") no-repeat";
+			self.guidiv.style.backgroundPosition = background(1);
+			div.appendChild(self.guidiv);
+
+			// margin auto doesn't work in IE quirks mode
+			// http://stackoverflow.com/questions/816343/why-will-this-div-img-not-center-in-ie8
+			// text-align is a hack to force it to work even if you forget the
+			// doctype.
+			self.guidiv.style.textAlign = 'center';
+
+			self.meterDiv = document.createElement("div");
+			self.meterDiv.style.width = silhouetteWidth();
+			self.meterDiv.style.height = '63px';
+			self.meterDiv.style.margin = 'auto';
+			self.meterDiv.style.cursor = 'pointer';
+			self.meterDiv.style.position = 'relative';
+			self.meterDiv.style.background = "url(" + url + ") no-repeat";
+			self.meterDiv.style.backgroundPosition = meter(2);
+			self.guidiv.appendChild(self.meterDiv);
+
+			self.coverDiv = document.createElement("div");
+			self.coverDiv.style.width = silhouetteWidth();
+			self.coverDiv.style.height = '63px';
+			self.coverDiv.style.margin = 'auto';
+			self.coverDiv.style.cursor = 'pointer';
+			self.coverDiv.style.position = 'relative';
+			self.coverDiv.style.background = "url(" + url + ") no-repeat";
+			self.coverDiv.style.backgroundPosition = meter(1);
+			self.meterDiv.appendChild(self.coverDiv);
+
+			self.active = false;
+			self.guidiv.onmousedown = mouseHandler;
+		}
+
+		self.isActive = function() {
+			return self.active;
+		}
+
+		self.setActivity = function(level) {
+			self.guidiv.onmouseout = function() {
+			};
+			self.guidiv.onmouseover = function() {
+			};
+			self.guidiv.style.backgroundPosition = background(2);
+			self.coverDiv.style.backgroundPosition = meter(1, 5);
+			self.meterDiv.style.backgroundPosition = meter(2, 5);
+
+			var totalHeight = 31;
+			var maxHeight = 9;
+
+			// When volume goes up, the black image loses height,
+			// creating the perception of the colored one increasing.
+			var height = (maxHeight + totalHeight - Math.floor(level / 100
+					* totalHeight));
+			self.coverDiv.style.height = height + "px";
+		}
+
+		self.setEnabled = function(enable) {
+			var guidiv = self.guidiv;
+			self.active = false;
+			if (enable) {
+				self.coverDiv.style.backgroundPosition = meter(1);
+				self.meterDiv.style.backgroundPosition = meter(1);
+				guidiv.style.backgroundPosition = background(1);
+				guidiv.onmousedown = mouseHandler;
+				guidiv.onmouseover = function() {
+					guidiv.style.backgroundPosition = background(3);
+				};
+				guidiv.onmouseout = function() {
+					guidiv.style.backgroundPosition = background(1);
+				};
+			} else {
+				self.coverDiv.style.backgroundPosition = meter(3);
+				self.meterDiv.style.backgroundPosition = meter(3);
+				guidiv.style.backgroundPosition = background(1);
+				guidiv.onmousedown = null;
+				guidiv.onmouseout = function() {
+				};
+				guidiv.onmouseover = function() {
+				};
+			}
+		}
+	}
+}

+ 52 - 0
main/inc/lib/wami-recorder/example/client/index.html

@@ -0,0 +1,52 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html>
+<head>
+<!-- swfobject is a commonly used library to embed Flash content -->
+<script type="text/javascript"
+	src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+
+<!-- Setup the recorder interface -->
+<script type="text/javascript" src="recorder.js"></script>
+
+<!-- GUI code... take it or leave it -->
+<script type="text/javascript" src="gui.js"></script>
+
+<script>
+	function setupRecorder() {
+		Wami.setup({
+			id : "wami",
+			onReady : setupGUI
+		});
+	}
+
+	function setupGUI() {
+		var gui = new Wami.GUI({
+			id : "wami",
+			recordUrl : "https://wami-recorder.appspot.com/audio",
+			playUrl : "https://wami-recorder.appspot.com/audio"
+		});
+
+		gui.setPlayEnabled(false);
+	}
+</script>
+</head>
+
+<body onload="setupRecorder()">
+	<div id="wami" style="margin-left: 100px;"></div>
+	<noscript>WAMI requires Javascript</noscript>
+
+	<div
+		style="position: absolute; left: 400px; top: 20px; font-family: arial, sans-serif; font-size: 82%">
+		Right-click to Download<br /> <br /> <a
+			href="https://wami-recorder.googlecode.com/hg/example/client/index.html">index.html</a><br />
+		<a
+			href="https://wami-recorder.googlecode.com/hg/example/client/Wami.swf">Wami.swf</a><br />
+		<a
+			href="https://wami-recorder.googlecode.com/hg/example/client/buttons.png">buttons.png</a><br />
+		<a
+			href="https://wami-recorder.googlecode.com/hg/example/client/recorder.js">recorder.js</a><br />
+		<a
+			href="https://wami-recorder.googlecode.com/hg/example/client/gui.js">gui.js</a><br />
+	</div>
+</body>
+</html>

+ 273 - 0
main/inc/lib/wami-recorder/example/client/recorder.js

@@ -0,0 +1,273 @@
+var Wami = window.Wami || {};
+
+// Returns a (very likely) unique string with of random letters and numbers
+Wami.createID = function() {
+	return "wid" + ("" + 1e10).replace(/[018]/g, function(a) {
+		return (a ^ Math.random() * 16 >> a / 4).toString(16)
+	});
+}
+
+// Creates a named callback in WAMI and returns the name as a string.
+Wami.nameCallback = function(cb, cleanup) {
+	Wami._callbacks = Wami._callbacks || {};
+	var id = Wami.createID();
+	Wami._callbacks[id] = function() {
+		if (cleanup) {
+			Wami._callbacks[id] = null;
+		}
+		cb.apply(null, arguments);
+	};
+	var named = "Wami._callbacks['" + id + "']";
+	return named;
+}
+
+// This method ensures that a WAMI recorder is operational, and that
+// the following API is available in the Wami namespace. All functions
+// must be named (i.e. cannot be anonymous).
+//
+// Wami.startPlaying(url, startfn = null, finishedfn = null, failedfn = null);
+// Wami.stopPlaying()
+//
+// Wami.startRecording(url, startfn = null, finishedfn = null, failedfn = null);
+// Wami.stopRecording()
+//
+// Wami.getRecordingLevel() // Returns a number between 0 and 100
+// Wami.getPlayingLevel() // Returns a number between 0 and 100
+//
+// Wami.hide()
+// Wami.show()
+//
+// Manipulate the WAMI recorder's settings. In Flash
+// we need to check if the microphone permission has been granted.
+// We might also set/return sample rate here, etc.
+//
+// Wami.getSettings();
+// Wami.setSettings(options);
+//
+// Optional way to set up browser so that it's constantly listening
+// This is to prepend audio in case the user starts talking before
+// they click-to-talk.
+//
+// Wami.startListening()
+//
+Wami.setup = function(options) {
+	if (Wami.startRecording) {
+		// Wami's already defined.
+		if (options.onReady) {
+			options.onReady();
+		}
+		return;
+	}
+
+	// Assumes that swfobject.js is included if Wami.swfobject isn't
+	// already defined.
+	Wami.swfobject = Wami.swfobject || swfobject;
+
+	if (!Wami.swfobject) {
+		alert("Unable to find swfobject to help embed the SWF.");
+	}
+
+	var _options;
+	setOptions(options);
+	embedWamiSWF(_options.id, Wami.nameCallback(delegateWamiAPI));
+
+	function supportsTransparency() {
+		// Detecting the OS is a big no-no in Javascript programming, but
+		// I can't think of a better way to know if wmode is supported or
+		// not... since NOT supporting it (like Flash on Ubuntu) is a bug.
+		return (navigator.platform.indexOf("Linux") == -1);
+	}
+
+	function setOptions(options) {
+		// Start with default options
+		_options = {
+			swfUrl : "Wami.swf",
+			onReady : function() {
+				Wami.hide();
+			},
+			onSecurity : checkSecurity,
+			onError : function(error) {
+				alert(error);
+			}
+		};
+
+		if (typeof options == 'undefined') {
+			alert('Need at least an element ID to place the Flash object.');
+		}
+
+		if (typeof options == 'string') {
+			_options.id = options;
+		} else {
+			_options.id = options.id;
+		}
+
+		if (options.swfUrl) {
+			_options.swfUrl = options.swfUrl;
+		}
+
+		if (options.onReady) {
+			_options.onReady = options.onReady;
+		}
+
+		if (options.onLoaded) {
+			_options.onLoaded = options.onLoaded;
+		}
+
+		if (options.onSecurity) {
+			_options.onSecurity = options.onSecurity;
+		}
+
+		if (options.onError) {
+			_options.onError = options.onError;
+		}
+
+		// Create a DIV for the SWF under _options.id
+
+		var container = document.createElement('div');
+		container.style.position = 'absolute';
+		_options.cid = Wami.createID();
+		container.setAttribute('id', _options.cid);
+
+		var swfdiv = document.createElement('div');
+		var id = Wami.createID();
+		swfdiv.setAttribute('id', id);
+
+		container.appendChild(swfdiv);
+		document.getElementById(_options.id).appendChild(container);
+
+		_options.id = id;
+	}
+
+	function checkSecurity() {
+		var settings = Wami.getSettings();
+		if (settings.microphone.granted) {
+			_options.onReady();
+		} else {
+			// Show any Flash settings panel you want:
+			// http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/SecurityPanel.html
+			Wami.showSecurity("privacy", "Wami.show", Wami
+					.nameCallback(_options.onSecurity), Wami
+					.nameCallback(_options.onError));
+		}
+	}
+
+	// Embed the WAMI SWF and call the named callback function when loaded.
+	function embedWamiSWF(id, initfn) {
+		var flashVars = {
+			visible : false,
+			loadedCallback : initfn
+		}
+
+		var params = {
+			allowScriptAccess : "always"
+		}
+
+		if (supportsTransparency()) {
+			params.wmode = "transparent";
+		}
+
+		if (typeof console !== 'undefined') {
+			flashVars.console = true;
+		}
+
+		var version = '10.0.0';
+		document.getElementById(id).innerHTML = "WAMI requires Flash "
+				+ version
+				+ " or greater<br />https://get.adobe.com/flashplayer/";
+
+		// This is the minimum size due to the microphone security panel
+		Wami.swfobject.embedSWF(_options.swfUrl, id, 214, 137, version, null,
+				flashVars, params);
+
+		// Without this line, Firefox has a dotted outline of the flash
+		Wami.swfobject.createCSS("#" + id, "outline:none");
+	}
+
+	// To check if the microphone settings were 'remembered', we
+	// must actually embed an entirely new Wami client and check
+	// whether its microphone is granted. If it is, it was remembered.
+	function checkRemembered(finishedfn) {
+		var id = Wami.createID();
+		var div = document.createElement('div');
+		div.style.top = '-999px';
+		div.style.left = '-999px';
+		div.setAttribute('id', id);
+		var body = document.getElementsByTagName('body').item(0);
+		body.appendChild(div);
+
+		var fn = Wami.nameCallback(function() {
+			var swf = document.getElementById(id);
+			Wami._remembered = swf.getSettings().microphone.granted;
+			Wami.swfobject.removeSWF(id);
+			eval(finishedfn + "()");
+		});
+
+		embedWamiSWF(id, fn);
+	}
+
+	// Attach all the audio methods to the Wami namespace in the callback.
+	function delegateWamiAPI() {
+		var recorder = document.getElementById(_options.id);
+
+		function delegate(name) {
+			Wami[name] = function() {
+				return recorder[name].apply(recorder, arguments);
+			}
+		}
+		delegate('startPlaying');
+		delegate('stopPlaying');
+		delegate('startRecording');
+		delegate('stopRecording');
+		delegate('startListening');
+		delegate('stopListening');
+		delegate('getRecordingLevel');
+		delegate('getPlayingLevel');
+		delegate('setSettings');
+
+		// Append extra information about whether mic settings are sticky
+		Wami.getSettings = function() {
+			var settings = recorder.getSettings();
+			settings.microphone.remembered = Wami._remembered;
+			return settings;
+		}
+
+		Wami.showSecurity = function(panel, startfn, finishedfn, failfn) {
+			// Flash must be on top for this.
+			var container = document.getElementById(_options.cid);
+
+			var augmentedfn = Wami.nameCallback(function() {
+				checkRemembered(finishedfn);
+				container.style.cssText = "position: absolute;";
+			});
+
+			container.style.cssText = "position: absolute; z-index: 99999";
+
+			recorder.showSecurity(panel, startfn, augmentedfn, failfn);
+		}
+
+		Wami.show = function() {
+			if (!supportsTransparency()) {
+				recorder.style.visibility = "visible";
+			}
+		}
+
+		Wami.hide = function() {
+			// Hiding flash in all the browsers is tricky. Please read:
+			// https://code.google.com/p/wami-recorder/wiki/HidingFlash
+			if (!supportsTransparency()) {
+				recorder.style.visibility = "hidden";
+			}
+		}
+
+		// If we already have permissions, they were previously 'remembered'
+		Wami._remembered = recorder.getSettings().microphone.granted;
+
+		if (_options.onLoaded) {
+			_options.onLoaded();
+		}
+
+		if (!_options.noSecurityCheck) {
+			checkSecurity();
+		}
+	}
+}

+ 12 - 0
main/inc/lib/wami-recorder/example/server/README.txt

@@ -0,0 +1,12 @@
+Each directory (or sub-directory of 'gae') contains an example server
+in a particular language designed to handle audio incoming from the
+Wami recorder.
+
+The PHP example requires the configuration of a PHP-enabled
+web-server, while the python server can be run on any machine with
+python installed.
+
+If you do not wish to host your own server, you can try out the Google
+App Engine (GAE) examples.  The GAE python example stores data in a
+"blobstore", for which Google provides a handy web-based management
+console.

+ 9 - 0
main/inc/lib/wami-recorder/example/server/php/record.php

@@ -0,0 +1,9 @@
+<?php
+# Save the audio to a URL-accessible directory for playback.
+parse_str($_SERVER['QUERY_STRING'], $params);
+$name = isset($params['name']) ? $params['name'] : 'output.wav';
+$content = file_get_contents('php://input');
+$fh = fopen($name, 'w') or die("can't open file");
+fwrite($fh, $content);
+fclose($fh);
+?>

+ 53 - 0
main/inc/lib/wami-recorder/example/server/python/server.py

@@ -0,0 +1,53 @@
+# Run from the commandline:
+#
+# python server.py
+# POST audio to   http://localhost:9000
+# GET audio from  http://localhost:9000
+#
+# A simple server to collect audio using python.  To be more secure,
+# you might want to check the file names and place size restrictions
+# on the incoming data.
+
+import cgi
+from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+
+class WamiHandler(BaseHTTPRequestHandler):
+    dirname = "/tmp/"
+    
+    def do_GET(self):
+        f = open(self.get_name())
+        self.send_response(200)
+        self.send_header('content-type','audio/x-wav')
+        self.end_headers()
+        self.wfile.write(f.read())
+        f.close()
+
+    def do_POST(self):
+        f = open(self.get_name(), "wb")
+        # Note that python's HTTPServer doesn't support chunked transfer.
+        # Thus, it requires a content-length.
+        length = int(self.headers.getheader('content-length'))
+        print "POST of length " + str(length)
+        f.write(self.rfile.read(length))
+        f.close();
+
+    def get_name(self):
+        filename = 'output.wav';
+        qs = self.path.split('?',1);
+        if len(qs) == 2:
+            params = cgi.parse_qs(qs[1])
+            if params['name']:
+                filename = params['name'][0];
+        return WamiHandler.dirname + filename
+
+def main():
+    try:
+        server = HTTPServer(('', 9000), WamiHandler)
+        print 'Started server...'
+        server.serve_forever()
+    except KeyboardInterrupt:
+        print 'Stopping server'
+        server.socket.close()
+
+if __name__ == '__main__':
+    main()

+ 9 - 0
main/inc/lib/wami-recorder/example/wami-recorder.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- This is just a Google gadget to toss an example onto the code.google.com page. -->
+<Module>
+  <ModulePrefs title="Wami Recorder Example"
+               author="Ian McGraw"
+               author_email="wami-toolkit@csail.mit.edu"
+               description="A Google gadget to exemplify the simplest wami-recorder use-case." />
+  <Content type="url" href="https://wami-recorder.appspot.com/client/index.html"/>
+</Module>

+ 171 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/audio/AuContainer.as

@@ -0,0 +1,171 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.audio
+{
+	import edu.mit.csail.wami.utils.External;
+	
+	import flash.utils.ByteArray;
+	import flash.utils.Endian;
+	
+	/**
+	 * This container is better for streaming, because it explicitly
+	 * says what to do when the length of the audio is unknown.  It's typically
+	 * associated with mu-law compression (which wouldn't be too hard too implement)
+	 * but here we're using linear PCM.
+	 */
+	public class AuContainer implements IAudioContainer
+	{
+		public function isLengthRequired():Boolean {
+			return false;
+		}
+		
+		public function toByteArray(format:AudioFormat, length:int = -1):ByteArray
+		{
+			var dataLength:uint = 0xffffffff;
+			if (length > -1) 
+			{
+				dataLength = length;
+			}
+			
+			if (format.endian != Endian.BIG_ENDIAN) 
+			{
+				throw new Error("AU is a container for big endian data");
+			}
+			// http://en.wikipedia.org/wiki/Au_file_format			
+			var header:ByteArray = new ByteArray();
+			header.endian = format.endian;
+			header.writeUTFBytes(".snd");
+			header.writeInt(24);               // Data offset
+			header.writeInt(dataLength);
+			
+			var bits:uint = getEncodingFromBits(format);
+			header.writeInt(bits);
+			header.writeInt(format.rate);	
+			header.writeInt(format.channels);
+			header.position = 0;
+			External.debugBytes(header);
+			return header;
+		}	
+		
+		private function getEncodingFromBits(format:AudioFormat):uint
+		{
+			if (format.bits == 16) 
+			{
+				return 3;
+			}
+			else if (format.bits == 24) 
+			{
+				return 4;
+			}
+			else if (format.bits == 32) 
+			{
+				return 5;
+			}
+			
+			throw new Error("Bits not supported");
+		}
+		
+		private function getBitsFromEncoding(encoding:uint):uint
+		{
+			if (encoding == 3) 
+			{
+				return 16;
+			}
+			else if (encoding == 4) 
+			{
+				return 24;
+			}
+			else if (encoding == 5) 
+			{
+				return 32;
+			}
+			
+			throw new Error("Encoding not supported: " + encoding);
+		}
+		
+		public function fromByteArray(header:ByteArray):AudioFormat
+		{
+			if (header.bytesAvailable < 24)
+			{
+				return notAu(header, "Header not yet long enough for Au");
+			}
+			
+			var b:ByteArray = new ByteArray();
+			header.readBytes(b, 0, 24);
+			External.debugBytes(b);
+			header.position = 0;
+			
+			header.endian = Endian.BIG_ENDIAN;     // Header is big-endian 
+
+			var magic:String = header.readUTFBytes(4);
+			if (magic != ".snd")
+			{
+				return notAu(header, "Not an AU header, first bytes should be .snd");
+			}
+			
+			var dataOffset:uint = header.readInt();  
+			var dataLength:uint = header.readInt();  
+
+			if (header.bytesAvailable < dataOffset - 12)
+			{
+				return notAu(header, "Header of length " + header.bytesAvailable + " not long enough yet to include offset of length " + dataOffset);
+			}
+			
+			var encoding:uint = header.readInt();
+
+			var bits:uint;
+			try {
+				bits = getBitsFromEncoding(encoding);
+			} catch (e:Error) {
+				return notAu(header, e.message);
+			}
+
+			var rate:uint = header.readInt();
+			var channels:uint = header.readInt();
+
+			header.position = dataOffset;
+			
+			var format:AudioFormat;
+			try 
+			{
+				format = new AudioFormat(rate, channels, bits, Endian.BIG_ENDIAN);
+			} catch (e:Error) 
+			{
+				return notAu(header, e.message);
+			}
+			
+			return format;
+		}
+		
+		private function notAu(header:ByteArray, msg:String):AudioFormat
+		{
+			External.debug("Not Au: " + msg);
+			header.position = 0;
+			return null;
+		}
+	}
+}

+ 158 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/audio/AudioFormat.as

@@ -0,0 +1,158 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.audio
+{
+	import flash.utils.Endian;
+	
+	/**
+	 * This class keeps track of all the information that defines the 
+	 * audio format independent of the actual audio container 
+	 * (e.g. .wav or .au)
+	 */
+	public class AudioFormat
+	{
+		// Allow 8 and 16 kHz as well.
+		public static var allrates:Boolean = false;
+
+		public var channels:uint;
+		public var rate:uint;
+		public var bits:uint;
+		public var endian:String; 
+		
+		public function AudioFormat(rate:uint, channels:uint, bits:uint, endian:String)
+		{
+			this.rate = rate;
+			this.channels = channels;
+			this.endian = endian;
+			this.bits = bits;
+			
+			validate();
+		}
+
+		// flash.media.Microphone quasi-rounds sample rates in kHz
+		public static function toRoundedRate(rate:uint):uint
+		{
+			if (rate == 5512)
+			{
+				return 5;
+			}
+			else if (rate == 8000)
+			{
+				return 8;
+			}
+			else if (rate == 11025)
+			{
+				return 11;
+			}
+			else if (rate == 16000)
+			{
+				return 16;
+			}
+			else if (rate == 22050)
+			{
+				return 22;
+			}
+			else if (rate == 44100)
+			{
+				return 44;
+			}
+			
+			throw new Error("Unsupported sample rate in Hz: " + rate);
+		}
+		
+		
+		public static function fromRoundedRate(rate:uint):uint
+		{
+			if (rate == 5)
+			{
+				return 5512;
+			}
+			else if (rate == 8)
+			{
+				return 8000;
+			}
+			else if (rate == 11)
+			{
+				return 11025;
+			}
+			else if (rate == 16)
+			{
+				return 16000;
+			}
+			else if (rate == 22)
+			{
+				return 22050;
+			}
+			else if (rate == 44)
+			{
+				return 44100;
+			}
+			
+			throw new Error("Unsupported sample rate rounded in kHz: " + rate);
+		}
+		
+		public function validate():void
+		{
+			if (bits != 8 && bits != 16 && bits != 32)
+			{
+				throw new Error("Unsupported number of bits per sample: " + bits);
+			}
+			
+			if (channels != 1 && channels != 2)
+			{
+				throw new Error("Unsupported number of channels: " + channels);
+			}
+			
+			if (endian != Endian.BIG_ENDIAN && endian != Endian.LITTLE_ENDIAN)
+			{
+				throw new Error("Unsupported endian type: " + endian);
+			}
+			
+			var msg:String = "";
+			if (rate < 100) 
+			{
+				throw new Error("Rate should be in Hz");	
+			} 
+			else if (rate != 5512 && rate != 8000 && rate != 11025 && rate != 16000 && rate != 22050 && rate != 44100)
+			{
+				msg = "Sample rate of " + rate + " is not supported.";
+				msg += " See flash.media.Microphone documentation."
+				throw new Error(msg);
+			}
+			else if (!allrates && (rate == 8000 || rate == 16000 || rate == 11025)) {
+				msg = "8kHz and 16kHz are supported for recording but not playback.  11kHz doesn't work in Ubuntu.";
+				msg += "  Enable all rates via a parameter passed into the Flash."
+				throw new Error(msg);
+			}
+		}
+		
+		public function toString():String
+		{
+			return "Rate: " + rate + " Channels " + channels + " Bits: " + bits + " Endian: " + endian;
+		}
+	}
+}

+ 125 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/audio/DecodePipe.as

@@ -0,0 +1,125 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.audio
+{
+	import edu.mit.csail.wami.audio.AudioFormat;
+	import edu.mit.csail.wami.audio.IAudioContainer;
+	import edu.mit.csail.wami.utils.External;
+	import edu.mit.csail.wami.utils.Pipe;
+	
+	import flash.utils.ByteArray;
+	import flash.utils.Endian;
+	
+	/**
+	 * Convert WAVE data coming in to the float-based format flash uses.
+	 */
+	public class DecodePipe extends Pipe
+	{
+		private var format:AudioFormat;
+		private var header:ByteArray = new ByteArray();
+		private var containers:Vector.<IAudioContainer>;
+		
+		public function DecodePipe(containers:Vector.<IAudioContainer>) {
+			if (containers.length == 0) {
+				throw new Error("Must have at least one container.");
+			}
+			this.containers = containers;
+		}
+		
+		override public function write(bytes:ByteArray):void
+		{
+			if (format == null) 
+			{
+				// Try to get header by parsing from each container
+				bytes.readBytes(header, header.length, bytes.length);
+				for each (var container:IAudioContainer in containers) {
+					format = container.fromByteArray(header);
+					if (format != null) {
+						// Put the leftover bytes back
+						bytes = new ByteArray();
+						header.readBytes(bytes); 
+						External.debug("Format: " + format);
+						break;
+					}
+				}
+			}
+			
+			if (format != null && bytes.bytesAvailable)
+			{
+				bytes.endian = format.endian;
+				super.write(decode(bytes));
+			}
+		}
+		
+		private function decode(bytes:ByteArray):ByteArray
+		{
+			var decoded:ByteArray = new ByteArray();
+			while (bytes.bytesAvailable)
+			{
+				var sample1:Number = getSample(bytes);
+				var sample2:Number = sample1;
+				if (format.channels == 2)
+				{
+					sample2 = getSample(bytes);
+				}
+
+				// cheap way to upsample
+				var repeat:uint = 44100 / format.rate;
+				while (repeat-- > 0) {
+					decoded.writeFloat(sample1);
+					decoded.writeFloat(sample2);
+				}
+			}
+			decoded.position = 0;
+			return decoded;
+		}
+		
+		private function getSample(bytes:ByteArray):Number
+		{
+			var sample:Number;
+			
+			if (format.bits == 8)
+			{
+				sample = bytes.readByte()/0x7f;
+			}
+			else if (format.bits == 16)
+			{
+				sample = bytes.readShort()/0x7fff;
+			}
+			else if (format.bits == 32)
+			{
+				sample = bytes.readInt()/0x7fffffff;
+			}
+			else
+			{
+				throw new Error("Unsupported bits per sample: " + format.bits);
+			}
+			
+			return sample;
+		}
+	}
+}

+ 126 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/audio/EncodePipe.as

@@ -0,0 +1,126 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.audio
+{
+	import edu.mit.csail.wami.audio.AudioFormat;
+	import edu.mit.csail.wami.audio.IAudioContainer;
+	import edu.mit.csail.wami.utils.Pipe;
+	
+	import flash.utils.ByteArray;
+
+	/**
+	 * Convert float format to raw audio accoring to the audio format passed in.
+	 */
+	public class EncodePipe extends Pipe
+	{
+		private var format:AudioFormat;
+		private var container:IAudioContainer;
+		
+		// Buffer if container requires length.  In this case,
+		// we cannot write the data to the sink until the very end.
+		private var buffer:ByteArray;          
+		private var headerWritten:Boolean;
+		
+		function EncodePipe(format:AudioFormat, container:IAudioContainer)
+		{
+			this.format = format;
+			this.container = container;
+			this.buffer = new ByteArray();
+			headerWritten = false;
+		}
+		
+		override public function write(bytes:ByteArray):void
+		{
+			var transcoded:ByteArray = new ByteArray();
+			transcoded.endian = format.endian;
+
+			while (bytes.bytesAvailable >= 4)
+			{
+				var sample:int;
+				if (format.bits == 16)
+				{
+				 	sample = bytes.readFloat()*0x7fff;
+					transcoded.writeShort(sample);
+					if (format.channels == 2)
+					{
+						transcoded.writeShort(sample);
+					}
+				}
+				else if (format.bits == 32)
+				{
+					sample = bytes.readFloat()*0x7fffffff;
+					transcoded.writeInt(sample);
+					if (format.channels == 2)
+					{
+						transcoded.writeInt(sample);
+					}					
+				}
+				else
+				{
+					throw new Error("Unsupported bits per sample: " + format.bits);
+				}
+			}
+			transcoded.position = 0;
+			handleEncoded(transcoded);
+		}
+
+		private function handleEncoded(bytes:ByteArray):void {
+			if (container == null) {
+				// No container, just stream it on
+				super.write(bytes);
+				return;
+			}
+
+			if (container.isLengthRequired()) 
+			{
+				buffer.writeBytes(bytes, bytes.position, bytes.bytesAvailable);
+				return;
+			} 
+
+			if (!headerWritten) 
+			{
+				var header:ByteArray = container.toByteArray(format);
+				super.write(header);
+				headerWritten = true;
+			}
+			super.write(bytes);
+		}
+		
+		override public function close():void
+		{
+			if (container != null && container.isLengthRequired())
+			{
+				// Write the audio (including the header).
+				buffer.position = 0;
+				super.write(container.toByteArray(format, buffer.length));
+				super.write(buffer);
+			}
+			
+			super.close();
+		}
+	}
+}

+ 54 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/audio/IAudioContainer.as

@@ -0,0 +1,54 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.audio
+{
+	import flash.utils.ByteArray;
+	
+	/**
+	 * There are a number of ways to store raw audio.  WAV is a container from
+	 * Microsoft.  AU is a container from Sun Microsystems.  This interface
+	 * helps us separate the container format from the audio format itself.
+	 */
+	public interface IAudioContainer
+	{
+		function toByteArray(format:AudioFormat, length:int = -1):ByteArray;
+
+		/**
+		 * If successful, the position is left at the first byte after
+		 * the header.  If the bytes do not represent the expected container
+		 * header null is returned and the position is returned to 0.
+		 */
+		function fromByteArray(bytes:ByteArray):AudioFormat;
+		
+		/**
+		 * Some containers (e.g. WAV) require the length of the data to be specified,
+		 * and thus are not amenable to streaming.  Others (e.g. AU) have well
+		 * defined ways of dealing with data of unknown length.
+		 */
+		function isLengthRequired():Boolean;
+	}
+}

+ 131 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/audio/WaveContainer.as

@@ -0,0 +1,131 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.audio
+{
+	import edu.mit.csail.wami.utils.External;
+	
+	import flash.utils.ByteArray;
+	import flash.utils.Endian;
+	
+	/**
+	 * This class builds a WAVE header formt the audio format.
+	 */
+	public class WaveContainer implements IAudioContainer
+	{
+		public function isLengthRequired():Boolean {
+			return true;
+		}
+		
+		public function toByteArray(audioFormat:AudioFormat, length:int = -1):ByteArray
+		{
+			// https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
+			var id:String = (audioFormat.endian == Endian.LITTLE_ENDIAN) ? "RIFF" : "RIFX";
+			var bytesPerSample:uint = audioFormat.channels*audioFormat.bits/8;
+			
+			var header:ByteArray = new ByteArray();
+			
+			// Little-endian is generally the way to go for WAVs
+			header.endian = Endian.LITTLE_ENDIAN;
+			header.writeUTFBytes(id);
+			header.writeInt(length > 0 ? 36 + length : 0);
+			header.writeUTFBytes("WAVE");
+			header.writeUTFBytes("fmt ");
+			header.writeInt(16);
+			header.writeShort(1);
+			header.writeShort(audioFormat.channels);
+			header.writeInt(audioFormat.rate);
+			header.writeInt(audioFormat.rate*bytesPerSample);
+			header.writeShort(bytesPerSample);
+			header.writeShort(audioFormat.bits);
+			header.writeUTFBytes('data');
+			header.writeInt(length);
+			header.position = 0;
+			return header;
+		}	
+		
+		public function fromByteArray(header:ByteArray):AudioFormat
+		{
+			if (header.bytesAvailable < 44) {
+				var msg:String = "This header is not yet long enough ";
+				msg += "(need 44 bytes only have " + header.bytesAvailable + ")."
+				return notWav(header, msg);
+			}
+			
+			var endian:String = Endian.LITTLE_ENDIAN;
+			var chunkID:String = header.readUTFBytes(4);     
+			if (chunkID == "RIFX")
+			{
+				endian = Endian.BIG_ENDIAN;
+			}
+			else if (chunkID != "RIFF")
+			{
+				return notWav(header, "Does not look like a WAVE header: " + chunkID);
+			}
+			
+			header.endian = Endian.LITTLE_ENDIAN;                 // Header is little-endian 
+			var totalLength:uint = header.readInt() + 8;
+			var waveFmtStr:String = header.readUTFBytes(8);       // "WAVEfmt "
+			if (waveFmtStr != "WAVEfmt ") 
+			{
+				return notWav(header, "RIFF header, but not a WAV.");
+			}
+			var subchunkSize:uint = header.readUnsignedInt();     // 16
+			var audioFormat:uint = header.readShort();            // 1
+			if (audioFormat != 1) {
+				return notWav(header, "Currently we only support linear PCM");
+			}
+			var channels:uint = header.readShort();
+			var rate:uint = header.readInt();
+			var bps:uint = header.readInt();
+			var bytesPerSample:uint = header.readShort();
+			var bits:uint = header.readShort();
+			var dataStr:String = header.readUTFBytes(4);          // "data"
+			var length:uint = header.readInt();
+			
+			var format:AudioFormat;
+			try 
+			{
+				format = new AudioFormat(rate, channels, bits, endian);
+			} catch (e:Error) 
+			{
+				return notWav(header, e.message);
+			}
+			return format;
+		}
+
+		/**
+		 * Emit error message for debugging, reset the ByteArray and 
+		 * return null.
+		 */
+		private function notWav(header:ByteArray, msg:String):AudioFormat
+		{
+			External.debug("Not WAV: " + msg);
+			header.position = 0;
+			return null;
+		}
+	}
+}

File diff suppressed because it is too large
+ 1 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/client/FlashSettings.as


+ 104 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/client/Wami.mxml

@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<mx:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
+				xmlns:mx="library://ns.adobe.com/flex/mx" 
+				minWidth="1" minHeight="1" backgroundAlpha="0"
+				applicationComplete="applicationCompleteHandler(event)">
+	<!--- 
+	The WAMI recorder is not meant to be seen, except for when the security panel
+	is in view.  There is, however, an interface for the times you want to run it
+	in a debugger.
+	-->
+	<fx:Script>
+		<![CDATA[			
+			import edu.mit.csail.wami.utils.External;
+			
+			import mx.controls.Button;
+			import mx.controls.Label;
+			import mx.core.UIComponent;
+			
+			private function applicationCompleteHandler(event:Event):void {
+				External.debug('WAMI Flash Version 1.0: ' + Security.sandboxType);
+				Security.allowInsecureDomain("*");
+				var params:WamiParams = new WamiParams(LoaderInfo(this.root.loaderInfo).parameters);
+				
+				var audio:WamiAudio = new WamiAudio(params);
+				var settings:FlashSettings = new FlashSettings(stage);
+
+				if (params.visible) 
+				{
+					showDebugInterface(params, audio);
+				}
+				
+				External.call(params.loadedCallback);
+			}
+			
+			private function showDebugInterface(params:WamiParams, audio:WamiAudio):void
+			{
+				this.horizontalScrollPolicy = "off";
+				this.verticalScrollPolicy = "off";
+				
+				var label:Label = new Label();
+				var interval:uint;
+				
+				var recordButton:Button = new Button();
+				recordButton.label = "Record";
+				addElement(recordButton);
+				recordButton.addEventListener(MouseEvent.CLICK, function(evt:MouseEvent):void
+				{
+					audio.startRecording(params.testRecordUrl);
+					label.setStyle("color", "#500000");
+					interval = setInterval(function():void
+					{
+						label.text = "Activity: " + audio.getRecordingLevel();	
+					}, 250);
+				});
+				
+				var stopButton:Button = new Button();
+				stopButton.label = "Stop";
+				addElement(stopButton);
+				stopButton.addEventListener(MouseEvent.CLICK, function(evt:MouseEvent):void
+				{
+					if (interval) clearInterval(interval);
+					interval = 0;
+					label.text = "";
+					audio.stopRecording();
+					audio.stopPlaying();
+				});
+				
+				var playButton:Button = new Button();
+				playButton.label = "Play";
+				addElement(playButton);
+				var uic:UIComponent = new UIComponent();
+				var video:Video = new Video();
+				uic.addChild(video);
+				addChild(uic);
+				playButton.addEventListener(MouseEvent.CLICK, function(evt:MouseEvent):void
+				{
+					function ns_onMetaData(item:Object):void {
+						trace("metaData");
+						// Resize video instance.
+						video.width = item.width;
+						video.height = item.height;
+						// Center video instance on Stage.
+						video.x = (stage.stageWidth - video.width) / 2;
+						video.y = (stage.stageHeight - video.height) / 2;
+					}
+					
+					function ns_onCuePoint(item:Object):void {
+						trace("cuePoint");
+						trace(item.name + "\t" + item.time);
+					}
+
+					audio.startPlaying(params.testPlayUrl);
+					label.setStyle("color", "#000500");
+					interval = setInterval(function():void
+					{
+						label.text = "Activity: " + audio.getPlayingLevel();	
+					}, 250);
+				});
+				
+				addChild(label);
+			}
+		]]>
+	</fx:Script>
+</mx:Application>

+ 109 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/client/WamiAudio.as

@@ -0,0 +1,109 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.client 
+{
+	import edu.mit.csail.wami.play.IPlayer;
+	import edu.mit.csail.wami.play.WamiPlayer;
+	import edu.mit.csail.wami.record.IRecorder;
+	import edu.mit.csail.wami.record.WamiRecorder;
+	import edu.mit.csail.wami.utils.External;
+	
+	import flash.display.MovieClip;
+
+	public class WamiAudio extends MovieClip
+	{
+		private var recorder:IRecorder;
+		private var player:IPlayer;
+		
+		private var checkSettingsIntervalID:int = 0;
+		private var checkSettingsInterval:int = 1000;
+
+		function WamiAudio(params:WamiParams)
+		{
+			recorder = new WamiRecorder(params.getMicrophone(), params);
+			player = new WamiPlayer();
+			
+			External.addCallback("startListening", startListening);
+			External.addCallback("stopListening", stopListening);
+			External.addCallback("startRecording", startRecording);
+			External.addCallback("stopRecording",stopRecording);
+			External.addCallback("getRecordingLevel", getRecordingLevel);
+			
+			External.addCallback("startPlaying",startPlaying);
+			External.addCallback("stopPlaying",stopPlaying);
+			External.addCallback("getPlayingLevel", getPlayingLevel);
+		}
+		
+		internal function startPlaying(url:String, 
+									   startedCallback:String = null, 
+									   finishedCallback:String = null,
+									   failedCallback:String = null):void
+		{
+			recorder.stop(true);
+			player.start(url, new WamiListener(startedCallback, finishedCallback, failedCallback));
+		}
+		
+		internal function stopPlaying():void
+		{
+			player.stop();
+		}
+		
+		internal function getPlayingLevel():int
+		{
+			return player.level();
+		}
+		
+		private function startListening(paddingMillis:uint = 200):void
+		{
+			recorder.listen(paddingMillis);
+		}
+		
+		private function stopListening():void
+		{
+			recorder.unlisten();
+		}
+		
+		internal function startRecording(url:String,
+										 startedCallback:String = null, 
+										 finishedCallback:String = null, 
+										 failedCallback:String = null):void
+		{
+			recorder.start(url, new WamiListener(startedCallback, finishedCallback, failedCallback));
+		}
+		
+		internal function stopRecording():void
+		{
+			recorder.stop();
+		}
+		
+		internal function getRecordingLevel():int
+		{
+			return recorder.level();
+		}
+	}
+}
+

+ 61 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/client/WamiListener.as

@@ -0,0 +1,61 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.client
+{
+	import edu.mit.csail.wami.utils.External;
+	import edu.mit.csail.wami.utils.StateListener;
+
+	/**
+	 * Translates audio events into Javascript callbacks.
+	 */
+	public class WamiListener implements StateListener
+	{
+		private var startCallback:String, finishedCallback:String, failedCallback:String;
+		
+		function WamiListener(startCallback:String, finishedCallback:String, failedCallback:String) 
+		{
+			this.startCallback = startCallback;
+			this.finishedCallback = finishedCallback;
+			this.failedCallback = failedCallback;
+		}
+		
+		public function started():void
+		{
+			External.call(startCallback);
+		}
+		
+		public function finished():void
+		{
+			External.call(finishedCallback);
+		}
+		
+		public function failed(error:Error):void
+		{
+			External.call(failedCallback, error.message);
+		}
+	}
+}

+ 180 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/client/WamiParams.as

@@ -0,0 +1,180 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.client
+{	
+	import edu.mit.csail.wami.audio.AudioFormat;
+	import edu.mit.csail.wami.utils.External;
+	
+	import flash.media.Microphone;
+	import flash.utils.ByteArray;
+	import flash.utils.Endian;
+	
+	/**
+	 * A class documents the possible parameters and sets a few defaults.
+	 * The defaults are set up to stream to localhost.
+	 */
+	public class WamiParams
+	{
+		private var mic:Microphone;
+		
+		// Show the debug interface.
+		public var visible:Boolean = true;
+
+		// Append this many milliseconds of audio before 
+		// and after calls to startRecording/stopRecording.
+		public var paddingMillis:uint = 250;
+
+		// Send the audio using multiple HTTP Posts.
+		public var stream:Boolean = false;
+				
+		// The URLs used in the debugging interface.
+		public var testRecordUrl:String = "https://wami-recorder.appspot.com/audio";
+		public var testPlayUrl:String = "https://wami-recorder.appspot.com/audio";
+
+		// Callbacks for loading the client.
+		public var loadedCallback:String;
+		public var format:AudioFormat;
+	
+		public function WamiParams(params:Object):void
+		{
+			mic = Microphone.getMicrophone();
+			
+			External.addCallback("setSettings", setSettings);
+			External.addCallback("getSettings", getSettings);
+			
+			if (params.stream != undefined)
+			{
+				stream = params.stream == "true";
+			}
+			
+			if (params.visible != undefined) 
+			{
+				visible = params.visible == "true";
+			}
+			
+			if (params.console != undefined) {
+				External.debugToConsole = params.console == "true";
+			}
+			
+			// Override to allow recording at 8000 and 16000 as well.
+			// Note that playback at these sample-rates will be sped up.
+			if (params.allrates != undefined) {
+				AudioFormat.allrates = params.allrates == "true";
+			}
+			
+			loadedCallback = params.loadedCallback;
+
+			var rate:uint = 22050;
+			if (params.rate != undefined) 
+			{
+				rate = uint(params.rate);
+			}
+			format = new AudioFormat(rate, 1, 16, Endian.LITTLE_ENDIAN);
+		}
+		
+		public function getMicrophone():Microphone {
+			return mic;
+		}
+		
+		// Settings (including microphone security) are passed back here.
+		internal function getSettings():Object
+		{
+			var json:Object = {
+				"container" : (stream) ? "au" : "wav",
+				"encoding" : "pcm",
+				"signed" : true,
+				"sampleSize" : format.bits,
+				"bigEndian" : format.endian == Endian.BIG_ENDIAN,
+				"sampleRate" : format.rate,
+				"numChannels" : format.channels,
+				"interleaved" : true,
+				"microphone" : {
+					"granted" : (mic != null && !mic.muted)
+				}
+			};
+			
+			return json;
+		}
+		
+		internal function setSettings(json:Object):void
+		{	
+			if (json) 
+			{
+				// For now the type also specifies streaming or not.
+				if (json.container == "au")
+				{
+					stream = true;
+				}
+				else if (json.container == "wav") 
+				{
+					stream = false;
+				}
+
+				if (json.encoding) 
+				{
+					throw new Error("Encodings such as mu-law could be implemented.");	
+				}
+				
+				if (json.signed)
+				{
+					throw new Error("Not implemented yet.");	
+				}
+				
+				if (json.bigEndian) 
+				{
+					throw new Error("Automatically determined.");
+				}
+				
+				if (json.numChannels) 
+				{
+					format.channels = json.numChannels;	
+				}
+				
+				if (json.sampleSize) 
+				{
+					format.bits = json.sampleSize;	
+				}
+				
+				if (json.interleaved)
+				{
+					throw new Error("Always true.");
+				}
+				
+				if (json.microphone)
+				{
+					throw new Error("Only the user can change the microphone security settings.");	
+				}
+				
+				if (json.sampleRate) 
+				{
+					format.rate = json.sampleRate;
+				}
+				
+			}
+		}
+	}
+}

+ 39 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/play/IPlayer.as

@@ -0,0 +1,39 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.play
+{
+	import edu.mit.csail.wami.utils.StateListener;
+
+	public interface IPlayer
+	{
+		function start(url:String, listener:StateListener):void; 
+		function stop():void;
+		
+		// Audio level (between 0 and 100)
+		function level():int; 
+	}
+}

+ 179 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/play/WamiPlayer.as

@@ -0,0 +1,179 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.play
+{
+	import edu.mit.csail.wami.audio.AuContainer;
+	import edu.mit.csail.wami.audio.DecodePipe;
+	import edu.mit.csail.wami.audio.IAudioContainer;
+	import edu.mit.csail.wami.audio.WaveContainer;
+	import edu.mit.csail.wami.utils.BytePipe;
+	import edu.mit.csail.wami.utils.External;
+	import edu.mit.csail.wami.utils.Pipe;
+	import edu.mit.csail.wami.utils.StateListener;
+	
+	import flash.events.Event;
+	import flash.events.HTTPStatusEvent;
+	import flash.events.IOErrorEvent;
+	import flash.events.ProgressEvent;
+	import flash.events.SampleDataEvent;
+	import flash.events.SecurityErrorEvent;
+	import flash.media.Sound;
+	import flash.media.SoundChannel;
+	import flash.net.URLLoader;
+	import flash.net.URLLoaderDataFormat;
+	import flash.net.URLRequest;
+	import flash.net.URLRequestMethod;
+	import flash.utils.ByteArray;
+	
+	public class WamiPlayer implements IPlayer
+	{
+		private var currentChannel:SoundChannel = null;
+		private var currentAudio:ByteArray;
+		private var listener:StateListener;
+		
+		public function start(url:String, listener:StateListener):void
+		{
+			this.listener = listener;
+			var loader:URLLoader = new URLLoader();
+			loader.dataFormat = URLLoaderDataFormat.BINARY;
+			
+			loader.addEventListener(Event.COMPLETE, completeHandler);
+			loader.addEventListener(Event.OPEN, openHandler);
+			loader.addEventListener(ProgressEvent.PROGRESS, progressHandler);
+			loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
+			loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
+			loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
+			
+			var request:URLRequest = new URLRequest(url);
+			request.method = URLRequestMethod.GET;
+			
+			try {
+				loader.load(request);
+			} catch (error:Error) {
+				listener.failed(error);
+			}
+			
+			function completeHandler(event:Event):void {
+				listener.started();
+				
+				loader.removeEventListener(Event.COMPLETE, completeHandler);
+				loader.removeEventListener(Event.OPEN, openHandler);
+				loader.removeEventListener(ProgressEvent.PROGRESS, progressHandler);
+				loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
+				loader.removeEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
+				loader.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler)
+				
+				play(loader.data);
+			}
+			
+			function openHandler(event:Event):void {
+				External.debug("openHandler: " + event);
+			}
+			
+			function progressHandler(event:ProgressEvent):void {
+				//External.debug("progressHandler loaded:" + event.bytesLoaded + " total: " + event.bytesTotal);
+			}
+			
+			function securityErrorHandler(event:SecurityErrorEvent):void {
+				listener.failed(new Error("Security error while playing: " + event.errorID));
+			}
+			
+			function httpStatusHandler(event:HTTPStatusEvent):void {
+				External.debug("httpStatusHandler: " + event);
+			}
+			
+			function ioErrorHandler(event:IOErrorEvent):void {
+				listener.failed(new Error("IO error while playing: " + event.errorID));
+			}	
+		}
+		
+		public function stop():void
+		{
+			if (currentChannel != null) 
+			{
+				External.debug("Stop playing.");
+				currentChannel.removeEventListener(Event.SOUND_COMPLETE, stop);
+				currentChannel.stop();
+				External.debug("Listener finished.");
+				listener.finished();
+				currentChannel = null;
+			}
+		}
+		
+		public function level():int
+		{
+			if (currentChannel != null) {
+				return 100 * ((currentChannel.leftPeak + currentChannel.rightPeak) / 2.0);
+			}
+			
+			return 0;
+		}
+		
+		protected function play(audio:ByteArray):void
+		{
+			stop();  // Make sure we're stopped
+			
+			var containers:Vector.<IAudioContainer> = new Vector.<IAudioContainer>();
+			containers.push(new WaveContainer());
+			containers.push(new AuContainer());
+
+			External.debug("Playing audio of " + audio.length + " bytes.");
+			var decoder:Pipe = new DecodePipe(containers);
+			var pipe:BytePipe = new BytePipe();
+			decoder.setSink(pipe);
+			decoder.write(audio);
+			decoder.close();
+			
+			currentAudio = pipe.getByteArray();
+			External.debug("Playing audio with " + currentAudio.length/4 + " samples.");
+			
+			var sound:Sound = new Sound();
+			sound.addEventListener(SampleDataEvent.SAMPLE_DATA, handleSampleEvent);
+
+			currentChannel = sound.play();
+			currentChannel.addEventListener(Event.SOUND_COMPLETE, function(event:Event):void {
+				sound.removeEventListener(SampleDataEvent.SAMPLE_DATA, handleSampleEvent);
+				stop();
+			});
+		}
+		
+		private function handleSampleEvent(event:SampleDataEvent):void
+		{
+			if (currentAudio == null) return;
+			
+			var MAX_SAMPLES_PER_EVENT:uint = 4000;
+			var count:uint = 0;
+			// External.debug("Audio " + currentAudio.bytesAvailable +  " " + event.data.endian);
+			while (currentAudio.bytesAvailable && count < MAX_SAMPLES_PER_EVENT) 
+			{
+				event.data.writeFloat(currentAudio.readFloat());
+				event.data.writeFloat(currentAudio.readFloat());
+				count += 1;
+			}
+		}
+	}
+}

+ 5 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/record/ChunkPipe.as

@@ -0,0 +1,5 @@
+/* 
* Copyright (c) 2011
* Spoken Language Systems Group
* MIT Computer Science and Artificial Intelligence Laboratory
* Massachusetts Institute of Technology
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package edu.mit.csail.wami.record
{
	import edu.mit.csail.wami.utils.Pipe;
+	
+	import flash.utils.ByteArray;
+
+	/**
	 * Data can be written to this pipe in any increment, but only chunks of a
	 * fixed size will be passed on to the sink.
	 */
	public class ChunkPipe extends Pipe
	{
		private var chunkSize:int;
		private var closing:Boolean;
		private var buffer:ByteArray;
		
		public function ChunkPipe(size:int) {
			this.chunkSize = size;
			buffer = new ByteArray();
		}
		
		override public function write(data:ByteArray):void 
		{
			while (true)
			{
				var available:int = data.bytesAvailable + buffer.bytesAvailable;
				if (available < chunkSize && !closing) break;  
				// if we get here, there's enough data for another chunk
				
				var chunk:ByteArray = new ByteArray();

				// Add as much as we can from the buffer to this chunk
				var bufferAvailable:int = Math.min(buffer.bytesAvailable, chunkSize);
				buffer.readBytes(chunk, chunk.length, bufferAvailable);
				
				// Add as much as we can from the data passed in to this chunk
				var chunkRemainder:int = Math.max(chunkSize - bufferAvailable, 0);
				var dataAvailable:int = Math.min(data.bytesAvailable, chunkRemainder);
				data.readBytes(chunk, chunk.length, dataAvailable);

				// Write the chunk out
				chunk.position = 0;
				if (chunk.length > 0) super.write(chunk);
				if (closing) break;
			}
			
			updateBuffer(data);
		}

		private function updateBuffer(data:ByteArray):void
		{
			if (buffer.bytesAvailable == 0) 
			{
				// The buffer hits 0 bytes available whenever
				// there was enough data to create a chunk...
				// still should clear out the junk we've written.
				buffer.clear();	
			}
			
			data.readBytes(buffer, buffer.length, data.bytesAvailable);
			buffer.position = 0;
		}
		
		override public function close():void
		{
			closing = true;
			write(new ByteArray());
			super.close();
		}
	}
}

+ 49 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/record/IRecorder.as

@@ -0,0 +1,49 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.record
+{
+	import edu.mit.csail.wami.utils.StateListener;
+
+	public interface IRecorder
+	{
+		// Start and stop recording.  Calling start while recording
+		// or calling stop when not recording should have no effect.
+		function start(url:String, listener:StateListener):void; 
+		function stop(force:Boolean = false):void;
+		
+		// It can be helpful to buffer a certain amount of audio to 
+		// prepend (and append) to the audio collected between start
+		// and stop.  This means, Flash needs to constantly listen.
+		// There are other times when it's obvious no recording will
+		// be done, and so listening is unnecesary.
+		function listen(paddingMillis:uint):void;
+		function unlisten():void;
+		
+		// Audio level (between 0 and 100)
+		function level():int; 
+	}
+}

+ 111 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/record/MultiPost.as

@@ -0,0 +1,111 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.record
+{	
+	import edu.mit.csail.wami.utils.External;
+	import edu.mit.csail.wami.utils.Pipe;
+	import edu.mit.csail.wami.utils.StateListener;
+	
+	import flash.utils.ByteArray;
+	
+	public class MultiPost extends Pipe implements StateListener
+	{
+		private var url:String;
+		private var contentType:String = null;
+		private var partIndex:int = 0;
+		private var timeoutMillis:int;
+		private var totalBytes:int = 0;
+		private var totalPostsMade:int = 0;
+		private var totalPostsDone:int = 0;
+		private var error:Error = null;
+		private var listener:StateListener;
+		
+		/**
+		 * Does of POST of the data passed in to every call to "write"
+		 */
+		public function MultiPost(url:String, type:String, timeoutMillis:int, listener:StateListener)
+		{
+			this.url = url;
+			this.contentType = type;
+			this.timeoutMillis = timeoutMillis;
+			this.listener = listener;
+		}
+		
+		override public function write(bytes:ByteArray):void
+		{
+			if (getError() != null) {
+				throw getError();
+			}
+			
+			var type:String = contentType.replace("%s", partIndex++);
+			var post:Pipe = new SinglePost(url, type, timeoutMillis, this);
+			post.write(bytes);
+			post.close();
+			totalBytes += bytes.length;
+			totalPostsMade++;
+		}
+		
+		// A final POST containing a -1 signifies the end of the MultiPost stream.
+		override public function close():void
+		{
+			External.debug("Total multi-posted bytes: " + totalBytes);
+			var arr:ByteArray = new ByteArray();
+			arr.writeInt(-1);
+			arr.position = 0;
+			write(arr);
+			super.close();
+		}
+		
+		public function started():void
+		{
+			// nothing to do
+		}
+		
+		public function finished():void
+		{
+			totalPostsDone++;
+			checkFinished();
+		}
+		
+		public function failed(error:Error):void
+		{
+			this.error = error;
+		}
+		
+		public function getError():Error 
+		{
+			return error;
+		}
+		
+		private function checkFinished():void 
+		{
+			if (totalPostsDone == totalPostsMade && super.isClosed()) {
+				listener.finished();
+			}
+		}
+	}		
+}

+ 159 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/record/SinglePost.as

@@ -0,0 +1,159 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.record
+{	
+	import edu.mit.csail.wami.utils.External;
+	import edu.mit.csail.wami.utils.Pipe;
+	import edu.mit.csail.wami.utils.StateListener;
+	
+	import flash.events.Event;
+	import flash.events.HTTPStatusEvent;
+	import flash.events.IOErrorEvent;
+	import flash.events.ProgressEvent;
+	import flash.events.SecurityErrorEvent;
+	import flash.net.URLLoader;
+	import flash.net.URLRequest;
+	import flash.net.URLRequestMethod;
+	import flash.utils.ByteArray;
+	import flash.utils.setInterval;
+	
+	/**
+	 * Write data and POST on close.
+	 */
+	public class SinglePost extends Pipe
+	{	
+		private var url:String;
+		private var contentType:String = null;
+		private var listener:StateListener;
+
+		private var finished:Boolean = false;
+		private var buffer:ByteArray = new ByteArray();
+		private var timeoutMillis:int;
+		
+		public function SinglePost(url:String, type:String, timeoutMillis:int, listener:StateListener)
+		{
+			this.url = url;
+			this.contentType = type;
+			this.listener = listener;
+			this.timeoutMillis = timeoutMillis;
+		}
+		
+		override public function write(bytes:ByteArray):void
+		{
+			bytes.readBytes(buffer, buffer.length, bytes.bytesAvailable);
+		}
+		
+		override public function close():void 
+		{
+			buffer.position = 0;
+			External.debug("POST " + buffer.length + " bytes of type " + contentType);
+			buffer.position = 0;
+			var loader:URLLoader = new URLLoader();
+			
+			loader.addEventListener(Event.COMPLETE, completeHandler);
+			loader.addEventListener(Event.OPEN, openHandler);
+			loader.addEventListener(ProgressEvent.PROGRESS, progressHandler);
+			loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
+			loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
+			loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
+			
+			var request:URLRequest = new URLRequest(url);
+			request.method = URLRequestMethod.POST;
+			request.contentType = contentType;
+			request.data = buffer;
+			if (buffer.bytesAvailable == 0) {
+				External.debug("Note that flash does a GET request if bytes.length == 0");
+			}
+
+			try {
+				loader.load(request);
+			} catch (error:Error) {
+				if (listener) 
+				{
+					listener.failed(error);
+				}
+			}
+			
+			super.close();
+		}
+		
+		private function completeHandler(event:Event):void {
+			External.debug("POST: completeHandler");
+			var loader:URLLoader = URLLoader(event.target);
+			loader.removeEventListener(Event.COMPLETE, completeHandler);
+			loader.removeEventListener(Event.OPEN, openHandler);
+			loader.removeEventListener(ProgressEvent.PROGRESS, progressHandler);
+			loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
+			loader.removeEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
+			loader.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
+			listener.finished();
+			finished = true;
+		}
+		
+		private function openHandler(event:Event):void {
+			External.debug("POST openHandler: " + event);
+			setInterval(checkFinished, timeoutMillis);
+		}
+		
+		private function checkFinished():void {
+			if (!finished && listener) {
+				listener.failed(new Error("POST is taking too long."));
+			}
+			finished = true;
+		}
+		
+		private function progressHandler(event:ProgressEvent):void {
+			External.debug("POST progressHandler loaded:" + event.bytesLoaded + " total: " + event.bytesTotal);
+		}
+		
+		private function securityErrorHandler(event:SecurityErrorEvent):void {
+			if (!finished && listener) 
+			{
+				listener.failed(new Error("Record security error: " + event.errorID));
+			}
+			finished = true;
+		}
+		
+		private function httpStatusHandler(event:HTTPStatusEvent):void {
+			// Apparently the event.status can be zero in some environments where nothing is wrong:
+			// http://johncblandii.com/2008/04/flex-3-firefox-beta-3-returns-0-for-http-status-codes.html
+			if (!finished && listener && event.status != 200 && event.status != 0) 
+			{
+				listener.failed(new Error("HTTP status error: " + event.status));
+			}
+			finished = true;
+		}
+		
+		private function ioErrorHandler(event:IOErrorEvent):void {
+			if (!finished && listener) 
+			{
+				listener.failed(new Error("Record IO error: " + event.errorID));
+			}
+			finished = true;
+		}
+	}		
+}

+ 269 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/record/WamiRecorder.as

@@ -0,0 +1,269 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.record
+{
+	import edu.mit.csail.wami.audio.AuContainer;
+	import edu.mit.csail.wami.audio.AudioFormat;
+	import edu.mit.csail.wami.audio.EncodePipe;
+	import edu.mit.csail.wami.audio.IAudioContainer;
+	import edu.mit.csail.wami.audio.WaveContainer;
+	import edu.mit.csail.wami.client.WamiParams;
+	import edu.mit.csail.wami.utils.BytePipe;
+	import edu.mit.csail.wami.utils.External;
+	import edu.mit.csail.wami.utils.Pipe;
+	import edu.mit.csail.wami.utils.StateListener;
+	
+	import flash.events.SampleDataEvent;
+	import flash.events.StatusEvent;
+	import flash.media.Microphone;
+	import flash.media.SoundCodec;
+	import flash.utils.Endian;
+	import flash.utils.clearInterval;
+	import flash.utils.setInterval;
+	
+	public class WamiRecorder implements IRecorder
+	{
+		private static var CHUNK_DURATION_MILLIS:Number = 200;
+		
+		private var mic:Microphone = null;
+		private var params:WamiParams;
+		private var audioPipe:Pipe;
+
+		// For adding some audio padding to start and stop.
+		private var circularBuffer:BytePipe;
+		private var stopInterval:uint;
+		private var paddingMillis:uint = 0;  // initially 0, but listen changes it.
+		private var listening:Boolean = false;
+		
+		// To determine if the amount of audio recorded matches up with 
+		// the length of time we've recorded (i.e. not dropping any frames)
+		private var handled:uint;
+		private var startTime:Date;
+		private var stopTime:Date;
+		private var listener:StateListener;
+
+		public function WamiRecorder(mic:Microphone, params:WamiParams)
+		{	
+			this.params = params;
+			this.circularBuffer = new BytePipe(getPaddingBufferSize());
+			this.mic = mic;
+			mic.addEventListener(StatusEvent.STATUS, onMicStatus);
+			
+			if (getChunkSize() <= 0)
+			{
+				throw new Error("Desired duration is too small, even for streaming chunks: " + getChunkSize());
+			}
+		}
+		
+		/**
+		 * The WAMI recorder can listen constantly, keeping a buffer of the last
+		 * few milliseconds of audio.  Often people start talking before they click the
+		 * button, so we prepend paddingMillis milliseconds to the audio.
+		 */
+		public function listen(paddingMillis:uint):void {
+			if (!listening) {
+				this.paddingMillis = paddingMillis;
+				mic.rate = AudioFormat.toRoundedRate(params.format.rate);
+				mic.codec = SoundCodec.NELLYMOSER;  // Just to clarify 5, 8, 11, 16, 22 and 44 kHz
+				mic.setSilenceLevel(0, 10000);
+				mic.addEventListener(SampleDataEvent.SAMPLE_DATA, sampleHandler);
+				External.debug("Listening...");
+				listening = true;
+			}
+		}
+		
+		public function unlisten():void {
+			if (listening) {
+				mic.removeEventListener(SampleDataEvent.SAMPLE_DATA, sampleHandler);
+				listening = false;
+				if (paddingMillis > 0) {
+					circularBuffer = new BytePipe(getPaddingBufferSize());
+				}
+				External.debug("Unlistening.");
+			}
+		}
+		
+		protected function onMicStatus(event:StatusEvent):void
+		{
+			External.debug("status: " + event.code);
+			if (event.code == "Microphone.Unmuted") 
+			{
+				listen(this.paddingMillis);
+			} else if (event.code == "Microphone.Muted") {
+				unlisten();
+			}
+		}
+		
+		public function start(url:String, listener:StateListener):void 
+		{	
+			// Forces security if mic is still muted in debugging mode.
+			listen(this.paddingMillis);
+
+			// Flash might be able to decide on a different sample rate
+			// than the one you suggest depending on your audio card...
+			params.format.rate = AudioFormat.fromRoundedRate(mic.rate);
+			External.debug("Recording at rate: " + params.format.rate);
+
+			stop(true);
+			audioPipe = createAudioPipe(url, listener);
+
+			if (paddingMillis > 0) {
+				// Prepend a small amount of audio we've already recorded.
+				circularBuffer.close();
+				audioPipe.write(circularBuffer.getByteArray());
+				circularBuffer = new BytePipe(getPaddingBufferSize());
+			}
+			
+			listener.started();
+
+			handled = 0;
+			startTime = new Date();
+		}
+		
+		public function createAudioPipe(url:String, listener:StateListener):Pipe
+		{
+			this.listener = listener;
+			
+			var post:Pipe;
+			var container:IAudioContainer;
+			if (params.stream)
+			{
+				// The chunk parameter is something I made up.  It would need
+				// to be handled on the server-side to piece all the chunks together.
+				post = new MultiPost(url, "audio/basic; chunk=%s", 3*1000, listener);
+				params.format.endian = Endian.BIG_ENDIAN;
+				container = new AuContainer();
+			}
+			else
+			{
+				post = new SinglePost(url, "audio/x-wav", 30*1000, listener);
+				container = new WaveContainer();
+			}
+			
+			// Setup the audio pipes.  A transcoding pipe converts floats
+			// to shorts and passes them on to a chunking pipe, which spits
+			// out chunks to a pipe that possibly adds a WAVE header
+			// before passing the chunks on to a pipe that does HTTP posts.
+			var pipe:Pipe = new EncodePipe(params.format, container);
+			pipe.setSink(new ChunkPipe(getChunkSize()))
+				.setSink(post);
+
+			return pipe;
+		}
+		
+		internal function sampleHandler(evt:SampleDataEvent):void
+		{
+			evt.data.position = 0;
+			try 
+			{
+				if (audioPipe)
+				{
+					audioPipe.write(evt.data);
+					handled += evt.data.length / 4;
+				}
+				else if (paddingMillis > 0)
+				{
+					circularBuffer.write(evt.data);
+				}
+			}
+			catch (error:Error)
+			{
+				audioPipe = null;
+				stop(true);
+				listener.failed(error);
+			}
+		}
+		
+		public function stop(force:Boolean = false):void 
+		{
+			clearInterval(stopInterval);
+
+			if (force) 
+			{
+				reallyStop();
+			} 
+			else 
+			{
+				stopInterval = setInterval(function():void {
+					clearInterval(stopInterval);
+					reallyStop();
+				}, paddingMillis);
+			}
+		}
+		
+		public function level():int 
+		{
+			if (!audioPipe) return 0;
+			return mic.activityLevel;
+		}
+		
+		private function reallyStop():void
+		{
+			if (!audioPipe) return;
+
+			try {
+				audioPipe.close();
+			} catch(error:Error) {
+				listener.failed(error);
+			}
+			
+			audioPipe = null;
+			validateAudioLength();
+			
+			if (this.paddingMillis == 0) {
+				// No need if we're not padding the audio
+				unlisten();
+			}
+		}
+		
+		private function validateAudioLength():void
+		{
+			stopTime = new Date();
+			var seconds:Number = ((stopTime.time - startTime.time + paddingMillis) / 1000.0);
+			var expectedSamples:uint = uint(seconds*params.format.rate);
+			External.debug("Expected Samples: " + expectedSamples + " Actual Samples: " + handled);
+			startTime = null;
+			stopTime = null;
+		}
+		
+		private function getBytesPerSecond():uint 
+		{
+			return params.format.channels * (params.format.bits/8) * params.format.rate;
+		}
+		
+		private function getChunkSize():uint
+		{
+			return params.stream ? getBytesPerSecond() * CHUNK_DURATION_MILLIS / 1000.0 : int.MAX_VALUE;
+		}
+		
+		private function getPaddingBufferSize():uint
+		{
+			return uint(getBytesPerSecond()*params.paddingMillis/1000.0);
+		}
+		
+	}
+}

+ 93 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/utils/BytePipe.as

@@ -0,0 +1,93 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.utils
+{
+	import flash.utils.ByteArray;
+
+	/**
+	 * Accumulates the written bytes into an array.  If a max number of bytes 
+	 * is specified this will act as a circular buffer. 
+	 */
+	public class BytePipe extends Pipe
+	{
+		private var buffer:ByteArray = new ByteArray();
+		private var done:Boolean = false;
+		private var maxBytes:uint;
+		private var start:uint = 0;
+		
+		function BytePipe(maxBytes:uint = uint.MAX_VALUE):void 
+		{
+			this.maxBytes = maxBytes;
+		}
+		
+		override public function write(bytes:ByteArray):void
+		{
+			if (maxBytes <= 0) return;  // no room!
+			
+			var available:uint = Math.min(maxBytes - buffer.length, bytes.bytesAvailable);
+			if (available > 0) 
+			{
+				bytes.readBytes(buffer, buffer.length, available);
+			}
+			
+			while (bytes.bytesAvailable) 
+			{
+				// Read bytes into the circular buffer.
+				available = Math.min(buffer.length - start, bytes.bytesAvailable);
+				bytes.readBytes(buffer, start, available);
+				start = (start + available) % maxBytes;
+			}
+	
+			buffer.position = 0;
+		}
+		
+		override public function close():void
+		{
+			super.close();
+			done = true;
+		}
+		
+		public function getByteArray():ByteArray
+		{
+			if (!done)
+			{
+				throw new Error("BytePipe should be done before accessing byte array.");
+			}
+			
+			var array:ByteArray = new ByteArray();
+			buffer.position = start;
+			buffer.readBytes(array);
+			buffer.position = 0;
+			if (start > 0) 
+			{
+				buffer.readBytes(array, array.length, start);
+			}
+			array.position = 0;
+			return array;
+		}
+	}
+}

+ 144 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/utils/External.as

@@ -0,0 +1,144 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.utils
+{
+	import flash.external.ExternalInterface;
+	import flash.utils.ByteArray;
+	
+	/**
+	 * Make external calls only if available.
+	 */
+	public class External
+	{
+		public static var debugToConsole:Boolean = false;
+		
+		public static function call(functionName:String, ... arguments):void
+		{
+			if (ExternalInterface.available && functionName) 
+			{
+				try 
+				{
+					trace("External.call: " + functionName + "(" + arguments + ")");
+					ExternalInterface.call(functionName, arguments);
+				}
+				catch (e:Error)
+				{
+					trace("Error calling external function: " + e.message);
+				}
+			}
+			else
+			{	
+				trace("No ExternalInterface - External.call: " + functionName + "(" + arguments + ")");
+			}
+		}	
+		
+		public static function addCallback(functionName:String, closure:Function):void
+		{
+			if (ExternalInterface.available && functionName) 
+			{
+				try
+				{
+					External.debug("External.addCallback: " + functionName);
+					ExternalInterface.addCallback(functionName, closure);
+				}
+				catch (e:Error)
+				{
+					External.debug("Error calling external function: " + e.message);
+				}
+			}
+			else
+			{
+				External.debug("No ExternalInterface - External.addCallback: " + functionName);
+			}
+		}
+		
+		public static function debug(msg:String):void 
+		{
+			if (debugToConsole) {
+				ExternalInterface.call("console.log", "FLASH: " + msg);
+			}
+			else 
+			{
+				trace(msg);
+			}
+		}
+		
+		public static function debugBytes(bytes:ByteArray):void 
+		{
+			debug(bytesToHex(bytes));
+		}
+		
+		public static function bytesToHex(bytes:ByteArray):String 
+		{
+			var position:int = bytes.position;
+			var count:int = 0; 
+			var str:String = "<";
+			while (bytes.bytesAvailable) 
+			{
+				if (count%4 == 0) 
+				{
+					str += " 0x";
+				}
+				var byte:uint = bytes.readUnsignedByte();
+				var nib1:uint = byte/16;
+				var nib2:uint = byte%16;
+				str += getHex(nib1) + getHex(nib2);
+				count++;
+			}
+			str += " >";
+			
+			// Reset position
+			bytes.position = position;
+			return str;
+		}
+		
+		private static function getHex(nibble:uint):String
+		{
+			switch(nibble) 
+			{
+				case 0: return '0';
+				case 1: return '1';
+				case 2: return '2';
+				case 3: return '3';
+				case 4: return '4';
+				case 5: return '5';
+				case 6: return '6';
+				case 7: return '7';
+				case 8: return '8';
+				case 9: return '9';
+				case 10: return 'a';
+				case 11: return 'b';
+				case 12: return 'c';
+				case 13: return 'd';
+				case 14: return 'e';	
+				case 15: return 'f';
+			}
+			
+			return "ERROR(" + nibble + ")";
+		}
+	}
+}

+ 3 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/utils/Pipe.as

@@ -0,0 +1,3 @@
+/* 
* Copyright (c) 2011
* Spoken Language Systems Group
* MIT Computer Science and Artificial Intelligence Laboratory
* Massachusetts Institute of Technology
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package edu.mit.csail.wami.utils
{
	import flash.utils.ByteArray;
	
+	/**
	 * A pipe is a base class that can be written to, and will 
	 * in turn write to a sink.  Overriding "write" and "close"
	 * allows a subclass to perform operations on the incoming
	 * bytes before passing them on.
	 */
+	public class Pipe
	{
		private var sink:Pipe = null;
		private var closed:Boolean = false;
		
		public function write(bytes:ByteArray):void
		{
			if (closed)
			{
				throw new Error("Writing to closed pipe");	
			}
			
			if (sink)
			{
				sink.write(bytes);
			}
		}
		
		public function setSink(pipe:Pipe):Pipe 
		{
			sink = pipe;
			return sink;
		}

		public function close():void 
		{
			if (closed)
			{
				throw new Error("Pipe already closed");
			}

			if (sink) 
			{
				sink.close();
			}
			
			closed = true;
		}
		
		public function isClosed():Boolean {
			return closed;
		}
	}
}

+ 35 - 0
main/inc/lib/wami-recorder/src/edu/mit/csail/wami/utils/StateListener.as

@@ -0,0 +1,35 @@
+/* 
+* Copyright (c) 2011
+* Spoken Language Systems Group
+* MIT Computer Science and Artificial Intelligence Laboratory
+* Massachusetts Institute of Technology
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy,
+* modify, merge, publish, distribute, sublicense, and/or sell copies
+* of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+package edu.mit.csail.wami.utils
+{
+	public interface StateListener
+	{	
+		function started():void;
+		function finished():void;
+		function failed(error:Error):void;
+	}
+}

Some files were not shown because too many files changed in this diff