Home | History | Annotate | Download | only in frontend
      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 
      7 var 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 
    103 window.ToneGen = ToneGen;
    104 
    105 var 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 
    227 window.AudioPlay = AudioPlay;
    228