PyAudio – Numpy – SciPy – DSP EXample


Its always fun to do some side projects to stay sharp with particular tools and methodologies. This post will talk about a prototype CHU FSK decoder that I have recently started working on. It may end up being ported to C++, but I definitely find Python / Numpy / Scipy / Matplotlib / Octave allows rapid proof of concept during early project investigations.

The project chudecode started as a means to investigate Python’s audio dsp capability, to provide near real-time audio processing. There are other projects in this space, but I enjoy learning by doing.

This prototype CHU decoder combines Python/Numpy/SciPy and PyAaudio to decode FSK modulated UTC date and time information from CHU (Canadian standard time signal). Tested on Mac (Catalina) and Linux.

Radio station CHU is operated by Canada’s National Research Council (NRC), and can be heard here in Melbourne, Australia. The CHU station is located 15 km southwest of Ottawa.

Please see the official CHU website for more details of their broadcast schedule.

CHU signal is broadcast on 14670 kHz, 7850 kHz and 3330 kHz. HF propagation from Canada to Melbourne Australia means that 7850 kHz is the most favourable band in early morning and later evening (reduced D-Layer absorption), with 14670kHz occasionally received during daylight hours.

Great circle distances are > 16,000 km

When enabled, a Butterworth bandpass filter (order 15) is used to cut down on unwanted frequencies prior to running tone detection. A simplified Goertzel algorithm tone detector is employed to detect the mark and space frequencies. Filtering is still experimental at this stage.

Shown below is an example output from the decoder. A simple state machine is implemented to detect frame start and run the decoder. This has mainly been kept inline for performance reasons, but performance testing/refactor will follow at a later date.

(sb3_octave) frank@mintmac:~/repos/chudecode$ ./chu_decode.py ./chu_1004z_2020_06_01.wav 
rate = 48000, chunk = 160, theoretical buffer/sec = 300.0 
Frequency resolution = 300.0 Hz
Theoretical buffer/sec = 300.
01001010011001000000110010000001101100111011000001010110011010111101011111111010111111110001100011101111010111
11110101
00110001
10111111
10111111
01101011
00001010
11001110
01000000
01000000
10010100
[10, 15, 8, 12, 15, 13, 15, 13, 13, 6, 5, 0, 7, 3, 0, 2, 0, 2, 2, 9]
[9, 2, 2, 0, 2, 0, 3, 7, 0, 5, 6, 13, 13, 15, 13, 15, 12, 8, 15, 10]
score1 = 0, score2 = 10
CHU Year: 2020
CHU z: 2
CHU x: 9
CHU tt: 37
CHU aa: 5
UT1 = UTC - 0.2 seconds
TAI = UTC + 37 seconds

<>
00110100011010101100110100000001100000101011011000100110011010001101010110011010000000110000010101101100010011
11000100
00001010
10000000
10101100
01101000
11000100
00001010
10000000
10101100
01101000
[2, 3, 5, 0, 0, 1, 3, 5, 1, 6, 2, 3, 5, 0, 0, 1, 3, 5, 1, 6]
[6, 1, 5, 3, 1, 0, 0, 5, 3, 2, 6, 1, 5, 3, 1, 0, 0, 5, 3, 2]
score1 = 10, score2 = 0
CHU Time: 10:05:32
CHU Date/Time: 2020-06-01 10:05:32
UTC Date/Time: 2020-06-12 07:59:00.555803

<>
00110100011010101100110100000001100000101011011001100110011010001101010110011010000000110000010101101100110011
11001100
00001010
10000000
10101100
01101000
11001100
00001010
10000000
10101100
01101000
[3, 3, 5, 0, 0, 1, 3, 5, 1, 6, 3, 3, 5, 0, 0, 1, 3, 5, 1, 6]
[6, 1, 5, 3, 1, 0, 0, 5, 3, 3, 6, 1, 5, 3, 1, 0, 0, 5, 3, 3]
score1 = 10, score2 = 0
CHU Time: 10:05:33
CHU Date/Time: 2020-06-01 10:05:33
UTC Date/Time: 2020-06-12 07:59:01.553511

<>
00110100011010100100110100000001100000101001011000010110011010001101010010001010000000110000010101101100001010
11000010
00001010
10000000
10100100
01101000
11000010
00001010
10000000
10100100
01101000
[4, 3, 5, 0, 0, 1, 2, 5, 1, 6, 4, 3, 5, 0, 0, 1, 2, 5, 1, 6]
[6, 1, 5, 2, 1, 0, 0, 5, 3, 4, 6, 1, 5, 2, 1, 0, 0, 5, 3, 4]
score1 = 10, score2 = 0
CHU Time: 10:05:34
CHU Date/Time: 2020-05-31 10:05:34
UTC Date/Time: 2020-06-12 07:59:02.554865

Here is another example processing (near real time) an audio stream over a USB Audio code connected to an FT991A and a multi-band antenna.

It was tuned to 7850 kHz.

<>
01001010011001000000110010000001101100111011000001010110011010111101011111111010111111110001100011101111010111
11110101
00110001
10111111
10111111
01101011
00001010
11001110
01000000
01000000
10010100
[10, 15, 8, 12, 15, 13, 15, 13, 13, 6, 5, 0, 7, 3, 0, 2, 0, 2, 2, 9]
[9, 2, 2, 0, 2, 0, 3, 7, 0, 5, 6, 13, 13, 15, 13, 15, 12, 8, 15, 10]
score1 = 0, score2 = 10
CHU Year: 2020
CHU z: 2
CHU x: 9
CHU tt: 37
CHU aa: 5
UT1 = UTC - 0.2 seconds
TAI = UTC + 37 seconds

<>
00110100011001100010110000010011100010100111011000100110011010001100110001011000001001110001010011101100010011
11000100
00101001
00001001
01100010
01101000
11000100
00101001
00001001
01100010
01101000
[2, 3, 9, 4, 9, 0, 4, 6, 1, 6, 2, 3, 9, 4, 9, 0, 4, 6, 1, 6]
[6, 1, 6, 4, 0, 9, 4, 9, 3, 2, 6, 1, 6, 4, 0, 9, 4, 9, 3, 2]
score1 = 10, score2 = 0
CHU Time: 09:49:32
CHU Date/Time: 2020-06-12 09:49:32
UTC Date/Time: 2020-06-12 09:49:32.629415

<>
00110100011001100010110000010011100010100111011001100110011010001100110001011000001001110001010011101100110011
11001100
00101001
00001001
01100010
01101000
11001100
00101001
00001001
01100010
01101000
[3, 3, 9, 4, 9, 0, 4, 6, 1, 6, 3, 3, 9, 4, 9, 0, 4, 6, 1, 6]
[6, 1, 6, 4, 0, 9, 4, 9, 3, 3, 6, 1, 6, 4, 0, 9, 4, 9, 3, 3]
score1 = 10, score2 = 0
CHU Time: 09:49:33
CHU Date/Time: 2020-06-12 09:49:33
UTC Date/Time: 2020-06-12 09:49:33.626969

<>
00000000000000000000000000000011000000000111011000000110011010001100110001011000001001110001010011101100001011
11000010
00101001
00001001
01100010
01101000
11000000
00000001
00000001
00000000
00000000
[4, 3, 9, 4, 9, 0, 4, 6, 1, 6, 0, 3, 8, 0, 8, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 8, 0, 8, 3, 0, 6, 1, 6, 4, 0, 9, 4, 9, 3, 4]
score1 = 2, score2 = 0

 

So far so good. Updates to follow. You can see my Git Repo on Bitbucket for ongoing work.