This week I'd like to talk about a few things that have interested me about the event loop and it's importance when creating UI in JavaScript. I've been recently working on a front-end project (creating a web-audio drum sequencer using Tone.js audio library and Nexus UI for the instrument interface) made with the Vue.js front-end framework. At first, thought, the task seemed deceptively simple, but upon implementation, I realized my naivety in thinking that it could be solved by simply 'looping' through an array of toggled buttons within a matrix (buttons that represent a instrument[x] and a point in time[y]) and calling particular functions that play certain sound at specified times.
I realized I was looking at it as a 'single threaded problem', partially because visually I had laid out in a 2D matrix of off an on switches, and imagined the execution of the code to be relative to the visual context. But, the construction of the actual working code was a little different. It needed to be asynchronous and event driven because when you select a button, you are choosing an option in a still un-determined future patterns, and the computer will not know what sound to make or pattern to play once all other functions have finished and the event has occurred.
Here's a look at the starting code:
const sequencer = new Nexus.Sequencer('#target',{
size: [400,200],
mode: 'toggle',
rows: 2,
columns: 10,
});
Calling the sequencer (as we do when we create createSound function and tie events to it) will append a nice little grid of toggle-able buttons to the page for us to start programming. But here's where the visual naive implementation was thought up, so we're going to dive a little deeper into the event loop first.
Because JavaScript is single threaded, meaning that one line of code must be executed and completed before the next line can be read, asynchronous code is not inherent to the language, but can be achieved by adding on functionality and using the event loop. Although the event loop is there, you have to create a situation for your-self where it can be utilized to create asynchronous functionality.
That's what made me realize I needed to re-arrange the order of my code to ensure that all functions would be called before the event loop could fire.
Here are the inner workings of the createSound function. A sequencer is built on the page when this function is called and these 2 events are defined and bound to the sequencer object. When ever a button is toggled, the on.change event is fired. Whenever the loop has started, the step event is played.
sequencer.on('change', (v) => {
if (v.row === 1) {
this.instruments.push('snare');
}
if (v.row === 2) {
this.instruments.push('bass');
}
});
sequencer.on('step', (v) => {
for (let i = 0; i <= v.length; i++) {
if (v[i]) {
this.players[this.instruments[i]].start();
}
}
});
this.instruments = [];
this.sequencer = sequencer;
Each time a button is toggled, an instrument is pushed to the instruments array stored in the app's shared state property 'instruments'. Each sequencer is already set up to move column by column every few seconds, so when that time happens, the step event is called and only then does it loop through whatever is in state at that moment, and fires those functions, and clears the state.
I hope this was an interesting context to view and understand the basics of the event loop. My next post will be even more in depth look at the sequencer object itself, and a full demo of a working built out, event driven digital instrument built entirely with JavaScript.
Top comments (0)