Should FOS and SOS use DF-I when coefficients are modulateable?

See Why NaN from FOS here? - #16 by jamshark70

I later asked on dsp stackexchange, where it was confirmed that 1/ FOS (and I assume by extension, SOS) is using DF-II and 2/ DF-II is numerically equivalent to DF-I only if the filter is time-invariant (no coefficient modulation).

FOS/SOS are designed throughout for coefficient modulation, and use DF-II, and claim that the calculation is equivalent to the DF-I formula.

Options…?

  • New FOS/SOS UGens that use DF-I.
    • +1: Keeps backward compatibility.
    • -1: Users have to think about which form to use in which case.
  • Change FOS/SOS to use DF-I except when all coefficients are ir or scalar (then it would be legit to optimize to DF-II).
    • -1: Some change in behavior, though probably not noticeable in audio cases.
    • +1: More transparent to the user.
  • Change documentation to say “these UGens aren’t doing what you think” basically.
    • :face_with_raised_eyebrow:

Suggestions?

Edit: I tested with SOS and found that, in a typical filtering case, there’s numerical error between DF-I and DF-II but it’s not audible AFAICS. So I think I’d lean toward the first solution. I think DF-I would be slower, and if the difference isn’t audible in typical cases, I wouldn’t see much benefit in slowing those typical cases down for the sake of oddball math-engine cases. But, it’s not good that DF-I isn’t there at all.

hjh

Or, we could change FOS to do the right thing (option 2) and create SCLegacy_FOS. If people notice a difference, the old behaviour is still accessible.

What can happen, is that the same buffer can get overflow because you’re reducing them by half (and memory in the same degree). That’s not a “problem”, it’s just an implementation challenge. I used 64-bit precision, so that may have helped.

( I think the confusion is because this DF-II version of the filter is rather problematic with fixed-point number implementations, leading to the sort of problem such as overflow I mentioned above. But with today hardware and compilers, it’s not a problem. Maybe that’s the confusion?)

See: Direct Form II

a_i (feedback) and b_i (feedforward) coeffs can be changed dynamically, as I understand.

No, it’s not slower. It will just consume double the memory.

It isn’t only overflow. filters - Running average works with DF-I but not with DF-II - Signal Processing Stack Exchange

As a quick proof of concept, I rewrote SOS_next_a to use DF-I, leaving the other calculation functions alone. This makes it easy to compare the results of the two forms: I record the result of all-audio-rate coefficients into one channel of the buffer, and kr coefficients into the other channel, and then the difference between the channels is the error.

If the coefficients are constant (time-invariant), then the results are completely equivalent, 0.0 error throughout.

If I modulate the coefficients in a way that is typical for musical applications, then the two channels sound indistinguishable to my ear, but the channels are not numerically equivalent.

It happens that I was trying to do something with FOS where the coefficient modulation is not typical, and “sounds similar” is not good enough – I needed real numerical equivalence.

hjh

The ccrma site is clear about the difference. It’s all about a buffer that can get overflow, since it is used twice. The problem with overflow is particularly problematic with fixed-point numbers. But if you know how to manage it, with floating-point-numbers, it’s all fine and dandy. It’s not rocket science. (EDIT: of course, there must be other differences, but that’s the ‘big’ one)

Time invariant system has nothing to do if a filter can change coefficients or not. It just means the transfer-function is not a direct function of time.

I have a concrete case where DF-I gets the correct result and DF-II gets the wrong result.

I do see that the time-domain numerical differences between the two forms don’t matter for typical uses of filters upon audio signals (differences probably just amount to phase shifts, which are to be expected when filtering anyway – the precise amount of phase shift likely has some tolerance for error).

But the lack of a DF-I filter section does mean that some exotic cases are not fine (i.e. not possible to realize in the server presently, unless you use the single-sample demand-rate feedback trick).

hjh

Yes, actually sometimes audio signals are a more limited application of DSP. For example, that code to calculate “bad coefficients”, the complex numbers part could just be simplified. It’s almost never relevant with audio. Actually, I should rewrite it in a much simplified way.

If we read DSP in general, we will deal with a lot more stuff. And, of course, since it’s a different system, I don’t doubt it will behave differently. It just is “close enough” for audio usage, and you can optimize memory usage, or blow up the signal if you don’t avoid overflow.

Yes, actually in the version, the same buffer is used at the same time by different signals. I bet something can happen there.

Maybe it has to do with observation #2

  1. " It is canonical with respect to delay. This happens because delay elements associated with the two-pole and two-zero sections are shared."

“Canonical” probably means it’s the minimum delay. And I bet it will have undesirable consequences in some situations, since “too short” delays can mess up what you was expecting.

I did that, but as a jack client. It was not expensive at all.

There are audio engines that already offer this kind of flexibility. Steve Yi’s Pink, written in clojure, for example.