Home » Projects » Software Projects » Goertzel Morse Tone Detection

Goertzel Morse Tone Detection

morse tone detection

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.

2 comments

  1. Daryl VK3MCB says:

    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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.