@matt Thanks for the teaser 🙂. This stuff will be really useful for refactoring my envelope filter SVF. I 2x oversampled the naive chamberlin SVF to overcome the stability problem, but the ZDF approach will definitely reduce CPU usage on that one.
Based on Oliver's code, I see 7 MUL, 4 ADD, 3 SUB, which is going the opposite direction I need to go. This is efficient when you want to have arbitrary access to any or all of the 3 outputs of the biquad structure, but that isn't needed in a vocoder.
Here's a bonus for what I have come up with for the envelope detector to improve efficiency in the EVD section:
//
// Lazy RMS detector
// Inputs:
// sz = processing block size
// x = input signal (typically AC waveform)
// y = detector state variable (current RMS measurement)
// k = integration time constant, typically (1/RC)*(1/Fs)
//
void
lazy_rms_detector_tick(int sz, float *x, float *y, float k)
{
int i = 0;
for(i=0; i < sz; i++)
{
y[i] += k*(x[i]*x[i] - y[i]*y[i]); // Forward Euler integration, resolves to sqrt((1/k)*integral(x*x))
}
}
It gives me sqrt() function and averaging in a single shot with 3 MUL, 1 ADD, 1 SUB
The basic principle is that in an envelope detector you already want a slow/averaged response. Time required for this algorithm to resolve on sqrt(x) can be tuned with constant "k". I call it a "lazy" RMS detector because it lazily iterates the sqrt() solver at audio sample rate and "gets there when it gets there".
The time to resolve represent the desired averaging (attack/release times).

Below we see slow time constants for low signal amplitudes and fast time constants for large signal amplitudes (exaggerated here to demonstrate the effect). Would use longer time constants in a real application to reduce ripple. A second-stage smoothing filter might also be a good idea.
