1 /* 2 * License (MIT) 3 * 4 * Copyright (C) 2013 Matt Diamond 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a 7 * copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sublicense, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be included 15 * in all copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 */ 25 26 var recLength = 0, 27 recBuffersL = [], 28 recBuffersR = [], 29 sampleRate; 30 31 this.onmessage = function(e){ 32 switch(e.data.command){ 33 case 'init': 34 init(e.data.config); 35 break; 36 case 'record': 37 record(e.data.buffer); 38 break; 39 case 'exportWAV': 40 exportWAV(e.data.type); 41 break; 42 case 'exportMonoWAV': 43 exportMonoWAV(e.data.type); 44 break; 45 case 'getBuffers': 46 getBuffers(); 47 break; 48 case 'clear': 49 clear(); 50 break; 51 } 52 }; 53 54 function init(config){ 55 sampleRate = config.sampleRate; 56 } 57 58 function record(inputBuffer){ 59 recBuffersL.push(inputBuffer[0]); 60 recBuffersR.push(inputBuffer[1]); 61 recLength += inputBuffer[0].length; 62 } 63 64 function exportWAV(type){ 65 var bufferL = mergeBuffers(recBuffersL, recLength); 66 var bufferR = mergeBuffers(recBuffersR, recLength); 67 var interleaved = interleave(bufferL, bufferR); 68 var dataview = encodeWAV(interleaved); 69 var audioBlob = new Blob([dataview], { type: type }); 70 71 this.postMessage(audioBlob); 72 } 73 74 function exportMonoWAV(type){ 75 var bufferL = mergeBuffers(recBuffersL, recLength); 76 var dataview = encodeWAV(bufferL, true); 77 var audioBlob = new Blob([dataview], { type: type }); 78 79 this.postMessage(audioBlob); 80 } 81 82 function getBuffers() { 83 var buffers = []; 84 buffers.push( mergeBuffers(recBuffersL, recLength) ); 85 buffers.push( mergeBuffers(recBuffersR, recLength) ); 86 this.postMessage(buffers); 87 } 88 89 function clear(){ 90 recLength = 0; 91 recBuffersL = []; 92 recBuffersR = []; 93 } 94 95 function mergeBuffers(recBuffers, recLength){ 96 var result = new Float32Array(recLength); 97 var offset = 0; 98 for (var i = 0; i < recBuffers.length; i++){ 99 result.set(recBuffers[i], offset); 100 offset += recBuffers[i].length; 101 } 102 return result; 103 } 104 105 function interleave(inputL, inputR){ 106 var length = inputL.length + inputR.length; 107 var result = new Float32Array(length); 108 109 var index = 0, 110 inputIndex = 0; 111 112 while (index < length){ 113 result[index++] = inputL[inputIndex]; 114 result[index++] = inputR[inputIndex]; 115 inputIndex++; 116 } 117 return result; 118 } 119 120 function floatTo16BitPCM(output, offset, input){ 121 for (var i = 0; i < input.length; i++, offset+=2){ 122 var s = Math.max(-1, Math.min(1, input[i])); 123 output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); 124 } 125 } 126 127 function writeString(view, offset, string){ 128 for (var i = 0; i < string.length; i++){ 129 view.setUint8(offset + i, string.charCodeAt(i)); 130 } 131 } 132 133 function encodeWAV(samples, mono){ 134 var buffer = new ArrayBuffer(44 + samples.length * 2); 135 var view = new DataView(buffer); 136 137 /* RIFF identifier */ 138 writeString(view, 0, 'RIFF'); 139 /* file length */ 140 view.setUint32(4, 32 + samples.length * 2, true); 141 /* RIFF type */ 142 writeString(view, 8, 'WAVE'); 143 /* format chunk identifier */ 144 writeString(view, 12, 'fmt '); 145 /* format chunk length */ 146 view.setUint32(16, 16, true); 147 /* sample format (raw) */ 148 view.setUint16(20, 1, true); 149 /* channel count */ 150 view.setUint16(22, mono?1:2, true); 151 /* sample rate */ 152 view.setUint32(24, sampleRate, true); 153 /* byte rate (sample rate * block align) */ 154 view.setUint32(28, sampleRate * 4, true); 155 /* block align (channel count * bytes per sample) */ 156 view.setUint16(32, 4, true); 157 /* bits per sample */ 158 view.setUint16(34, 16, true); 159 /* data chunk identifier */ 160 writeString(view, 36, 'data'); 161 /* data chunk length */ 162 view.setUint32(40, samples.length * 2, true); 163 164 floatTo16BitPCM(view, 44, samples); 165 166 return view; 167 } 168