[feat](trx-rs): add ft8 decoder

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
2026-02-09 21:19:56 +01:00
parent 7bd1a70607
commit 1199ab85e9
206 changed files with 9613 additions and 5 deletions
+54
View File
@@ -0,0 +1,54 @@
#
# On MS Windows using Msys/MinGW gfortran invoke like this:
#
# FC=gfortran make
#
# On macOS using MacPorts gfortran invoke like this:
#
# FC=gfortran make
#
# or if the gfortran compiler is named gfortran-mp-8 or similar
#
# FC=gfortran-mp-8 make
#
# otherwise invoke like this:
#
# make
#
ifeq ($(OS),Windows_NT)
EXE = .exe
endif
EXES = hashcodes$(EXE) std_call_to_c28$(EXE) nonstd_to_c58$(EXE) \
free_text_to_f71$(EXE) grid4_to_g15$(EXE) grid6_to_g25$(EXE) \
gen_crc14$(EXE)
%.o: %.f90
$(FC) -c $(FFLAGS) -o $@ $<
all: $(EXES)
hashcodes$(EXE): hashcodes.o
${FC} -o $@ $^
std_call_to_c28$(EXE): std_call_to_c28.o
${FC} -o $@ $^
nonstd_to_c58$(EXE): nonstd_to_c58.o
${FC} -o $@ $^
free_text_to_f71$(EXE): free_text_to_f71.o
${FC} -o $@ $^
grid4_to_g15$(EXE): grid4_to_g15.o
${FC} -o $@ $^
grid6_to_g25$(EXE): grid6_to_g25.o
${FC} -o $@ $^
gen_crc14$(EXE): gen_crc14.o
${FC} -o $@ $^
clean:
-rm $(EXES) *.o
+13
View File
@@ -0,0 +1,13 @@
! Abbreviations for ARRL/RAC Sections as a Fortran 90 data statement:
data csec/ &
"AB ","AK ","AL ","AR ","AZ ","BC ","CO ","CT ","DE ","EB ", &
"EMA","ENY","EPA","EWA","GA ","GTA","IA ","ID ","IL ","IN ", &
"KS ","KY ","LA ","LAX","MAR","MB ","MDC","ME ","MI ","MN ", &
"MO ","MS ","MT ","NC ","ND ","NE ","NFL","NH ","NL ","NLI", &
"NM ","NNJ","NNY","NT ","NTX","NV ","OH ","OK ","ONE","ONN", &
"ONS","OR ","ORG","PAC","PR ","QC ","RI ","SB ","SC ","SCV", &
"SD ","SDG","SF ","SFL","SJV","SK ","SNJ","STX","SV ","TN ", &
"UT ","VA ","VI ","VT ","WCF","WI ","WMA","WNY","WPA","WTX", &
"WV ","WWA","WY ","DX "/
+67
View File
@@ -0,0 +1,67 @@
program free_text_to_f71
character*13 c13,w
character*71 f71
character*42 c
character*1 qa(10),qb(10)
data c/' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?'/
nargs=iargc()
if(nargs.ne.1) then
print*,'Usage: free_text_to_f71 "<message>"'
print*,'Example: free_text_to_f71 "TNX BOB 73 GL"'
go to 999
endif
call getarg(1,c13)
call mp_short_init
qa=char(0)
w=adjustr(c13)
do i=1,13
j=index(c,w(i:i))-1
if(j.lt.0) j=0
call mp_short_mult(qb,qa(2:10),9,42) !qb(1:9)=42*qa(2:9)
call mp_short_add(qa,qb(2:10),9,j) !qa(1:9)=qb(2:9)+j
enddo
write(f71,1000) qa(2:10)
1000 format(b7.7,8b8.8)
write(*,1010) c13,f71
1010 format('Free text: ',a13/'f71: ',a71)
999 end program free_text_to_f71
subroutine mp_short_ops(w,u)
! Multi-precision arithmetic with storage in character arrays.
character*1 w(*),u(*)
integer i,ireg,j,n,ir,iv,ii1,ii2
character*1 creg(4)
save ii1,ii2
equivalence (ireg,creg)
entry mp_short_init
ireg=256*ichar('2')+ichar('1')
do j=1,4
if (creg(j).eq.'1') ii1=j
if (creg(j).eq.'2') ii2=j
enddo
return
entry mp_short_add(w,u,n,iv)
ireg=256*iv
do j=n,1,-1
ireg=ichar(u(j))+ichar(creg(ii2))
w(j+1)=creg(ii1)
enddo
w(1)=creg(ii2)
return
entry mp_short_mult(w,u,n,iv)
ireg=0
do j=n,1,-1
ireg=ichar(u(j))*iv+ichar(creg(ii2))
w(j+1)=creg(ii1)
enddo
w(1)=creg(ii2)
return
return
end subroutine mp_short_ops
+36
View File
@@ -0,0 +1,36 @@
program gen_crc14
character m77*77,c14*14
integer mc(96),r(15),p(15),ncrc
! polynomial for 14-bit CRC 0x6757
data p/1,1,0,0,1,1,1,0,1,0,1,0,1,1,1/
nargs=iargc()
if(nargs.ne.1) then
print*,'Usage: gen_crc14 <77-bit message>'
print*,'Example: gen_crc14 "00000000000000000000000000100000010011011111110011011100100010100001010000001"'
go to 999
endif
! pad the 77bit message out to 96 bits
call getarg(1,m77)
read(m77,'(77i1)') mc(1:77)
mc(78:96)=0
! divide by polynomial
r=mc(1:15)
do i=0,81
r(15)=mc(i+15)
r=mod(r+r(1)*p,2)
r=cshift(r,1)
enddo
! the crc is in r(1:14) - print it in various ways:
write(c14,'(14b1)') r(1:14)
write(*,'(a40,1x,a14)') 'crc14 as a string: ',c14
read(c14,'(b14.14)') ncrc
write(*,'(a40,i6)') 'crc14 as an integer: ',ncrc
write(*,'(a40,1x,b14.14)') 'binary representation of the integer: ',ncrc
999 end program gen_crc14
+86
View File
@@ -0,0 +1,86 @@
This file contains the generator matrix for the FT8/FT4 (174,91) LDPC code.
The matrix has 91 columns and 83 rows.
1000001100101001110011100001000110111111001100011110101011110101000010011111001001111111110
0111011000011100001001100100111000100101110000100101100100110011010101001001001100010011001
1101110000100110010110010000001011111011001001110111110001100100000100001010000110111101110
0001101100111111010000010111100001011000110011010010110111010011001111101100011111110110001
0000100111111101101001001111111011100000010000011001010111111101000000110100011110000011101
0000011101111100110011001100000100011011100010000111001111101101010111000011110101001000101
0010100110110110001010101111111000111100101000000011011011110100111111100001101010011101101
0110000001010100111110101111010111110011010111011001011011010011101100001100100011000011111
1110001000000111100110001110010000110001000011101110110100100111100010000100101011101001000
0111011101011100100111000000100011101000000011100010011011011101101011100101011000110001100
1011000010111000000100010000001010001100001010111111100110010111001000010011010010000111110
0001100010100000110010010010001100011111110001100000101011011111010111000101111010100011001
0111011001000111000111101000001100000010101000000111001000011110000000011011000100101011100
1111111110111100110010111000000011001010100000110100000111111010111110110100011110110010111
0110011010100111001010100001010110001111100100110010010110100010101111110110011100010111000
1100010000100100001101101000100111111110100001011011000111000101000100110110001110100001100
0000110111111111011100111001010000010100110100011010000110110011010010110001110000100111000
0001010110110100100010000011000001100011011011001000101110011001100010010100100101110010111
0010100110101000100111000000110100111101111010000001110101100110010101001000100110110000111
0100111100010010011011110011011111111010010100011100101111100110000110111101011010111001010
1001100111000100011100100011100111010000110110010111110100111100100001001110000010010100000
0001100100011001101101110101000100011001011101100101011000100001101110110100111100011110100
0000100111011011000100101101011100110001111110101110111000001011100001101101111101101011100
0100100010001111110000110011110111110100001111111011110111101110101001001110101011111011010
1000001001110100001000111110111001000000101101100111010111110111010101101110101101011111111
1010101111100001100101111100010010000100110010110111010001110101011100010100010010101001101
0010101101010000000011100100101111000000111011000101101001101101001010111101101111011101000
1100010001110100101010100101001111010111000000100001100001110110000101100110100100110110000
1000111010111010000110100001001111011011001100111001000010111101011001110001100011001110110
0111010100111000010001000110011100111010001001110111100000101100110001000010000000010010111
0000011011111111100000111010000101000101110000110111000000110101101001011100000100100110100
0011101100110111010000010111100001011000110011000010110111010011001111101100001111110110001
1001101001001010010110100010100011101110000101111100101010011100001100100100100001000010110
1011110000101001111101000110010100110000100111001001011101111110100010010110000100001010010
0010011001100011101011100110110111011111100010110101110011100010101110110010100101001000100
0100011011110010001100011110111111100100010101110000001101001100000110000001010001000001100
0011111110110010110011101000010110101011111010011011000011000111001011100000011011111011111
1101111010000111010010000001111100101000001011000001010100111001011100011010000010100010111
1111110011010111110011001111001000111100011010011111101010011001101110111010000101000001001
1111000000100110000101000100011111101001010010010000110010101000111001000111010011001110110
0100010000010000000100010101100000011000000110010110111110010101110011011101011100000001001
0000100010001111110000110001110111110100101111111011110111100010101001001110101011111011010
1011100011111110111100011011011000110000011101110010100111111011000010100000011110001100000
0101101011111110101001111010110011001100101101110111101110111100100111011001100110101001000
0100100110100111000000010110101011000110010100111111011001011110110011011100100100000111011
0001100101000100110100001000010110111110010011100111110110101000110101101100110001111101000
0010010100011111011000101010110111000100000000110010111100001110111001110001010000000000001
0101011001000111000111111000011100000010101000000111001000011110000000001011000100101011100
0010101110001110010010010010001111110010110111010101000111100010110101010011011111111010000
0110101101010101000010100100000010100110011011110100011101010101110111101001010111000010011
1010000110001010110100101000110101001110001001111111111010010010101001001111011011001000010
0001000011000010111001011000011000111000100011001011100000101010001111011000000001110101100
1110111100110100101001000001100000010111111011100000001000010011001111011011001011101011000
0111111010011100000011000101010000110010010110101001110000010101100000110110111000000000000
0011011010010011111001010111001011010001111111011110010011001101111100000111100111101000011
1011111110110010110011101100010110101011111000011011000011000111001011100000011111111011111
0111111011100001100000100011000011000101100000111100110011001100010101111101010010110000100
1010000001100110110010110010111111101101101011111100100111110101001001100110010000010010011
1011101100100011011100100101101010111100010001111100110001011111010011001100010011001101001
1101111011011001110110111010001110111110111001000000110001011001101101010110000010011011010
1101100110100111000000010110101011000110010100111110011011011110110011011100100100000011011
1001101011010100011010101110110101011111011100000111111100101000000010101011010111111100010
1110010110010010000111000111011110000010001001011000011100110001011011010111110100111100001
0100111100010100110110101000001001000010101010001011100001101101110010100111001100110101001
1000101110001011010100000111101011010100011001111101010001000100000111011111011101110000111
0010001010000011000111001001110011110001000101101001010001100111101011010000010010110110100
0010000100111011100000111000111111100010101011100101010011000011100011101110011100011000000
0101110110010010011010110110110111010111000111110000100001010001100000011010010011100001001
0110011010101011011110011101010010110010100111101110011011100110100101010000100111100101011
1001010110000001010010000110100000101101011101001000101000111000110111010110100010111010101
1011100011001110000000100000110011110000011010011100001100101010011100100011101010110001010
1111010000110011000111010110110101000110000101100000011111101001010101110101001001110100011
0110110110100010001110111010010000100100101110010101100101100001001100111100111110011100100
1010011000110110101111001011110001111011001100001100010111111011111010101110011001111111111
0101110010110000110110000110101000000111110111110110010101001010100100001000100110100010000
1111000100011111000100000110100001001000011110000000111111001001111011001101110110000000101
0001111110111011010100110110010011111011100011010010110010011101011100110000110101011011101
1111110010111000011010111100011100001010010100001100100111010000001010100101110100000011010
1010010100110100010000110011000000101001111010101100000101011111001100100010111000110100110
1100100110001001110110011100011111000011110100111011100011000101010111010111010100010011000
0111101110110011100010110010111100000001100001101101010001100110010000111010111010010110001
0010011001000100111010111010110111101011010001001011100101000110011111010001111101000010110
0110000010001100110010000101011101011001010010111111101110110101010111010110100101100000000
+55
View File
@@ -0,0 +1,55 @@
program grid4_to_g15
parameter (MAXGRID4=32400)
character*4 w,grid4
character c1*1,c2*2
logical is_grid4
is_grid4(grid4)=len(trim(grid4)).eq.4 .and. &
grid4(1:1).ge.'A' .and. grid4(1:1).le.'R' .and. &
grid4(2:2).ge.'A' .and. grid4(2:2).le.'R' .and. &
grid4(3:3).ge.'0' .and. grid4(3:3).le.'9' .and. &
grid4(4:4).ge.'0' .and. grid4(4:4).le.'9'
nargs=iargc()
if(nargs.ne.1) then
print*,'Convert a 4-character grid, signal report, etc., to a g15 value.'
print*,'Usage examples:'
print*,'grid4_to_g15 FN20'
print*,'grid4_to_g15 -11'
print*,'grid4_to_g15 +02'
print*,'grid4_to_g15 RRR'
print*,'grid4_to_g15 RR73'
print*,'grid4_to_g15 73'
print*,'grid4_to_g15 ""'
go to 999
endif
call getarg(1,w)
if(is_grid4(w) .and. w.ne.'RR73') then
j1=(ichar(w(1:1))-ichar('A'))*18*10*10
j2=(ichar(w(2:2))-ichar('A'))*10*10
j3=(ichar(w(3:3))-ichar('0'))*10
j4=(ichar(w(4:4))-ichar('0'))
igrid4=j1+j2+j3+j4
else
c1=w(1:1)
if(c1.ne.'+' .and. c1.ne.'-'.and. trim(w).ne.'RRR' .and. w.ne.'RR73' &
.and. trim(w).ne.'73' .and. len(trim(w)).ne.0) go to 900
if(c1.eq.'+' .or. c1.eq.'-') then
read(w,*,err=900) irpt
irpt=irpt+35
endif
if(len(trim(w)).eq.0) irpt=1
if(trim(w).eq.'RRR') irpt=2
if(w.eq.'RR73') irpt=3
if(trim(w).eq.'73') irpt=4
igrid4=MAXGRID4 + irpt
endif
write(*,1000) w,igrid4,igrid4
1000 format('Encoded word: ',a4,' g15 in binary: ',b15.15,' decimal:',i6)
go to 999
900 write(*,1900)
1900 format('Invalid input')
999 end program grid4_to_g15
+41
View File
@@ -0,0 +1,41 @@
program grid6_to_g25
parameter (MAXGRID4=32400)
character*6 w,grid6
character c1*1,c2*2
logical is_grid6
is_grid6(grid6)=len(trim(grid6)).eq.6 .and. &
grid6(1:1).ge.'A' .and. grid6(1:1).le.'R' .and. &
grid6(2:2).ge.'A' .and. grid6(2:2).le.'R' .and. &
grid6(3:3).ge.'0' .and. grid6(3:3).le.'9' .and. &
grid6(4:4).ge.'0' .and. grid6(4:4).le.'9' .and. &
grid6(5:5).ge.'A' .and. grid6(5:5).le.'X' .and. &
grid6(6:6).ge.'A' .and. grid6(6:6).le.'X'
nargs=iargc()
if(nargs.ne.1) then
print*,'Convert a 6-character grid to a g25 value.'
print*,'Usage: grid6_to_g25 IO91NP'
go to 999
endif
call getarg(1,w)
if(.not. is_grid6(w)) go to 900
j1=(ichar(w(1:1))-ichar('A'))*18*10*10*24*24
j2=(ichar(w(2:2))-ichar('A'))*10*10*24*24
j3=(ichar(w(3:3))-ichar('0'))*10*24*24
j4=(ichar(w(4:4))-ichar('0'))*24*24
j5=(ichar(w(5:5))-ichar('A'))*24
j6=(ichar(w(6:6))-ichar('A'))
igrid6=j1+j2+j3+j4+j5+j6
write(*,1000) w,igrid6,igrid6
1000 format('Encoded word: ',a6,' g25 in binary: ',b25.25/ &
30x,'decimal:',i9)
go to 999
900 write(*,1900)
1900 format('Invalid input')
999 end program grid6_to_g25
+34
View File
@@ -0,0 +1,34 @@
program hashcodes
parameter (NTOKENS=2063592)
integer*8 nprime,n8(3)
integer nbits(3),ihash(3)
character*11 callsign
character*38 c
data c/' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/'/
data nprime/47055833459_8/,nbits/10,12,22/
nargs=iargc()
if(nargs.ne.1) then
print*,'Usage: hashcodes <callsign>'
print*,'Examples: hashcodes PJ4/K1ABC'
print*,' hashcodes YW18FIFA'
go to 999
endif
call getarg(1,callsign)
callsign=adjustl(callsign)
do k=1,3
n8(k)=0
do i=1,11
j=index(c,callsign(i:i)) - 1
n8(k)=38*n8(k) + j
enddo
ihash(k)=ishft(nprime*n8(k),nbits(k)-64)
enddo
ih22_biased=ihash(3) + NTOKENS
write(*,1000) callsign,ihash,ih22_biased
1000 format('Callsign',9x,'h10',7x,'h12',7x,'h22'/41('-')/ &
a11,i9,2i10,/'Biased for storage in c28:',i14)
999 end program hashcodes
+24
View File
@@ -0,0 +1,24 @@
program nonstd_to_c58
integer*8 n58
character*11 callsign
character*38 c
data c/' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/'/
nargs=iargc()
if(nargs.ne.1) then
print*,'Usage: nonstd_to_c58 <callsign>'
print*,'Examples: nonstd_to_c58 PJ4/K1ABC'
print*,' nonstd_to_c58 YW18FIFA'
go to 999
endif
call getarg(1,callsign)
n58=0
do i=1,11
n58=n58*38 + index(c,callsign(i:i)) - 1
enddo
write(*,1000) callsign,n58,n58
1000 format('Callsign: ',a11/'c58 (binary): ' b58.58/'c58 (decimal):',i20)
999 end program nonstd_to_c58
+183
View File
@@ -0,0 +1,183 @@
This file specifies the sparse 83x174 parity-check matrix for the
FT8/FT4 (174,91) LDPC code. Each of the 174 columns contains
exactly 3 ones. The rows contain either 6 or 7 ones.
The matrix is specified by the following list consisting of
174 lines, each of which includes 3 numbers.
Each line corresponds to a column of the parity check matrix.
The three numbers are indices of the rows that contain a one in
the corresponding column. The indices range from 1 through 83.
16 45 73
25 51 62
33 58 78
1 44 45
2 7 61
3 6 54
4 35 48
5 13 21
8 56 79
9 64 69
10 19 66
11 36 60
12 37 58
14 32 43
15 63 80
17 28 77
18 74 83
22 53 81
23 30 34
24 31 40
26 41 76
27 57 70
29 49 65
3 38 78
5 39 82
46 50 73
51 52 74
55 71 72
44 67 72
43 68 78
1 32 59
2 6 71
4 16 54
7 65 67
8 30 42
9 22 31
10 18 76
11 23 82
12 28 61
13 52 79
14 50 51
15 81 83
17 29 60
19 33 64
20 26 73
21 34 40
24 27 77
25 55 58
35 53 66
36 48 68
37 46 75
38 45 47
39 57 69
41 56 62
20 49 53
46 52 63
45 70 75
27 35 80
1 15 30
2 68 80
3 36 51
4 28 51
5 31 56
6 20 37
7 40 82
8 60 69
9 10 49
11 44 57
12 39 59
13 24 55
14 21 65
16 71 78
17 30 76
18 25 80
19 61 83
22 38 77
23 41 50
7 26 58
29 32 81
33 40 73
18 34 48
13 42 64
5 26 43
47 69 72
54 55 70
45 62 68
10 63 67
14 66 72
22 60 74
35 39 79
1 46 64
1 24 66
2 5 70
3 31 65
4 49 58
1 4 5
6 60 67
7 32 75
8 48 82
9 35 41
10 39 62
11 14 61
12 71 74
13 23 78
11 35 55
15 16 79
7 9 16
17 54 63
18 50 57
19 30 47
20 64 80
21 28 69
22 25 43
13 22 37
2 47 51
23 54 74
26 34 72
27 36 37
21 36 63
29 40 44
19 26 57
3 46 82
14 15 58
33 52 53
30 43 52
6 9 52
27 33 65
25 69 73
38 55 83
20 39 77
18 29 56
32 48 71
42 51 59
28 44 79
34 60 62
31 45 61
46 68 77
6 24 76
8 10 78
40 41 70
17 50 53
42 66 68
4 22 72
36 64 81
13 29 47
2 8 81
56 67 73
5 38 50
12 38 64
59 72 80
3 26 79
45 76 81
1 65 74
7 18 77
11 56 59
14 39 54
16 37 66
10 28 55
15 60 70
17 25 82
20 30 31
12 67 68
23 75 80
27 32 62
24 69 75
19 21 71
34 53 61
35 46 47
33 59 76
40 43 83
41 42 63
49 75 83
20 44 48
42 49 57
+11
View File
@@ -0,0 +1,11 @@
! Abbreviations for US States and Canadian Provinces as a Fortran 90
! data statement:
data cmult/ &
"AL ","AK ","AZ ","AR ","CA ","CO ","CT ","DE ","FL ","GA ", &
"HI ","ID ","IL ","IN ","IA ","KS ","KY ","LA ","ME ","MD ", &
"MA ","MI ","MN ","MS ","MO ","MT ","NE ","NV ","NH ","NJ ", &
"NM ","NY ","NC ","ND ","OH ","OK ","OR ","PA ","RI ","SC ", &
"SD ","TN ","TX ","UT ","VT ","VA ","WA ","WV ","WI ","WY ", &
"NB ","NS ","QC ","ON ","MB ","SK ","AB ","BC ","NWT","NF ", &
"LB ","NU ","YT ","PEI","DC "/
+31
View File
@@ -0,0 +1,31 @@
program std_call_to_c28
parameter (NTOKENS=2063592,MAX22=4194304)
character*6 call_std
character a1*37,a2*36,a3*10,a4*27
data a1/' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'/
data a2/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'/
data a3/'0123456789'/
data a4/' ABCDEFGHIJKLMNOPQRSTUVWXYZ'/
nargs=iargc()
if(nargs.ne.1) then
print*,'Usage: std_call_to_c28 <call_std>'
print*,'Example: std_call_to_c28 K1ABC'
go to 999
endif
call getarg(1,call_std)
call_std=adjustr(call_std)
i1=index(a1,call_std(1:1))-1
i2=index(a2,call_std(2:2))-1
i3=index(a3,call_std(3:3))-1
i4=index(a4,call_std(4:4))-1
i5=index(a4,call_std(5:5))-1
i6=index(a4,call_std(6:6))-1
n28=NTOKENS + MAX22 + 36*10*27*27*27*i1 + 10*27*27*27*i2 + &
27*27*27*i3 + 27*27*i4 + 27*i5 + i6
write(*,1000) call_std,n28
1000 format('Callsign: ',a6,2x,'c28 as decimal integer:',i10)
999 end program std_call_to_c28