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 /**
 27  * @module workers/recorderExt
 28  */
 29 
 30 var recLength = 0,
 31   recBuffersL = [],
 32   recBuffersR = [],
 33   sampleRate;
 34 
 35 this.onmessage = function(e){
 36   switch(e.data.command){
 37     case 'init':
 38       init(e.data.config);
 39       break;
 40     case 'record':
 41       record(e.data.buffer);
 42 	  isSilent(e.data.buffer.length == 2? e.data.buffer[0]:e.data.buffer);///////////MOD
 43       break;
 44     case 'exportWAV':
 45       exportWAV(e.data.type);
 46       break;
 47     case 'exportMonoWAV':
 48       exportMonoWAV(e.data.type);
 49       break;
 50     case 'getBuffers':
 51     ////////////////////////MOD start
 52       if(e.data.id){
 53     	  getBuffersFor(e.data.id);
 54     	  break;
 55       }
 56     ////////////////////////MOD end
 57       getBuffers();
 58       break;
 59     case 'clear':
 60       clear();
 61       break;
 62     ////////////////////////MOD start
 63 	  case 'initDetection':
 64 	    initDetection(e.data.config);
 65 	    break;
 66 	  case 'start':
 67 	    start();
 68 	    break;
 69 	  case 'isSilent':
 70 	    isSilent(e.data.buffer);
 71 	    break;
 72 	  case 'stop':
 73 	    stop();
 74 	    break;
 75     ////////////////////////MOD end
 76   }
 77 };
 78 
 79 function init(config){
 80   sampleRate = config.sampleRate;
 81 }
 82 
 83 function record(inputBuffer){
 84   recBuffersL.push(inputBuffer[0]);
 85   recBuffersR.push(inputBuffer[1]);
 86   recLength += inputBuffer[0].length;
 87 }
 88 
 89 function exportWAV(type){
 90   var bufferL = mergeBuffers(recBuffersL, recLength);
 91   var bufferR = mergeBuffers(recBuffersR, recLength);
 92   var interleaved = interleave(bufferL, bufferR);
 93   var dataview = encodeWAV(interleaved);
 94   var audioBlob = new Blob([dataview], { type: type });
 95 
 96   this.postMessage(audioBlob);
 97 }
 98 
 99 function exportMonoWAV(type){
100   var bufferL = mergeBuffers(recBuffersL, recLength);
101   var dataview = encodeWAV(bufferL, true);
102   var audioBlob = new Blob([dataview], { type: type });
103 
104   this.postMessage(audioBlob);
105 }
106 
107 function getBuffers() {
108   var buffers = [];
109   buffers.push( mergeBuffers(recBuffersL, recLength) );
110   buffers.push( mergeBuffers(recBuffersR, recLength) );
111   this.postMessage(buffers);
112 }
113 
114 function clear(){
115   recLength = 0;
116   recBuffersL = [];
117   recBuffersR = [];
118 }
119 
120 function mergeBuffers(recBuffers, recLength){
121   var result = new Float32Array(recLength);
122   var offset = 0;
123   for (var i = 0; i < recBuffers.length; i++){
124     result.set(recBuffers[i], offset);
125     offset += recBuffers[i].length;
126   }
127   return result;
128 }
129 
130 function interleave(inputL, inputR){
131   var length = inputL.length + inputR.length;
132   var result = new Float32Array(length);
133 
134   var index = 0,
135     inputIndex = 0;
136 
137   while (index < length){
138     result[index++] = inputL[inputIndex];
139     result[index++] = inputR[inputIndex];
140     inputIndex++;
141   }
142   return result;
143 }
144 
145 function floatTo16BitPCM(output, offset, input){
146   for (var i = 0; i < input.length; i++, offset+=2){
147     var s = Math.max(-1, Math.min(1, input[i]));
148     output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
149   }
150 }
151 
152 function writeString(view, offset, string){
153   for (var i = 0; i < string.length; i++){
154     view.setUint8(offset + i, string.charCodeAt(i));
155   }
156 }
157 
158 function encodeWAV(samples, mono){
159   var buffer = new ArrayBuffer(44 + samples.length * 2);
160   var view = new DataView(buffer);
161 
162   /* RIFF identifier */
163   writeString(view, 0, 'RIFF');
164   /* file length */
165   view.setUint32(4, 32 + samples.length * 2, true);
166   /* RIFF type */
167   writeString(view, 8, 'WAVE');
168   /* format chunk identifier */
169   writeString(view, 12, 'fmt ');
170   /* format chunk length */
171   view.setUint32(16, 16, true);
172   /* sample format (raw) */
173   view.setUint16(20, 1, true);
174   /* channel count */
175   view.setUint16(22, mono?1:2, true);
176   /* sample rate */
177   view.setUint32(24, sampleRate, true);
178   /* byte rate (sample rate * block align) */
179   view.setUint32(28, sampleRate * 4, true);
180   /* block align (channel count * bytes per sample) */
181   view.setUint16(32, 4, true);
182   /* bits per sample */
183   view.setUint16(34, 16, true);
184   /* data chunk identifier */
185   writeString(view, 36, 'data');
186   /* data chunk length */
187   view.setUint32(40, samples.length * 2, true);
188 
189   floatTo16BitPCM(view, 44, samples);
190 
191   return view;
192 }
193 
194 
195 ///////////////////////////////////////////////////////////////// MOD:
196 
197 /*
198  * 	Copyright (C) 2012-2013 DFKI GmbH
199  * 	Deutsches Forschungszentrum fuer Kuenstliche Intelligenz
200  * 	German Research Center for Artificial Intelligence
201  * 	http://www.dfki.de
202  * 
203  * 	Permission is hereby granted, free of charge, to any person obtaining a 
204  * 	copy of this software and associated documentation files (the 
205  * 	"Software"), to deal in the Software without restriction, including 
206  * 	without limitation the rights to use, copy, modify, merge, publish, 
207  * 	distribute, sublicense, and/or sell copies of the Software, and to 
208  * 	permit persons to whom the Software is furnished to do so, subject to 
209  * 	the following conditions:
210  * 
211  * 	The above copyright notice and this permission notice shall be included 
212  * 	in all copies or substantial portions of the Software.
213  * 
214  * 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
215  * 	OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
216  * 	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
217  * 	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
218  * 	CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
219  * 	TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
220  * 	SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
221  */
222 
223 
224 var silenceCount = 0, //how many silent blobs have there been in a row now?
225 	speechCount = 0, //how many blobs have been loud in a row now?
226 	lastInput = 0, // how long has there been no good loud input?
227 	recording= false,
228 	noiseTreshold = 0.1, //the bigger, the more is counted as silent
229 	sampleRate = 0,
230 	pauseCount = 3,
231 	resetCount = 15,
232 	maxBlobSize = 15,
233 	blobSizeCount = 0,
234 	blobNumber = 0;
235 
236 var self = this;
237   
238 //self.onmessage = function(e){
239 //  switch(e.data.command){
240 //    case 'init':
241 //      initDetection(e.data.config);
242 //      break;
243 //    case 'start':
244 //      start();
245 //      break;
246 //    case 'isSilent':
247 //      isSilent(e.data.buffer);
248 //      break;
249 //    case 'stop':
250 //      stop();
251 //      break;
252 //  }
253 //};
254 
255 function getBuffersFor(id) {
256   var buffers = [];
257   buffers.push( mergeBuffers(recBuffersL, recLength) );
258   buffers.push( mergeBuffers(recBuffersR, recLength) );
259   this.postMessage({buffers: buffers, id: id});
260 }
261 
262 /**
263  * sets the config and echos back
264  * @param config
265  * @private
266  */
267 function initDetection(config){
268   if (config.sampleRate){
269 	  sampleRate = config.sampleRate;
270 	  if(typeof sampleRate !== 'number'){
271 		  sampleRate = parseInt(sampleRate, 10);
272 	  }
273   }
274   if (config.noiseTreshold){
275 	  noiseTreshold = config.noiseTreshold;
276 	  if(typeof noiseTreshold !== 'number'){
277 		  noiseTreshold = parseFloat(noiseTreshold);
278 	  }
279   }
280   if (config.pauseCount){
281 	  pauseCount = config.pauseCount;
282 	  if(typeof pauseCount !== 'number'){
283 		  pauseCount = parseInt(pauseCount, 10);
284 	  }
285   }
286   if (config.resetCount){
287 	  resetCount = config.resetCount;
288 	  if(typeof resetCount !== 'number'){
289 		  resetCount = parseInt(resetCount, 10);
290 	  }
291   }
292   self.postMessage('Silence Detection initialized');
293 }
294 
295 /**
296  * recieves an audioBlob and decides whether or not there has been a real input (min. 3 loud blobs in a row)
297  * and a real pause (min. <pauseCount> silent blobs in a row) afterwards. In this case it dictates a pause. 
298  * If some time has gone by without any real input, it sends a signal to clear the buffers.
299  * @param inputBuffer
300  * @private
301  */
302 function isSilent(inputBuffer){
303 	if (recording){
304 		blobNumber++;
305 		if (blobNumber==3){
306 			self.postMessage('Silence detected!');
307 		}
308 		var thisSilent = true;
309 		var bound = 0;
310 		for (var i = 0; i < inputBuffer.length; i++) {
311 			if (( inputBuffer[i]> noiseTreshold)||( inputBuffer[i]<0-noiseTreshold)){
312 				if (inputBuffer[i]>bound) bound= inputBuffer[i];
313 				thisSilent = false;
314 			}
315 		}
316 		if (thisSilent){
317 			if (silenceCount>=pauseCount){
318 				self.postMessage('Silence detected!');
319 				speechCount = 0;
320 				silenceCount = 0;
321 				lastInput = 0;
322 				blobSizeCount = 0;
323 			}
324 			if (speechCount>=pauseCount){
325 				blobSizeCount++;
326 				silenceCount++;
327 			} 
328 			else {
329 				speechCount = 0;
330 				lastInput++;
331 			}
332 		} 
333 		else {
334 			if (speechCount>=pauseCount){
335 				silenceCount=0;
336 				blobSizeCount++;
337 			} 
338 			else {
339 				speechCount++;
340 				lastInput++;
341 			}
342 		}
343 		if (speechCount>pauseCount){
344 			
345 		}
346 		if (blobSizeCount >= maxBlobSize){
347 			self.postMessage('Send partial!');
348 			blobSizeCount = 0;
349 		}
350 		if (speechCount==0 && lastInput>resetCount){
351 			this.postMessage('clear');
352 			lastInput= 0;
353 		}
354 		
355 	}
356 }
357 
358 /**
359  * resets everything and switches the worker to recording mode.
360  * @private
361  */
362 function start(){
363 	silenceCount=0;
364 	speechCount =0;
365 	lastInput = 0;
366 	recording = true;
367 	self.postMessage('Silence Detection started');
368 	blobNumber = 0;
369 }
370 function stop(){
371 	recording = false;
372 	if (speechCount>0){
373 		self.postMessage('Silence detected!');
374 		speechCount = 0;
375 		silenceCount = 0;
376 		lastInput = 0;
377 		blobsizeCount = 0;
378 	}
379 	self.postMessage('Silence Detection stopped');
380 }
381 
382