• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
7var ToneGen = function() {
8  /**
9  * Initializes tone generator.
10   */
11  this.init = function() {
12    this.audioContext = new AudioContext();
13  }
14
15  /**
16   * Sets sample rate
17   * @param {int} sample rate
18   */
19  this.setSampleRate = function(sampleRate) {
20    this.sampleRate = sampleRate;
21  }
22
23  /**
24   * Sets start/end frequencies and logarithmic sweep
25   * @param {int} start frequency
26   * @param {int} end frequency
27   * @param {boolean} logarithmic sweep or not
28   */
29  this.setFreq = function(freqStart, freqEnd, sweepLog) {
30    this.freqStart = freqStart;
31    this.freqEnd = freqEnd;
32    this.sweepLog = sweepLog;
33  }
34
35  /**
36   * Sets tone duration
37   * @param {float} duration in seconds
38   */
39  this.setDuration = function(duration) {
40    this.duration = parseFloat(duration);
41  }
42
43  /**
44   * Sets left and right gain value
45   * @param {float} left gain between 0 and 1
46   * @param {float} right gain between 0 and 1
47   */
48  this.setGain = function(leftGain, rightGain) {
49    this.leftGain = parseFloat(leftGain);
50    this.rightGain = parseFloat(rightGain);
51  }
52
53  /**
54   * Generates sine tone buffer
55   */
56  this.genBuffer = function() {
57    this.buffer = this.audioContext.createBuffer(2,
58        this.sampleRate * this.duration, this.sampleRate);
59    var leftChannel = this.buffer.getChannelData(0);
60    var rightChannel = this.buffer.getChannelData(1);
61    var phi;
62    var k = this.freqEnd / this.freqStart;
63    var beta = this.duration / Math.log(k);
64    for (var i = 0; i < leftChannel.length; i++) {
65      if (this.sweepLog) {
66        phi = 2 * Math.PI * this.freqStart * beta *
67            (Math.pow(k, i / leftChannel.length) - 1.0);
68      } else {
69        var f = this.freqStart + (this.freqEnd - this.freqStart) *
70            i / leftChannel.length / 2;
71        phi = f * 2 * Math.PI * i / this.sampleRate;
72      }
73      leftChannel[i] = this.leftGain * Math.sin(phi);
74      rightChannel[i] = this.rightGain * Math.sin(phi);
75    }
76  }
77
78  /**
79   * Returns generated sine tone buffer
80   * @return {AudioBuffer} audio buffer
81   */
82  this.getBuffer = function() {
83    return this.buffer;
84  }
85
86  /**
87   * Returns append buffer
88   * @return {AudioBuffer} append audio buffer
89   */
90  this.getAppendTone = function(sampleRate) {
91    var tone_freq = 1000, duration = 0.5;
92    this.setFreq(tone_freq, tone_freq, false);
93    this.setDuration(duration);
94    this.setGain(1, 1);
95    this.setSampleRate(sampleRate);
96    this.genBuffer();
97    return this.getBuffer();
98  }
99
100  this.init();
101}
102
103window.ToneGen = ToneGen;
104
105var AudioPlay = function() {
106  var playCallback = null;
107  var sampleRate;
108  var playing = false;
109
110  /**
111   * Initializes audio play object
112   */
113  this.init = function() {
114    this.audioContext = new AudioContext();
115    this.genChannel();
116    this.buffer = null;
117    sampleRate = this.audioContext.sampleRate;
118  }
119
120  /**
121   * Loads audio file
122   * @param {blob} audio file
123   * @param {function} callback function when file loaded
124   */
125  this.loadFile = function(file_blob, done_cb) {
126    if (file_blob) {
127      var audioContext = this.audioContext;
128      reader = new FileReader();
129      reader.onloadend = function(e) {
130        audioContext.decodeAudioData(e.target.result,
131            function(buffer) {
132              done_cb(file_blob.name, buffer);
133            });
134      };
135      reader.readAsArrayBuffer(file_blob);
136    }
137  }
138
139  /**
140   * Sets audio path
141   */
142  this.genChannel = function() {
143    this.node = (this.audioContext.createScriptProcessor ||
144        this.audioContext.createJavaScriptNode).call(
145        this.audioContext, 4096, 2, 2);
146    this.splitter = this.audioContext.createChannelSplitter(2);
147    this.merger = this.audioContext.createChannelMerger(2);
148    this.node.connect(this.splitter);
149    this.splitter.connect(this.merger, 0, 0);
150    this.splitter.connect(this.merger, 1, 1);
151    this.merger.connect(this.audioContext.destination);
152
153    this.node.onaudioprocess = function(e) {
154      for (var i = 0; i < e.inputBuffer.numberOfChannels; i++) {
155        e.outputBuffer.getChannelData(i).set(
156            e.inputBuffer.getChannelData(i), 0);
157      }
158      if (!playing) return;
159      if (playCallback) {
160        var tmpLeft = e.inputBuffer.getChannelData(0).subarray(
161            -FFT_SIZE-1, -1);
162        var tmpRight = e.inputBuffer.getChannelData(1).subarray(
163            -FFT_SIZE-1, -1);
164        playCallback(tmpLeft, tmpRight, sampleRate);
165      }
166    }
167  }
168
169  /**
170   * Plays audio
171   * @param {function} callback function when audio end
172   * @param {function} callback function to get current buffer
173   */
174  this.play = function(done_cb, play_cb) {
175    playCallback = play_cb;
176    this.source = this.audioContext.createBufferSource();
177    this.source.buffer = this.buffer;
178    this.source.onended = function(e) {
179      playing = false;
180      this.disconnect();
181      if (done_cb) {
182        done_cb();
183      }
184    }
185    this.source.connect(this.node);
186    this.source.start(0);
187    playing = true;
188  }
189
190  /**
191   * Stops audio
192   */
193  this.stop = function() {
194    playing = false;
195    this.source.stop();
196    this.source.disconnect();
197  }
198
199  /**
200   * Sets audio buffer
201   * @param {AudioBuffer} audio buffer
202   * @param {boolean} append tone or not
203   */
204  this.setBuffer = function(buffer, append) {
205    if (append) {
206      function copyBuffer(src, dest, offset) {
207        for (var i = 0; i < dest.numberOfChannels; i++) {
208          dest.getChannelData(i).set(src.getChannelData(i), offset);
209        }
210      }
211      var appendTone = tonegen.getAppendTone(buffer.sampleRate);
212      var bufferLength = appendTone.length * 2 + buffer.length;
213      var newBuffer = this.audioContext.createBuffer(buffer.numberOfChannels,
214          bufferLength, buffer.sampleRate);
215      copyBuffer(appendTone, newBuffer, 0);
216      copyBuffer(buffer, newBuffer, appendTone.length);
217      copyBuffer(appendTone, newBuffer, appendTone.length + buffer.length);
218      this.buffer = newBuffer;
219    } else {
220      this.buffer = buffer;
221    }
222  }
223
224  this.init();
225}
226
227window.AudioPlay = AudioPlay;
228