WebAudio — Using reverb for karaoke!

Viral Tagdiwala
3 min readJul 5, 2021

Not everyone finds themselves trying to making a karaoke box in the browser, but if you’ve found yourself making one, and are forced to support all browsers & all devices (i.e. you can’t use worklets) OR you just happen to love web audio, this article might be for you!

The correct term for that “karaoke” like effect would be a reverb! So all we gotta do is implement reverb right?

Wrong. A whole lot goes behind getting the effect right, the biggest contributor here will be your reverb kernel/impulse file!

Let’s start from the beginning and decode this —

Like most audio applications, we start off with generating a context that will be used later on to generate our audio nodes

const audioCtx = new window.AudioContext();

Now, web audio does make our life a lot easier than we’d think, before writing this article I thought of releasing an npm library for generating karaoke-like reverb but the setup is so simple, it makes little sense to add a dependency to your project.

The Convolver Node!

From the official docs —

The ConvolverNode interface is an AudioNode that performs a Linear Convolution on a given AudioBuffer, often used to achieve a reverb effect. A ConvolverNode always has exactly one input and one output.

The answer lies right here, this node performs a linear convolution operation between your audio buffer and…. the impulse kernel!

For someone who has studied DSP (Digital signal processing), they know how using various different forms of impulse kernels, we can in a way “shape” our output matrix!

There are a lot of filters (essential .wav files) available out there, but choosing the right one makes the biggest difference!

It comes down to latency vs. complexity. If your filter is 10 seconds long, you need to store the audio buffer of the last 10 sec to be able to calculate the current output audio sample with a latency of basically zero (ignoring the time required for calculations here) simply by doing:

where 𝑙 is the length of the impulse response ℎ and 𝑥[0] is always assumed to be the current sample.

While this gives you basically zero lag, it boils down to the performance of naive convolution which can easily be too much especially if the impulse response is very long.

Back to code —

After some trial & error, for my purposes, I found this impulse kernel provided in tunajs library to work the best — YMMV. You can opt to use tuna for generating reverb, but in this article, we’ll be sticking to pure webaudio based implementation!

We now create the main reverb node and add in the impulse response kernel!

//We are assuming you have a media stream obtained via getUserMediaconst inputAudioStream = audioCtx.createMediaStreamSource(stream)const reverbNode = audioCtx.createConvolver();const response = await fetch("impulse_rev.wav");
const arraybuffer = await response.arrayBuffer();
reverbNode.buffer = await audioCtx.decodeAudioData(arraybuffer);
inputAudioStream.connect(reverbNode);
reverbNode.connect(audioCtx.destination);

This makes our audio graph quite straightforward,

Audio graph implementation

And that’s pretty much it! You can make this system fancier by adding a gain node to the trailing edge and decreasing the gain a little bit so the sound doesn’t hit too hard (this might happen if you don’t have static filters in place for noise reduction P.S. you can checkout my previous articles to see how to do that!)

--

--