In continuing to explore the many areas of digital signal processing, reverb has cropped up many times as an area of great interest, so I’ve decided to dedicate a series of future posts on this topic. I’m going to start at the beginning, looking at Schroeder’s design, the first digital reverberator solution, and proceed forward looking at how it’s design was improved upon by Moorer, leading eventually to Feedback Delay Networks (FDN) and other types of artificial reverbs. All of these stages will include actual implementation, with code/algorithms, and possibly some plug-ins as a result. However, my goal is not to develop any kind of high-end, competetive product at this point, as some commercial reverb algorithms are closely guarded secrets. Moreover, digital reverb remains as one of the foremost challenges in DSP. This process will, however, provide greater understanding of digital audio in addition to honing my skills in DSP coding and design.
Reverberation is of course just a dense series of echoes. There is also a loss of energy in particular frequency ranges that depend on the material the sound bounces off of. When all the complexities of natural reverb are accounted for, calculations to simulate this reach into the hundreds of billions or more per second! Human ears cannot fully perceive the full compelxity of natural reverb, however, so this makes the calculations required much more manageable for many reverb designs (convolution is still very computationally expensive, though).
One of the fundamental building blocks of digital reverb is the comb filter, which Schroeder used in his design. It circulates a signal through a delay line, adding the delayed version, scaled with a constant, g, to the original.
The constant g is given by the formula:
where tau (t) is the delay time, or loop time, of the comb filter and RVT is the reverb time desired, which is defined as the time it takes for the delayed signal to reach -60dB (considered silence).
When analyzing the impulse response of natural reverberation, however, we see many dense series of echoes that are not equally spaced out with apparently random amplitudes. Additionally, the echoes become more diffuse as the amplitudes decrease as the delayed signals build up in the space. This leads to one of the most important properties of good reverb design, which is the diffusion of the delayed signal’s echoes — in other words it would be unnatural to hear individual pulses as the signal becomes reverberated. Schroeder proposed the use of four comb filters (in parallel) as one of his solutions to this problem, each with it’s own distinct loop time. To further ensure the diffusion of echoes, the four loop times should be relatively prime, otherwise the delayed signals would match up too frequently in phase to create a pumping or puffing sound, especially noticeable in the decay.
Another important property of reverb is for the decay to be exponential. This is satisfied by the comb filter, as can be seen in the above diagram, whereby the impulse response will start out at 1 (assuming an impulse at amplitude 1) and then subsequently being scaled by g, then g2, g3, etc.
To further thicken up the sound of his reverberator, Schroeder fed the summed signals from the four comb filters through two all-pass filters in series. These filters allow all frequencies to pass, but alter the phase of varying frequencies. Their design is very much like a comb filter but with a feed-forward section, as can be seen below.
The two all-pass filters Schroeder uses also have their own unique loop times just as the comb filters. Unlike the comb filters, however, the reverb time specified for the all-pass filters are different because their purpose is to thicken and diffuse the echoes of the signal, not to apply additional reverberation.
Schroeder accompanied his design with suggested values to simulate a concert hall. These values are given below (source: Dodge & Jerse, “Computer Music”, pg. 301):
The RVT value of the comb filters is variable and can be specified by the user, but is normally around the order of 1.o second.
The actual implementation of these two filters is fairly straightforward in C++. The code is given below:
Now let’s look at some audio samples to hear how this all sounds. All the code was written by me, including implementation of the comb filters and all-pass filters as well as the mix. Furthermore, I implemented a wet/dry option into the mixing stage as well as an output level due to the fact that the processed audio can increase in levels quite a bit depending on the source audio. As far as mixing goes, at its most basic it is just adding signals together, but when mixing several audio buffers (as in the four parallel comb filters) it is a good idea to scale each sample by a factor of 1/N, where N = number of audio buffers being mixed ( 1/(sqrt(N) can also be used in some cases).
In the above example with the single comb filter applied (with a loop time of 29.7 msec) we can hear the distinct echoes/delays of the signal at the beginning. As the audio decays we can also hear some unnatural pulsation happening (some pulsation is present in the original audio, but the comb filter augments it).
Adding in all the comb filters and the 2 all-pass networks as per Schroeder’s design diffuses the echoes noticeably and the tail sounds a little more natural as well. But for a more realistic sound we of course need the dry signal in the mix as well.
It’s worth listening to a more percussive sound to hear the reverb’s effect on it. Here is a short piano riff and a single comb filter applied to it, and the echo effect is very noticeable and quite disturbing.
Now applying the reverb in its entirety onto the piano riff with a 30% wet mix results in a more natural reverb.
It is, however, not perfect by any means. We can still hear a slight echo after each attack, and the reverb sound is a little bright and metallic sounding. As stated at the beginning, the echoes from reverberation lose energy as well as amplitude as they reflect off surfaces and travel through air, and this has not been accounted for in this design. To improve on this, adding in a simple low-pass filter in the comb filters was used as a solution. This will be one of the things I’ll be looking at going forward as well as more elaborate reverb designs that attempt to more realistically simulate natural reverberation.
Posted on September 4, 2012, in Audio, Programming and tagged all-pass filter, C/C++, comb filter, DSP, impulse response, low-pass, mixing, Moorer, reverb, Schroeder. Bookmark the permalink. 8 Comments.