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