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.
