Synthesizing Hi-Hats with Web Audio

Introduction

I just came across Chris Lowis's cool Synthesising Drum Sounds with the Web Audio API article, which covers synthesizing drum kick and snare sounds. It stops short of synthesizing the hi-hat, however:

Synthesising a realistic-sounding hi-hat is hard. When you strike a disk of metal the sound that is produced is a complex mixture of unevenly-spaced harmonics which decay at different rates. It’s not an impossible task, and if you’re interested there’s some further reading below, but for this post we’re going to cheat a little by sampling an existing sound.

Taking a close look at Gordon Reid's Sound On Sound article on hi-hat synthesis, at least synthesizing the TR-808's hi-hat actually seems pretty feasible:

...multiple oscillators are fed through a filter that removes the low frequencies, and through a VCA controlled by a simple contour generator.

Sounds simple enough. Let's give it a shot!

The Spec

Reid provides a helpful block diagram of the 808 hi-hat sound, which breaks down to this: six square wave oscillators feed into a bandpass filter, which feeds into a highpass filter, which goes through a volume envelope. Somehow, the oscillators and filters simulate the sound of the metal, while the envelope provides the hit, attack, and decay, of the sound.

Square

We're entering the section of the article where there's sounds, so make sure your volume is not too loud.

I wouldn't have guessed that the 808 hat is composed of square waves. Here's what they sound like: Square wave, 200hz .

Let's try to mimic a closed hi-hat's decay: Square wave, quick decay

Yeah... so, somehow six of those will turn into a hi-hat?

Six of Those

Gordon Reid includes one image in his article that displays how these six square waves are arrayed. Below is a little calculator with the root frequency (called fundamental here) plugged in and the ratio of each of the oscillators' frequency to that root. Note, we don't actually synthesize the fundamental.

Fundamental:

Osc1 ratio:

Osc2 ratio:

Osc3 ratio:

Osc4 ratio:

Osc5 ratio:

Osc6 ratio:

Hear it!

var $inputs = $("#six-square-calculator input[type=number]"); var ratios = $inputs.map(function(i, el) { return parseFloat(el.value); }).toArray(); var fundamental = ratios.shift(); var gain = context.createGain(); var when = context.currentTime; var oscs = ratios.map(function(ratio) { var osc = context.createOscillator(); osc.type = "square"; osc.frequency.value = fundamental * ratio; osc.connect(gain); osc.start(when); osc.stop(when + 0.2); return osc; }); gain.connect(context.destination); gain.gain.value = 0.1; gain.gain.exponentialRampToValueAtTime(0.00001, when + 0.1);

The event listener for that button

Ok, six square waves sounds pretty complex, and it's starting to sound precussive, but it's still nothing like a hi-hat. Let's take the next step and run it through a bandpass filter.

Filter Time

Examples from here out will use the values in the calculator above, so if you've gotten them out of whack give the page a refresh if you want to be sure you're hearing what I'm hearing.

Below is a similar tool for setting up the bandpass filter. Click the button to hear the oscillators defined above played through the filter:

Bandpass: hz



Hear it filtered

Crazily enough the sound is starting to get there. It's still plenty clear that there are synths underneath the sound, but I can hear this as a percussive hit rather than a musical tone. Now let's refine the envelope a little bit:

Envelope tweaked

gain.gain.setValueAtTime(0.00001, when); gain.gain.exponentialRampToValueAtTime(1, when + 0.02); gain.gain.exponentialRampToValueAtTime(1/3, when + 0.03); gain.gain.exponentialRampToValueAtTime(0.00001, when + 0.3);

Here's the timing for the volume envelope. gain is a GainNode, and gain.gain is an AudioParam. Confusing, I know. Walking through it, we have a quick attack for the first 20ms, then fade down to a third of the volume within the next 10ms, and fade all the way out over the following 270ms.

Ok. Even closer. Adding the attack and the very quick decay created the snap we're used to hearing in hi-hats. Now let's plug in the last piece of the diagram, the highpass filter.

Highpass

Highpass: hz



With the highpass

Well, that pretty much does it. Adding the highpass pulled the last of the note-y sound out and what we're left with sounds a lot like a hi-hat! Or, at least what we're used to hearing as a hi-hat. All in about 30 lines of code!

var context = new AudioContext(); var fundamental = 40; var ratios = [2, 3, 4.16, 5.43, 6.79, 8.21]; // Always useful var when = context.currentTime; var gain = context.createGain(); // Bandpass var bandpass = context.createBiquadFilter(); bandpass.type = "bandpass"; bandpass.frequency.value = 10000; // Highpass var highpass = context.createBiquadFilter(); highpass.type = "highpass"; highpass.frequency.value = 7000; // Connect the graph bandpass.connect(highpass); highpass.connect(gain); gain.connect(context.destination); // Create the oscillators ratios.forEach(function(ratio) { var osc = context.createOscillator(); osc.type = "square"; // Frequency is the fundamental * this oscillator's ratio osc.frequency.value = fundamental * ratio; osc.connect(bandpass); osc.start(when); osc.stop(when + 0.3); }); // Define the volume envelope gain.gain.setValueAtTime(0.00001, when); gain.gain.exponentialRampToValueAtTime(1, when + 0.02); gain.gain.exponentialRampToValueAtTime(0.3, when + 0.03); gain.gain.exponentialRampToValueAtTime(0.00001, when + 0.3);

Our final hi-hat synthesizer

Conclusion

Thanks to Chris Lowis for getting the ball rolling w/ the kick + snare synthesis, and of course Gordon Reid's Synth Secrets "63-part series". Reid's articles are extremely good, and it's kind of a shame that they aren't more interactive considering that we now have web audio. Maybe there's something to be done about that?