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