Shmethan is an audio sequencer built using the basic Web Audio API, JavaScript, jQuery, HTML, and CSS. Users can click on purple squares to create drum and synth patterns. Clicking on the green circle will start the beat, and clicking again will stop it. You can edit the beat while it's playing, too. Sliders on the bottom right control the beat's tempo, the volumes of the drum and synth samples, and a simple audio effect called 'Magic Sauce' (slap-back delay, feedback, and compression).
The core of the audio sequencing consists in scheduling audio events to be played at specific moments in time. This is done using the method outlined in Chris Wilson's A Tale of Two Clocks. The basic idea is to use the built-in Web Audio Api clock to precisely schedule audio events, and to use the powerful but less-precise JavaScript setTimeout
function to call the schedule function repeatedly. This is accomplished using a Web Worker that runs in a separate global context so it doesn't interfere with the program in the main thread. I used Chris Wilson's midi drum machine as reference for the Web Worker.
The audio samples are hosted on github and are loaded into the program using an XMLHttpRequest
. A buffers
hash stores createNode
functions that, when invoked, return audio nodes connected to the user's speakers (i.e. audioContext.destination
).
const loadSynthSound = (instrumentName) => {
const oReq = new XMLHttpRequest();
oReq.open('GET', 'https://raw.githubusercontent.com/ethannkschneider/ecto-drum/master/audio_files/' + instrumentName + ".mp3", true);
oReq.responseType = 'arraybuffer';
oReq.onload = () => {
audioContext.decodeAudioData(oReq.response, (audioBuffer) => {
let createNode = function() {
let source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(synthGainNode);
return source;
};
buffers[instrumentName] = { createNode };
});
};
oReq.send();
};
I referenced this example drum machine for the sample loading functions.
Each purple square (or node) on the sequencer is a separate div
element on the page. To create demo beats, I added classes to the elements that I wanted in each beat, and then added a click
event listener on the demo buttons that resets the grid and then 'clicks' each node in the beat.
$(".grid-node").click( (e) => {
let $node = $(e.target);
let nodeBeat = $node.data("beat");
let nodeSound = $node.data("sound");
$node.toggleClass("clicked-node");
$node.hasClass("clicked-node") ?
activateNode(nodeBeat, nodeSound) : deactivateNode(nodeBeat, nodeSound);
});
//RESET and DEMO BEAT buttons
$("#reset").click( (e) => {
let $clickedNodes = $(".clicked-node");
$clickedNodes.click();
});
// I added specific classes to nodes that are in demo beats
// First reset the board, then click demo nodes
$("#demo1").click( (e) => {
$("#reset").click();
$(".demo-beat-one").click();
});
$("#demo2").click( (e) => {
$("#reset").click();
$(".demo-beat-two").click();
});
For this project, I decided not to use a high-level audio library (like Tone.js) because I wanted to learn how the Web Audio Api works at a lower level. When I started out, I planned on using a smaller library called WAAClock to help with note scheduling. However, I ran into some issues implementing this API (e.g. the beat not starting at the beginning), so I decided to switch to using the basic Web Audio Api in order to have more control of the audio scheduling.
The main references I used in creating this sequencer were both by Chris Wilson: his excellent article A Tale of Two Clocks and his related take on a midi drum machine.
I also made heavy use of this gradient generator to create most of the color gradients.