Creating dub delay effects with the Web Audio API

July 23, 2014

Introduction

King Tubby sat at a mixing desk [credit]

Dub music is a sub-genre of reggae. Typical of the style is the remixing of reggae records, first stripping them down to the bass and drums and then applying swirling, psychedelic delay effects to what’s left. In this post we’ll take a look at the classic dub delay sound, and try to recreate some of its character using the web audio API.

You'll need a browser that supports the Web Audio API to play the embedded demos. Chrome 36 and above work well, but these demos rely on cycles in the Web Audio graph which Firefox 31 has some problems with.

The chop

Dub delay can be applied to any element of a song, but often it’s applied to the “chop” - an off beat, minor-chord stab played on a keyboard or guitar. Here’s a chop

We’ve implemented it using a straight-forward HTML5 audio element wrapping an OGG-encoded file with the controls and looping enabled


<audio src="/demos/dub_delay/chop.ogg" controls="true" loop="true" />

Delay

The basis of the dub delay sound is, of course, the delay. You can think of a delay effect as simply taking the incoming sound and holding on to it for a pre-determined length of time, before letting it go again. Here’s how we create a delay using the Web Audio API


(function () {
  var ctx = new AudioContext();
  var audioElement = $('#delay audio')[0];

  audioElement.addEventListener('play', function() {
	var source = ctx.createMediaElementSource(audioElement);

	var delay = ctx.createDelay();
	delay.delayTime.value = 0.5;

	source.connect(delay);
	source.connect(ctx.destination);
	delay.connect(ctx.destination);
  });
})();

First we create an AudioContext to hold the nodes in our processing graph. The first node in our graph is the MediaElementSource node, which allows routes the audio from a HTML5 audio element into the graph. We have to wait for the play event to fire to be sure that the audio has loaded and has been decoded ready to hand off to the audio context.

We then create a Delay node, with a delay time of 0.5 seconds. Finally we connect the delay to the destination. Note here that we’ve also connected the source itself to the destination, if we don’t do that, we’ll just here the delayed version of the chop, and not the original - this way, we hear both:

Feedback

The delay on its own is a good start, but somehow it sounds a little too sterile. We need to add some feedback to start getting the swirling, psychedelic sounds of dub. To recreate this sound, we have to understand a little bit about how dub producers created it.

In an analogue studio, each mixing desk channel typically has an “auxiliary send” which allows some of the channel’s audio to be routed to an external effects unit, for example a analogue tape delay. If the return from that unit is sent back to another channel of the mixing desk - that channel too has an auxiliary send. By increasing the send on the return channel, the delayed sound can be routed through the delay unit again, and again, and again… creating what is known as feedback. Here’s how we do that in code


(function() {
  var ctx = new AudioContext();
  var audioElement = $('#feedback audio')[0];

  audioElement.addEventListener('play', function(){
	var source = ctx.createMediaElementSource(audioElement);

	var delay = ctx.createDelay();
	delay.delayTime.value = 0.5;

	var feedback = ctx.createGain();
	feedback.gain.value = 0.8;

	delay.connect(feedback);
	feedback.connect(delay);

	source.connect(delay);
	source.connect(ctx.destination);
	delay.connect(ctx.destination);
  });
})();

This is very similar to the code we saw above, but we’ve introduced a Gain node between the output of the delay node and the input of the delay node. Connecting the delay node to itself is enough to create the feedback loop, but without the gain node we’d be feeding 100% of the delayed sound back into the delay - creating a delay that grows forever. Introducing the gain node allows us to control this. Here’s how it sounds

Notice as the audio element itself loops, the tail of the feedback keeps playing - the original source and the feedback combine in interesting rhythmic ways depending on the amount of feedback and the length of the delay. This is a characteristic sound of dub.

Degradation in the audio chain

When working with the Web Audio API we are manipulating digital signals. Each echo in our feedback chain is a perfect copy of the original, just slightly quieter. It’s as close to “perfect” as we can get. Dub producers work with much less perfect equipment - analogue mixing desks and analogue-tape based delay units. Each time the signal passes through that processing chain it loses some fidelity - and this contributes to the sound of the dub delay. Creating a clone of this sound using digital signals is something of a holy grail, but to start you on that journey, lets introduce something to “dirty” up the feedback.


(function () {
  var ctx = new AudioContext();
  var audioElement = $('#filter audio')[0];

  audioElement.addEventListener('play', function(){
	var source = ctx.createMediaElementSource(audioElement);

	var delay = ctx.createDelay();
	delay.delayTime.value = 0.5;

	var feedback = ctx.createGain();
	feedback.gain.value = 0.8;

	var filter = ctx.createBiquadFilter();
	filter.frequency.value = 1000;

	delay.connect(feedback);
	feedback.connect(filter);
	filter.connect(delay);

	source.connect(delay);
	source.connect(ctx.destination);
	delay.connect(ctx.destination);
  });
})();

Here, we’ve introduced a BiquadFilter node. By default, this is a low-pass filter, which filters out frequencies above the frequency parameter. This is a simple way of adding some interest and it mimics the way an analogue signal chain would have a bigger effect on higher frequencies than lower ones.

As each echo decays away it also loses some of its high frequency content.

Putting it all together with knobs on!

Of course, most of the creative uses of dub delay don’t involve just the static application of the effect to the incoming sound. Instead the producer plays the equipment like an instrument, allowing the feedback to rise and dominate, stopping the original source to just play the echos, and adjusting the delay time and other filters in creative ways. Let’s try hooking up some simple sliders to some of the parameters of our dub delay effect.


(function () {
  var ctx = new AudioContext();
  audioElement = $('#sliders audio')[0]

  audioElement.addEventListener('play', function(){
	source = ctx.createMediaElementSource(audioElement);

	delay = ctx.createDelay();
	delay.delayTime.value = 0.5;

	feedback = ctx.createGain();
	feedback.gain.value = 0.8;

	filter = ctx.createBiquadFilter();
	filter.frequency.value = 1000;

	delay.connect(feedback);
	feedback.connect(filter);
	filter.connect(delay);

	source.connect(delay);
	source.connect(ctx.destination);
	delay.connect(ctx.destination);
  });

  var controls = $("div#sliders");

  controls.find("input[name='delayTime']").on('input', function() {
	delay.delayTime.value = $(this).val();
  });

  controls.find("input[name='feedback']").on('input', function() {
	feedback.gain.value = $(this).val();
  });

  controls.find("input[name='frequency']").on('input', function() {
	filter.frequency.value = $(this).val();
  });
})();

Now, try adjusting the values of the parameters and experiment with how they sound together. Starting and stopping the audio element is also possible, since the effects are happening “inside” the audio context.

delayTime: feedback: cutoff freq:

Summary

We’ve used the Web Audio API to create a simple recreation of some classic dub delay sounds. Where next?

  • You might have noticed a small artifact when adjusting the delay time. The specification states “When the delay time is changed, the implementation must make the transition smoothly, without introducing noticeable clicks or glitches to the audio stream”. When a change is made to the delay time parameter, the implementations make a smooth transition to the new value (called “dezippering”). In this case the more rapid change in time creates an artifact, which an analogue tape recorder wouldn’t have due to the mechanical constraints of the device on the speed of the change. You can experiment with using .setValueAtTime(value, 0) to remove the dezippering effect.
  • Ping-pong delay is an effect where each echo is sent alternately to the left and right channels. You sometimes here this effect in use, and it is possible to implement it using an extra delay node together with the channel splitter and channel merger nodes.

References

Thanks

It took me a long time to write this post, as I hit some pretty odd bugs along the way. A huge thanks to Tom, Chris and James for providing advice, space and for putting up with my noise.

Also thanks to Chris Wilson for pointing out the issue with artifacts/dezippering.

Published