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