├── 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 | # 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 --------------------------------------------------------------------------------