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.