1
$\begingroup$

I'm reading an analog signal at a constant interval on an embedded device, and I want to extract a specific frequency component. Since the signal is continuously being read I want to constantly scan the most recent $N$ samples for this frequency component. For efficiency sake preferably without recomputing the FFT every time a new sample is added, since $N-1$ measurements overlap.

As I only care about one specific frequency component, the Goertzel filter is the natural choice. I implemented this naively, but since the derivation only works with exactly $N$ samples I have to reapply it over the entire window each time a new sample is added.

On a whim, I decided to ask ChatGPT to try to convert it to use a sliding window. It suggested replacing the first stage filter (see the Wikipedia page for notation) of $$ s[n]=x[n]+2\cos(\omega_0)s[n-1]-s[n-2] $$ with $$ s[n]=x[n]-x[n-N]+2\cos(\omega_0)s[n-1]-s[n-2] $$ where $x[n]$ is now being continuously added to instead of length exactly $N$.

Much to my surprise, this extremely basic trick worked perfectly, and I now have a functional sliding window Goertzel filter that works at least for my application. However when asked to explain it ChatGPT gives a fairly cop-out answer (it "reverses" the effect of the oldest sample, ignoring its compounding effect on the $s[n-1]$ and $s[n-2]$ terms that make the filter IIR), and I can't see a theoretical reason this should work. I've also tried looking for textbook references although none of them contain this exact trick. If the C++ code would be helpful I can post it, although the only difference from Goertzel filter definition is the first stage filter described above. Can someone please explain the theoretical reason that this trick works?

$\endgroup$
1
  • $\begingroup$ You should also ask why it won't work for arbitrarily large $n$ (hint: because the Goertzel filter is metastable, and subject to unbounded error when fed numerical noise), and alternatives to a Goertzel for continuous band-pass filtering. $\endgroup$
    – TimWescott
    Commented Jun 20 at 2:53

1 Answer 1

1
$\begingroup$

Fundamentally, I'd justify it using the superposition principle.

One way to conceptually think about it is that it is computing the difference between two first stage filters, one of which is delayed by $N$ samples. By doing so, it isolates the change caused by the last $N$ samples.

There are probably multiple ways you could mathematically arrive at the same result. Here is a z-transform approach: $$S[z] = \frac{X[z]}{1-2\cos[\omega_0]z^{-1}+z^{-2}} - \frac{z^{-N}X[z]}{1-2\cos[\omega_0]z^{-1}+z^{-2}}=\frac{\left(1-z^{-N}\right)X[z]}{1-2\cos[\omega_0]z^{-1}+z^{-2}}$$ Here is a difference equation approach, where the first expression $s_0$ is the first stage filter, and $s_N$ is the first stage filter whose output lags by $N$ samples: $$s_0[n]=x[n] + 2\cos[\omega_0]s_0[n-1]-s_0[n-2]$$ $$s_N[n]=x[n-N] + 2\cos[\omega_0]s_N[n-1]-s_N[n-2]$$ Taking the difference: $$s[n]=s_0[n]-s_N[n]\\=x[n] - x[n-N] + 2\cos[\omega_0]\left(s_0[n-1]-s_N[n-1]\right)-\left(s_0[n-2]-s_N[n-2]\right)\\=x[n]-x[n-N]+2\cos[\omega_0]s[n-1]-s[n-2]$$ To make it explicit, in the final expression I have used: $$s[n]=s_0[n]-s_N[n]$$ So: $$s[n-1]=s_0[n-1]-s_N[n-1]$$ $$s[n-2]=s_0[n-2]-s_N[n-2]$$

$\endgroup$

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.