Faust Compiler Bug: Tf2 Filter & Sequential Composition Error

by Kenji Nakamura 62 views

Hey guys,

I've stumbled upon a rather pesky bug in the Faust compiler and wanted to share it with you all. It seems like the compiler is having trouble figuring out the number of inputs for a fi.tf2 filter, specifically when its coefficients are derived from parameters within a with block. Let's dive into the details and see if we can figure this out together!

Understanding the Issue

The core issue arises when using the fi.tf2 filter, a second-order transfer function filter in Faust, and calculating its coefficients dynamically within a with block. The compiler throws a sequential composition error, indicating a mismatch in the number of inputs and outputs between connected components. This error suggests that the compiler isn't correctly inferring the input requirements of the fi.tf2 filter under these specific circumstances.

To really get a handle on this, let's break down the problem with a couple of code examples and the error message we're seeing.

Demonstrating the Correct Behavior

First, to show that the basic structure can work, I've created a minimal example. This example uses a similar structure to the problematic code but avoids the dynamic coefficient calculation within the with block. This helps us isolate the issue and confirm that the fundamental concept of signal processing in Faust is working as expected.

// This code works perfectly.
import("stdfaust.lib");
multiply(factor) = _ * factor;
myFactor = hslider("factor", 1, 0, 10, 0.1);
stage1 = multiply(myFactor);
process = _ : stage1;

In this snippet, we're simply multiplying the input signal by a factor controlled by a horizontal slider (hslider). The multiply function takes a factor as input and multiplies the incoming signal (_) by it. myFactor is a slider that allows us to adjust the multiplication factor. Finally, the process line connects the input signal to stage1, which performs the multiplication. This code runs flawlessly, demonstrating that sequential composition works fine when the filter coefficients aren't dynamically calculated.

Key Takeaway: This example shows that Faust can handle basic signal processing chains correctly. The issue seems to be specific to the dynamic calculation of filter coefficients.

The Failing Code: Allpass Filter

Now, let's look at the code that's actually causing the problem. This is an implementation of an allpass filter, where the coefficients are calculated based on frequency (freq) and quality factor (q) parameters within a with block. I've made the code as explicit as possible, naming the signal x at each stage to eliminate any ambiguity.

// This code fails.
import("stdfaust.lib");

allpass_filter(freq, q, x) = x : fi.tf2(b0, a1, 1, 1, a1, b0) with {
    w0             = 2 * ma.PI * freq / ma.SR;
    cos_w0         = cos(w0);
    sin_w0         = sin(w0);
    alpha          = sin_w0 / (2 * q);
    one_plus_alpha = 1 + alpha;
    one_minus_alpha= 1 - alpha;
    b0             = one_minus_alpha / one_plus_alpha;
    one_plus_b0    = 1 + b0;
    a1             = -cos_w0 * one_plus_b0;
};

stage1(x) = allpass_filter(1000, 1, x);

process = _ : stage1;

Here's a breakdown of what's happening:

  • allpass_filter(freq, q, x): This function defines the allpass filter. It takes the frequency (freq), quality factor (q), and input signal (x) as arguments.
  • x : fi.tf2(b0, a1, 1, 1, a1, b0): This is where the fi.tf2 filter is used. It takes five coefficients as input (b0, a1, 1, 1, a1, b0).
  • with { ... }: This block calculates the filter coefficients (b0 and a1) based on the input parameters freq and q. We're using trigonometric functions (cos and sin), and some basic arithmetic to derive these coefficients.
  • stage1(x) = allpass_filter(1000, 1, x): This function instantiates the allpass_filter with a frequency of 1000 Hz and a quality factor of 1.
  • process = _ : stage1: This connects the input signal (_) to stage1, which applies the allpass filter.

Despite the explicit naming of signals and clear structure, this code fails to compile, leading us to the dreaded error message.

The Error Message

The compiler throws the following error:

ERROR : sequential composition x:B The number of outputs [1] of x must be equal to the number of inputs [0] of B ... Here x = x1; has 1 output while B = ... has 0 inputs

Let's dissect this error message:

  • sequential composition x:B: This indicates that the error occurs during the sequential composition of two components, which we'll call x and B.
  • The number of outputs [1] of x must be equal to the number of inputs [0] of B: This is the core of the problem. Faust requires that the number of outputs from the first component (x) must match the number of inputs expected by the second component (B).
  • Here x = x1; has 1 output while B = ... has 0 inputs: This pinpoints the specific components involved. x is identified as x1 (likely the input signal in our process definition) and has 1 output. B is the problematic component, which the compiler believes has 0 inputs.

In essence, the compiler thinks that the fi.tf2 filter, when its coefficients are calculated within the with block, is not expecting any input signals. This is clearly incorrect, as fi.tf2 should accept a single input signal.

Why is this happening?

The exact reason for this behavior is still a bit of a mystery, but here are some potential explanations:

  1. Compiler Bug: As I initially suspected, this could be a bug in the Faust compiler itself. The compiler might be failing to correctly analyze the data flow when fi.tf2 is used with dynamically calculated coefficients within a with block.
  2. Type Inference Issues: Faust uses type inference to determine the number of inputs and outputs of functions and components. It's possible that the compiler's type inference mechanism is struggling to correctly deduce the input type of fi.tf2 in this specific scenario.
  3. Scope and Binding: The with block introduces a new scope, and it's conceivable that the way the coefficients are bound to the fi.tf2 filter within this scope is causing issues with the compiler's analysis.

Next Steps and Possible Solutions

So, what can we do about this? Here are a few avenues to explore:

  1. Simplify the Code: Try further simplifying the code to isolate the exact cause of the issue. For example, you could try hardcoding the coefficient values instead of calculating them within the with block. This would help determine if the problem is specifically related to the dynamic calculation.
  2. Experiment with Different Faust Versions: If possible, try compiling the code with different versions of the Faust compiler. This might reveal if the bug is specific to a particular version.
  3. Report the Bug: The most important step is to report this bug to the Faust development team (grame-cncm). Providing a clear and concise bug report with a minimal working example will help them identify and fix the issue.
  4. Workarounds (if possible): While we wait for a fix, we might be able to find a workaround. This could involve restructuring the code to avoid the dynamic coefficient calculation within the with block, or potentially using a different filter implementation.

Conclusion

This sequential composition error with the fi.tf2 filter and dynamic coefficient calculation is a serious issue that needs to be addressed in the Faust compiler. By understanding the problem, exploring potential causes, and reporting the bug, we can help improve the robustness and reliability of Faust. I'm hoping this detailed explanation helps others who might be facing the same issue, and I'm looking forward to discussing this further with the community! Let's keep hacking and making awesome audio stuff!