1176578e28066262d983ec67896be50d49cf86e0ahenryhsu/* 2176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Copyright (c) 2014 The Chromium OS Authors. All rights reserved. 3176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Use of this source code is governed by a BSD-style license that can be 4176578e28066262d983ec67896be50d49cf86e0ahenryhsu * found in the LICENSE file. 5176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 6176578e28066262d983ec67896be50d49cf86e0ahenryhsu 7176578e28066262d983ec67896be50d49cf86e0ahenryhsuvar Recorder = function(source){ 8176578e28066262d983ec67896be50d49cf86e0ahenryhsu var bufferLen = 4096; 9176578e28066262d983ec67896be50d49cf86e0ahenryhsu var toneFreq = 1000, errorMargin = 0.05; 10176578e28066262d983ec67896be50d49cf86e0ahenryhsu 11176578e28066262d983ec67896be50d49cf86e0ahenryhsu var context = source.context; 12176578e28066262d983ec67896be50d49cf86e0ahenryhsu var sampleRate = context.sampleRate; 13176578e28066262d983ec67896be50d49cf86e0ahenryhsu var recBuffersL = [], recBuffersR = [], recLength = 0; 14176578e28066262d983ec67896be50d49cf86e0ahenryhsu this.node = (context.createScriptProcessor || 15176578e28066262d983ec67896be50d49cf86e0ahenryhsu context.createJavaScriptNode).call(context, bufferLen, 2, 2); 16176578e28066262d983ec67896be50d49cf86e0ahenryhsu var detectAppend = false, autoStop = false, recordCallback; 17176578e28066262d983ec67896be50d49cf86e0ahenryhsu var recording = false; 18176578e28066262d983ec67896be50d49cf86e0ahenryhsu var freqString; 19176578e28066262d983ec67896be50d49cf86e0ahenryhsu 20176578e28066262d983ec67896be50d49cf86e0ahenryhsu this.node.onaudioprocess = function(e) { 21176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (!recording) return; 22176578e28066262d983ec67896be50d49cf86e0ahenryhsu 23176578e28066262d983ec67896be50d49cf86e0ahenryhsu var length = e.inputBuffer.getChannelData(0).length; 24176578e28066262d983ec67896be50d49cf86e0ahenryhsu var tmpLeft = new Float32Array(length); 25176578e28066262d983ec67896be50d49cf86e0ahenryhsu var tmpRight = new Float32Array(length); 26176578e28066262d983ec67896be50d49cf86e0ahenryhsu tmpLeft.set(e.inputBuffer.getChannelData(0), 0); 27176578e28066262d983ec67896be50d49cf86e0ahenryhsu tmpRight.set(e.inputBuffer.getChannelData(1), 0); 28176578e28066262d983ec67896be50d49cf86e0ahenryhsu 29176578e28066262d983ec67896be50d49cf86e0ahenryhsu recBuffersL.push(tmpLeft); 30176578e28066262d983ec67896be50d49cf86e0ahenryhsu recBuffersR.push(tmpRight); 31176578e28066262d983ec67896be50d49cf86e0ahenryhsu recLength += length; 32176578e28066262d983ec67896be50d49cf86e0ahenryhsu var stop = false; 33176578e28066262d983ec67896be50d49cf86e0ahenryhsu 34176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (autoStop && detectTone(getFreqList(tmpLeft))) 35176578e28066262d983ec67896be50d49cf86e0ahenryhsu stop = true; 36176578e28066262d983ec67896be50d49cf86e0ahenryhsu 37176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (recordCallback) { 38176578e28066262d983ec67896be50d49cf86e0ahenryhsu var tmpLeft = recBuffersL[recBuffersL.length - 1].subarray( 39176578e28066262d983ec67896be50d49cf86e0ahenryhsu -FFT_SIZE-1, -1); 40176578e28066262d983ec67896be50d49cf86e0ahenryhsu var tmpRight = recBuffersR[recBuffersR.length - 1].subarray( 41176578e28066262d983ec67896be50d49cf86e0ahenryhsu -FFT_SIZE-1, -1); 42176578e28066262d983ec67896be50d49cf86e0ahenryhsu recordCallback(tmpLeft, tmpRight, sampleRate, stop); 43176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 44176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 45176578e28066262d983ec67896be50d49cf86e0ahenryhsu 46176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 47176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Starts recording 48176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {function} callback function to get current buffer 49176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {boolean} detect append tone or not 50176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {boolean} auto stop when detecting append tone 51176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 52176578e28066262d983ec67896be50d49cf86e0ahenryhsu this.record = function(cb, detect, stop) { 53176578e28066262d983ec67896be50d49cf86e0ahenryhsu recordCallback = cb; 54176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectAppend = detect; 55176578e28066262d983ec67896be50d49cf86e0ahenryhsu autoStop = stop; 56176578e28066262d983ec67896be50d49cf86e0ahenryhsu recording = true; 57176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 58176578e28066262d983ec67896be50d49cf86e0ahenryhsu 59176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 60176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Stops recording 61176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 62176578e28066262d983ec67896be50d49cf86e0ahenryhsu this.stop = function() { 63176578e28066262d983ec67896be50d49cf86e0ahenryhsu recording = false; 64176578e28066262d983ec67896be50d49cf86e0ahenryhsu recBuffersL = mergeBuffers(recBuffersL, recLength); 65176578e28066262d983ec67896be50d49cf86e0ahenryhsu recBuffersR = mergeBuffers(recBuffersR, recLength); 66176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (detectAppend) { 67176578e28066262d983ec67896be50d49cf86e0ahenryhsu var freqList = getFreqList(recBuffersL); 68176578e28066262d983ec67896be50d49cf86e0ahenryhsu var index = getToneIndices(freqList); 69176578e28066262d983ec67896be50d49cf86e0ahenryhsu removeAppendTone(index[0], index[1]); 70176578e28066262d983ec67896be50d49cf86e0ahenryhsu exportFreqList(freqList); 71176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 72176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 73176578e28066262d983ec67896be50d49cf86e0ahenryhsu 74176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 75176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Gets frequencies list 76176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {Float32Array} buffer 77176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @return {array} frequencies list 78176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 79176578e28066262d983ec67896be50d49cf86e0ahenryhsu getFreqList = function(buffer) { 80176578e28066262d983ec67896be50d49cf86e0ahenryhsu var prevPeak = 0; 81176578e28066262d983ec67896be50d49cf86e0ahenryhsu var valid = true; 82176578e28066262d983ec67896be50d49cf86e0ahenryhsu var freqList = []; 83176578e28066262d983ec67896be50d49cf86e0ahenryhsu for (i = 1; i < recLength; i++) { 84176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (buffer[i] > 0.1 && 85176578e28066262d983ec67896be50d49cf86e0ahenryhsu buffer[i] >= buffer[i - 1] && buffer[i] >= buffer[i + 1]) { 86176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (valid) { 87176578e28066262d983ec67896be50d49cf86e0ahenryhsu var freq = sampleRate / (i - prevPeak); 88176578e28066262d983ec67896be50d49cf86e0ahenryhsu freqList.push([freq, prevPeak, i]); 89176578e28066262d983ec67896be50d49cf86e0ahenryhsu prevPeak = i; 90176578e28066262d983ec67896be50d49cf86e0ahenryhsu valid = false; 91176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 92176578e28066262d983ec67896be50d49cf86e0ahenryhsu } else if (buffer[i] < -0.1) { 93176578e28066262d983ec67896be50d49cf86e0ahenryhsu valid = true; 94176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 95176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 96176578e28066262d983ec67896be50d49cf86e0ahenryhsu return freqList; 97176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 98176578e28066262d983ec67896be50d49cf86e0ahenryhsu 99176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 100176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Checks average frequency is in allowed error margin 101176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {float} average frequency 102176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @return {boolean} checked result pass or fail 103176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 104176578e28066262d983ec67896be50d49cf86e0ahenryhsu checkFreq = function (average) { 105176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (Math.abs(average - toneFreq) / toneFreq < errorMargin) 106176578e28066262d983ec67896be50d49cf86e0ahenryhsu return true; 107176578e28066262d983ec67896be50d49cf86e0ahenryhsu return false; 108176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 109176578e28066262d983ec67896be50d49cf86e0ahenryhsu 110176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 111176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Detects append tone while recording. 112176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {array} frequencies list 113176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @return {boolean} detected or not 114176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 115176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectTone = function(freqList) { 116176578e28066262d983ec67896be50d49cf86e0ahenryhsu var passCriterion = 50; 117176578e28066262d983ec67896be50d49cf86e0ahenryhsu // Initialize function static variables 118176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (typeof detectTone.startDetected == 'undefined') { 119176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectTone.startDetected = false; 120176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectTone.canStop = false; 121176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectTone.accumulateTone = 0; 122176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 123176578e28066262d983ec67896be50d49cf86e0ahenryhsu 124176578e28066262d983ec67896be50d49cf86e0ahenryhsu var windowSize = 10, windowSum = 0, i; 125176578e28066262d983ec67896be50d49cf86e0ahenryhsu var detected = false; 126176578e28066262d983ec67896be50d49cf86e0ahenryhsu for (i = 0; i < freqList.length && i < windowSize; i++) { 127176578e28066262d983ec67896be50d49cf86e0ahenryhsu windowSum += freqList[i][0]; 128176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 129176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (checkFreq(windowSum / Math.min(windowSize, freqList.length))) { 130176578e28066262d983ec67896be50d49cf86e0ahenryhsu detected = true; 131176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectTone.accumulateTone++; 132176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 133176578e28066262d983ec67896be50d49cf86e0ahenryhsu for (; i < freqList.length; i++) { 134176578e28066262d983ec67896be50d49cf86e0ahenryhsu windowSum = windowSum + freqList[i][0] - freqList[i - windowSize][0]; 135176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (checkFreq(windowSum / windowSize)) { 136176578e28066262d983ec67896be50d49cf86e0ahenryhsu detected = true; 137176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectTone.accumulateTone++; 138176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 139176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 140176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (detected) { 141176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (detectTone.accumulateTone > passCriterion) { 142176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (!detectTone.startDetected) 143176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectTone.startDetected = true; 144176578e28066262d983ec67896be50d49cf86e0ahenryhsu else if (detectTone.canStop) { 145176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectTone.startDetected = false; 146176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectTone.canStop = false; 147176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectTone.accumulateTone = 0; 148176578e28066262d983ec67896be50d49cf86e0ahenryhsu return true; 149176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 150176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 151176578e28066262d983ec67896be50d49cf86e0ahenryhsu } else { 152176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectTone.accumulateTone = 0; 153176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (detectTone.startDetected) 154176578e28066262d983ec67896be50d49cf86e0ahenryhsu detectTone.canStop = true; 155176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 156176578e28066262d983ec67896be50d49cf86e0ahenryhsu return false; 157176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 158176578e28066262d983ec67896be50d49cf86e0ahenryhsu 159176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 160176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Gets start and end indices from a frquencies list except append tone 161176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {array} frequencies list 162176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @return {array} start and end indices 163176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 164176578e28066262d983ec67896be50d49cf86e0ahenryhsu getToneIndices = function(freqList) { 165176578e28066262d983ec67896be50d49cf86e0ahenryhsu // find start and end indices 166176578e28066262d983ec67896be50d49cf86e0ahenryhsu var flag, j, k; 167176578e28066262d983ec67896be50d49cf86e0ahenryhsu var windowSize = 10, windowSum; 168176578e28066262d983ec67896be50d49cf86e0ahenryhsu var index = new Array(2); 169176578e28066262d983ec67896be50d49cf86e0ahenryhsu var scanRange = [[0, freqList.length, 1], [freqList.length - 1, -1, -1]]; 170176578e28066262d983ec67896be50d49cf86e0ahenryhsu 171176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (freqList.length == 0) return index; 172176578e28066262d983ec67896be50d49cf86e0ahenryhsu 173176578e28066262d983ec67896be50d49cf86e0ahenryhsu for (i = 0; i < 2; i++) { 174176578e28066262d983ec67896be50d49cf86e0ahenryhsu flag = false; 175176578e28066262d983ec67896be50d49cf86e0ahenryhsu windowSum = 0; 176176578e28066262d983ec67896be50d49cf86e0ahenryhsu for (j = scanRange[i][0], k = 0; k < windowSize && j != scanRange[i][1]; 177176578e28066262d983ec67896be50d49cf86e0ahenryhsu j += scanRange[i][2], k++) { 178176578e28066262d983ec67896be50d49cf86e0ahenryhsu windowSum += freqList[j][0]; 179176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 180176578e28066262d983ec67896be50d49cf86e0ahenryhsu for (; j != scanRange[i][1]; j += scanRange[i][2]) { 181176578e28066262d983ec67896be50d49cf86e0ahenryhsu windowSum = windowSum + freqList[j][0] - 182176578e28066262d983ec67896be50d49cf86e0ahenryhsu freqList[j - scanRange[i][2] * windowSize][0]; 183176578e28066262d983ec67896be50d49cf86e0ahenryhsu var avg = windowSum / windowSize; 184176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (checkFreq(avg) && !flag) { 185176578e28066262d983ec67896be50d49cf86e0ahenryhsu flag = true; 186176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 187176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (!checkFreq(avg) && flag) { 188176578e28066262d983ec67896be50d49cf86e0ahenryhsu index[i] = freqList[j][1]; 189176578e28066262d983ec67896be50d49cf86e0ahenryhsu break; 190176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 191176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 192176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 193176578e28066262d983ec67896be50d49cf86e0ahenryhsu return index; 194176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 195176578e28066262d983ec67896be50d49cf86e0ahenryhsu 196176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 197176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Removes append tone from recorded buffer 198176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {int} start index 199176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {int} end index 200176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 201176578e28066262d983ec67896be50d49cf86e0ahenryhsu removeAppendTone = function(start, end) { 202176578e28066262d983ec67896be50d49cf86e0ahenryhsu if (!isNaN(start) && !isNaN(end) && end > start) { 203176578e28066262d983ec67896be50d49cf86e0ahenryhsu recBuffersL = truncateBuffers(recBuffersL, recLength, start, end); 204176578e28066262d983ec67896be50d49cf86e0ahenryhsu recBuffersR = truncateBuffers(recBuffersR, recLength, start, end); 205176578e28066262d983ec67896be50d49cf86e0ahenryhsu recLength = end - start; 206176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 207176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 208176578e28066262d983ec67896be50d49cf86e0ahenryhsu 209176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 210176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Exports frequency list for debugging purpose 211176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 212176578e28066262d983ec67896be50d49cf86e0ahenryhsu exportFreqList = function(freqList) { 213176578e28066262d983ec67896be50d49cf86e0ahenryhsu freqString = sampleRate + '\n'; 214176578e28066262d983ec67896be50d49cf86e0ahenryhsu for (var i = 0; i < freqList.length; i++) { 215176578e28066262d983ec67896be50d49cf86e0ahenryhsu freqString += freqList[i][0] + ' ' + freqList[i][1] + ' ' + 216176578e28066262d983ec67896be50d49cf86e0ahenryhsu freqList[i][2] + '\n'; 217176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 218176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 219176578e28066262d983ec67896be50d49cf86e0ahenryhsu 220176578e28066262d983ec67896be50d49cf86e0ahenryhsu this.getFreq = function() { 221176578e28066262d983ec67896be50d49cf86e0ahenryhsu return freqString; 222176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 223176578e28066262d983ec67896be50d49cf86e0ahenryhsu 224176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 225176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Clears recorded buffer 226176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 227176578e28066262d983ec67896be50d49cf86e0ahenryhsu this.clear = function() { 228176578e28066262d983ec67896be50d49cf86e0ahenryhsu recLength = 0; 229176578e28066262d983ec67896be50d49cf86e0ahenryhsu recBuffersL = []; 230176578e28066262d983ec67896be50d49cf86e0ahenryhsu recBuffersR = []; 231176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 232176578e28066262d983ec67896be50d49cf86e0ahenryhsu 233176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 234176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Gets recorded buffer 235176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 236176578e28066262d983ec67896be50d49cf86e0ahenryhsu this.getBuffer = function() { 237176578e28066262d983ec67896be50d49cf86e0ahenryhsu var buffers = []; 238176578e28066262d983ec67896be50d49cf86e0ahenryhsu buffers.push(recBuffersL); 239176578e28066262d983ec67896be50d49cf86e0ahenryhsu buffers.push(recBuffersR); 240176578e28066262d983ec67896be50d49cf86e0ahenryhsu return buffers; 241176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 242176578e28066262d983ec67896be50d49cf86e0ahenryhsu 243176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 244176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Exports WAV format file 245176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @return {blob} audio file blob 246176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 247176578e28066262d983ec67896be50d49cf86e0ahenryhsu this.exportWAV = function(type) { 248176578e28066262d983ec67896be50d49cf86e0ahenryhsu type = type || 'audio/wav'; 249176578e28066262d983ec67896be50d49cf86e0ahenryhsu var interleaved = interleave(recBuffersL, recBuffersR); 250176578e28066262d983ec67896be50d49cf86e0ahenryhsu var dataview = encodeWAV(interleaved); 251176578e28066262d983ec67896be50d49cf86e0ahenryhsu var audioBlob = new Blob([dataview], { type: type }); 252176578e28066262d983ec67896be50d49cf86e0ahenryhsu return audioBlob; 253176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 254176578e28066262d983ec67896be50d49cf86e0ahenryhsu 255176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 256176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Truncates buffer from start index to end index 257176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {Float32Array} audio buffer 258176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {int} buffer length 259176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {int} start index 260176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {int} end index 261176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @return {Float32Array} a truncated buffer 262176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 263176578e28066262d983ec67896be50d49cf86e0ahenryhsu truncateBuffers = function(recBuffers, recLength, startIdx, endIdx) { 264176578e28066262d983ec67896be50d49cf86e0ahenryhsu var buffer = new Float32Array(endIdx - startIdx); 265176578e28066262d983ec67896be50d49cf86e0ahenryhsu for (var i = startIdx, j = 0; i < endIdx; i++, j++) { 266176578e28066262d983ec67896be50d49cf86e0ahenryhsu buffer[j] = recBuffers[i]; 267176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 268176578e28066262d983ec67896be50d49cf86e0ahenryhsu return buffer; 269176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 270176578e28066262d983ec67896be50d49cf86e0ahenryhsu 271176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 272176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Merges buffer into an array 273176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {array} a list of Float32Array of audio buffer 274176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {int} buffer length 275176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @return {Float32Array} a merged buffer 276176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 277176578e28066262d983ec67896be50d49cf86e0ahenryhsu mergeBuffers = function(recBuffers, recLength) { 278176578e28066262d983ec67896be50d49cf86e0ahenryhsu var result = new Float32Array(recLength); 279176578e28066262d983ec67896be50d49cf86e0ahenryhsu var offset = 0; 280176578e28066262d983ec67896be50d49cf86e0ahenryhsu for (var i = 0; i < recBuffers.length; i++){ 281176578e28066262d983ec67896be50d49cf86e0ahenryhsu result.set(recBuffers[i], offset); 282176578e28066262d983ec67896be50d49cf86e0ahenryhsu offset += recBuffers[i].length; 283176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 284176578e28066262d983ec67896be50d49cf86e0ahenryhsu return result; 285176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 286176578e28066262d983ec67896be50d49cf86e0ahenryhsu 287176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 288176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Interleaves left and right channel buffer 289176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {Float32Array} left channel buffer 290176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {Float32Array} right channel buffer 291176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @return {Float32Array} an interleaved buffer 292176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 293176578e28066262d983ec67896be50d49cf86e0ahenryhsu interleave = function(inputL, inputR) { 294176578e28066262d983ec67896be50d49cf86e0ahenryhsu var length = inputL.length + inputR.length; 295176578e28066262d983ec67896be50d49cf86e0ahenryhsu var result = new Float32Array(length); 296176578e28066262d983ec67896be50d49cf86e0ahenryhsu 297176578e28066262d983ec67896be50d49cf86e0ahenryhsu var index = 0, 298176578e28066262d983ec67896be50d49cf86e0ahenryhsu inputIndex = 0; 299176578e28066262d983ec67896be50d49cf86e0ahenryhsu 300176578e28066262d983ec67896be50d49cf86e0ahenryhsu while (index < length){ 301176578e28066262d983ec67896be50d49cf86e0ahenryhsu result[index++] = inputL[inputIndex]; 302176578e28066262d983ec67896be50d49cf86e0ahenryhsu result[index++] = inputR[inputIndex]; 303176578e28066262d983ec67896be50d49cf86e0ahenryhsu inputIndex++; 304176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 305176578e28066262d983ec67896be50d49cf86e0ahenryhsu return result; 306176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 307176578e28066262d983ec67896be50d49cf86e0ahenryhsu 308176578e28066262d983ec67896be50d49cf86e0ahenryhsu floatTo16BitPCM = function(output, offset, input) { 309176578e28066262d983ec67896be50d49cf86e0ahenryhsu for (var i = 0; i < input.length; i++, offset+=2){ 310176578e28066262d983ec67896be50d49cf86e0ahenryhsu var s = Math.max(-1, Math.min(1, input[i])); 311176578e28066262d983ec67896be50d49cf86e0ahenryhsu output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); 312176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 313176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 314176578e28066262d983ec67896be50d49cf86e0ahenryhsu 315176578e28066262d983ec67896be50d49cf86e0ahenryhsu writeString = function(view, offset, string) { 316176578e28066262d983ec67896be50d49cf86e0ahenryhsu for (var i = 0; i < string.length; i++){ 317176578e28066262d983ec67896be50d49cf86e0ahenryhsu view.setUint8(offset + i, string.charCodeAt(i)); 318176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 319176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 320176578e28066262d983ec67896be50d49cf86e0ahenryhsu 321176578e28066262d983ec67896be50d49cf86e0ahenryhsu /** 322176578e28066262d983ec67896be50d49cf86e0ahenryhsu * Encodes audio buffer into WAV format raw data 323176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @param {Float32Array} an interleaved buffer 324176578e28066262d983ec67896be50d49cf86e0ahenryhsu * @return {DataView} WAV format raw data 325176578e28066262d983ec67896be50d49cf86e0ahenryhsu */ 326176578e28066262d983ec67896be50d49cf86e0ahenryhsu encodeWAV = function(samples) { 327176578e28066262d983ec67896be50d49cf86e0ahenryhsu var buffer = new ArrayBuffer(44 + samples.length * 2); 328176578e28066262d983ec67896be50d49cf86e0ahenryhsu var view = new DataView(buffer); 329176578e28066262d983ec67896be50d49cf86e0ahenryhsu 330176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* RIFF identifier */ 331176578e28066262d983ec67896be50d49cf86e0ahenryhsu writeString(view, 0, 'RIFF'); 332176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* file length */ 333176578e28066262d983ec67896be50d49cf86e0ahenryhsu view.setUint32(4, 32 + samples.length * 2, true); 334176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* RIFF type */ 335176578e28066262d983ec67896be50d49cf86e0ahenryhsu writeString(view, 8, 'WAVE'); 336176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* format chunk identifier */ 337176578e28066262d983ec67896be50d49cf86e0ahenryhsu writeString(view, 12, 'fmt '); 338176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* format chunk length */ 339176578e28066262d983ec67896be50d49cf86e0ahenryhsu view.setUint32(16, 16, true); 340176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* sample format (raw) */ 341176578e28066262d983ec67896be50d49cf86e0ahenryhsu view.setUint16(20, 1, true); 342176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* channel count */ 343176578e28066262d983ec67896be50d49cf86e0ahenryhsu view.setUint16(22, 2, true); 344176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* sample rate */ 345176578e28066262d983ec67896be50d49cf86e0ahenryhsu view.setUint32(24, sampleRate, true); 346176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* byte rate (sample rate * block align) */ 347176578e28066262d983ec67896be50d49cf86e0ahenryhsu view.setUint32(28, sampleRate * 4, true); 348176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* block align (channel count * bytes per sample) */ 349176578e28066262d983ec67896be50d49cf86e0ahenryhsu view.setUint16(32, 4, true); 350176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* bits per sample */ 351176578e28066262d983ec67896be50d49cf86e0ahenryhsu view.setUint16(34, 16, true); 352176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* data chunk identifier */ 353176578e28066262d983ec67896be50d49cf86e0ahenryhsu writeString(view, 36, 'data'); 354176578e28066262d983ec67896be50d49cf86e0ahenryhsu /* data chunk length */ 355176578e28066262d983ec67896be50d49cf86e0ahenryhsu view.setUint32(40, samples.length * 2, true); 356176578e28066262d983ec67896be50d49cf86e0ahenryhsu 357176578e28066262d983ec67896be50d49cf86e0ahenryhsu floatTo16BitPCM(view, 44, samples); 358176578e28066262d983ec67896be50d49cf86e0ahenryhsu 359176578e28066262d983ec67896be50d49cf86e0ahenryhsu return view; 360176578e28066262d983ec67896be50d49cf86e0ahenryhsu } 361176578e28066262d983ec67896be50d49cf86e0ahenryhsu 362176578e28066262d983ec67896be50d49cf86e0ahenryhsu source.connect(this.node); 363176578e28066262d983ec67896be50d49cf86e0ahenryhsu this.node.connect(context.destination); 364176578e28066262d983ec67896be50d49cf86e0ahenryhsu}; 365176578e28066262d983ec67896be50d49cf86e0ahenryhsu 366176578e28066262d983ec67896be50d49cf86e0ahenryhsuwindow.Recorder = Recorder; 367