In part five we’ll restructure the gain staging in our synthesizer to prevent clipping using the Web Audio API’s DynamicsCompressorNode and GainNode . This will touch on some of the basics of dynamics processing. In the last post, we gave the user the ability to play multiple notes at once with the computer keyboard. However, if they do this, they will likely here distortion – multiple notes will overload the main bus. We want to prevent that with a limiter.
Why Add Dynamics Processing?
There are many cases where you’d want to add dynamics processing. In audio engineering, you use a compressor to reduce the dynamic range of an audio source. Sometimes it’s applied as a way to color the sound, but more importantly, it can be used to prevent overloading buses. That’s how we’re using it here. Listen to this recording I made of our synthesizer playing multiple notes.
That distortion is a result of the master bus being overloaded. Now listen to how it sounds after we put a limiter on the master bus.
How to Get Rid of Clipping/Distortion
We git rid of the clipping by adding a limiter to the master bus. The master bus in this case is the GainNode through which all our voices will be routed – the master gain node.
// Create AudioContext var audioCtx = new(window.AudioContext || window.webkitAudioContext)(); var pregainNode = audioCtx.createGain(); var masterGainNode = audioCtx.createGain(); masterGainNode.gain.setValueAtTime(-0.3, audioCtx.currentTime); // Amount of gain in db -infinity to +infinity. -0.3 leaves a little headroom. var limiterNode = audioCtx.createDynamicsCompressor(); // Creating a compressor but setting a high threshold and // high ratio so it acts as a limiter. More explanation at // https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode limiterNode.threshold.setValueAtTime(-5.0, audioCtx.currentTime); // In Decibels limiterNode.knee.setValueAtTime(0, audioCtx.currentTime); // In Decibels limiterNode.ratio.setValueAtTime(40.0, audioCtx.currentTime); // In Decibels limiterNode.attack.setValueAtTime(0.001, audioCtx.currentTime); // Time is seconds limiterNode.release.setValueAtTime(0.1, audioCtx.currentTime); // Time is seconds pregainNode.connect(limiterNode); limiterNode.connect(masterGainNode); masterGainNode.connect(audioCtx.destination);
Next we’re going to update the Voice.start() method to connect the oscillator output to the pregainNode instead of connecting it directly to the AudioContext’s destination.
The pregain node is not really being used at this time, but this flow is a good starting place for future enhancements.
The pregain node is not really necessary right now, but this flow is a good starting place for future enhancements.
That’s it! You should be able to hold multiple keys at once and still have a nice undistorted sound. You will still notice a pop when you start and stop notes – especially when using a sine wave. This is caused by the immediate jump from – ∞dB to -1dB (or some audible level). The next post in this series is about envelopes, and it will show you how to solve that problem.