Here is how to do Morse tone detection quite easily with a Goertzel filter in software. Audio converts to timed on-off pulses.
Once you have audio samples flowing into your software, you can apply a tone detector. Shown below is the code for sending each audio sample, one at a time, into a Goertzel filter.
procedure TMorseReceiver.ProcessBlock(const Buffer: pointer; BufferSize: DWORD); var pB: PSmallInt; Counter: Cardinal; begin { * Send Each Sample to Filter * } pB := Buffer; Counter := 0; while Counter <= BufferSize do begin MF.Process(pB^); Inc(Counter); Inc(pB); end; end;
Your Goertzel filter is equivalent to s single-frequency Fourier Transform. In my case, I set it up to process audio at 8,000 samples per second for Morse tone detection at 800 Hz, my CW pitch. The filter outputs a magnitude pulse, shown in green above, every Block samples. I set Block = 20 samples.
function TMorseReceiverFilter.ProcessGoertzel(const aSample: double): boolean; var Mag, x: double; begin Result := false; inc(Counter); { * Blackman Nutall Window pre-computed * } x := aSample * BNWindow[Counter]; { * Coeff pre-computed * } Q0 := Coeff * Q1 - Q2 + x; Q2 := Q1; Q1 := Q0; if Counter = Block then begin Mag := Sqr(Q1) + Sqr(Q2) - (Q1 * Q2 * Coeff); Mag := SQRT(Mag / Divisor) * Level; {* Magnitude is global variable for output, which is smoothed and clipped *} Magnitude := (smoothOldMag * Magnitude) + (smoothNewMag * Mag); Magnitude := MIN(Magnitude, C_MAGCLIP); Result := true; Counter := 1; Q1 := 0; Q2 := 0; end; end;
To reduce the signal noise, Magnitude is smoothed with a running average filter. Also, the Magnitude level is clipped at a maximum value. The red line above shows the action of the edge detector, which converts the raw Magnitude to an on-off pulse train, shown in yellow.
Goertzel Morse Tone Detection Timing
So how do you set the Block size for Goertzel action. Two things. A smaller block size provides a faster response but less filtering. A larger block size provides a slower response, but improves filtering. I chose 20 samples as a decent compromise, which provides a magnitude reading every 2.5 milliseconds, and a filter width close to 400 Hz.
Is this timing good enough for fast Morse? At 40 WPM, a dot duration is 30 milliseconds. This means there are 12 updates from the filter at 2.5 milliseconds, which is enough to detect the edges and duration of a dot.
I experimented with various Block sizes and found 20 reasonable to decode Morse from 5 to 35 WPM.
This is very nice work John, thank you for sharing it. I arrived here by looking for a Goertzel fixed-length running filter. It’s heartwarming to see it implemented in Pascal/Delphi code; Pascal was my first proper computer language at university and is still a pleasure to read.
Thanks, Daryl. The Goertzel filter is amazing for what it does. Isn’t math great!, hi!