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 ToneGen = function() {
8176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
9176578e28066262d983ec67896be50d49cf86e0ahenryhsu  * Initializes tone generator.
10176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
11176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.init = function() {
12176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.audioContext = new AudioContext();
13176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
14176578e28066262d983ec67896be50d49cf86e0ahenryhsu
15176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
16176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Sets sample rate
17176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {int} sample rate
18176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
19176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.setSampleRate = function(sampleRate) {
20176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.sampleRate = sampleRate;
21176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
22176578e28066262d983ec67896be50d49cf86e0ahenryhsu
23176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
24176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Sets start/end frequencies and logarithmic sweep
25176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {int} start frequency
26176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {int} end frequency
27176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {boolean} logarithmic sweep or not
28176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
29176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.setFreq = function(freqStart, freqEnd, sweepLog) {
30176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.freqStart = freqStart;
31176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.freqEnd = freqEnd;
32176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.sweepLog = sweepLog;
33176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
34176578e28066262d983ec67896be50d49cf86e0ahenryhsu
35176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
36176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Sets tone duration
37176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {float} duration in seconds
38176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
39176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.setDuration = function(duration) {
40176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.duration = parseFloat(duration);
41176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
42176578e28066262d983ec67896be50d49cf86e0ahenryhsu
43176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
44176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Sets left and right gain value
45176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {float} left gain between 0 and 1
46176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {float} right gain between 0 and 1
47176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
48176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.setGain = function(leftGain, rightGain) {
49176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.leftGain = parseFloat(leftGain);
50176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.rightGain = parseFloat(rightGain);
51176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
52176578e28066262d983ec67896be50d49cf86e0ahenryhsu
53176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
54176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Generates sine tone buffer
55176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
56176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.genBuffer = function() {
57176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.buffer = this.audioContext.createBuffer(2,
58176578e28066262d983ec67896be50d49cf86e0ahenryhsu        this.sampleRate * this.duration, this.sampleRate);
59176578e28066262d983ec67896be50d49cf86e0ahenryhsu    var leftChannel = this.buffer.getChannelData(0);
60176578e28066262d983ec67896be50d49cf86e0ahenryhsu    var rightChannel = this.buffer.getChannelData(1);
617319add0f89304bc863b9d27a3d43db912f6ea20henryhsu    var phi;
627319add0f89304bc863b9d27a3d43db912f6ea20henryhsu    var k = this.freqEnd / this.freqStart;
637319add0f89304bc863b9d27a3d43db912f6ea20henryhsu    var beta = this.duration / Math.log(k);
64176578e28066262d983ec67896be50d49cf86e0ahenryhsu    for (var i = 0; i < leftChannel.length; i++) {
657319add0f89304bc863b9d27a3d43db912f6ea20henryhsu      if (this.sweepLog) {
667319add0f89304bc863b9d27a3d43db912f6ea20henryhsu        phi = 2 * Math.PI * this.freqStart * beta *
677319add0f89304bc863b9d27a3d43db912f6ea20henryhsu            (Math.pow(k, i / leftChannel.length) - 1.0);
687319add0f89304bc863b9d27a3d43db912f6ea20henryhsu      } else {
697319add0f89304bc863b9d27a3d43db912f6ea20henryhsu        var f = this.freqStart + (this.freqEnd - this.freqStart) *
707319add0f89304bc863b9d27a3d43db912f6ea20henryhsu            i / leftChannel.length / 2;
717319add0f89304bc863b9d27a3d43db912f6ea20henryhsu        phi = f * 2 * Math.PI * i / this.sampleRate;
727319add0f89304bc863b9d27a3d43db912f6ea20henryhsu      }
73176578e28066262d983ec67896be50d49cf86e0ahenryhsu      leftChannel[i] = this.leftGain * Math.sin(phi);
74176578e28066262d983ec67896be50d49cf86e0ahenryhsu      rightChannel[i] = this.rightGain * Math.sin(phi);
75176578e28066262d983ec67896be50d49cf86e0ahenryhsu    }
76176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
77176578e28066262d983ec67896be50d49cf86e0ahenryhsu
78176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
79176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Returns generated sine tone buffer
80176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @return {AudioBuffer} audio buffer
81176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
82176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.getBuffer = function() {
83176578e28066262d983ec67896be50d49cf86e0ahenryhsu    return this.buffer;
84176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
85176578e28066262d983ec67896be50d49cf86e0ahenryhsu
86176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
87176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Returns append buffer
88176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @return {AudioBuffer} append audio buffer
89176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
90176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.getAppendTone = function(sampleRate) {
91176578e28066262d983ec67896be50d49cf86e0ahenryhsu    var tone_freq = 1000, duration = 0.5;
92176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.setFreq(tone_freq, tone_freq, false);
93176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.setDuration(duration);
94176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.setGain(1, 1);
95176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.setSampleRate(sampleRate);
96176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.genBuffer();
97176578e28066262d983ec67896be50d49cf86e0ahenryhsu    return this.getBuffer();
98176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
99176578e28066262d983ec67896be50d49cf86e0ahenryhsu
100176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.init();
101176578e28066262d983ec67896be50d49cf86e0ahenryhsu}
102176578e28066262d983ec67896be50d49cf86e0ahenryhsu
103176578e28066262d983ec67896be50d49cf86e0ahenryhsuwindow.ToneGen = ToneGen;
104176578e28066262d983ec67896be50d49cf86e0ahenryhsu
105176578e28066262d983ec67896be50d49cf86e0ahenryhsuvar AudioPlay = function() {
106176578e28066262d983ec67896be50d49cf86e0ahenryhsu  var playCallback = null;
107176578e28066262d983ec67896be50d49cf86e0ahenryhsu  var sampleRate;
108176578e28066262d983ec67896be50d49cf86e0ahenryhsu  var playing = false;
109176578e28066262d983ec67896be50d49cf86e0ahenryhsu
110176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
111176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Initializes audio play object
112176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
113176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.init = function() {
114176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.audioContext = new AudioContext();
115176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.genChannel();
116176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.buffer = null;
117176578e28066262d983ec67896be50d49cf86e0ahenryhsu    sampleRate = this.audioContext.sampleRate;
118176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
119176578e28066262d983ec67896be50d49cf86e0ahenryhsu
120176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
121176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Loads audio file
122176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {blob} audio file
123176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {function} callback function when file loaded
124176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
125176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.loadFile = function(file_blob, done_cb) {
126176578e28066262d983ec67896be50d49cf86e0ahenryhsu    if (file_blob) {
127176578e28066262d983ec67896be50d49cf86e0ahenryhsu      var audioContext = this.audioContext;
128176578e28066262d983ec67896be50d49cf86e0ahenryhsu      reader = new FileReader();
129176578e28066262d983ec67896be50d49cf86e0ahenryhsu      reader.onloadend = function(e) {
130176578e28066262d983ec67896be50d49cf86e0ahenryhsu        audioContext.decodeAudioData(e.target.result,
131176578e28066262d983ec67896be50d49cf86e0ahenryhsu            function(buffer) {
132176578e28066262d983ec67896be50d49cf86e0ahenryhsu              done_cb(file_blob.name, buffer);
133176578e28066262d983ec67896be50d49cf86e0ahenryhsu            });
134176578e28066262d983ec67896be50d49cf86e0ahenryhsu      };
135176578e28066262d983ec67896be50d49cf86e0ahenryhsu      reader.readAsArrayBuffer(file_blob);
136176578e28066262d983ec67896be50d49cf86e0ahenryhsu    }
137176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
138176578e28066262d983ec67896be50d49cf86e0ahenryhsu
139176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
140176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Sets audio path
141176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
142176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.genChannel = function() {
143176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.node = (this.audioContext.createScriptProcessor ||
144176578e28066262d983ec67896be50d49cf86e0ahenryhsu        this.audioContext.createJavaScriptNode).call(
145176578e28066262d983ec67896be50d49cf86e0ahenryhsu        this.audioContext, 4096, 2, 2);
146176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.splitter = this.audioContext.createChannelSplitter(2);
147176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.merger = this.audioContext.createChannelMerger(2);
148176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.node.connect(this.splitter);
149176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.splitter.connect(this.merger, 0, 0);
150176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.splitter.connect(this.merger, 1, 1);
151176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.merger.connect(this.audioContext.destination);
152176578e28066262d983ec67896be50d49cf86e0ahenryhsu
153176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.node.onaudioprocess = function(e) {
154176578e28066262d983ec67896be50d49cf86e0ahenryhsu      for (var i = 0; i < e.inputBuffer.numberOfChannels; i++) {
155176578e28066262d983ec67896be50d49cf86e0ahenryhsu        e.outputBuffer.getChannelData(i).set(
156176578e28066262d983ec67896be50d49cf86e0ahenryhsu            e.inputBuffer.getChannelData(i), 0);
157176578e28066262d983ec67896be50d49cf86e0ahenryhsu      }
158176578e28066262d983ec67896be50d49cf86e0ahenryhsu      if (!playing) return;
159176578e28066262d983ec67896be50d49cf86e0ahenryhsu      if (playCallback) {
160176578e28066262d983ec67896be50d49cf86e0ahenryhsu        var tmpLeft = e.inputBuffer.getChannelData(0).subarray(
161176578e28066262d983ec67896be50d49cf86e0ahenryhsu            -FFT_SIZE-1, -1);
162176578e28066262d983ec67896be50d49cf86e0ahenryhsu        var tmpRight = e.inputBuffer.getChannelData(1).subarray(
163176578e28066262d983ec67896be50d49cf86e0ahenryhsu            -FFT_SIZE-1, -1);
164176578e28066262d983ec67896be50d49cf86e0ahenryhsu        playCallback(tmpLeft, tmpRight, sampleRate);
165176578e28066262d983ec67896be50d49cf86e0ahenryhsu      }
166176578e28066262d983ec67896be50d49cf86e0ahenryhsu    }
167176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
168176578e28066262d983ec67896be50d49cf86e0ahenryhsu
169176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
170176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Plays audio
171176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {function} callback function when audio end
172176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {function} callback function to get current buffer
173176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
174176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.play = function(done_cb, play_cb) {
175176578e28066262d983ec67896be50d49cf86e0ahenryhsu    playCallback = play_cb;
176176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.source = this.audioContext.createBufferSource();
177176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.source.buffer = this.buffer;
178176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.source.onended = function(e) {
179176578e28066262d983ec67896be50d49cf86e0ahenryhsu      playing = false;
180176578e28066262d983ec67896be50d49cf86e0ahenryhsu      this.disconnect();
181176578e28066262d983ec67896be50d49cf86e0ahenryhsu      if (done_cb) {
182176578e28066262d983ec67896be50d49cf86e0ahenryhsu        done_cb();
183176578e28066262d983ec67896be50d49cf86e0ahenryhsu      }
184176578e28066262d983ec67896be50d49cf86e0ahenryhsu    }
185176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.source.connect(this.node);
186176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.source.start(0);
187176578e28066262d983ec67896be50d49cf86e0ahenryhsu    playing = true;
188176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
189176578e28066262d983ec67896be50d49cf86e0ahenryhsu
190176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
191176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Stops audio
192176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
193176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.stop = function() {
194176578e28066262d983ec67896be50d49cf86e0ahenryhsu    playing = false;
195176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.source.stop();
196176578e28066262d983ec67896be50d49cf86e0ahenryhsu    this.source.disconnect();
197176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
198176578e28066262d983ec67896be50d49cf86e0ahenryhsu
199176578e28066262d983ec67896be50d49cf86e0ahenryhsu  /**
200176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * Sets audio buffer
201176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {AudioBuffer} audio buffer
202176578e28066262d983ec67896be50d49cf86e0ahenryhsu   * @param {boolean} append tone or not
203176578e28066262d983ec67896be50d49cf86e0ahenryhsu   */
204176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.setBuffer = function(buffer, append) {
205176578e28066262d983ec67896be50d49cf86e0ahenryhsu    if (append) {
206176578e28066262d983ec67896be50d49cf86e0ahenryhsu      function copyBuffer(src, dest, offset) {
207176578e28066262d983ec67896be50d49cf86e0ahenryhsu        for (var i = 0; i < dest.numberOfChannels; i++) {
208176578e28066262d983ec67896be50d49cf86e0ahenryhsu          dest.getChannelData(i).set(src.getChannelData(i), offset);
209176578e28066262d983ec67896be50d49cf86e0ahenryhsu        }
210176578e28066262d983ec67896be50d49cf86e0ahenryhsu      }
211176578e28066262d983ec67896be50d49cf86e0ahenryhsu      var appendTone = tonegen.getAppendTone(buffer.sampleRate);
212176578e28066262d983ec67896be50d49cf86e0ahenryhsu      var bufferLength = appendTone.length * 2 + buffer.length;
213176578e28066262d983ec67896be50d49cf86e0ahenryhsu      var newBuffer = this.audioContext.createBuffer(buffer.numberOfChannels,
214176578e28066262d983ec67896be50d49cf86e0ahenryhsu          bufferLength, buffer.sampleRate);
215176578e28066262d983ec67896be50d49cf86e0ahenryhsu      copyBuffer(appendTone, newBuffer, 0);
216176578e28066262d983ec67896be50d49cf86e0ahenryhsu      copyBuffer(buffer, newBuffer, appendTone.length);
217176578e28066262d983ec67896be50d49cf86e0ahenryhsu      copyBuffer(appendTone, newBuffer, appendTone.length + buffer.length);
218176578e28066262d983ec67896be50d49cf86e0ahenryhsu      this.buffer = newBuffer;
219176578e28066262d983ec67896be50d49cf86e0ahenryhsu    } else {
220176578e28066262d983ec67896be50d49cf86e0ahenryhsu      this.buffer = buffer;
221176578e28066262d983ec67896be50d49cf86e0ahenryhsu    }
222176578e28066262d983ec67896be50d49cf86e0ahenryhsu  }
223176578e28066262d983ec67896be50d49cf86e0ahenryhsu
224176578e28066262d983ec67896be50d49cf86e0ahenryhsu  this.init();
225176578e28066262d983ec67896be50d49cf86e0ahenryhsu}
226176578e28066262d983ec67896be50d49cf86e0ahenryhsu
227176578e28066262d983ec67896be50d49cf86e0ahenryhsuwindow.AudioPlay = AudioPlay;
228