diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 0b7f347..ed431dc 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -2,13 +2,14 @@
-
-
-
-
-
+
+
+
+
+
+
+
-
@@ -43,96 +44,85 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -210,6 +200,9 @@
+
+
+
@@ -296,39 +289,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -342,15 +303,11 @@
-
+
-
-
-
-
-
+
@@ -364,15 +321,15 @@
-
+
-
+
-
+
@@ -386,161 +343,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -575,6 +378,9 @@
+
+
+
@@ -739,46 +545,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -997,14 +763,6 @@
-
-
-
-
-
-
-
-
@@ -1013,14 +771,6 @@
-
-
-
-
-
-
-
-
@@ -1108,14 +858,6 @@
-
-
-
-
-
-
-
-
@@ -1142,11 +884,79 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/scripts/components/code-playground.js b/app/scripts/components/code-playground.js
index 0e69d57..3f90ca6 100644
--- a/app/scripts/components/code-playground.js
+++ b/app/scripts/components/code-playground.js
@@ -49,13 +49,14 @@ var CodePlayground = React.createClass({
name : 'brace-editor',
mode : 'javascript',
theme : 'monokai',
- height : '500px',
- width : '500px',
+ height : '100%',
+ width : '620px',
fontSize : 12,
value: '',
showGutter : true,
onChange : null,
- onLoad : null
+ onLoad : null,
+ wrapBehaviour: false
};
},
setInitialState: function(){
@@ -69,6 +70,7 @@ var CodePlayground = React.createClass({
this.editor.getSession().setMode('ace/mode/'+nextProps.mode);
this.editor.setTheme('ace/theme/'+nextProps.theme);
this.editor.setFontSize(nextProps.fontSize);
+ this.editor.setWrapBehavioursEnabled(nextProps.wrapBehaviour);
this.editor.setValue(nextProps.value);
this.editor.renderer.setShowGutter(nextProps.showGutter);
if (nextProps.onLoad) {
diff --git a/sandbox/audioTest/Audio Recorder.html b/sandbox/audioTest/Audio Recorder.html
new file mode 100644
index 0000000..0c7a68f
--- /dev/null
+++ b/sandbox/audioTest/Audio Recorder.html
@@ -0,0 +1,72 @@
+
+
+
+
+ Audio Recorder
+
+
+
+
+
+
+
+
+
+
+
+
+

+

+
+
+
\ No newline at end of file
diff --git a/sandbox/audioTest/js/audiodisplay.js b/sandbox/audioTest/js/audiodisplay.js
new file mode 100644
index 0000000..a99622c
--- /dev/null
+++ b/sandbox/audioTest/js/audiodisplay.js
@@ -0,0 +1,18 @@
+function drawBuffer( width, height, context, data ) {
+ var step = Math.ceil( data.length / width );
+ var amp = height / 2;
+ context.fillStyle = "silver";
+ context.clearRect(0,0,width,height);
+ for(var i=0; i < width; i++){
+ var min = 1.0;
+ var max = -1.0;
+ for (j=0; j max)
+ max = datum;
+ }
+ context.fillRect(i,(1+min)*amp,1,Math.max(1,(max-min)*amp));
+ }
+}
diff --git a/sandbox/audioTest/js/main.js b/sandbox/audioTest/js/main.js
new file mode 100644
index 0000000..5631492
--- /dev/null
+++ b/sandbox/audioTest/js/main.js
@@ -0,0 +1,200 @@
+/* Copyright 2013 Chris Wilson
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+window.AudioContext = window.AudioContext || window.webkitAudioContext;
+
+var audioContext = new AudioContext();
+var audioInput = null,
+ realAudioInput = null,
+ inputPoint = null,
+ audioRecorder = null;
+var rafID = null;
+var analyserContext = null;
+var canvasWidth, canvasHeight;
+var recIndex = 0;
+
+/* TODO:
+
+- offer mono option
+- "Monitor input" switch
+*/
+
+function saveAudio() {
+ audioRecorder.exportWAV( doneEncoding );
+ // could get mono instead by saying
+ // audioRecorder.exportMonoWAV( doneEncoding );
+}
+
+function gotBuffers( buffers ) {
+ var canvas = document.getElementById( "wavedisplay" );
+ console.log('gotBuffers');
+ debugger;
+ drawBuffer( canvas.width, canvas.height, canvas.getContext('2d'), buffers[0] );
+
+ // the ONLY time gotBuffers is called is right after a new recording is completed -
+ // so here's where we should set up the download.
+ audioRecorder.exportWAV( doneEncoding );
+}
+
+function doneEncoding( blob ) {
+ Recorder.setupDownload( blob, "myRecording" + ((recIndex<10)?"0":"") + recIndex + ".wav" );
+ recIndex++;
+}
+
+function toggleRecording( e ) {
+ if (e.classList.contains("recording")) {
+ // stop recording
+ audioRecorder.stop();
+ e.classList.remove("recording");
+ audioRecorder.getBuffers( gotBuffers );
+ } else {
+ // start recording
+ if (!audioRecorder)
+ return;
+ e.classList.add("recording");
+ audioRecorder.clear();
+ audioRecorder.record();
+ }
+}
+
+function convertToMono( input ) {
+ var splitter = audioContext.createChannelSplitter(2);
+ var merger = audioContext.createChannelMerger(2);
+
+ input.connect( splitter );
+ splitter.connect( merger, 0, 0 );
+ splitter.connect( merger, 0, 1 );
+ return merger;
+}
+
+function cancelAnalyserUpdates() {
+ window.cancelAnimationFrame( rafID );
+ rafID = null;
+}
+
+function updateAnalysers(time) {
+ if (!analyserContext) {
+ var canvas = document.getElementById("analyser");
+ canvasWidth = canvas.width;
+ canvasHeight = canvas.height;
+ analyserContext = canvas.getContext('2d');
+ }
+
+ // analyzer draw code here
+ {
+ var SPACING = 3;
+ var BAR_WIDTH = 1;
+ var numBars = Math.round(canvasWidth / SPACING);
+ var freqByteData = new Uint8Array(analyserNode.frequencyBinCount);
+
+ analyserNode.getByteFrequencyData(freqByteData);
+
+ analyserContext.clearRect(0, 0, canvasWidth, canvasHeight);
+ analyserContext.fillStyle = '#F6D565';
+ analyserContext.lineCap = 'round';
+ var multiplier = analyserNode.frequencyBinCount / numBars;
+
+ // Draw rectangle for each frequency bin.
+ for (var i = 0; i < numBars; ++i) {
+ var magnitude = 0;
+ var offset = Math.floor( i * multiplier );
+ // gotta sum/average the block, or we miss narrow-bandwidth spikes
+ for (var j = 0; j< multiplier; j++)
+ magnitude += freqByteData[offset + j];
+ magnitude = magnitude / multiplier;
+ var magnitude2 = freqByteData[i * multiplier];
+ analyserContext.fillStyle = "hsl( " + Math.round((i*360)/numBars) + ", 100%, 50%)";
+ analyserContext.fillRect(i * SPACING, canvasHeight, BAR_WIDTH, -magnitude);
+ }
+ }
+
+ rafID = window.requestAnimationFrame( updateAnalysers );
+}
+
+function toggleMono() {
+ if (audioInput != realAudioInput) {
+ audioInput.disconnect();
+ realAudioInput.disconnect();
+ audioInput = realAudioInput;
+ } else {
+ realAudioInput.disconnect();
+ audioInput = convertToMono( realAudioInput );
+ }
+
+ audioInput.connect(inputPoint);
+}
+
+function gotStream(stream) {
+ // Once we have a successful stream we can initialize the rest of our audio shit
+ // Audio Context, {}
+ // MediaStreamAudioSourceNode {} (visual?)
+ // Gain Node, {} (volume of audio graph)
+
+ console.log(audioContext);
+
+ // Takes created AudioContext and creates a gainNode
+ inputPoint = audioContext.createGain();
+ console.log(inputPoint);
+
+ // Create a MediaStreamSource from our stream object AudioNode from the stream.
+ realAudioInput = audioContext.createMediaStreamSource(stream);
+ console.log(realAudioInput);
+ audioInput = realAudioInput;
+ audioInput.connect(inputPoint);
+
+// audioInput = convertToMono( input );
+
+ // Visual
+ analyserNode = audioContext.createAnalyser();
+ analyserNode.fftSize = 2048;
+ inputPoint.connect( analyserNode );
+
+ // Creates Recorder object
+ audioRecorder = new Recorder( inputPoint );
+
+
+ zeroGain = audioContext.createGain();
+ zeroGain.gain.value = 0.0;
+ inputPoint.connect( zeroGain );
+ zeroGain.connect( audioContext.destination );
+ updateAnalysers();
+}
+
+function initAudio() {
+ if (!navigator.getUserMedia)
+ navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
+ if (!navigator.cancelAnimationFrame)
+ navigator.cancelAnimationFrame = navigator.webkitCancelAnimationFrame || navigator.mozCancelAnimationFrame;
+ if (!navigator.requestAnimationFrame)
+ navigator.requestAnimationFrame = navigator.webkitRequestAnimationFrame || navigator.mozRequestAnimationFrame;
+
+ navigator.getUserMedia(
+ {
+ "audio": {
+ "mandatory": {
+ "googEchoCancellation": "false",
+ "googAutoGainControl": "false",
+ "googNoiseSuppression": "false",
+ "googHighpassFilter": "false"
+ },
+ "optional": []
+ },
+ }, gotStream, function(e) {
+ alert('Error getting audio');
+ console.log(e);
+ });
+}
+
+window.addEventListener('load', initAudio );
diff --git a/sandbox/audioTest/js/mic128.png b/sandbox/audioTest/js/mic128.png
new file mode 100644
index 0000000..b9ed1a0
Binary files /dev/null and b/sandbox/audioTest/js/mic128.png differ
diff --git a/sandbox/audioTest/js/recorder.js b/sandbox/audioTest/js/recorder.js
new file mode 100644
index 0000000..d3941dc
--- /dev/null
+++ b/sandbox/audioTest/js/recorder.js
@@ -0,0 +1,121 @@
+/*License (MIT)
+
+Copyright © 2013 Matt Diamond
+
+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.
+*/
+
+(function(window){
+
+ var WORKER_PATH = 'js/recorderWorker.js';
+
+ var Recorder = function(source, cfg){
+ var config = cfg || {};
+ console.log('config ->');
+ console.log(config);
+ var bufferLen = config.bufferLen || 4096;
+ this.context = source.context;
+ if(!this.context.createScriptProcessor){
+ this.node = this.context.createJavaScriptNode(bufferLen, 2, 2);
+ } else {
+ this.node = this.context.createScriptProcessor(bufferLen, 2, 2);
+ }
+
+ var worker = new Worker(config.workerPath || WORKER_PATH);
+ worker.postMessage({
+ command: 'init',
+ config: {
+ sampleRate: this.context.sampleRate
+ }
+ });
+ var recording = false,
+ currCallback;
+
+ this.node.onaudioprocess = function(e){
+ if (!recording) return;
+ worker.postMessage({
+ command: 'record',
+ buffer: [
+ e.inputBuffer.getChannelData(0),
+ e.inputBuffer.getChannelData(1)
+ ]
+ });
+ }
+
+ this.configure = function(cfg){
+ for (var prop in cfg){
+ if (cfg.hasOwnProperty(prop)){
+ config[prop] = cfg[prop];
+ }
+ }
+ }
+
+ this.record = function(){
+ recording = true;
+ }
+
+ this.stop = function(){
+ recording = false;
+ }
+
+ this.clear = function(){
+ worker.postMessage({ command: 'clear' });
+ }
+
+ this.getBuffers = function(cb) {
+ currCallback = cb || config.callback;
+ worker.postMessage({ command: 'getBuffers' })
+ }
+
+ this.exportWAV = function(cb, type){
+ currCallback = cb || config.callback;
+ type = type || config.type || 'audio/wav';
+ if (!currCallback) throw new Error('Callback not set');
+ worker.postMessage({
+ command: 'exportWAV',
+ type: type
+ });
+ }
+
+ this.exportMonoWAV = function(cb, type){
+ currCallback = cb || config.callback;
+ type = type || config.type || 'audio/wav';
+ if (!currCallback) throw new Error('Callback not set');
+ worker.postMessage({
+ command: 'exportMonoWAV',
+ type: type
+ });
+ }
+
+ worker.onmessage = function(e){
+ console.log(e);
+ var blob = e.data;
+ currCallback(blob);
+ }
+
+ source.connect(this.node);
+ this.node.connect(this.context.destination); // if the script node is not connected to an output the "onaudioprocess" event is not triggered in chrome.
+ };
+
+ Recorder.setupDownload = function(blob, filename){
+ var url = (window.URL || window.webkitURL).createObjectURL(blob);
+ var link = document.getElementById("save");
+ link.href = url;
+ link.download = filename || 'output.wav';
+ }
+
+ window.Recorder = Recorder;
+
+})(window);
diff --git a/sandbox/audioTest/js/recorderWorker.js b/sandbox/audioTest/js/recorderWorker.js
new file mode 100644
index 0000000..dce4442
--- /dev/null
+++ b/sandbox/audioTest/js/recorderWorker.js
@@ -0,0 +1,161 @@
+/*License (MIT)
+
+ Copyright © 2013 Matt Diamond
+
+ 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.
+ */
+
+var recLength = 0,
+ recBuffersL = [],
+ recBuffersR = [],
+ sampleRate;
+
+this.onmessage = function(e){
+ switch(e.data.command){
+ case 'init':
+ init(e.data.config);
+ break;
+ case 'record':
+ record(e.data.buffer);
+ break;
+ case 'exportWAV':
+ exportWAV(e.data.type);
+ break;
+ case 'exportMonoWAV':
+ exportMonoWAV(e.data.type);
+ break;
+ case 'getBuffers':
+ getBuffers();
+ break;
+ case 'clear':
+ clear();
+ break;
+ }
+};
+
+function init(config){
+ sampleRate = config.sampleRate;
+}
+
+function record(inputBuffer){
+ recBuffersL.push(inputBuffer[0]);
+ recBuffersR.push(inputBuffer[1]);
+ recLength += inputBuffer[0].length;
+}
+
+function exportWAV(type){
+ var bufferL = mergeBuffers(recBuffersL, recLength);
+ var bufferR = mergeBuffers(recBuffersR, recLength);
+ var interleaved = interleave(bufferL, bufferR);
+ var dataview = encodeWAV(interleaved);
+ var audioBlob = new Blob([dataview], { type: type });
+
+ this.postMessage(audioBlob);
+}
+
+function exportMonoWAV(type){
+ var bufferL = mergeBuffers(recBuffersL, recLength);
+ var dataview = encodeWAV(bufferL, true);
+ var audioBlob = new Blob([dataview], { type: type });
+
+ this.postMessage(audioBlob);
+}
+
+function getBuffers() {
+ var buffers = [];
+ buffers.push( mergeBuffers(recBuffersL, recLength) );
+ buffers.push( mergeBuffers(recBuffersR, recLength) );
+ this.postMessage(buffers);
+}
+
+function clear(){
+ recLength = 0;
+ recBuffersL = [];
+ recBuffersR = [];
+}
+
+function mergeBuffers(recBuffers, recLength){
+ var result = new Float32Array(recLength);
+ var offset = 0;
+ for (var i = 0; i < recBuffers.length; i++){
+ result.set(recBuffers[i], offset);
+ offset += recBuffers[i].length;
+ }
+ return result;
+}
+
+function interleave(inputL, inputR){
+ var length = inputL.length + inputR.length;
+ var result = new Float32Array(length);
+
+ var index = 0,
+ inputIndex = 0;
+
+ while (index < length){
+ result[index++] = inputL[inputIndex];
+ result[index++] = inputR[inputIndex];
+ inputIndex++;
+ }
+ return result;
+}
+
+function floatTo16BitPCM(output, offset, input){
+ for (var i = 0; i < input.length; i++, offset+=2){
+ var s = Math.max(-1, Math.min(1, input[i]));
+ output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
+ }
+}
+
+function writeString(view, offset, string){
+ for (var i = 0; i < string.length; i++){
+ view.setUint8(offset + i, string.charCodeAt(i));
+ }
+}
+
+function encodeWAV(samples, mono){
+ var buffer = new ArrayBuffer(44 + samples.length * 2);
+ var view = new DataView(buffer);
+
+ /* RIFF identifier */
+ writeString(view, 0, 'RIFF');
+ /* file length */
+ view.setUint32(4, 32 + samples.length * 2, true);
+ /* RIFF type */
+ writeString(view, 8, 'WAVE');
+ /* format chunk identifier */
+ writeString(view, 12, 'fmt ');
+ /* format chunk length */
+ view.setUint32(16, 16, true);
+ /* sample format (raw) */
+ view.setUint16(20, 1, true);
+ /* channel count */
+ view.setUint16(22, mono?1:2, true);
+ /* sample rate */
+ view.setUint32(24, sampleRate, true);
+ /* byte rate (sample rate * block align) */
+ view.setUint32(28, sampleRate * 4, true);
+ /* block align (channel count * bytes per sample) */
+ view.setUint16(32, 4, true);
+ /* bits per sample */
+ view.setUint16(34, 16, true);
+ /* data chunk identifier */
+ writeString(view, 36, 'data');
+ /* data chunk length */
+ view.setUint32(40, samples.length * 2, true);
+
+ floatTo16BitPCM(view, 44, samples);
+
+ return view;
+}
diff --git a/sandbox/audioTest/js/save.svg b/sandbox/audioTest/js/save.svg
new file mode 100644
index 0000000..5838f7a
--- /dev/null
+++ b/sandbox/audioTest/js/save.svg
@@ -0,0 +1,546 @@
+
+
+