Web Audio API
+
AngularJS



@batemanchris

The Web Audio API



Why?


  • Synthesize
  • Modify and mix
  • Analyze



Audio Trackr

removing vowels is still cool, right?



Wanted to push the Web Audio API to its limits.


Loading and Playing Audio Files




Two Options

  • Audio Buffer Source
  • Media Element Source

Audio Buffer Source

Load and decode files manually
var request = new XMLHttpRequest();
request.open('GET', myURL, true);
request.responseType = 'arraybuffer';
request.onload = requestLoad;
request.send();

function requestLoad() {
    // yay, finally done loading
    _audioContext.decodeAudioData(request.response, doneDecoding);
}

function doneDecoding(buffer) {
    // yay, finally done decoding
    playBuffer(buffer);
}

function playBuffer(buffer) {
    var bufferSourceNode = _audioContext.createBufferSource();
    bufferSourceNode.buffer = buffer;

    // connect to output
    buffer.connect(_audioContext.destination);

    //play
    buffer.start(0);
}

Media Element Source

Use HTML Audio element - get buffering and decoding for free

Thank you WebRTC
// create an HTML Audio Element, or use an existing one
var audio = new Audio(myURL);

// Audio Element will let us know when it's buffered enough
audio.addEventListener('canplaythrough', function(e) {    var mediaSourceNode = _audioContext.createMediaElementSource(audio);

// connect to output mediaSourceNode.connect(_audioContext.destination); // play audio.play(); });

createMediaElementSource()
Caveats


  • Not supported in Firefox yet (don't worry, they'll get there)
  • iOS will only play one Audio Element at a time
    • and there's no way to detect whether a browser will play more than
      one Audio Element at a time
  • Connections per Hostname
    • Most browsers will open up to 6 simultaneous
      HTTP requests per hostname (reference)
    • If you try to load 7+ Audio Elements at the same time, the first 6 HTTP requests
      will pause after buffering a bit, waiting to see if you're going to play them or not.
      Meanwhile, your remaining files aren't loading at all.

Angular - HTML


<body ng-app="Page" ng-controller="PageController">
    <div class="songs">
        <span ng-repeat="song in songs">
            <input type="radio" ng-model="$parent.currentSong" ng-value="song" />
            <label>{{song.name+' ('+song.band+')'}}</label>
        </span>
    </div>
    
    <div class="controls" ng-show="currentSong">
        <h2>{{currentSong.name}}</h2>
        <button ng-show="!playing" ng-click="playTracks()">Play</button>
        <button ng-show="playing" ng-click="stopTracks()">Stop</button>
    </div>
    
    <div class="track-container">
        <div ng-controller="TrackController" class="track" ng-class="{loading:loading==true}" ng-repeat="(key, track) in currentSong.tracks">
            <input type="range" ng-model="trackVolume" />
            <canvas></canvas>
            <span class="spinner" ng-show="loading"></span>
        </div>
    </div>
</body>

Angular - Main Controller


just a part of the controller
 _page.controller('PageController', function($scope) {
    
    $scope.playTracks = function(tracks) {
        angular.forEach(tracks, function(track, key) {
            track.play();
        });
        $scope.playing = true;
    };    
    function tick() {
        angular.forEach($scope.currentSong.tracks, function(track, key) {
            if (track.analyser) {
                _drawStuff(track.cCtx, track.analyser);
            }
        });
        window.requestAnimationFrame(tick);
    }
});

Angular - Track Controller


just a part of the controller
 _page.controller('TrackController', function($scope, $element) {
    $scope.trackVolume = 100;
    $scope.canvas = $element[0].getElementsByTagName('canvas')[0];
    
    $scope.track.play = function() {
        $scope.track.audio.play();
    };
    
    $scope.track.stop = function() {
        $scope.track.audio.pause();
    };
    
    $scope.$watch('trackVolume', function(value) {
        value = value / 100;
        if ($scope.track.gainNode) {
            $scope.track.gainNode.gain.value = value;
        }
    });
});

Audio Visualization


var analyser = _aCtx.createAnalyser();
analyser.smoothingTimeConstant = 0.6;
analyser.fftSize = 256;

// Each frame:var byteFreqArr = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(byteFreqArr); var timeDomainArr = new Uint8Array(analyser.frequencyBinCount); analyser.getByteTimeDomainData(timeDomainArr); canvasContext.clearRect(0, 0, _cWidth, _cHeight); canvasContext.beginPath(); for (var i=0,iLen=byteFreqArr.length; i<iLen; i++) { canvasContext.fillRect(i*_freqDrawWidth, _cHeight - (byteFreqArr[i] / 256 * _cHeight), (_freqDrawWidth - 2), _cHeight); var percent = timeDomainArr[i] / 256; var offset = _cHeight - (percent * _cHeight) - 1; canvasContext.lineTo(i*_timeDrawWidth, offset); } canvasContext.stroke();




Questions?

Web Audio API+AngularJS

By Chris Bateman

Web Audio API+AngularJS

  • 10,528