wami-recorder Revision: c5b24bbc70e9

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

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -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() {
};
}
}
}
}

@ -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>

@ -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();
}
}
}

@ -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.

@ -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);
?>

@ -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()

@ -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>

@ -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;
}
}
}

@ -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;
}
}
}

@ -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;
}
}
}

@ -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();
}
}
}

@ -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;
}
}

@ -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;
}
}
}

@ -0,0 +1,2 @@
/* * 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; import flash.display.*; import flash.media.*; import flash.system.*; import flash.utils.clearInterval; import flash.utils.setInterval;
/** * Flash settings. This class is largely to get around a shortcoming in * Flash. Although, you can listen for changes to the microphone status, * there appears to be no good way to listen for a status event to tell you * when the settings dialogue has been closed. This hack should suffice. */ public class FlashSettings extends flash.display.MovieClip { private static var MAX_CHECKS:uint = 5; private var theStage:Stage; private var checkSettingsIntervalID:int = 0; private var showedPanel:Boolean = false; private var checkAttempts:int = 0; private var listener:StateListener; public function FlashSettings(s:Stage) { super(); theStage = s; External.addCallback("showSecurity", showSecurity); } // Possible values that settings parameter can take are those of the // string constants documented by Adobe in flash.system.SecurityPanel. public function settingsPanel(settings:String, listener:StateListener):void { this.showedPanel = false; this.checkAttempts = 0; this.listener = listener; flash.system.Security.showSettings(settings); checkSettings(); } private function checkSettings():void { clearInterval(checkSettingsIntervalID); var closed:Boolean = false; if (showingPanel()) { if (!showedPanel) { if (listener) { listener.started(); } } showedPanel = true; } else if (showedPanel) { closed = true; } if (closed) { if (listener) { listener.finished(); } return; } External.debug("check attempts: " + checkAttempts); checkAttempts++; if (checkAttempts > MAX_CHECKS && showedPanel != true) { External.debug("failed"); if (listener) { listener.failed(new Error("Security panel never showed up. Perhaps the browser is zoomed out too far. Try to zoom in and refresh.")); } return; } checkSettingsIntervalID = setInterval(checkSettings, 250); } private function showingPanel():Boolean { var showing:Boolean = false; var dummy:BitmapData; dummy = new BitmapData(1,1); try { // Try to capture the stage: triggers a Security error when the settings dialog box is open // Unfortunately, this is how we have to poll the settings dialogue to know when it closes dummy.draw(theStage); } catch (error:Error) { External.debug("Still not closed, could not capture the stage: " + theStage); showing = true; } dummy.dispose(); dummy = null; return showing; } internal function showSecurity(panel:String, startedCallback:String = null, finishedCallback:String = null, failedCallback:String = null):void { settingsPanel(panel, new WamiListener(startedCallback, finishedCallback, failedCallback)); } } }

@ -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>

@ -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();
}
}
}

@ -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);
}
}
}

@ -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;
}
}
}
}
}

@ -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;
}
}

@ -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;
}
}
}
}

@ -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(); } } }

@ -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;
}
}

@ -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();
}
}
}
}

@ -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;
}
}
}

@ -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);
}
}
}

@ -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;
}
}
}

@ -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 + ")";
}
}
}

@ -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; } } }

@ -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;
}
}
Loading…
Cancel
Save