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......