I plan to create some tutorials and post them on this page. I have completed five so far.
:
I have implemented some of the concepts described in these tutorials as C++ classes. C++ code and documentation are available here.
:
- Delay Line With Loss
- Feedback Delay Networks
- Schroeder Reverberators
- Simple State Space Filter
- Multiport Networks
I have implemented some of the concepts described in these tutorials as C++ classes. C++ code and documentation are available here.
Delay Line With Loss
Sample delays are an important component of several reverb algorithms. I like to think of a sample delay as a propagation delay that can also introduce loss and frequency shaping. Thus I define my lossy delay line with the following transfer function:
where N is the sample delay and g and d, which I call gain and damping, respectively, are parameters of a simple low-pass filter. The corresponding difference equation is
The magnitude of the frequency response and the impulse response are shown below.
The magnitude of the frequency response varies between a low frequency maximum and a high frequency minimum given by
For a path of length L, I will assume that the magnitude of the frequency response is given by
where A(f) is the frequency-dependent absorption coefficient in dB/m. Since this coefficient varies by orders of magnitude over the range of audible frequencies (see, for example, here), I will assume that A(0) = 0 and let A be the value of A(f) at the Nyquist frequency. Then we have
so that
The sample delay N also depends on the path length L, the sampling frequency f_s, and the speed of sound, c.
Thus we specify a lossy delay line to model a propagation path of length L (m) with high frequency absorption coefficient A (dB/m).
C++ Implementation
The implementation files Delayline.h and Delayline.cpp are included here.
C++ Implementation
The implementation files Delayline.h and Delayline.cpp are included here.
Feedback Delay Networks
Feedback delay networks (FDN) can be used to create a reverb effect for an audio signal. This tutorial shows that a FDN can be viewed as a generalization of a classic state space filter. It also examines a specific type of FDN and provides suggestions for C++ implementation.
Introduction
A discrete time, linear, time-invariant system (LTIS) can be represented by the following equations:
Introduction
A discrete time, linear, time-invariant system (LTIS) can be represented by the following equations:
where u(n) is the single input, y(n) is the single output, and x(n) is a vector of state variables. A is a N by N state transition matrix (N is the number of state variables), B is a column vector (N rows), and C is a row vector (N columns). D is a scalar value, which, for simplicity, is assumed to be zero.
Upon application of the z transform, the state space filter looks like this:
Upon application of the z transform, the state space filter looks like this:
These equations can be represented by a block diagram:
To turn this structure into a FDN, replace the one sample delay with a more general transform H(z) as shown below.
For example, instead of a one sample delay, H(z) can represent a bank of delay lines with different delays, i.e.,
In the time domain, this is equivalent to
H(z) can also represent a bank of lossy delay lines as defined in the previous tutorial.
which is equivalent to
As discussed in the previous tutorial, the sample delays as well as the g and d parameters can be determined from path lengths and the high frequency absorption coefficient. We will address the topic of path lengths later in this tutorial.
Choosing the State Transition Matrix and Associated Vectors
This section edited on February 6, 2021 to introduce a change in the feedback matrix.
The A, B, and C parameters are chosen as follows:
Choosing the State Transition Matrix and Associated Vectors
This section edited on February 6, 2021 to introduce a change in the feedback matrix.
The A, B, and C parameters are chosen as follows:
where O is a N by N matrix of ones and 0 <= R < 2. The parameter R affects the impulse response of the FDN and can be made available as a user parameter. For R = 2, the FDN teeters on the edge of stability and for R > 2, it is definitely unstable!
This choice leads to considerable simplification of the system. First of all, the output is simply the average of the components of the state variable.
This choice leads to considerable simplification of the system. First of all, the output is simply the average of the components of the state variable.
The input to the k-th delay line is given by
Thus the vector v(n) is given by
This leads to the following block diagram:
We can also display the FDN without vector notation:
where
Choosing Path Lengths
The path lengths represent various propagation paths that may exist in the "room", i.e., the reverberant space being modeled by the FDN. For my version of the FDN, I determine N path lengths varying exponentially from a maximum to a minimum. The maximum is the room length, which I derive from the room area (a popular user input) assuming the floor of the room is a golden rectangle. The minimum path is one tenth the maximum. I have not performed any comparisons of this with other methods. You are welcome to experiment!
The path lengths represent various propagation paths that may exist in the "room", i.e., the reverberant space being modeled by the FDN. For my version of the FDN, I determine N path lengths varying exponentially from a maximum to a minimum. The maximum is the room length, which I derive from the room area (a popular user input) assuming the floor of the room is a golden rectangle. The minimum path is one tenth the maximum. I have not performed any comparisons of this with other methods. You are welcome to experiment!
Summary of Calculations
Extension of the FDN Reverb
A more elaborate FDN can be created by replacing the simple vector of ones B with a more general vector B(z) as in the figure below.
- Choose an "order" N. It does not have to be very high - N = 6 works fine for me.
- Determine a set of N path lengths based on the room area (user input) as just discussed.
- Determine the N sample delays and gain and damping values based on the path lengths and the high frequency absorption coefficient (user input)
- Compute the output of each of the filters Gk(z), using the current FDN input and previous FDN output as inputs.
- The next output is the average of the filter outputs.
Extension of the FDN Reverb
A more elaborate FDN can be created by replacing the simple vector of ones B with a more general vector B(z) as in the figure below.
For example, B(z) may be given by
where o is a column vector of ones, D(z) is a bank of delays similar to H(z), and H is a Hadamard matrix (please excuse the confusing notation). This structure is an example of what is sometimes called a diffuser and provides a more interesting input to the feedback loop than the FDN described earlier.
The Hadamard matrix is a square matrix whose elements are either +1 or -1 and whose order is either 1, 2, or a multiple of 4. The output of the delay bank is a vector of delayed versions of the scalar input with different delays and the output of the Hadamard operation is a vector of sums and differences of these delayed inputs. In simple terms, the diffuser mixes things up a bit ahead of the feedback loop.
If A and C are still defined as in the FDN described earlier, the complete system looks like this:
The Hadamard matrix is a square matrix whose elements are either +1 or -1 and whose order is either 1, 2, or a multiple of 4. The output of the delay bank is a vector of delayed versions of the scalar input with different delays and the output of the Hadamard operation is a vector of sums and differences of these delayed inputs. In simple terms, the diffuser mixes things up a bit ahead of the feedback loop.
If A and C are still defined as in the FDN described earlier, the complete system looks like this:
In this figure, the thin arrows represent scalar values and the thick arrows represent vectors. The blank box simply expands the scalar input u into a vector of copies of u. DDB is the diffuser delay bank and FDB is the FDN delay bank (previously known as H(z)). H is the hadamard matrix and the box labeled R expands the scalar output y into a vector of copies of y multiplied by the "reflectance" R.
The following set of difference equations describes the operation of this system:
The following set of difference equations describes the operation of this system:
The figure below compares the impulse response of the more basic FDN described earlier and this "enhanced" FDN with similar delay values. In this example, the DDB delays are significantly larger than the FDB delays. Other examples, not shown here, indicate that the result varies significantly with delay values. Some experiments with an audio plug-in based on the EFDN show that it is livelier than the basic FDN.
Schroeder Reverberators
The subject of artificial reverberation was initiated in the early 1960s by Manfred Schroeder and Ben Logan(ref). They and others used various combinations of comb filters and all pass filters to add a reverb effect to an audio signal. This tutorial provides an overview of what I will call Schroeder reverb and discuss my own implementation in more detail.
We begin with a review of comb and all pass filters.
Comb Filter
The basic feedforward comb filter is defined by the following difference equation
We begin with a review of comb and all pass filters.
Comb Filter
The basic feedforward comb filter is defined by the following difference equation
The transfer function for this filter is given by
The magnitude of the frequency response looks like a comb!
The impulse response of the feedforward comb filter is not very interesting, consisting only of the original impulse and a single echo. The feedback comb filter, given by the following difference equation and transfer function, offers a bit more.
While the frequency response still looks like a comb, the impulse response of the feedback comb filter is a series of echos.
Note that the absolute value of R must be less than 1 to insure stability.
The feedback comb filter can be viewed as a rough model of a sound wave being reflected back and forth between two walls. The sample delay N models the propagation delay and the factor R models the loss due to absorption by the walls. We can extend the model by replacing the sample delay with a lossy delay line. This leads to a low-pass feedback comb filter with a transfer function given by
The feedback comb filter can be viewed as a rough model of a sound wave being reflected back and forth between two walls. The sample delay N models the propagation delay and the factor R models the loss due to absorption by the walls. We can extend the model by replacing the sample delay with a lossy delay line. This leads to a low-pass feedback comb filter with a transfer function given by
This formulation attempts to separate the loss due partial reflection at the walls (R) and the loss during propagation (g and d). We can compute N, g, and d from a path length and high frequency absorption coefficient as discussed in a previous tutorial. Again, R must be less than 1 to maintain stability.
The difference equation for the low pass feedback comb filter is given by
The difference equation for the low pass feedback comb filter is given by
This can also be written in a simple state variable form:
The frequency response of the low pass feedback comb filter still looks like a comb, but has a low pass envelope. The impulse response is similar to the basic feedback comb filter, but a closer look at one of the echos reveals additional echos, giving the primary echo a bit more duration.
The Schroeder reverb described later on uses a bank of low pass feedback comb filters.
All Pass Filter
The difference equation and transfer function for the basic all pass filter are
All Pass Filter
The difference equation and transfer function for the basic all pass filter are
The magnitude of h(z) is 1 for all frequencies, hence the name of the filter. The impulse response is a series of echos.
Once again, we can replace the sample delay with a lossy delay line. The transfer function becomes.
With this modification the filter is no longer all pass. The magnitude of the frequency response looks like this:
The impulse response is similar to that of the basic all pass filter, but includes secondary echos, like the low pass feedback comb filter
In spite of the contradiction, I call this the low pass all pass filter.
C++ code for both the low pass comb filter and the low pass all pass filter is available here.
C++ code for both the low pass comb filter and the low pass all pass filter is available here.
A Schroeder Reverb Design
There is a popular Schroeder reverb called Freeverb developed by "Jezar at Dreampoint". Freeverb consists of a bank of 8 low pass feedback comb filters followed by a cascade of 4 all pass filters so that the overall transfer function is
The all pass filters in Freeverb have the following transfer function:
This variation is all pass only for a particular value of R. The parameters of the filters, including sample delays, were all apparently chosen by Jezar and seem to provide a good reverb effect.
For my implementation, I have chosen to use the same overall structure of a bank of 8 low pass feedback comb filters followed by a chain of 4 all pass filters. However, my version uses the low pass all pass filter. The sample delay N and the g and d parameters of each filter are computed from a path length L (meters), a high frequency absorption coefficient A (dB/meter), and the sampling frequency f_s, as previously described.
For my implementation, I have chosen to use the same overall structure of a bank of 8 low pass feedback comb filters followed by a chain of 4 all pass filters. However, my version uses the low pass all pass filter. The sample delay N and the g and d parameters of each filter are computed from a path length L (meters), a high frequency absorption coefficient A (dB/meter), and the sampling frequency f_s, as previously described.
The path lengths for the filters are determine in the same way as for the FDN, i.e., a sequence of lengths starting from a maximum length, based on the room area and geometric assumptions, and decreasing exponentially to a minimum length.
Here is an example of the impulse response of this Schroeder reverb
Here is an example of the impulse response of this Schroeder reverb
I have implemented this design as a VST audio plugin for Windows 64 and 32 bit. You can download a copy of the plugin from my audio plugin page.
Simple State Space Filter
This tutorial describes a popular state space filter that is also described in my book. It can be used as either a low-pass, high-pass, or band-pass filter. It is computationally efficient and I have used it in my wah-wah plugin.
The filter is defined by the following set of equations:
The filter is defined by the following set of equations:
Applying the z transform, we have
where D is another sort of damping factor and F_1 is given by
where f_c is a center or cut-off frequency and f_s is the sampling frequency.
From this it is straightforward to determine that
From this it is straightforward to determine that
where
I have excluded a common factor of z since it only introduces a one sample delay. Plots of the magnitudes of the frequency responses of these transfer functions show that the filter provides a high-pass, band-pass, and low-pass output.
C++ code for the filter is available here.
Multiport Networks
A few years ago (2013 to be exact) I published a paper in the AES Journal on something I called multiport acoustic elements. If you are a member of AES, you can access the paper here. A multiport acoustic element represents an acoustic entity that has two or more interface ports, each with an incoming and outgoing acoustic wave. My paper describes three such elements:
The inputs and outputs at the ports of the elements are acoustic pressure waves and the relations between the inputs and outputs are determined by assumptions about the behavior of the elements and the properties of acoustic plane waves. Recently I have been developing a set of audio signal processing elements based on these acoustic elements. The input and output audio signal processing element port are audio sample sequences rather than acoustic plane waves.
For the waveguide element, I simply use a pair of delay lines with loss as described in the first tutorial. The relation among the port inputs and outputs is given by:
- Waveguide - Represents a medium that supports propagation of plane acoustic waves. Bidirectional. Characterized by a time delay and attenuation.
- Junction - A connection among the ends of two or more waveguides.
- Reflector - A termination of or discontinuity in a waveguide. Characterized by a reflection coefficient.
The inputs and outputs at the ports of the elements are acoustic pressure waves and the relations between the inputs and outputs are determined by assumptions about the behavior of the elements and the properties of acoustic plane waves. Recently I have been developing a set of audio signal processing elements based on these acoustic elements. The input and output audio signal processing element port are audio sample sequences rather than acoustic plane waves.
For the waveguide element, I simply use a pair of delay lines with loss as described in the first tutorial. The relation among the port inputs and outputs is given by:
where x is a port input, y is a port output, and the subscript is the index of the port. The damping d and sample delay N are determined just as they are for the delay line.
The junction model is based on an idealized connection of the ends of two or more waveguides with the same acoustic impedance. The input/output relation is given by
The junction model is based on an idealized connection of the ends of two or more waveguides with the same acoustic impedance. The input/output relation is given by
where P is the number of ports in the junction.
The reflector represents a waveguide termination or a connection between two waveguides with different acoustic impedance. The input/output relation is given by
The reflector represents a waveguide termination or a connection between two waveguides with different acoustic impedance. The input/output relation is given by
where Gamma is a reflection coefficient ranging between -1 and +1.
I have coded a simple C++ class hierarchy consisting of a base class called MuitiPort and three derived classes called WaveGuide, Junction, and Reflector. The port inputs and outputs are implemented as shared pointers so that the elements can easily be connected to one another to create a network. An example of such a network is shown below.
I have coded a simple C++ class hierarchy consisting of a base class called MuitiPort and three derived classes called WaveGuide, Junction, and Reflector. The port inputs and outputs are implemented as shared pointers so that the elements can easily be connected to one another to create a network. An example of such a network is shown below.
The network contains five junctions, indicated by the circles and numbered from 0 to 4, and eight waveguides, indicated by the rectangles and numbered from 0 to 7. This network is set up to process an audio input on the west port of junction 0. The audio output is the sum of the outputs of the west port of junction 0 and the east port of junction 4. The two figures below display the impulse response of this network. For the figure to the left, the waveguide sample delays were all set to the same value. The resulting response is a series of evenly spaced echoes. For the figure to the right, the delays were randomized. This results in a response that corresponds to a reverb effect!
To facilitate construction of a network, I have coded a class called MPnetwork that has methods for adding and interconnecting network elements.
To facilitate construction of a network, I have coded a class called MPnetwork that has methods for adding and interconnecting network elements.
I have coded a plugin called Netverb based on this network. You may obtain a copy (Windows only) from my Audio Plugins page. C++ code for the multiport network elements is available here.