drum-machine
Version:
A simple drum machine / sequencer written in javascript
356 lines (258 loc) • 9.88 kB
JavaScript
const loadSampleSet = require('load-sample-set');
const selectElement = require('select-element');
const getSetFormValues = require('get-set-form-values');
const adsrGainNode = require('adsr-gain-node');
const simpleTracker = require('./simple-tracker');
const FileSaver = require('file-saver');
const getSetControls = require('./get-set-controls');
const getSetAudioOptions = new getSetControls();
const ctx = new AudioContext();
const defaultTrack = require('./default-track');
var buffers;
var currentSampleData;
var storage;
function initializeSampleSet(ctx, dataUrl, track) {
var sampleSetPromise = loadSampleSet(ctx, dataUrl);
sampleSetPromise.then(function (data) {
buffers = data.buffers;
sampleData = data.data;
if (!track) {
track = storage.getTrack();
}
if (!track.settings.measureLength) {
track.settings.measureLength = 16;
}
currentSampleData = sampleData;
setupTrackerHtml(sampleData, track.settings.measureLength);
schedule.loadTrackerValues(track.beat);
schedule.setupEvents();
});
}
window.onload = function () {
let formValues = new getSetFormValues();
let form = document.getElementById("trackerControls");
formValues.set(form, defaultTrack.settings);
getSetAudioOptions.setTrackerControls(defaultTrack.settings);
initializeSampleSet(ctx, defaultTrack.settings.sampleSet, defaultTrack);
setupBaseEvents();
storage = new tracksLocalStorage();
storage.setupStorage();
};
var instrumentData = {};
function setupTrackerHtml(data, measureLength) {
instrumentData = data;
instrumentData.title = instrumentData.filename;
schedule.drawTracker(data.filename.length, measureLength, instrumentData);
return;
}
function disconnectNode(node, options) {
let totalLength =
options.attackTime + options.sustainTime + options.releaseTime;
setTimeout(() => {
node.disconnect();
}, totalLength * 1000);
}
function scheduleAudioBeat(beat, triggerTime) {
let instrumentName = instrumentData.filename[beat.rowId];
let instrument = buffers[instrumentName].get();
let options = getSetAudioOptions.getTrackerControls();
function play(source) {
source.detune.value = options.detune;
// Gain
let node = routeGain(source)
node = routeDelay(node);
// node = routeCompressor(node);
node.connect(ctx.destination);
source.start(triggerTime);
}
function routeGain (source) {
let gain = new adsrGainNode(ctx);
gain.mode = 'linearRampToValueAtTime';
let options = getSetAudioOptions.getTrackerControls();
let gainNode;
// Not enabled - default gain
if (!options.gainEnabled) {
gainNode = gain.getGainNode(triggerTime);
source.connect(gainNode);
return gainNode;
}
gain.setOptions(options);
gainNode = gain.getGainNode(triggerTime);
source.connect(gainNode);
return gainNode;
}
// Note delay always uses above gain - even if not enabled
function routeDelay(node) {
if (!options.delayEnabled) {
return node;
}
// create delay node
let delay = ctx.createDelay();
delay.delayTime.value = options.delay;
// create adsr gain node
let gain = new adsrGainNode(ctx);
gain.mode = 'linearRampToValueAtTime';
gain.setOptions(options);
let feedbackGain = gain.getGainNode(triggerTime);
// create filter
let filter = ctx.createBiquadFilter();
filter.frequency.value = options.filter;
// delay -> feedbackGain
delay.connect(feedbackGain);
disconnectNode(delay, options);
// feedback -> filter
feedbackGain.connect(filter);
// filter ->delay
filter.connect(delay);
node.connect(delay);
return delay;
}
play(instrument);
}
var schedule = new simpleTracker(ctx, scheduleAudioBeat);
function setupBaseEvents() {
// var initializedCtx;
document.getElementById('play').addEventListener('click', function (e) {
ctx.resume().then(() => {
console.log('Playback resumed successfully');
});
let storage = new tracksLocalStorage();
let track = storage.getTrack();
schedule.measureLength = track.settings.measureLength;
schedule.stop();
schedule.runSchedule(getSetAudioOptions.options.bpm);
});
document.getElementById('pause').addEventListener('click', function (e) {
schedule.stop();
});
document.getElementById('stop').addEventListener('click', function (e) {
schedule.stop();
schedule = new simpleTracker(ctx, scheduleAudioBeat);
});
document.getElementById('bpm').addEventListener('change', function (e) {
getSetAudioOptions.setTrackerControls();
if (schedule.running) {
schedule.stop();
schedule.runSchedule(getSetAudioOptions.options.bpm);
}
});
document.getElementById('measureLength').addEventListener('input', (e) => {
$('#measureLength').bind('keypress keydown keyup', (e) => {
if (e.keyCode == 13) {
e.preventDefault();
let value = document.getElementById('measureLength').value;
let length = parseInt(value);
if (length < 1) return;
schedule.measureLength = length;
let track = schedule.getTrackerValues();
setupTrackerHtml(currentSampleData, length);
schedule.measureLength = length;
schedule.loadTrackerValues(track)
schedule.setupEvents();
}
});
});
$('.base').on('change', function () {
getSetAudioOptions.setTrackerControls();
});
}
$('#sampleSet').on('change', function () {
initializeSampleSet(ctx, this.value);
});
function tracksLocalStorage() {
this.setLocalStorage = function (update) {
var storage = {};
storage['Select'] = 'Select';
for (var i = 0, len = localStorage.length; i < len; ++i) {
let item = localStorage.key(i);
storage[item] = item;
}
// Create select element
var s = new selectElement(
'load-storage', // id to append the select list to
'beat-list', // id of the select list
storage //
);
if (update) {
s.update('beat-list', storage);
} else {
s.create();
}
};
this.getFilename = function () {
let filename = $('#filename').val();
if (!filename) {
filename = 'untitled';
}
return filename;
}
/**
* Get complete song
*/
this.getTrack = function () {
let formData = getSetAudioOptions.getTrackerControls();
let beat = schedule.getTrackerValues();
let song = { "beat": beat, "settings": formData };
return song;
}
this.alert = function (message) {
let appMessage = document.getElementById('app-message');
appMessage.innerHTML = message
appMessage.style.display = 'block'
setTimeout(function () {
appMessage.style.display = 'none'
}, 2000)
}
this.setupStorage = function () {
this.setLocalStorage();
document.getElementById('save').addEventListener('click', (e) => {
e.preventDefault();
let song = this.getTrack();
let json = JSON.stringify(song);
let filename = this.getFilename();
localStorage.setItem(filename, json);
this.setLocalStorage('update');
$("#beat-list").val(filename);
this.alert(`The track has been saved to local storage as <strong>${filename}</strong>`)
});
// saveAsJson
document.getElementById('saveAsJson').addEventListener('click', (e) => {
e.preventDefault();
let song = this.getTrack();
let json = JSON.stringify(song);
let filename = this.getFilename();
var blob = new Blob([json], {type: "application/json"});
FileSaver.saveAs(blob, filename + ".json");
});
$('#filename').bind('keypress keydown keyup', (e) => {
if (e.keyCode == 13) {
e.preventDefault();
}
});
document.getElementById('beat-list').addEventListener('change', (e) => {
let item = $('#beat-list').val();
if (item === 'Select') {
document.getElementById('filename').value = '';
return;
}
document.getElementById('filename').value = item;
let track = JSON.parse(localStorage.getItem(item));
let formValues = new getSetFormValues();
let form = document.getElementById("trackerControls");
formValues.set(form, track.settings);
getSetAudioOptions.setTrackerControls(track.settings);
schedule.stop();
schedule.measureLength = track.settings.measureLength;
initializeSampleSet(ctx, track.settings.sampleSet, track);
});
document.getElementById('delete').addEventListener('click', (e) => {
e.preventDefault();
let elem = document.getElementById('beat-list');
let toDelete = elem.options[elem.selectedIndex].text;
localStorage.removeItem(toDelete);
document.getElementById('filename').value = '';
this.setLocalStorage('update');
this.alert(`Track has been deleted`)
});
};
}