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