UNPKG

drum-machine

Version:

A simple drum machine / sequencer written in javascript

356 lines (258 loc) 9.88 kB
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`) }); }; }