├── README.md
├── basicft8.py
└── samples
├── 170923_082000.wav
├── 170923_082015.wav
├── 170923_082030.wav
└── 170923_082045.wav
/README.md:
--------------------------------------------------------------------------------
1 | # basicft8
2 |
3 | basicft8.py is a simple demodulator for the WSJT-X FT8 mode. It misses many
4 | possible decodes, but in return it's relatively easy to understand.
5 | You can see a detailed explanation of the code at
6 | http://www.rtmrtm.org/basicft8/ .
7 | Please feel free to copy and modify this code for your own amusement.
8 | I've found that writing my own demodulators for FT8, JT65, etc
9 | has been very interesting and educational.
10 |
11 | The code is written in Python, and will work with either Python 3 or
12 | Python 2.7. You'll need a few extra Python packages. Here's how to
13 | install them on Ubuntu Linux:
14 | ```
15 | sudo apt-get install python-numpy
16 | sudo apt-get install python-pyaudio
17 | ```
18 |
19 | If you have a Mac with macports:
20 | ```
21 | sudo port install py-numpy
22 | sudo port install py-pyaudio
23 | ```
24 |
25 | For Windows:
26 | ```
27 | pip install numpy
28 | pip install pyaudio
29 | ```
30 |
31 | This repository contains a few sample FT8 .wav files to help test the
32 | demodulator. To read one, provide the file name on the command line,
33 | like this:
34 |
35 | ```
36 | python basicft8.py samples/170923_082000.wav
37 | ```
38 |
39 | When I do this, I see two lines of output corresponding to two decodes:
40 | ```
41 | 1962.5 CQ FK8HA RG37
42 | 1237.5 ZL1RPL YV5DRN RRR
43 | ```
44 |
45 | There are at least seven more signals that could be decoded, but that
46 | would require more sophisticated demodulation algorithms.
47 |
48 | To listen to audio from the default sound source (presumably hooked up
49 | to a radio set to e.g. 14.074 USB), try this:
50 |
51 | ```
52 | python basicft8.py
53 | ```
54 |
55 | Robert, AB1HL
56 |
--------------------------------------------------------------------------------
/basicft8.py:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/python
2 |
3 | #
Introduction
4 |
5 | # This document explains the inner workings of a program that
6 | # demodulates Franke and Taylor's FT8 digital mode. I hope the
7 | # explanation helps others write their own home-brew FT8 software,
8 | # perhaps using this code as a starting point. You can find the
9 | # program's Python source code at
10 | # https://github.com/rtmrtmrtmrtm/basicft8.
11 |
12 | # FT8 Summary
13 |
14 | # An FT8 cycle starts every 15 seconds, at 0, 15, 30 and 45 seconds
15 | # past the minute. An FT8 signal starts 0.5 seconds into a cycle and
16 | # lasts 12.64 seconds. It consists of 79 symbols, each 0.16 seconds
17 | # long. Each symbol is a single steady tone. For any given signal
18 | # there are eight possible tones (i.e. it is 8-FSK). The tone spacing
19 | # is 6.25 Hertz.
20 |
21 | # To help receivers detect the presence of signals and to estimate
22 | # where they start in time and in frequency, there are three sequences
23 | # of seven fixed tones embedded in each signal. Each fixed sequence is
24 | # called a Costas synchronization array, and consists of the tone
25 | # sequence 2, 5, 6, 0, 4, 1, and 3.
26 |
27 | # The other 58 symbols carry information.
28 | # Each symbol conveys 3 bits (since it's 8-FSK),
29 | # yielding 174 bits. The 174 bits are a "codeword", which must be
30 | # given to a Low Density Parity Check (LDPC) decoder to yield 87 bits.
31 | # The LDPC decoder uses the extra bits to correct bits corrupted by
32 | # noise, interference, fading, etc. The 87 bits consists of 75 bits of
33 | # "packed message" plus a 12-bit Cyclic Redundancy Check (CRC). The
34 | # CRC is an extra check to verify that the output of the LDPC decoder
35 | # really is a proper message.
36 |
37 | # The 75-bit packed message can have one of a few formats. The most
38 | # common format is two call signs (each packed into 28 bits) and
39 | # either a signal strength report or a grid square (packed into 16
40 | # bits).
41 |
42 | # This demodulator uses upper-side-band audio from a receiver, so it
43 | # sees a roughly 2500-Hz slice of spectrum. An FT8 signal is 50 Hz
44 | # wide (8 tones times 6.25 Hz per tone), and there may be many signals
45 | # in the audio. The demodulator does not initially know the
46 | # frequencies of any of them, so it must search in the 2500 Hz. While
47 | # most signals start roughly 0.5 seconds into a cycle, differences in clock
48 | # settings mean that the demodulator must search in time as well as
49 | # frequency.
50 |
51 | # To illustrate, here is a spectrogram from an FT8 cycle. Time
52 | # progresses along the x-axis, starting at the beginning of a cycle,
53 | # and ending about 13.5 seconds later. The y-axis shows a slice of
54 | # about 800 Hz. Three signals are visible; the middle signal starts a
55 | # little early. Each signal visibly shifts frequency as it progresses
56 | # from one 8-FSK symbol to the next. With a bit of imagination you can
57 | # see that the signals have identical first and last runs of seven
58 | # symbols; these are the Costas arrays.
59 |
60 | #
61 |
62 | # Here's a summary of the stages in which an FT8 sender constructs a signal,
63 | # along with the size of each stage's output.
64 | #
65 | # - Start with a message like CQ AB1HL FN42.
66 | #
- Pack the message: 72 bits.
67 | #
- Add 3 zero bits: 75 bits.
68 | #
- Add a CRC-12: 87 bits.
69 | #
- Encode with LDPC: 174 bits.
70 | #
- Turn each 3 bits into a symbol number from 0 to 8: 58 symbol numbers.
71 | #
- Add three 7-symbol Costas arrays: 79 symbol numbers.
72 | #
- Generate 8-FSK audio: 12.64 seconds of audio.
73 | #
- Send the audio to a radio transmitter.
74 | #
75 |
76 | # Demodulator Strategy
77 |
78 | # This demodulator looks at an entire FT8 cycle's worth of audio
79 | # samples at a time. It views the audio as a two-dimensional matrix of
80 | # "bins", with frequency in 6.25-Hz units along one axis, and time in
81 | # 0.16-second symbol-times along the other axis. Much like the
82 | # spectrogram image above. Each bin corresponds to a
83 | # single tone lasting for one symbol time. Each bin's value indicates
84 | # how much signal energy was received at the corresponding frequency
85 | # and time. This arrangement is convenient because, roughly speaking,
86 | # one can demodulate 8-FSK by seeing which of the relevant 8 bins is
87 | # strongest during each symbol time. The demodulator searches for
88 | # Costas synchronization arrays in this matrix. For each
89 | # plausible-looking triplet of Costas arrays at the same base
90 | # frequency and with the right spacing in time, the demodulator
91 | # extracts bits from FSK symbols and sees if the LDPC decoder can
92 | # interpret the bits as a correct codeword. If the LDPC succeeds, and the
93 | # CRC is correct, the demodulator unpacks the message and prints it.
94 |
95 | # The demodulator requires an audio sample rate of 12000
96 | # samples/second. It turns the audio into bins by
97 | # repeated use of Fast Fourier Transforms (FFTs), one per symbol time.
98 | # A symbol time is 1920 samples. Each FFT takes 1920 audio samples and
99 | # returns (1920/2)+1 output bins, each containing the strength of a
100 | # different frequency within those audio samples. The FFT output bins
101 | # correspond to frequencies at multiples of 12000/1920, or 6.25 Hz,
102 | # which is the FT8 tone spacing. Thus the demodulator forms its matrix
103 | # of bins by handing the audio samples, 1920 at a time, to FFTs, and
104 | # stacking the results to form a matrix.
105 |
106 | # This program catches only a fraction of the FT8 signals that wsjt-x
107 | # can decode. Perhaps the most serious deficiency is that the program
108 | # only works well for signals that arrive aligned near 1920-sample
109 | # boundaries in time, and near 6.25-Hz boundaries in frequency. It
110 | # would be more clever to look for signals on half- or quarter-bin
111 | # boundaries, in time and in frequency.
112 |
113 | # Code
114 |
115 | # With preliminaries out of the way, here is the demodulator code.
116 |
117 | ##
118 | ## simple FT8 decoder
119 | ##
120 | ## Robert Morris, AB1HL
121 | ##
122 |
123 | # These imports tell python which modules to include. Numpy provides
124 | # FFTs and convenient array manipulation. pyaudio provides access to
125 | # sound cards.
126 |
127 | import numpy
128 | import pyaudio
129 | import wave
130 | import sys
131 | import time
132 | import re
133 | import threading
134 |
135 | ## FT8 modulation and protocol definitions.
136 | ## 1920-point FFT at 12000 samples/second
137 | ## yields 6.25 Hz spacing, 0.16 seconds/symbol
138 | ## encode chain:
139 | ## 75 bits
140 | ## append 12 bits CRC (for 87 bits)
141 | ## LDPC(174,87) yields 174 bits
142 | ## that's 58 3-bit FSK-8 symbols
143 | ## insert three 7-symbol Costas sync arrays
144 | ## at symbol #s 0, 36, 72 of final signal
145 | ## thus: 79 FSK-8 symbols
146 | ## total transmission time is 12.64 seconds
147 |
148 | class FT8:
149 |
150 | # The process() function demodulates one FT8 cycle. It is
151 | # called with 13.64 seconds of audio at 12,000
152 | # samples/second: samples is a numpy array with about 164,000
153 | # elements. 13.64 seconds is enough samples for a whole signal
154 | # (12.64 seconds) plus half a second of slop at the start and end.
155 | # process() computes for at most ten seconds so that it doesn't overlap
156 | # with the call for the next 15-second FT8 cycle; the call to
157 | # time.time() records the starting wall-clock time in seconds.
158 |
159 | def process(self, samples):
160 | ## set up to quit after 10 seconds.
161 | t0 = time.time()
162 |
163 | # How many symbols does samples hold? // is Python's integer division.
164 | # self.block is 1920, the number of samples in one FT8 symbol.
165 |
166 | nblocks = len(samples) // self.block ## number of symbol times in samples[]
167 |
168 | # Perform one FFT for each symbol-time's worth of samples.
169 | # Each FFT returns an array with nbins elements. The matrix m
170 | # will hold the results; m[i][j] holds the strength of
171 | # frequency j*6.25 Hz during the i'th symbol-time.
172 | # The FFT returns complex numbers that indicate
173 | # phase as well as amplitude; the abs() essentially throws away
174 | # the phase.
175 |
176 | ## one FFT per symbol time.
177 | ## each FFT bin corresponds to one FSK tone.
178 | nbins = (self.block // 2) + 1 ## number of bins in FFT output
179 | m = numpy.zeros((nblocks, nbins))
180 | for i in range(0, nblocks):
181 | block = samples[i*self.block:(i+1)*self.block]
182 | bins = numpy.fft.rfft(block)
183 | bins = abs(bins)
184 | m[i] = bins
185 |
186 | # Much of this code deals with arrays of numbers. Thus block
187 | # above holds the 1920 samples of a single symbol time, bins
188 | # holds the (1920/2)+1 FFT result elements, and the final
189 | # assignment copies bins to a slice through the m matrix.
190 |
191 | # Next the code will look for Costas arrays in m. A Costas
192 | # array in m looks like a 7x8 sub-matrix with one
193 | # high-valued element in each column, and the other elements
194 | # with low values. The high-valued elements correspond to the
195 | # i'th tone of the Costas array. We'll find Costas arrays in m
196 | # using exhaustive matrix multiplications of sub-matrices of m
197 | # with a Costas template matrix that has a 1 in each element
198 | # that should be high-valued, and a -1/7 in each other
199 | # element. The sum over the product's elements will be large
200 | # if we've found a real Costas sync array, and close to zero
201 | # otherwise. The reason to use -1/7 rather than -1 is to avoid
202 | # having the results dominated by the sum of the large number
203 | # of elements that should be low-valued.
204 |
205 | ## prepare a template of the Costas sync array
206 | ## we expect to receive at start, middle, and
207 | ## end of a transmission.
208 | costas_matrix = numpy.ones((7, 8)) * (-1 / 7.0)
209 | costas_symbols = [ 2, 5, 6, 0, 4, 1, 3 ]
210 | for i in range(0, len(costas_symbols)):
211 | costas_matrix[i][costas_symbols[i]] = 1
212 |
213 | # Now examine every symbol-time and FFT frequency bin at which
214 | # a signal could start (there are a few thousand of them). The
215 | # signal variable holds the 79x8 matrix for one signal. Sum
216 | # the strengths of the Costas arrays for that potential
217 | # signal, and append the strength to the candidates array.
218 | # candidates will end up holding the likelihood of there being
219 | # an FT8 signal for every possible starting position.
220 |
221 | ## first pass: look for Costas sync arrays.
222 | candidates = [ ]
223 | ## for each start time
224 | for bi in range(0, nbins-8):
225 | ## a signal's worth of FFT bins -- 79 symbols, 8 FSK tones.
226 | for si in range(0, nblocks - 79):
227 | signal = m[si:si+79,bi:bi+8]
228 | strength = 0.0
229 | strength += numpy.sum(signal[0:7,0:8] * costas_matrix)
230 | strength += numpy.sum(signal[36:43,0:8] * costas_matrix)
231 | strength += numpy.sum(signal[72:79,0:8] * costas_matrix)
232 | candidates.append( [ bi, si, strength ] )
233 |
234 | # Sort the candidate signals, strongest first.
235 |
236 | ## sort the candidates, strongest Costas sync first.
237 | candidates = sorted(candidates, key = lambda e : -e[2])
238 |
239 | # Now we'll look at the candidate start positions, strongest
240 | # first, and see if the LDPC decoder can extract a signal from
241 | # each of them. This is the second pass in a two-pass scheme:
242 | # the first pass is the code above that looks
243 | # for plausible Costas sync arrays, and the second pass is the
244 | # code below that tries LDPC
245 | # decoding on the strongest candidates. Why two
246 | # passes, rather than simply trying LDPC decoding at
247 | # each possible signal start position? Because LDPC decoding
248 | # takes a lot of CPU time, and in our 10-second budget there's
249 | # only enough time to try it on a modest number of candidates.
250 | # The Costas sync detection above, however, is cheap enough
251 | # that it's no problem to do it for every possible signal
252 | # start position. The result is that we only try expensive
253 | # LDPC decoding for start positions that have a good chance of
254 | # actually being signals.
255 |
256 | # The assignment to signal extracts the 79x8 bins that
257 | # correspond to this candidate signal. signal[3][4] contains the
258 | # strength of the 4th FSK tone in symbol 3. If it is the
259 | # highest among the 8 elements of signal[3], then
260 | # symbol 3's value is probably 4 (yielding the three bits 100).
261 | # The call to process1() does most of the remaining work (see
262 | # below). This loop quits after 10 seconds.
263 |
264 | ## look at candidates, best first.
265 | for cc in candidates:
266 | if time.time() - t0 >= 10:
267 | ## quit after 10 seconds.
268 | break
269 |
270 | bi = cc[0]
271 | si = cc[1]
272 | ## a signal's worth of FFT bins -- 79 symbols, 8 FSK tones.
273 | signal = m[si:si+79,bi:bi+8]
274 |
275 | msg = self.process1(signal)
276 |
277 | if msg != None:
278 | bin_hz = self.rate / float(self.block)
279 | hz = bi * bin_hz
280 | print("%6.1f %s" % (hz, msg))
281 |
282 |
283 | # fsk_bits() is a helper function that turns a 58x8 array of tone
284 | # strengths into 58*3 bits. It does this by deciding which tone is
285 | # strongest for each of the 58 symbols. s58 holds, for each symbol, the index of the
286 | # strongest tone at that symbol time. bits3 generates each symbol's three bits, and
287 | # numpy.concatenate() flattens them into 174 bits.
288 |
289 | ## given 58 symbols worth of 8-FSK tones,
290 | ## decide which tone is the real one, and
291 | ## turn the resulting symbols into bits.
292 | ## returns log-likelihood for each bit,
293 | ## since that's what the LDPC decoder wants,
294 | ## but the probability is faked.
295 | def fsk_bits(self, m58):
296 | ## strongest tone for each symbol time.
297 | s58 = [ numpy.argmax(x) for x in m58 ]
298 |
299 | ## turn each 3-bit symbol into three bits.
300 | ## most-significant bit first.
301 | bits3 = [ [ (x>>2)&1, (x>>1)&1, x&1 ] for x in s58 ]
302 | a174 = numpy.concatenate(bits3)
303 |
304 | return a174
305 |
306 | # process() calls process1() for each candidate signal.
307 | # m79[0..79][0..8] holds the eight tone strengths for each received
308 | # symbol.
309 |
310 | ## m79 is 79 8-bucket mini FFTs, for 8-FSK demodulation.
311 | ## m79[0..79][0..8]
312 | ## returns None or a text message.
313 | def process1(self, m79):
314 |
315 | # Drop the three 7-symbol Costas arrays.
316 |
317 | m58 = numpy.concatenate( [ m79[7:36], m79[43:72] ] )
318 |
319 | # Demodulate the 58 8-FSK symbols into 174 bits.
320 |
321 | a174 = self.fsk_bits(m58)
322 |
323 | # The LDPC decoder wants log-likelihood estimates, indicating
324 | # how sure we are of each bit's value. This code isn't clever
325 | # enough to produce estimates, so it fakes them. 4.6 indicates
326 | # a zero, and -4.6 indicates a one.
327 |
328 | ## turn hard bits into 0.99 vs 0.01 log-likelihood,
329 | ## log_e( P(bit=0) / P(bit=1) )
330 | two = numpy.array([ 4.6, -4.6 ], dtype=numpy.int32)
331 | log174 = two[a174]
332 |
333 | # Call the LDPC decoder with the 174-bit codeword. The
334 | # decoder has a big set of parity formulas that must be
335 | # satisfied by the bits in the codeword. Usually the codeword
336 | # contains errored bits, due to noise, interference, fading,
337 | # and badly aligned symbol sampling. The decoder tries to
338 | # guess which bits are incorrect, and flips them in an attempt
339 | # to cause the parity formulas to be satisfied.
340 | # If it succeeds, it returns 87 bits containing
341 | # the original message (the bits the sender handed its LDPC encoder).
342 | # Otherwise, after flipping
343 | # different combinations of bits for a while, it gives up.
344 |
345 | ## decode LDPC(174,87)
346 | a87 = ldpc_decode(log174)
347 |
348 | # A zero-length result array indicates that the decoder failed.
349 |
350 | if len(a87) == 0:
351 | ## failure.
352 | return None
353 |
354 | # The LDPC decode succeeded! FT8 double-checks the result with
355 | # a CRC. The CRC rarely fails if the LDPC
356 | # decode succeeded.
357 |
358 | ## check the CRC-12
359 | cksum = crc(numpy.append(a87[0:72], numpy.zeros(4, dtype=numpy.int32)),
360 | crc12poly)
361 | if numpy.array_equal(cksum, a87[-12:]) == False:
362 | ## CRC failed.
363 | ## It's rare for LDPC to claim success but then CRC to fail.
364 | return None
365 |
366 | # The CRC succeeded, so it's highly likely that a87 contains
367 | # a correct message. Drop the 12 CRC bits and unpack the remainder into
368 | # a human-readable message, which process() will print.
369 |
370 | ## a87 is 75 bits of msg and 12 bits of CRC.
371 | a72 = a87[0:72]
372 |
373 | msg = self.unpack(a72)
374 |
375 | return msg
376 |
377 | # That's the end of the guts of the FT8 demodulator!
378 |
379 | # The remaining code is either not really part of
380 | # demodulation (e.g. the FT8 message format unpacker), or it's
381 | # fairly generic (the sound card and .wav file readers, and the
382 | # LDPC decoder).
383 |
384 | # Open the default sound card for input.
385 |
386 | def opencard(self):
387 | self.rate = 12000
388 | self.pya = pyaudio.PyAudio()
389 | self.card = self.pya.open(format=pyaudio.paInt16,
390 | ## input_device_index=XXX,
391 | channels=1,
392 | rate=self.rate,
393 | output=False,
394 | input=True)
395 |
396 | # gocard() reads samples from the sound card. Each time it
397 | # accumulates a full FT8 cycle's worth of samples,
398 | # starting at a cycle boundary, it passes them
399 | # to process(). gocard() calls process() in a separate thread,
400 | # because it needs to read samples for the next cycle while
401 | # process() is decoding.
402 |
403 | def gocard(self):
404 | buffered = numpy.array([], dtype=numpy.int16)
405 | while True:
406 | chunk = self.card.read(1024)
407 | chunk = numpy.fromstring(chunk, dtype=numpy.int16)
408 | buffered = numpy.append(buffered, chunk)
409 |
410 | ## do we have all the samples for a full cycle?
411 | ## the nominal end of transmission occurs at 13.14 seconds.
412 | ## at that point we should have
413 |
414 | sec = self.second()
415 | if sec >= 13.64 and len(buffered) > 13.64 * self.rate:
416 | ## it's the end of a cycle and we have enough samples.
417 | ## find the sample number at which the cycle started
418 | ## (i.e. 0.5 seconds before the nominal signal start time).
419 | start = len(buffered) - int(sec * self.rate)
420 | if start < 0:
421 | start = 0
422 | samples = buffered[start:]
423 |
424 | ## decode in a separate thread, so that we can read
425 | ## sound samples for the next FT8 cycle while the
426 | ## thread decodes the cycle that just finished.
427 | th = threading.Thread(target=lambda s=samples: self.process(samples))
428 | th.daemon = True
429 | th.start()
430 |
431 | buffered = numpy.array([], dtype=numpy.int16)
432 |
433 | # second() returns the number of seconds since the start of the
434 | # current 15-second FT8 cycle.
435 |
436 | def second(self):
437 | t = time.time()
438 | dt = t - self.start_time
439 | dt /= 15.0
440 | return 15.0 * (dt - int(dt))
441 |
442 | # This program can read a .wav file instead of a sound card. The
443 | # .wav file must contain one FT8 cycle at 12000 samples/second.
444 | # That is the format that wsjt-x produces when it records audio.
445 |
446 | def openwav(self, filename):
447 | self.wav = wave.open(filename)
448 | self.rate = self.wav.getframerate()
449 | assert self.rate == 12000
450 | assert self.wav.getnchannels() == 1 ## one channel
451 | assert self.wav.getsampwidth() == 2 ## 16-bit audio
452 |
453 | def readwav(self, chan):
454 | frames = self.wav.readframes(8192)
455 | samples = numpy.fromstring(frames, numpy.int16)
456 | return samples
457 |
458 | def gowav(self, filename, chan):
459 | self.openwav(filename)
460 | bufbuf = [ ]
461 | while True:
462 | buf = self.readwav(chan)
463 | if buf.size < 1:
464 | break
465 | bufbuf.append(buf)
466 | samples = numpy.concatenate(bufbuf)
467 |
468 | self.process(samples)
469 |
470 | # This code unpacks FT8 messages into human-readable
471 | # form. At a high level it interprets 72 bits of input as two call
472 | # signs and a grid or signal report.
473 |
474 | def unpack(self, a72):
475 | ## re-arrange the 72 bits into a format like JT65,
476 | ## for which this unpacker was originally written.
477 | a = [ ]
478 | for i in range(0, 72, 6):
479 | x = a72[i:i+6]
480 | y = (x[0] * 32 +
481 | x[1] * 16 +
482 | x[2] * 8 +
483 | x[3] * 4 +
484 | x[4] * 2 +
485 | x[5] * 1)
486 | a.append(y)
487 |
488 | ## a[] has 12 0..63 symbols, or 72 bits.
489 | ## turn them into the original human-readable message.
490 | ## unpack([61, 37, 30, 28, 9, 27, 61, 58, 26, 3, 49, 16]) -> "G3LTF DL9KR JO40"
491 | nc1 = 0 ## 28 bits of first call
492 | nc1 |= a[4] >> 2 ## 4 bits
493 | nc1 |= a[3] << 4 ## 6 bits
494 | nc1 |= a[2] << 10 ## 6 bits
495 | nc1 |= a[1] << 16 ## 6 bits
496 | nc1 |= a[0] << 22 ## 6 bits
497 |
498 | nc2 = 0 ## 28 bits of second call
499 | nc2 |= (a[4] & 3) << 26 ## 2 bits
500 | nc2 |= a[5] << 20 ## 6 bits
501 | nc2 |= a[6] << 14 ## 6 bits
502 | nc2 |= a[7] << 8 ## 6 bits
503 | nc2 |= a[8] << 2 ## 6 bits
504 | nc2 |= a[9] >> 4 ## 2 bits
505 |
506 | ng = 0 ## 16 bits of grid
507 | ng |= (a[9] & 15) << 12 ## 4 bits
508 | ng |= a[10] << 6 ## 6 bits
509 | ng |= a[11]
510 |
511 | if ng >= 32768:
512 | txt = self.unpacktext(nc1, nc2, ng)
513 | return txt
514 |
515 | NBASE = 37*36*10*27*27*27
516 |
517 | if nc1 == NBASE+1:
518 | c2 = self.unpackcall(nc2)
519 | grid = self.unpackgrid(ng)
520 | return "CQ %s %s" % (c2, grid)
521 |
522 | if nc1 >= 267649090 and nc1 <= 267698374:
523 | ## CQ with suffix (e.g. /QRP)
524 | n = nc1 - 267649090
525 | sf = self.charn(n % 37)
526 | n /= 37
527 | sf = self.charn(n % 37) + sf
528 | n /= 37
529 | sf = self.charn(n % 37) + sf
530 | n /= 37
531 | c2 = self.unpackcall(nc2)
532 | grid = self.unpackgrid(ng)
533 | return "CQ %s/%s %s" % (c2, sf, grid)
534 |
535 | c1 = self.unpackcall(nc1)
536 | if c1 == "CQ9DX ":
537 | c1 = "CQ DX "
538 | m = re.match(r'^ *E9([A-Z][A-Z]) *$', c1)
539 | if m != None:
540 | c1 = "CQ " + m.group(1)
541 | c2 = self.unpackcall(nc2)
542 | grid = self.unpackgrid(ng)
543 | msg = "%s %s %s" % (c1, c2, grid)
544 |
545 | if "000AAA" in msg:
546 | return None
547 |
548 | return msg
549 |
550 | ## convert packed character to Python string.
551 | ## 0..9 a..z space
552 | def charn(self, c):
553 | if c >= 0 and c <= 9:
554 | return chr(ord('0') + c)
555 | if c >= 10 and c < 36:
556 | return chr(ord('A') + c - 10)
557 | if c == 36:
558 | return ' '
559 | ## sys.stderr.write("jt65 charn(%d) bad\n" % (c))
560 | return '?'
561 |
562 | ## x is an integer, e.g. nc1 or nc2, containing all the
563 | ## call sign bits from a packed message.
564 | ## 28 bits.
565 | def unpackcall(self, x):
566 | a = [ 0, 0, 0, 0, 0, 0 ]
567 | a[5] = self.charn((x % 27) + 10) ## + 10 b/c only alpha+space
568 | x = int(x / 27)
569 | a[4] = self.charn((x % 27) + 10)
570 | x = int(x / 27)
571 | a[3] = self.charn((x % 27) + 10)
572 | x = int(x / 27)
573 | a[2] = self.charn(x%10) ## digit only
574 | x = int(x / 10)
575 | a[1] = self.charn(x % 36) ## letter or digit
576 | x = int(x / 36)
577 | a[0] = self.charn(x)
578 | return ''.join(a)
579 |
580 | ## extract maidenhead locator
581 | def unpackgrid(self, ng):
582 | ## start of special grid locators for sig strength &c.
583 | NGBASE = 180*180
584 |
585 | if ng == NGBASE+1:
586 | return " "
587 | if ng >= NGBASE+1 and ng < NGBASE+31:
588 | return " -%02d" % (ng - (NGBASE+1)) ## sig str, -01 to -30 DB
589 | if ng >= NGBASE+31 and ng < NGBASE+62:
590 | return "R-%02d" % (ng - (NGBASE+31))
591 | if ng == NGBASE+62:
592 | return "RO "
593 | if ng == NGBASE+63:
594 | return "RRR "
595 | if ng == NGBASE+64:
596 | return "73 "
597 |
598 | lat = (ng % 180) - 90
599 | ng = int(ng / 180)
600 | lng = (ng * 2) - 180
601 |
602 | g = "%c%c%c%c" % (ord('A') + int((179-lng)/20),
603 | ord('A') + int((lat+90)/10),
604 | ord('0') + int(((179-lng)%20)/2),
605 | ord('0') + (lat+90)%10)
606 |
607 | if g[0:2] == "KA":
608 | ## really + signal strength
609 | sig = int(g[2:4]) - 50
610 | return "+%02d" % (sig)
611 |
612 | if g[0:2] == "LA":
613 | ## really R+ signal strength
614 | sig = int(g[2:4]) - 50
615 | return "R+%02d" % (sig)
616 |
617 | return g
618 |
619 | def unpacktext(self, nc1, nc2, nc3):
620 | c = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-./?"
621 |
622 | nc3 &= 32767
623 | if (nc1 & 1) != 0:
624 | nc3 += 32768
625 | nc1 >>= 1
626 | if (nc2 & 1) != 0:
627 | nc3 += 65536
628 | nc2 >>= 1
629 |
630 | msg = [""] * 22
631 |
632 | for i in range(4, -1, -1):
633 | j = nc1 % 42
634 | msg[i] = c[j]
635 | nc1 = nc1 // 42
636 |
637 | for i in range(9, 4, -1):
638 | j = nc2 % 42
639 | msg[i] = c[j]
640 | nc2 = nc2 // 42
641 |
642 | for i in range(12, 9, -1):
643 | j = nc3 % 42
644 | msg[i] = c[j]
645 | nc3 = nc3 // 42
646 |
647 | return ''.join(msg)
648 |
649 | def __init__(self):
650 | self.block = 1920 ## samples per FT8 symbol, at 12000 samples/second
651 |
652 | ## set self.start_time to the UNIX time of the start
653 | ## of the last UTC minute.
654 | now = int(time.time())
655 | gm = time.gmtime(now)
656 | self.start_time = now - gm.tm_sec
657 |
658 | # Now comes the LDPC decoder. The decoder is driven by tables that
659 | # describe the parity checks that the codeword must satify.
660 |
661 | # Each row of Nm describes one parity check.
662 | # Each number is an index into the codeword (1-origin).
663 | # The codeword bits mentioned in each row must exclusive-or to zero.
664 | # There are 87 rows.
665 | # Nm is a copy of wsjt-x's bpdecode174.f90.
666 | Nm = [
667 | [ 1, 30, 60, 89, 118, 147, 0 ],
668 | [ 2, 31, 61, 90, 119, 147, 0 ],
669 | [ 3, 32, 62, 91, 120, 148, 0 ],
670 | [ 4, 33, 63, 92, 121, 149, 0 ],
671 | [ 2, 34, 64, 93, 122, 150, 0 ],
672 | [ 5, 33, 65, 94, 123, 148, 0 ],
673 | [ 6, 34, 66, 95, 124, 151, 0 ],
674 | [ 7, 35, 67, 96, 120, 152, 0 ],
675 | [ 8, 36, 68, 97, 125, 153, 0 ],
676 | [ 9, 37, 69, 98, 126, 152, 0 ],
677 | [ 10, 38, 70, 99, 127, 154, 0 ],
678 | [ 11, 39, 71, 100, 126, 155, 0 ],
679 | [ 12, 40, 61, 101, 128, 145, 0 ],
680 | [ 10, 33, 60, 95, 128, 156, 0 ],
681 | [ 13, 41, 72, 97, 126, 157, 0 ],
682 | [ 13, 42, 73, 90, 129, 156, 0 ],
683 | [ 14, 39, 74, 99, 130, 158, 0 ],
684 | [ 15, 43, 75, 102, 131, 159, 0 ],
685 | [ 16, 43, 71, 103, 118, 160, 0 ],
686 | [ 17, 44, 76, 98, 130, 156, 0 ],
687 | [ 18, 45, 60, 96, 132, 161, 0 ],
688 | [ 19, 46, 73, 83, 133, 162, 0 ],
689 | [ 12, 38, 77, 102, 134, 163, 0 ],
690 | [ 19, 47, 78, 104, 135, 147, 0 ],
691 | [ 1, 32, 77, 105, 136, 164, 0 ],
692 | [ 20, 48, 73, 106, 123, 163, 0 ],
693 | [ 21, 41, 79, 107, 137, 165, 0 ],
694 | [ 22, 42, 66, 108, 138, 152, 0 ],
695 | [ 18, 42, 80, 109, 139, 154, 0 ],
696 | [ 23, 49, 81, 110, 135, 166, 0 ],
697 | [ 16, 50, 82, 91, 129, 158, 0 ],
698 | [ 3, 48, 63, 107, 124, 167, 0 ],
699 | [ 6, 51, 67, 111, 134, 155, 0 ],
700 | [ 24, 35, 77, 100, 122, 162, 0 ],
701 | [ 20, 45, 76, 112, 140, 157, 0 ],
702 | [ 21, 36, 64, 92, 130, 159, 0 ],
703 | [ 8, 52, 83, 111, 118, 166, 0 ],
704 | [ 21, 53, 84, 113, 138, 168, 0 ],
705 | [ 25, 51, 79, 89, 122, 158, 0 ],
706 | [ 22, 44, 75, 107, 133, 155, 172 ],
707 | [ 9, 54, 84, 90, 141, 169, 0 ],
708 | [ 22, 54, 85, 110, 136, 161, 0 ],
709 | [ 8, 37, 65, 102, 129, 170, 0 ],
710 | [ 19, 39, 85, 114, 139, 150, 0 ],
711 | [ 26, 55, 71, 93, 142, 167, 0 ],
712 | [ 27, 56, 65, 96, 133, 160, 174 ],
713 | [ 28, 31, 86, 100, 117, 171, 0 ],
714 | [ 28, 52, 70, 104, 132, 144, 0 ],
715 | [ 24, 57, 68, 95, 137, 142, 0 ],
716 | [ 7, 30, 72, 110, 143, 151, 0 ],
717 | [ 4, 51, 76, 115, 127, 168, 0 ],
718 | [ 16, 45, 87, 114, 125, 172, 0 ],
719 | [ 15, 30, 86, 115, 123, 150, 0 ],
720 | [ 23, 46, 64, 91, 144, 173, 0 ],
721 | [ 23, 35, 75, 113, 145, 153, 0 ],
722 | [ 14, 41, 87, 108, 117, 149, 170 ],
723 | [ 25, 40, 85, 94, 124, 159, 0 ],
724 | [ 25, 58, 69, 116, 143, 174, 0 ],
725 | [ 29, 43, 61, 116, 132, 162, 0 ],
726 | [ 15, 58, 88, 112, 121, 164, 0 ],
727 | [ 4, 59, 72, 114, 119, 163, 173 ],
728 | [ 27, 47, 86, 98, 134, 153, 0 ],
729 | [ 5, 44, 78, 109, 141, 0, 0 ],
730 | [ 10, 46, 69, 103, 136, 165, 0 ],
731 | [ 9, 50, 59, 93, 128, 164, 0 ],
732 | [ 14, 57, 58, 109, 120, 166, 0 ],
733 | [ 17, 55, 62, 116, 125, 154, 0 ],
734 | [ 3, 54, 70, 101, 140, 170, 0 ],
735 | [ 1, 36, 82, 108, 127, 174, 0 ],
736 | [ 5, 53, 81, 105, 140, 0, 0 ],
737 | [ 29, 53, 67, 99, 142, 173, 0 ],
738 | [ 18, 49, 74, 97, 115, 167, 0 ],
739 | [ 2, 57, 63, 103, 138, 157, 0 ],
740 | [ 26, 38, 79, 112, 135, 171, 0 ],
741 | [ 11, 52, 66, 88, 119, 148, 0 ],
742 | [ 20, 40, 68, 117, 141, 160, 0 ],
743 | [ 11, 48, 81, 89, 146, 169, 0 ],
744 | [ 29, 47, 80, 92, 146, 172, 0 ],
745 | [ 6, 32, 87, 104, 145, 169, 0 ],
746 | [ 27, 34, 74, 106, 131, 165, 0 ],
747 | [ 12, 56, 84, 88, 139, 0, 0 ],
748 | [ 13, 56, 62, 111, 146, 171, 0 ],
749 | [ 26, 37, 80, 105, 144, 151, 0 ],
750 | [ 17, 31, 82, 113, 121, 161, 0 ],
751 | [ 28, 49, 59, 94, 137, 0, 0 ],
752 | [ 7, 55, 83, 101, 131, 168, 0 ],
753 | [ 24, 50, 78, 106, 143, 149, 0 ],
754 | ]
755 |
756 | # Mn is the dual of Nm.
757 | # Each row corresponds to a codeword bit.
758 | # The numbers indicate which three parity
759 | # checks (rows in Nm) refer to the codeword bit.
760 | # 1-origin.
761 | # Mn is a copy of wsjt-x's bpdecode174.f90.
762 | Mn = [
763 | [ 1, 25, 69 ],
764 | [ 2, 5, 73 ],
765 | [ 3, 32, 68 ],
766 | [ 4, 51, 61 ],
767 | [ 6, 63, 70 ],
768 | [ 7, 33, 79 ],
769 | [ 8, 50, 86 ],
770 | [ 9, 37, 43 ],
771 | [ 10, 41, 65 ],
772 | [ 11, 14, 64 ],
773 | [ 12, 75, 77 ],
774 | [ 13, 23, 81 ],
775 | [ 15, 16, 82 ],
776 | [ 17, 56, 66 ],
777 | [ 18, 53, 60 ],
778 | [ 19, 31, 52 ],
779 | [ 20, 67, 84 ],
780 | [ 21, 29, 72 ],
781 | [ 22, 24, 44 ],
782 | [ 26, 35, 76 ],
783 | [ 27, 36, 38 ],
784 | [ 28, 40, 42 ],
785 | [ 30, 54, 55 ],
786 | [ 34, 49, 87 ],
787 | [ 39, 57, 58 ],
788 | [ 45, 74, 83 ],
789 | [ 46, 62, 80 ],
790 | [ 47, 48, 85 ],
791 | [ 59, 71, 78 ],
792 | [ 1, 50, 53 ],
793 | [ 2, 47, 84 ],
794 | [ 3, 25, 79 ],
795 | [ 4, 6, 14 ],
796 | [ 5, 7, 80 ],
797 | [ 8, 34, 55 ],
798 | [ 9, 36, 69 ],
799 | [ 10, 43, 83 ],
800 | [ 11, 23, 74 ],
801 | [ 12, 17, 44 ],
802 | [ 13, 57, 76 ],
803 | [ 15, 27, 56 ],
804 | [ 16, 28, 29 ],
805 | [ 18, 19, 59 ],
806 | [ 20, 40, 63 ],
807 | [ 21, 35, 52 ],
808 | [ 22, 54, 64 ],
809 | [ 24, 62, 78 ],
810 | [ 26, 32, 77 ],
811 | [ 30, 72, 85 ],
812 | [ 31, 65, 87 ],
813 | [ 33, 39, 51 ],
814 | [ 37, 48, 75 ],
815 | [ 38, 70, 71 ],
816 | [ 41, 42, 68 ],
817 | [ 45, 67, 86 ],
818 | [ 46, 81, 82 ],
819 | [ 49, 66, 73 ],
820 | [ 58, 60, 66 ],
821 | [ 61, 65, 85 ],
822 | [ 1, 14, 21 ],
823 | [ 2, 13, 59 ],
824 | [ 3, 67, 82 ],
825 | [ 4, 32, 73 ],
826 | [ 5, 36, 54 ],
827 | [ 6, 43, 46 ],
828 | [ 7, 28, 75 ],
829 | [ 8, 33, 71 ],
830 | [ 9, 49, 76 ],
831 | [ 10, 58, 64 ],
832 | [ 11, 48, 68 ],
833 | [ 12, 19, 45 ],
834 | [ 15, 50, 61 ],
835 | [ 16, 22, 26 ],
836 | [ 17, 72, 80 ],
837 | [ 18, 40, 55 ],
838 | [ 20, 35, 51 ],
839 | [ 23, 25, 34 ],
840 | [ 24, 63, 87 ],
841 | [ 27, 39, 74 ],
842 | [ 29, 78, 83 ],
843 | [ 30, 70, 77 ],
844 | [ 31, 69, 84 ],
845 | [ 22, 37, 86 ],
846 | [ 38, 41, 81 ],
847 | [ 42, 44, 57 ],
848 | [ 47, 53, 62 ],
849 | [ 52, 56, 79 ],
850 | [ 60, 75, 81 ],
851 | [ 1, 39, 77 ],
852 | [ 2, 16, 41 ],
853 | [ 3, 31, 54 ],
854 | [ 4, 36, 78 ],
855 | [ 5, 45, 65 ],
856 | [ 6, 57, 85 ],
857 | [ 7, 14, 49 ],
858 | [ 8, 21, 46 ],
859 | [ 9, 15, 72 ],
860 | [ 10, 20, 62 ],
861 | [ 11, 17, 71 ],
862 | [ 12, 34, 47 ],
863 | [ 13, 68, 86 ],
864 | [ 18, 23, 43 ],
865 | [ 19, 64, 73 ],
866 | [ 24, 48, 79 ],
867 | [ 25, 70, 83 ],
868 | [ 26, 80, 87 ],
869 | [ 27, 32, 40 ],
870 | [ 28, 56, 69 ],
871 | [ 29, 63, 66 ],
872 | [ 30, 42, 50 ],
873 | [ 33, 37, 82 ],
874 | [ 35, 60, 74 ],
875 | [ 38, 55, 84 ],
876 | [ 44, 52, 61 ],
877 | [ 51, 53, 72 ],
878 | [ 58, 59, 67 ],
879 | [ 47, 56, 76 ],
880 | [ 1, 19, 37 ],
881 | [ 2, 61, 75 ],
882 | [ 3, 8, 66 ],
883 | [ 4, 60, 84 ],
884 | [ 5, 34, 39 ],
885 | [ 6, 26, 53 ],
886 | [ 7, 32, 57 ],
887 | [ 9, 52, 67 ],
888 | [ 10, 12, 15 ],
889 | [ 11, 51, 69 ],
890 | [ 13, 14, 65 ],
891 | [ 16, 31, 43 ],
892 | [ 17, 20, 36 ],
893 | [ 18, 80, 86 ],
894 | [ 21, 48, 59 ],
895 | [ 22, 40, 46 ],
896 | [ 23, 33, 62 ],
897 | [ 24, 30, 74 ],
898 | [ 25, 42, 64 ],
899 | [ 27, 49, 85 ],
900 | [ 28, 38, 73 ],
901 | [ 29, 44, 81 ],
902 | [ 35, 68, 70 ],
903 | [ 41, 63, 76 ],
904 | [ 45, 49, 71 ],
905 | [ 50, 58, 87 ],
906 | [ 48, 54, 83 ],
907 | [ 13, 55, 79 ],
908 | [ 77, 78, 82 ],
909 | [ 1, 2, 24 ],
910 | [ 3, 6, 75 ],
911 | [ 4, 56, 87 ],
912 | [ 5, 44, 53 ],
913 | [ 7, 50, 83 ],
914 | [ 8, 10, 28 ],
915 | [ 9, 55, 62 ],
916 | [ 11, 29, 67 ],
917 | [ 12, 33, 40 ],
918 | [ 14, 16, 20 ],
919 | [ 15, 35, 73 ],
920 | [ 17, 31, 39 ],
921 | [ 18, 36, 57 ],
922 | [ 19, 46, 76 ],
923 | [ 21, 42, 84 ],
924 | [ 22, 34, 59 ],
925 | [ 23, 26, 61 ],
926 | [ 25, 60, 65 ],
927 | [ 27, 64, 80 ],
928 | [ 30, 37, 66 ],
929 | [ 32, 45, 72 ],
930 | [ 38, 51, 86 ],
931 | [ 41, 77, 79 ],
932 | [ 43, 56, 68 ],
933 | [ 47, 74, 82 ],
934 | [ 40, 52, 78 ],
935 | [ 54, 61, 71 ],
936 | [ 46, 58, 69 ],
937 | ]
938 |
939 | # This is an indirection table that moves a
940 | # codeword's 87 systematic (message) bits to the end.
941 | # It's copied from the wsjt-x source.
942 | colorder = [
943 | 0, 1, 2, 3, 30, 4, 5, 6, 7, 8, 9, 10, 11, 32, 12, 40, 13, 14, 15, 16,
944 | 17, 18, 37, 45, 29, 19, 20, 21, 41, 22, 42, 31, 33, 34, 44, 35, 47,
945 | 51, 50, 43, 36, 52, 63, 46, 25, 55, 27, 24, 23, 53, 39, 49, 59, 38,
946 | 48, 61, 60, 57, 28, 62, 56, 58, 65, 66, 26, 70, 64, 69, 68, 67, 74,
947 | 71, 54, 76, 72, 75, 78, 77, 80, 79, 73, 83, 84, 81, 82, 85, 86, 87,
948 | 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
949 | 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
950 | 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131,
951 | 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145,
952 | 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
953 | 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173
954 | ]
955 |
956 | # The LDPC decoder function.
957 | # Given a 174-bit codeword as an array of log-likelihood of zero,
958 | # return an 87-bit plain text, or zero-length array.
959 | # The algorithm is the sum-product algorithm
960 | # from Sarah Johnson's Iterative Error Correction book.
961 | ## codeword[i] = log ( P(x=0) / P(x=1) )
962 | def ldpc_decode(codeword):
963 | ## 174 codeword bits
964 | ## 87 parity checks
965 |
966 | mnx = numpy.array(Mn, dtype=numpy.int32)
967 | nmx = numpy.array(Nm, dtype=numpy.int32)
968 |
969 | ## Mji
970 | ## each codeword bit i tells each parity check j
971 | ## what the bit's log-likelihood of being 0 is
972 | ## based on information *other* than from that
973 | ## parity check.
974 | m = numpy.zeros((87, 174))
975 |
976 | for i in range(0, 174):
977 | for j in range(0, 87):
978 | m[j][i] = codeword[i]
979 |
980 | for iter in range(0, 30):
981 | ## Eji
982 | ## each check j tells each codeword bit i the
983 | ## log likelihood of the bit being zero based
984 | ## on the *other* bits in that check.
985 | e = numpy.zeros((87, 174))
986 |
987 | ## messages from checks to bits.
988 | ## for each parity check
989 | ##for j in range(0, 87):
990 | ## # for each bit mentioned in this parity check
991 | ## for i in Nm[j]:
992 | ## if i <= 0:
993 | ## continue
994 | ## a = 1
995 | ## # for each other bit mentioned in this parity check
996 | ## for ii in Nm[j]:
997 | ## if ii != i:
998 | ## a *= math.tanh(m[j][ii-1] / 2.0)
999 | ## e[j][i-1] = math.log((1 + a) / (1 - a))
1000 | for i in range(0, 7):
1001 | a = numpy.ones(87)
1002 | for ii in range(0, 7):
1003 | if ii != i:
1004 | x1 = numpy.tanh(m[range(0, 87), nmx[:,ii]-1] / 2.0)
1005 | x2 = numpy.where(numpy.greater(nmx[:,ii], 0.0), x1, 1.0)
1006 | a = a * x2
1007 | ## avoid divide by zero, i.e. a[i]==1.0
1008 | ## XXX why is a[i] sometimes 1.0?
1009 | b = numpy.where(numpy.less(a, 0.99999), a, 0.99)
1010 | c = numpy.log((b + 1.0) / (1.0 - b))
1011 | ## have assign be no-op when nmx[a,b] == 0
1012 | d = numpy.where(numpy.equal(nmx[:,i], 0),
1013 | e[range(0,87), nmx[:,i]-1],
1014 | c)
1015 | e[range(0,87), nmx[:,i]-1] = d
1016 |
1017 | ## decide if we are done -- compute the corrected codeword,
1018 | ## see if the parity check succeeds.
1019 | ## sum the three log likelihoods contributing to each codeword bit.
1020 | e0 = e[mnx[:,0]-1, range(0,174)]
1021 | e1 = e[mnx[:,1]-1, range(0,174)]
1022 | e2 = e[mnx[:,2]-1, range(0,174)]
1023 | ll = codeword + e0 + e1 + e2
1024 | ## log likelihood > 0 => bit=0.
1025 | cw = numpy.select( [ ll < 0 ], [ numpy.ones(174, dtype=numpy.int32) ])
1026 | if ldpc_check(cw):
1027 | ## success!
1028 | ## it's a systematic code, though the plain-text bits are scattered.
1029 | ## collect them.
1030 | decoded = cw[colorder]
1031 | decoded = decoded[-87:]
1032 | return decoded
1033 |
1034 | ## messages from bits to checks.
1035 | for j in range(0, 3):
1036 | ## for each column in Mn.
1037 | ll = codeword
1038 | if j != 0:
1039 | e0 = e[mnx[:,0]-1, range(0,174)]
1040 | ll = ll + e0
1041 | if j != 1:
1042 | e1 = e[mnx[:,1]-1, range(0,174)]
1043 | ll = ll + e1
1044 | if j != 2:
1045 | e2 = e[mnx[:,2]-1, range(0,174)]
1046 | ll = ll + e2
1047 | m[mnx[:,j]-1, range(0,174)] = ll
1048 |
1049 | ## could not decode.
1050 | return numpy.array([])
1051 |
1052 | # A helper function to decide if
1053 | # a 174-bit codeword passes the LDPC parity checks.
1054 | def ldpc_check(codeword):
1055 | for e in Nm:
1056 | x = 0
1057 | for i in e:
1058 | if i != 0:
1059 | x ^= codeword[i-1]
1060 | if x != 0:
1061 | return False
1062 | return True
1063 |
1064 | # The CRC-12 polynomial, copied from wsjt-x's 0xc06.
1065 | crc12poly = [ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0 ]
1066 |
1067 | # crc() is a copy of Evan Sneath's code, from
1068 | # https://gist.github.com/evansneath/4650991 .
1069 | # div is crc12poly.
1070 |
1071 | ##
1072 | ##
1073 | ## generate with x^3 + x + 1:
1074 | ## >>> xc.crc([1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0], [1, 0, 1, 1])
1075 | ## array([1, 0, 0])
1076 | ## check:
1077 | ## >>> xc.crc([1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0], [1, 0, 1, 1], [1, 0, 0])
1078 | ## array([0, 0, 0])
1079 | ##
1080 | ## 0xc06 is really 0x1c06 or [ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0 ]
1081 | ##
1082 | def crc(msg, div):
1083 | ## Append the code to the message. If no code is given, default to '000'
1084 | code = numpy.zeros(len(div)-1, dtype=numpy.int32)
1085 | assert len(code) == len(div) - 1
1086 | msg = numpy.append(msg, code)
1087 |
1088 | div = numpy.array(div, dtype=numpy.int32)
1089 | divlen = len(div)
1090 |
1091 | ## Loop over every message bit (minus the appended code)
1092 | for i in range(len(msg)-len(code)):
1093 | ## If that messsage bit is 1, perform modulo 2 multiplication
1094 | if msg[i] == 1:
1095 | ##for j in range(len(div)):
1096 | ## # Perform modulo 2 multiplication on each index of the divisor
1097 | ## msg[i+j] = (msg[i+j] + div[j]) % 2
1098 | msg[i:i+divlen] = numpy.mod(msg[i:i+divlen] + div, 2)
1099 |
1100 | ## Output the last error-checking code portion of the message generated
1101 | return msg[-len(code):]
1102 |
1103 | # The main function: look at the command-line arguments, decide
1104 | # whether to read from a sound card or from .wav files.
1105 |
1106 | def main():
1107 | if len(sys.argv) == 1:
1108 | r = FT8()
1109 | r.opencard()
1110 | r.gocard()
1111 | sys.exit(0)
1112 |
1113 | i = 1
1114 | while i < len(sys.argv):
1115 | r = FT8()
1116 | r.gowav(sys.argv[i], 0)
1117 | i += 1
1118 |
1119 | if __name__ == '__main__':
1120 | main()
1121 |
--------------------------------------------------------------------------------
/samples/170923_082000.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rtmrtmrtmrtm/basicft8/d804f22a6173c11840929c07fad755e63b22bae5/samples/170923_082000.wav
--------------------------------------------------------------------------------
/samples/170923_082015.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rtmrtmrtmrtm/basicft8/d804f22a6173c11840929c07fad755e63b22bae5/samples/170923_082015.wav
--------------------------------------------------------------------------------
/samples/170923_082030.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rtmrtmrtmrtm/basicft8/d804f22a6173c11840929c07fad755e63b22bae5/samples/170923_082030.wav
--------------------------------------------------------------------------------
/samples/170923_082045.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rtmrtmrtmrtm/basicft8/d804f22a6173c11840929c07fad755e63b22bae5/samples/170923_082045.wav
--------------------------------------------------------------------------------