0
$\begingroup$

I've tested out the Chamberlin digital state variable filter (DSVF) as a sine generator as per this well-known approach. At $f_0=686\text{Hz}$ tone and $f_s=48000\text{Hz}$ it sounds fine and the FFT shows exactly two peaks at -220Hz and 220Hz; at $f_0=687\text{Hz}$ it buzzes.

The Chamberlin filter looks as below. Setting $Q=\infty$ makes $-q=0$ and deletes the bandpass feedback. From there the bandpass is cosine and the lowpass is sine.

Chamberlin DSVF

The Chamberlin filter is known to be unstable above a certain fraction of the sample rate (generally $\dfrac{f_s}{6}$); apparently as an oscillator it's a bit more extreme. Lazzarini and Timoney proposed a slight adjustment that gives the filter exactly the same amplitude response as a biquadratic filter. I'd like to try that as a sine wave generator, but I have no idea what I'm doing!

That filter looks like this:

Lazzarini and Timoney's modified DSVF block diagram

I encourage you to read the paper, it's pretty cool.

My attempts have produced noise.

This isn't really super-critical for my project. I'm using it as a cheap LFO generator and it's stable up to 500Hz; I'm just curious if it can be improved.

Edit: I have now posted code because I can't figure out why the filter implementation is broken.

$\endgroup$

1 Answer 1

1
$\begingroup$

I liked that paper, too, when I first saw it. It does add to some of the confusion in the AES about the definition of Q. I really think that we need to nail things down to a standard definition that makes sense for audio people.

Also, when you use any second-order IIR filter for a sinusoid generator, it eventually boils down to an input (what you're calling "$s_n$") being 0 and setting the two initial states to exactly what you need to get the amplitude and phase to be what you want. Ultimately the math comes out to be

$$ x[n] = 2 \cos(\omega_0) x[n-1] - x[n-2] $$

$$ x[n] - 2 \cos(\omega_0) x[n-1] + x[n-2] = 0 $$

$$ X(z) - 2 \cos(\omega_0) X(z)z^{-1} + X(z)z^{-2} = 0 $$

$$ X(z)z^{2} - 2 \cos(\omega_0) X(z)z + X(z) = 0 $$

$$ X(z)(z^{2} - 2 \cos(\omega_0)z + 1) = 0 $$

$$ z^{2} - 2 \cos(\omega_0)z + 1 = 0 $$

$$ z_\text{p} = \begin{cases} \cos(\omega_0) \pm \sqrt{\cos^2(\omega_0)-1} \quad & \cos^2(\omega_0) \ge 1 \\ \\ \cos(\omega_0) \pm j\sqrt{1-\cos^2(\omega_0)} \\ =\cos(\omega_0) \pm j\sin(\omega_0) \\ = e^{ \pm j\omega_0} \quad & \cos^2(\omega_0) \le 1 \\ \end{cases}$$

But only the second case counts for real $\omega_0$.

It doesn't matter what form of filter you make, it boils down to that and, if your word size is big enough (like IEEE float or IEEE double), that's just the simplest way to generate a sinusoid.

At one time I worried about this being stable for long enough to be useful (because there are two poles directly on the unit circle $|z|=1$, so this is "critically stable" or "marginally stable", not fully stable) but James McCartney set me straight on that some 20+ years ago.

Now, this inherent stability is not necessarily the case if you want to make a quadrature sine and cosine wave generator:

$$ \begin{align} x[n] &= \cos(\omega_0)x[n-1] - \sin(\omega_0)y[n-1] \\ y[n] &= \sin(\omega_0)x[n-1] + \cos(\omega_0)y[n-1] \\ \end{align} $$

Then you need to make sure that your $\cos(\omega_0)$ and $\sin(\omega_0)$ coefficients square and add to 1. If they add to something less than 1, your cosine and sine waves will slowly die out and if they add to more than 1, the waves will slowly grow indefinitely. So that quadrature generator needs some adaptive feedback to scale this coefficients based on the amplitude of the complex sinusoidal wave.

$\endgroup$
5
  • $\begingroup$ Interesting. I have found the thing is stable with fixed or floating point and nearest rounding (if you implement this in Google Sheets, it will have a gain of almost 30 by the 5th cycle, though, so don't use Google Sheets for safety-critical calculations that might get people killed?), but if I start with $\cos(\omega_0)=1$ in the Chamberlin DSVF I get a peak of just slightly more than 1. I assume the amplitude is just frequency-dependent; none of these things are perfectly linear. In any case, maybe I'm just implementing the filter wrong. $\endgroup$
    – John Moser
    Commented Jan 20, 2023 at 19:58
  • $\begingroup$ Maybe I got the signs wrong. Maybe it's $$ x[n] = -2 \cos(\omega_0) x[n-1] + x[n-2] $$ I gotta check my memory of this. $\endgroup$ Commented Jan 20, 2023 at 22:04
  • 1
    $\begingroup$ No, I remembered correctly. That recursion formula above should always work, if implemented correctly. $\endgroup$ Commented Jan 20, 2023 at 22:26
  • $\begingroup$ From what I've been able to find, it only works if I bypass the bandpass $z^{-1}$. On the first iteration, the 1 in $z^{-1}$ is cycled back into the bandpass along with 0 from the highpass getting 0 from sine=0. The second iteration passes 1 to the lowpass filter section again. This causes the sine wave amplitude to grow out of control. It's stable if I bypass the $z^{-1}$ at the bandpass output, but that's not the Chamberlin filter anymore so I don't see how the oscillator is technically derived from it (it matches the rearranged one from the paper though). $\endgroup$
    – John Moser
    Commented Jan 21, 2023 at 3:42
  • 1
    $\begingroup$ Listen, @JohnMoser, I would not use either Hal's SVF or a variant of it for sinusoidal generation. I would use $$ x[n] = 2 \cos(\omega_0) x[n-1] - x[n-2] $$ if you're looking for just a single sine wave. If you want a quadrature pair (a cosine and sine wave), then I would use $$ \begin{align} x[n] &= g[n] \big( \cos(\omega_0)x[n-1] - \sin(\omega_0)y[n-1] \,\big)\\ y[n] &= g[n] \big( \sin(\omega_0)x[n-1] + \cos(\omega_0)y[n-1] \,\big) \\ \end{align} $$ with an appropriate negative feedback method to control $g[n]$. $\endgroup$ Commented Jan 21, 2023 at 4:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.