From d954369a94de4513c6a50f27b73bd0beb33144c8 Mon Sep 17 00:00:00 2001 From: "elgrecode@gmail.com" Date: Sat, 7 Mar 2015 13:24:16 -0800 Subject: [PATCH] #13 sample audio recording project to be migrated to main --- .idea/workspace.xml | 446 +++++------------- app/scripts/components/code-playground.js | 8 +- sandbox/audioTest/Audio Recorder.html | 72 +++ sandbox/audioTest/js/audiodisplay.js | 18 + sandbox/audioTest/js/main.js | 200 ++++++++ sandbox/audioTest/js/mic128.png | Bin 0 -> 14385 bytes sandbox/audioTest/js/recorder.js | 121 +++++ sandbox/audioTest/js/recorderWorker.js | 161 +++++++ sandbox/audioTest/js/save.svg | 546 ++++++++++++++++++++++ 9 files changed, 1251 insertions(+), 321 deletions(-) create mode 100644 sandbox/audioTest/Audio Recorder.html create mode 100644 sandbox/audioTest/js/audiodisplay.js create mode 100644 sandbox/audioTest/js/main.js create mode 100644 sandbox/audioTest/js/mic128.png create mode 100644 sandbox/audioTest/js/recorder.js create mode 100644 sandbox/audioTest/js/recorderWorker.js create mode 100644 sandbox/audioTest/js/save.svg 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 0000000000000000000000000000000000000000..b9ed1a0667563b0b363864d143a4a6e8af29d901 GIT binary patch literal 14385 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 WKm$Q4V+NoBNI^zbx>E8}$o~Nwq&C+8 literal 0 HcmV?d00001 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +