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 @@ \ 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Save + + + Jakub Steiner + + + + + hdd + hard drive + save + io + store + + + + + http://jimmac.musichall.cz + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +