Watching One Frequency Without an FFT
My VLF rig has been sitting in the backyard for a couple of days now, an 80-turn loop pointed roughly at Washington state, listening for the U.S. Navy transmitter NLK at 24.8 kHz. The whole point of SID monitoring is embarrassingly narrow: I don’t want a spectrum, I don’t want a waterfall, I want one number — how strong is 24.8 kHz right now — sampled once a second, plotted, and watched for the sudden step that says a solar flare just re-shaped the ionosphere over the Pacific.
Running a 4096-point FFT every block to read a single bin is like renting a moving truck to carry one book. There’s a better tool, and it comes from telephones.
When you pressed a key on an ’80s touch-tone phone, the exchange had to decode which two tones you sent, cheaply, on hardware with barely any RAM. The trick was the Goertzel algorithm: a tiny second-order filter tuned to exactly one frequency. It’s a three-line recurrence. You feed it samples, it accumulates two state variables, and at the end you read off the power in that one bin — no butterflies, no bit-reversal, no array of complex numbers.
double w = 2*M_PI*target/sr, coeff = 2*cos(w);
double q0, q1 = 0, q2 = 0;
for (int n = 0; n < N; n++) {
double s = sample[n];
q0 = coeff*q1 - q2 + s; q2 = q1; q1 = q0;
}
double power = q1*q1 + q2*q2 - coeff*q1*q2;
The same thing in Ruby, close enough to read aloud:
def goertzel(samples, target, rate)
coeff = 2 * Math.cos(2 * Math::PI * target / rate)
q1 = q2 = 0.0
samples.each do |s|
q0 = coeff * q1 - q2 + s
q2, q1 = q1, q0
end
q1**2 + q2**2 - coeff * q1 * q2
end
Feed both a synthetic 24.8 kHz tone sampled at 96 kHz and they agree exactly:
bin power @ 24800 Hz = 57600.0
The cost is O(N) work and O(1) memory, versus O(N log N) and a full output array for the FFT. For DTMF you ran eight of these in parallel, one per tone. For SID work I run one, at the naval transmitter’s frequency, and ignore the rest of the band entirely. The block length N sets your bin width: 480 samples at 96 kHz gives roughly a 200 Hz-wide window around the carrier, wide enough to survive a bit of drift, narrow enough to reject the mains-hum harmonics I’ve been fighting since day one. A decoder built for rotary-to-tone exchanges turns out to be exactly the right shape for staring at a single VLF carrier and waiting for the Sun to twitch.