Creating an audio waveform from your microphone input

I've recently started creating an online audio editor. One of the features I wanted to implement was to create a waveform for each track in the editor. This gives a nice overview of the content of each track.

While recording a new track, it would be cool to visually see the the waveform you're recording so I decided to generate a waveform in realtime while recording a new audio track.

Below I will go through the basics of how you can create such a waveform from your audio input device.

To get ahold of the microphone audio in a web browser, we're going to use getUserMedia. This will either resolve into an audio stream, or reject with an error.

Note: we're using navigator.mediaDevices.getUserMedia because navigator.getUserMedia has been deprecated in favor of the mediaDevices method. This currently only works in Chrome and Firefox.

Once we've got an audio stream, we can create a MedaiStreamSourceNode. This is an audio node which we can use in our web-audio chain of nodes.

We are also going to create an AnalyserNode to analyse our incomming audio, and a ScriptProcessorNode to process the data of the audio stream.

Note: we're using a ScriptProcessorNode which has been deprecated, and will be replaced by audio workers in the future. At the moment, no browser supports these audio workers, so we'll stick to the ScriptProcessorNode for now.

Now our incoming audio will go from the input MediaStreamSourceNode to the AnalyserNode to the ScriptProcessorNode. We'll also connect our scriptProcessor to the destination of our audioContext. There seems to be some strange behavior in Chrome where the scriptProcessor does not receive any input when it is not connected to the audioContext's destination.

Tip: the firefox developer tools have a very nice web-audio inspector. This tab allows you to visualize and inspect your audio chain.

Now our setup is complete, and we can start processing our incoming audio.

We start by adding an event handler to the scriptProcessor's onaudioprocess event.

scriptProcessor.onaudioprocess = processInput;

Every time the scriptProcessor processes audio, it is going to run the processInput function, so let's create that function next.

Tip: We use requestAnimationFrame, this will prevent the browser painting too much. It will ask the browser when a new frame starts and executes it's callback. The scriptProcessor's onaudioprocess event happens more than than the browser can paint the bars. By using requestAnimationFrame we'll postpone the painting until the browser re-renders the page again (roughly 60 times a second).

The first thing renderBars will do is clear all previously drawn bars from the canvas. We do this by using canvasContext.clearRect and passing the width and the height of the canvas.

Next it is going set the fillStyle to the color we configured and draw a bar for each of the values in the bars array. The middle of the canvas will be the value 0, and we'll draw the same bar above and below the middle of the canvas.

We can draw the bars by using canvas' canvasContext .fillRect method which accepts a starting x and y position and a width and height. We can calculate based on the index of our bar, and the height based on the value of our bar. I've added a bit of extra logic so you can define the width of a bar and the space between bars but it all boils down to this:

Top part of the bar:

x: index

y: half the canvas' height minus the space above the bar

width: width of the bar

height: The value of the bar

Bottom part of the bar:

x: index

y: half the canvas' height

width: width of the bar

height: The value of the bar

The full code can be found in this Codepen, I've tried to annotate the javascript so it's clear what's happening.