Drum Machine

by Paulo Ávila

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.js"></script>
  <div id="app" ng-app="drumMachineApp" ng-controller="RhythmCtrl">
    <div ng-repeat="pattern in rhythm.patterns">
      <label>{{ pattern.sound }}</label>
      <span ng-class="{cursor: Math.floor(cursor) === $index}" ng-repeat="beat in pattern.beats track by $index">
        <input type="checkbox"
          ng-click="patternClick(pattern.sound, $index)"
          ng-checked="beat > 0" />
      </span>
      <br />
    </div>
    <input type="number" ng-model="tempo" ng-change="tempoChange()"/>
    <br />
    <button ng-click="play()" >Play</button>
    <button ng-click="pause()" >Pause</button>
    <button ng-click="stop()" >Stop</button>
  </div>

CSS

#app {
  max-width: 600px;
  margin: auto;
  text-align: center;
  font-family: monospace;
}

#app .cursor {
  background-color: yellow;
}

#app label {
  display: inline-block;
  width: 50px;
}

#app input[type=number] {
  margin: 1em;
}

JavaScript

// http://joesul.li/van/tale-of-no-clocks/

AudioContext = window.AudioContext || webkitAudioContext;
OfflineAudioContext = window.OfflineAudioContext || webkitOfflineAudioContext;

var drumMachineApp = angular.module("drumMachineApp", []);

// A global AudioContext services + controllers can reach
drumMachineApp.service('contextService', function() {
  this.context = new AudioContext();
});

// Something to hold this rhythm
// TODO: investigate using objects instead of numbers for beats
drumMachineApp.service('rhythmService', function() {
  // Lets make it shorter for smaller screens
  if (window.innerWidth < 700) {
    this.rhythm = {
      patterns: [{
        sound: "hat",
        beats: [0, 0, 1, 0, 0, 0, 1, 0]
      }, {
        sound: "snare",
        beats: [0, 0, 0, 0, 1, 0, 0, 0]
      }, {
        sound: "kick",
        beats: [1, 0, 0, 0, 1, 0, 0, 0]
      }]
    };
  } else {
    this.rhythm = {
      patterns: [{
        sound: "hat",
        beats: [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0]
      }, {
        sound: "snare",
        beats: [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
      }, {
        sound: "kick",
        beats: [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]
      }]
    };
  }
});

// Place for some audio-specific code
drumMachineApp.service('audioService', function(contextService, $q) {
  this.context = contextService.context;

  this.loadBuffer = function(url) {
    var context = new OfflineAudioContext(2, 1, 44100);
    return $q(function(resolve, reject) {
      var request = new XMLHttpRequest();
      request.open('GET', url, true);
      request.responseType = 'arraybuffer';
      request.onload = function() {
        context.decodeAudioData(request.response, resolve, reject);
      };
      request.onerror = reject;
      request.send();
    });
  };

  this._buffers = {};
  // Retrieve a pre-loaded buffer
  this.getBuffer = function(name) {
    return this._buffers[name];
  };

  // Load up the sounds......