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