Skip to content

Instantly share code, notes, and snippets.

@mbarzeev
Last active November 18, 2016 16:20

Revisions

  1. mbarzeev revised this gist Jul 31, 2013. 2 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
  2. mbarzeev renamed this gist Jul 31, 2013. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. mbarzeev created this gist Jul 31, 2013.
    13 changes: 13 additions & 0 deletions app.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,13 @@
    // This is the "run" block of your main module
    .run(['$window', function (window) {
    // First, let's shim the requestAnimationFrame API,
    // with a setTimeout fallback
    window.requestAnimFrame = window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function (callback) {
    window.setTimeout(callback, 1000 / 60);
    };
    }])
    5 changes: 5 additions & 0 deletions main-view.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,5 @@
    <!-- Given that you have a $scope with mytempo defined on it -->
    <div>
    <input type="number" ng-model="mytempo">
    <metronome tempo="{{mytempo}}"></metronome>
    </div>
    99 changes: 99 additions & 0 deletions metronome-drtv.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,99 @@
    'use strict';

    angular.module('MyModule')
    .directive('metronome', ['$window', function (window) {
    return {
    restrict: 'E',

    scope: true,

    link: function ($scope, element, attrs) {
    $scope.audioContext = new webkitAudioContext();
    $scope.isPlaying = false;
    $scope.current16thNote;
    $scope.tempo = parseInt(attrs.tempo, 10);
    $scope.lookahead = 25.0;
    $scope.scheduleAheadTime = 0.1;
    $scope.nextNoteTime = 0.0;
    $scope.noteResolution = 0;
    $scope.noteLength = 0.05;
    $scope.timerID = 0;
    $scope.minimumTempo = 30.0;
    $scope.maximumTempo = 230.0;

    attrs.$observe('tempo', function (value) {
    $scope.tempo = parseInt(value, 10);
    });

    $scope.nextNote = function () {
    // Advance current note and time by a 16th note...
    var secondsPerBeat = 60.0 / $scope.tempo; // Notice this picks up the CURRENT
    // tempo value to calculate beat length.
    $scope.nextNoteTime += 0.25 * secondsPerBeat; // Add beat length to last beat time

    $scope.current16thNote++; // Advance the beat number, wrap to zero
    if ($scope.current16thNote === 16) {
    $scope.current16thNote = 0;
    }
    },

    $scope.scheduleNote = function (beatNumber, time) {
    if (($scope.noteResolution === 1) && (beatNumber % 2)) {
    return; // we're not playing non-8th 16th notes
    }
    if (($scope.noteResolution === 2) && (beatNumber % 4)) {
    return; // we're not playing non-quarter 8th notes
    }

    // create an oscillator
    var osc = $scope.audioContext.createOscillator();
    osc.connect($scope.audioContext.destination);
    if (!(beatNumber % 16)) { // beat 0 == low pitch
    osc.frequency.value = 220.0;
    } else if (beatNumber % 4) { // quarter notes = medium pitch
    osc.frequency.value = 440.0;
    } else { // other 16th notes = high pitch
    osc.frequency.value = 880.0;
    }

    // TODO: Once start()/stop() deploys on Safari and iOS, these should be changed.
    osc.noteOn(time);
    osc.noteOff(time + $scope.noteLength);
    },

    $scope.scheduler = function () {
    // while there are notes that will need to play before the
    // next interval, schedule them and advance the pointer.
    while ($scope.nextNoteTime < $scope.audioContext.currentTime + $scope.scheduleAheadTime) {
    $scope.scheduleNote($scope.current16thNote, $scope.nextNoteTime);
    $scope.nextNote();
    }
    $scope.timerID = window.setTimeout($scope.scheduler, $scope.lookahead);
    },

    $scope.play = function () {
    $scope.isPlaying = !$scope.isPlaying;

    if ($scope.isPlaying) { // start playing
    $scope.current16thNote = 0;
    $scope.nextNoteTime = $scope.audioContext.currentTime;
    $scope.scheduler(); // kick off scheduling
    return 'stop';
    } else {
    window.clearTimeout($scope.timerID);
    return 'play';
    }
    };

    $scope.getButtonText = function () {
    var result = $scope.isPlaying ? 'Stop': 'Play';
    return result;
    };
    },

    template: '<div>' +
    '<input id="bpm" type="number" name="bpm" ng-model="tempo" min="{{minimumTempo}}" max="{{maximumTempo}}"> Bpm' +
    '<button ng-click="play()">{{getButtonText()}}</button>' +
    '</div>'
    };
    }]);