Hello! Welcome back to our capstone project.
In the previous lesson, you mastered the art of designing a digital filter to meet specific audio requirements. We saw how to derive the coefficients for both FIR and IIR filters, culminating in a comparison that highlighted the powerful efficiency of IIR designs. You now have the blueprints for a filter, represented by a set of a (feedback) and b (feedforward) coefficients.
Today, we move from design to execution. This lesson will show you how to bring those coefficients to life and implement the designed filter on a real-time audio stream using its difference equation. We will forge the crucial link between the mathematical transfer function and the sample-by-sample processing code that actually modifies the sound.
1. The Bridge from Theory to Practice: The Difference Equation
The key to implementing any digital filter is its difference equation. This equation is a direct, time-domain representation of the filter's transfer function, . It tells us precisely how to compute the next output sample, , using the current input, past inputs, and past outputs.
Given your background, you'll recognize the process of deriving this equation. A filter's transfer function is a rational polynomial in :
By cross-multiplying and rearranging, we get:
Recalling that multiplication by in the z-domain corresponds to a delay of samples in the time domain, we can take the inverse Z-transform of this expression to arrive at the general difference equation for a causal, linear time-invariant (LTI) filter:
Let's break this down:
- is the current output sample we want to compute.
- The first sum, , is the feedforward part. It's a weighted sum of the current and past input samples. This is the FIR part of the filter.
- The second sum, , is the feedback or recursive part. It's a weighted sum of past output samples. This is what makes a filter an IIR filter. (For an FIR filter, all coefficients are zero).
To implement this, our program needs to perform this calculation for every single incoming audio sample. This requires two things: the filter coefficients (a and b) and a way to store the history of past inputs and outputs.
The Wikipedia article on Digital Filters provides a concise explanation of how to derive the difference equation from the transfer function. This will serve as a good formal summary of the concept.
Please read the section titled 'Difference equation'. It walks through a clear example, showing how to rearrange the transfer function and apply the inverse Z-transform to arrive at the final, computable equation for y[n]. Notice how it translates the mathematical terms into programmatic concepts like 'current raw input value' and 'last filtered (output) value'.
2. A Practical Implementation in C
Theory is essential, but seeing it in code makes it concrete. Let's watch how a simple analog filter is modeled, discretized into a difference equation, and then implemented in C for a real-time system.
(Sponsored) Real-Time Software Implementation of Analog Filters - Phil's Lab #20
This video from Phil's Lab is a perfect practical guide. It demonstrates the full workflow from an analog RC low-pass circuit to a functioning digital filter running on a microcontroller. We will focus on the digital implementation part.
Please watch the following two segments: Discretization (05:13 - 07:33): This part shows how the continuous-time differential equation for an RC filter is converted into a discrete-time difference equation using the Backward Euler method. Pay close attention to the final equation he derives: V_out[n] = (something) * V_in[n] + (something) * V_out[n-1]. This is a first-order IIR difference equation. Software Implementation (08:34 - 12:07): This is the core of the lesson. Observe carefully how he translates the difference equation into C code. Note the key software components: A struct to hold the filter's state (coefficients and previous output values). An init() function to calculate and store the coefficients. An update() function that takes one new input sample, calculates the new output sample using the difference equation, and manages the state buffers (shifting out[0] to out[1]).
This video demonstrates the canonical software pattern for implementing real-time filters. The update() function is the heart of the filter; it's the code that will run for every single audio sample that passes through our system.
3. Filter Structures and Efficiency
The general difference equation can be visualized as a block diagram. The way we arrange the blocks (adders, multipliers, and delays) is called the filter's realization or structure.
Direct Form I
The most straightforward structure is Direct Form I. It has two distinct parts: a feedforward section that processes the input x[n], and a feedback section that processes the output y[n].
This is the system diagram for the Direct Form I implementation of a general filter. Notice the two separate banks of delay elements (z⁻¹), one for the input signal (top) and one for the output signal (bottom).
This form is easy to understand as it directly mirrors the two sums in the difference equation. However, it's not the most memory-efficient, as it requires total delay elements (storage for past samples).
Direct Form II
By leveraging the linearity of the system, we can swap the order of the feedforward and feedback sections. This allows the two sets of delay elements to be combined into one, saving memory. This is called Direct Form II.
This structure implements the same transfer function but uses only delay elements. For audio processing on systems with limited memory, this is a significant advantage. The trade-off is that the internal state variable in Direct Form II can have a larger dynamic range, which can sometimes lead to numerical precision issues (overflow) in fixed-point implementations, but this is less of a concern with modern floating-point processing.
For our capstone project, using a Direct Form II structure is generally preferred for its efficiency.
4. Implementing the General Difference Equation
The simple first-order filter from the video is a great start. Now, let's generalize the implementation to work for any IIR filter, like the 7th-order filter we designed in the last lesson.
We need to implement this equation:
To do this in code, we need:
- Arrays to store the
bandacoefficients. - Arrays (often called delay lines or circular buffers) to store the past
Minput samples andNoutput samples.
Here is what a generic filter update function might look like in pseudocode, using a Direct Form I structure for clarity:
// Assume we have these available:
// b_coeffs[] = {b0, b1, b2, ...}
// a_coeffs[] = {a1, a2, a3, ...} (Note: a0 is assumed to be 1 and is not stored)
// x_history[] = {x[n-1], x[n-2], ...}
// y_history[] = {y[n-1], y[n-2], ...}
float processSample(float new_input) {
// ---- Part 1: Feedforward Sum ----
float feedforward_sum = b_coeffs[0] * new_input;
for (int i = 1; i < NUM_B_COEFFS; ++i) {
feedforward_sum += b_coeffs[i] * x_history[i-1];
}
// ---- Part 2: Feedback Sum ----
float feedback_sum = 0.0;
for (int i = 0; i < NUM_A_COEFFS; ++i) {
feedback_sum += a_coeffs[i] * y_history[i];
}
// ---- Part 3: Combine and get new output ----
float new_output = feedforward_sum - feedback_sum;
// ---- Part 4: Update history/state for next time ----
// Shift x_history (x[n-1] becomes x[n-2], etc.) and place new_input at the start
// Shift y_history (y[n-1] becomes y[n-2], etc.) and place new_output at the start
return new_output;
}
This loop is the digital filter. For each sample of our real-time audio stream, we call this function. The audio input provides new_input, and the function's return value is the corresponding sample of the filtered audio output.
Test your understanding!
You have designed a 2nd-order IIR filter with the following transfer function:
- What are the
bandacoefficients? (Be careful with the signs for theacoefficients based on the general formula!) - Write out the difference equation for .
- If the input
xhas been[..., 1, 1]and the outputyhas been[..., 2, 3], and the current inputx[n]is1, what is the value of the next output sampley[n]?
Show answer
-
Coefficients:
- From the numerator: , .
- From the denominator: , . Remember that the general form is in the denominator, but the difference equation uses . So, we take the coefficients from the polynomial as they are. This leads to . An alternative convention writes the denominator as , in which case the difference equation uses . Let's stick to the first convention. So .
-
Difference Equation:
-
Calculation:
Conclusion
Today, you have bridged the gap between abstract filter design and concrete real-time implementation. You've seen that at the heart of every digital filter is a simple (though potentially long) arithmetic calculation—the difference equation—that is repeated for every audio sample.
Key Takeaways:
- The difference equation is the direct recipe for implementing a digital filter. It computes the new output based on past inputs and outputs.
- It is derived directly from the filter's transfer function .
- A practical implementation requires managing the filter's state: the history of input and output samples, typically stored in arrays or circular buffers.
- Filter structures like Direct Form I and Direct Form II offer different trade-offs between implementation complexity, memory usage, and numerical stability. Direct Form II is often preferred for its memory efficiency.
Preview of the Next Lesson:
Now that you can design and implement a single filter, we can start building more complex tools. In the next lesson, we will use this knowledge to design a bank of parallel bandpass filters to serve as a multi-band audio equalizer. You'll see how combining several of the filters we learned to build today can create one of the most fundamental and powerful tools in audio production.