From d845bd046669eb8349b22f7875f3152cd8bcd4e1 Mon Sep 17 00:00:00 2001 From: Joe Taylor Date: Mon, 16 Sep 2024 14:49:07 -0400 Subject: [PATCH] Building sfrx[.exe] in the wsjtx repository is now basically operational. --- CMakeLists.txt | 38 ++- lib/ft8/ft8_downsample.f90 | 9 +- lib/superfox/ft8_params.f90 | 12 + lib/superfox/gtag.f90 | 3 + lib/superfox/julian.f90 | 39 +++ lib/superfox/popen_module.f90 | 103 ++++++++ lib/superfox/qpc/dbgprintf.c | 67 +++++ lib/superfox/qpc/dbgprintf.h | 64 +++++ lib/superfox/qpc/nhash2.c | 383 +++++++++++++++++++++++++++++ lib/superfox/qpc/nhash2.h | 22 ++ lib/superfox/qpc/np_qpc.c | 329 +++++++++++++++++++++++++ lib/superfox/qpc/np_qpc.h | 62 +++++ lib/superfox/qpc/np_rnd.c | 135 ++++++++++ lib/superfox/qpc/np_rnd.h | 59 +++++ lib/superfox/qpc/qpc_fwht.c | 233 ++++++++++++++++++ lib/superfox/qpc/qpc_fwht.h | 57 +++++ lib/superfox/qpc/qpc_main.cpp | 198 +++++++++++++++ lib/superfox/qpc/qpc_mod.f90 | 38 +++ lib/superfox/qpc/qpc_n127k50q128.c | 60 +++++ lib/superfox/qpc/qpc_subs.c | 115 +++++++++ lib/superfox/qpc_decode2.f90 | 218 ++++++++++++++++ lib/superfox/qpc_likelihoods2.f90 | 28 +++ lib/superfox/qpc_snr.f90 | 17 ++ lib/superfox/qpc_sync.f90 | 101 ++++++++ lib/superfox/sfox_ana.f90 | 17 ++ lib/superfox/sfox_demod.f90 | 79 ++++++ lib/superfox/sfox_mod.f90 | 73 ++++++ lib/superfox/sfox_prob.f90 | 55 +++++ lib/superfox/sfox_remove_ft8.f90 | 229 +++++++++++++++++ lib/superfox/sfox_unpack.f90 | 125 ++++++++++ lib/superfox/sfrx.f90 | 137 +++++++++++ lib/superfox/twkfreq2.f90 | 19 ++ 32 files changed, 3113 insertions(+), 11 deletions(-) create mode 100644 lib/superfox/ft8_params.f90 create mode 100644 lib/superfox/gtag.f90 create mode 100644 lib/superfox/julian.f90 create mode 100644 lib/superfox/popen_module.f90 create mode 100644 lib/superfox/qpc/dbgprintf.c create mode 100644 lib/superfox/qpc/dbgprintf.h create mode 100644 lib/superfox/qpc/nhash2.c create mode 100644 lib/superfox/qpc/nhash2.h create mode 100644 lib/superfox/qpc/np_qpc.c create mode 100644 lib/superfox/qpc/np_qpc.h create mode 100644 lib/superfox/qpc/np_rnd.c create mode 100644 lib/superfox/qpc/np_rnd.h create mode 100644 lib/superfox/qpc/qpc_fwht.c create mode 100644 lib/superfox/qpc/qpc_fwht.h create mode 100644 lib/superfox/qpc/qpc_main.cpp create mode 100644 lib/superfox/qpc/qpc_mod.f90 create mode 100644 lib/superfox/qpc/qpc_n127k50q128.c create mode 100644 lib/superfox/qpc/qpc_subs.c create mode 100644 lib/superfox/qpc_decode2.f90 create mode 100644 lib/superfox/qpc_likelihoods2.f90 create mode 100644 lib/superfox/qpc_snr.f90 create mode 100644 lib/superfox/qpc_sync.f90 create mode 100644 lib/superfox/sfox_ana.f90 create mode 100644 lib/superfox/sfox_demod.f90 create mode 100644 lib/superfox/sfox_mod.f90 create mode 100644 lib/superfox/sfox_prob.f90 create mode 100644 lib/superfox/sfox_remove_ft8.f90 create mode 100644 lib/superfox/sfox_unpack.f90 create mode 100644 lib/superfox/sfrx.f90 create mode 100644 lib/superfox/twkfreq2.f90 diff --git a/CMakeLists.txt b/CMakeLists.txt index ed31da3d4..13f55763f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -353,6 +353,10 @@ set (wsjt_FSRCS lib/wavhdr.f90 lib/qra/q65/q65_encoding_modules.f90 lib/ft8/ft8_a7.f90 + lib/superfox/sfox_mod.f90 + lib/superfox/julian.f90 + lib/superfox/popen_module.f90 + lib/superfox/qpc/qpc_mod.f90 # remaining non-module sources lib/addit.f90 @@ -597,8 +601,18 @@ set (wsjt_FSRCS lib/fst4/get_crc24.f90 lib/fst4/fst4_baseline.f90 lib/77bit/hash22calc.f90 + + lib/superfox/qpc_decode2.f90 + lib/superfox/qpc_likelihoods2.f90 + lib/superfox/qpc_snr.f90 + lib/superfox/qpc_sync.f90 + lib/superfox/sfox_ana.f90 + lib/superfox/sfox_demod.f90 + lib/superfox/sfox_remove_ft8.f90 + lib/superfox/sfox_unpack.f90 lib/superfox/sfox_wave.f90 lib/superfox/sfox_wave_gfsk.f90 + lib/superfox/twkfreq2.f90 ) # temporary workaround for a gfortran v7.3 ICE on Fedora 27 64-bit @@ -641,6 +655,15 @@ set (wsjt_CSRCS lib/wrapkarn.c ${ldpc_CSRCS} ${qra_CSRCS} + + lib/superfox/qpc/dbgprintf.c + lib/superfox/qpc/nhash2.c + lib/superfox/qpc/np_qpc.c + lib/superfox/qpc/np_rnd.c + lib/superfox/qpc/qpc_fwht.c + lib/superfox/qpc/qpc_n127k50q128.c + lib/superfox/qpc/qpc_subs.c + ) set (wsjt_qt_UISRCS @@ -1226,6 +1249,9 @@ target_link_libraries (echosim wsjt_fort wsjt_cxx) add_executable (ft8sim lib/ft8/ft8sim.f90) target_link_libraries (ft8sim wsjt_fort wsjt_cxx) +add_executable (sfrx lib/superfox/sfrx.f90) +target_link_libraries (sfrx wsjt_fort wsjt_cxx) + add_executable (msk144sim lib/msk144sim.f90) target_link_libraries (msk144sim wsjt_fort wsjt_cxx) @@ -1613,7 +1639,7 @@ install (TARGETS udp_daemon message_aggregator wsjtx_app_version BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime ) -install (TARGETS jt9 wsprd fmtave fcal fmeasure +install (TARGETS jt9 wsprd fmtave fcal fmeasure sfrx RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime ) @@ -1686,7 +1712,7 @@ if (WIN32) endif () install (FILES - ${sfox_dir}/sfrx.exe +# ${sfox_dir}/sfrx.exe ${sfox_dir}/sftx.exe ${sfox_dir}/foxchk.exe DESTINATION ${CMAKE_INSTALL_BINDIR} @@ -1696,7 +1722,7 @@ endif (WIN32) if (UNIX) install (FILES - lib/superfox/linux/sfrx +# lib/superfox/linux/sfrx lib/superfox/linux/sftx lib/superfox/linux/foxchk DESTINATION ${CMAKE_INSTALL_BINDIR} @@ -1709,7 +1735,7 @@ endif (UNIX) if (APPLE) install (FILES - lib/superfox/mac/sfrx +# lib/superfox/mac/sfrx lib/superfox/mac/sftx lib/superfox/mac/foxchk DESTINATION ${CMAKE_INSTALL_BINDIR} @@ -1729,7 +1755,7 @@ execute_process(COMMAND if(${CMAKE_DEB_HOST_ARCH} MATCHES "arm64") install (FILES - lib/superfox/arm/sfrx +# lib/superfox/arm/sfrx lib/superfox/arm/sftx lib/superfox/arm/foxchk DESTINATION ${CMAKE_INSTALL_BINDIR} @@ -1742,7 +1768,7 @@ endif() if(${CMAKE_DEB_HOST_ARCH} MATCHES "armhf") install (FILES - lib/superfox/arm32/sfrx +# lib/superfox/arm32/sfrx lib/superfox/arm32/sftx lib/superfox/arm32/foxchk DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/lib/ft8/ft8_downsample.f90 b/lib/ft8/ft8_downsample.f90 index f35522583..a57b5324a 100644 --- a/lib/ft8/ft8_downsample.f90 +++ b/lib/ft8/ft8_downsample.f90 @@ -8,10 +8,10 @@ subroutine ft8_downsample(dd,newdat,f0,c1) logical newdat,first complex c1(0:NFFT2-1) complex cx(0:NFFT1/2) - real dd(NMAX),x(NFFT1),taper(0:100) - equivalence (x,cx) + real dd(NMAX),x(NFFT1+2),taper(0:100) data first/.true./ - save cx,first,taper + save x,cx,first,taper + equivalence (x,cx) if(first) then pi=4.0*atan(1.0) @@ -23,11 +23,10 @@ subroutine ft8_downsample(dd,newdat,f0,c1) if(newdat) then ! Data in dd have changed, recompute the long FFT x(1:NMAX)=dd - x(NMAX+1:NFFT1)=0. !Zero-pad the x array + x(NMAX+1:NFFT1+2)=0. !Zero-pad the x array call four2a(cx,NFFT1,1,-1,0) !r2c FFT to freq domain newdat=.false. endif - df=12000.0/NFFT1 baud=12000.0/NSPS i0=nint(f0/df) diff --git a/lib/superfox/ft8_params.f90 b/lib/superfox/ft8_params.f90 new file mode 100644 index 000000000..139ff964e --- /dev/null +++ b/lib/superfox/ft8_params.f90 @@ -0,0 +1,12 @@ +! LDPC (174,91) code +parameter (KK=91) !Information bits (77 + CRC14) +parameter (ND=58) !Data symbols +parameter (NS=21) !Sync symbols (3 @ Costas 7x7) +parameter (NN=NS+ND) !Total channel symbols (79) +parameter (NSPS=1920) !Samples per symbol at 12000 S/s +parameter (NZ=NSPS*NN) !Samples in full 15 s waveform (151,680) +parameter (NMAX=15*12000) !Samples in iwave (180,000) +parameter (NFFT1=2*NSPS, NH1=NFFT1/2) !Length of FFTs for symbol spectra +parameter (NSTEP=NSPS/4) !Rough time-sync step size +parameter (NHSYM=NMAX/NSTEP-3) !Number of symbol spectra (1/4-sym steps) +parameter (NDOWN=60) !Downsample factor diff --git a/lib/superfox/gtag.f90 b/lib/superfox/gtag.f90 new file mode 100644 index 000000000..9cf93ab37 --- /dev/null +++ b/lib/superfox/gtag.f90 @@ -0,0 +1,3 @@ + integer ntag + data ntag/z"1234567"/ + !----------------------------------------------------------------------- diff --git a/lib/superfox/julian.f90 b/lib/superfox/julian.f90 new file mode 100644 index 000000000..002167fca --- /dev/null +++ b/lib/superfox/julian.f90 @@ -0,0 +1,39 @@ +module julian + +contains + + integer*8 function itime8() + + implicit integer (a-z) +! 1 2 3 4 5 6 7 + integer it(8) !y m d nmin h m s + integer*8 secday + data secday/86400/ + + call date_and_time(values=it) + iyr=it(1) + imo=it(2) + iday=it(3) + days=JD(iyr,imo,iday) - 2440588 !Days since epoch Jan 1, 1970 + ih=it(5) + im=it(6)-it(4) !it(4) corrects for time zone + is=it(7) + nsec=3600*ih + 60*im + is + itime8=days*secday + nsec + + return + end function itime8 + + integer function JD(I,J,K) + +! Return Julian Date for I=year, J=month, K=day +! Reference: Fliegel and Van Flandern, Comm. ACM 11, 10, 1968 + + JD = K - 32075 + 1461*(I + 4800 + (J - 14)/12)/4 & + + 367*(J - 2 - (J - 14)/12*12)/12 - 3 & + *((I + 4900 + (J - 14)/12)/100)/4 + + return + end function JD + +end module julian diff --git a/lib/superfox/popen_module.f90 b/lib/superfox/popen_module.f90 new file mode 100644 index 000000000..9aea9ff55 --- /dev/null +++ b/lib/superfox/popen_module.f90 @@ -0,0 +1,103 @@ +!******************************************************************************* +!> +! A simple module for `popen`. + + module popen_module + + use,intrinsic :: iso_c_binding + + implicit none + + private + + ! interfaces to C functions + interface + + function popen(command, mode) bind(C,name='popen') + !! initiate pipe streams to or from a process + import :: c_char, c_ptr + implicit none + character(kind=c_char),dimension(*) :: command + character(kind=c_char),dimension(*) :: mode + type(c_ptr) :: popen + end function popen + + function fgets(s, siz, stream) bind(C,name='fgets') + !! get a string from a stream + import :: c_char, c_ptr, c_int + implicit none + type (c_ptr) :: fgets + character(kind=c_char),dimension(*) :: s + integer(kind=c_int),value :: siz + type(c_ptr),value :: stream + end function fgets + + function pclose(stream) bind(C,name='pclose') + !! close a pipe stream to or from a process + import :: c_ptr, c_int + implicit none + integer(c_int) :: pclose + type(c_ptr),value :: stream + end function pclose + + end interface + + public :: c2f_string, get_command_as_string + +contains + + !******************************************************************************* + !> + ! Convert a C string to a Fortran string. + + function c2f_string(c) result(f) + + character(len=*),intent(in) :: c !! C string + character(len=:),allocatable :: f !! Fortran string + + integer :: i + + i = index(c,c_null_char) + + if (i<=0) then + f = c + else if (i==1) then + f = '' + else if (i>1) then + f = c(1:i-1) + end if + + end function c2f_string + + !******************************************************************************* + !> + ! Return the result of the command as a string. + + function get_command_as_string(command) result(str) + + character(len=*),intent(in) :: command !! the command to run + character(len=:),allocatable :: str !! the result of that command + + integer,parameter :: buffer_length = 1000 !! read stream in chunks of this size + + type(c_ptr) :: h !! for `popen` + integer(c_int) :: istat !! `pclose` status + character(kind=c_char,len=buffer_length) :: line !! buffer to read from `fgets` + + str = '' + h = c_null_ptr + h = popen(command//c_null_char,'r'//c_null_char) + + if (c_associated(h)) then + do while (c_associated(fgets(line,buffer_length,h))) + str = str//c2f_string(line) + end do + istat = pclose(h) + end if + + end function get_command_as_string + +!******************************************************************************* + end module popen_module +!******************************************************************************* + diff --git a/lib/superfox/qpc/dbgprintf.c b/lib/superfox/qpc/dbgprintf.c new file mode 100644 index 000000000..4a86e5556 --- /dev/null +++ b/lib/superfox/qpc/dbgprintf.c @@ -0,0 +1,67 @@ +// ------------------------------------------------------------------------------ +// dbgprintf.c +// Functions for printing debug information +// +// (c) 2024 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy +// ------------------------------------------------------------------------------ +// +// This source is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this source distribution. +// If not, see . +// ------------------------------------------------------------------------------ + +#include "dbgprintf.h" + +// print functions for debug purposes +void dbgprintf_vector_uchar(const char* name, const unsigned char* v, int vsize) +{ + int k; + + printf("%s=", name); + for (k = 0; k < vsize; k++) { + if ((k & 0x0F) == 0) + printf("\n"); + printf("%02X ", v[k]); + } + printf("\n"); +} + +void dbgprintf_vector_float(const char* name, const float* v, int vsize) +{ + int k; + + printf("%s=", name); + for (k = 0; k < vsize; k++) { + if ((k & 0x07) == 0) + printf("\n"); + printf("%10.3f ", v[k]); + } + printf("\n"); +} + +void dbgprintf_rows_float(const char* name, const float* v, int vsize, int nrows) +{ + int k; + int j; + + printf("%s=\n", name); + for (j = 0; j < nrows; j++) { + printf("r%d:", j); + for (k = 0; k < vsize; k++) { + if ((k & 0x07) == 0) + printf("\n"); + printf("%7.3f ", v[k]); + } + printf("\n"); + v += vsize; + } +} diff --git a/lib/superfox/qpc/dbgprintf.h b/lib/superfox/qpc/dbgprintf.h new file mode 100644 index 000000000..dc906f097 --- /dev/null +++ b/lib/superfox/qpc/dbgprintf.h @@ -0,0 +1,64 @@ +// ------------------------------------------------------------------------------ +// dbgprintf.h +// Functions for printing debug information +// +// (c) 2024 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy +// ------------------------------------------------------------------------------ +// +// This source is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this source distribution. +// If not, see . +// ------------------------------------------------------------------------------ + +#ifndef _dbgprintf_h_ +#define _dbgprintf_h_ + +// Define DBGPRINTF_ANYWAY +// in order to print debug information also +// when _DEBUG is not defined + +#define DBGPRINTF_ANYWAY + +#ifdef _DEBUG +#define DBG_PRINTF +#else +#ifdef DBGPRINTF_ANYWAY +#define DBG_PRINTF +#endif +#endif + +#include + +// print functions for debug purposes +#ifdef DBG_PRINTF + +#ifdef __cplusplus +extern "C" { +#endif + + void dbgprintf_vector_uchar(const char* name, const unsigned char* v, int vsize); + void dbgprintf_vector_float(const char* name, const float* v, int vsize); + void dbgprintf_rows_float(const char* name, const float* v, int vsize, int nrows); + +#ifdef __cplusplus +} +#endif + +#else + +#define dbgprintf_vector_uchar(a,b) +#define dbgprintf_vector_float(a,b) +#define dbgprintf_rows_float(a, b, c, d) + +#endif + +#endif // _dbgprintf_h_ diff --git a/lib/superfox/qpc/nhash2.c b/lib/superfox/qpc/nhash2.c new file mode 100644 index 000000000..78817c7d9 --- /dev/null +++ b/lib/superfox/qpc/nhash2.c @@ -0,0 +1,383 @@ +/* + + File name: nhash2.c + + *------------------------------------------------------------------------------ + * + * This file is part of the WSPR application, Weak Signal Propogation Reporter + * + * File Name: nhash.c + * Description: Functions to produce 32-bit hashes for hash table lookup + * + * Copyright (C) 2008-2014 Joseph Taylor, K1JT + * License: GNU GPL v3+ + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Files: lookup3.c + * Copyright: Copyright (C) 2006 Bob Jenkins + * License: public-domain + * You may use this code any way you wish, private, educational, or commercial. + * It's free. + * + *------------------------------------------------------------------------------- +*/ + +/* +These are functions for producing 32-bit hashes for hash table lookup. +hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() +are externally useful functions. Routines to test the hash are included +if SELF_TEST is defined. You can use this free for any purpose. It's in +the public domain. It has no warranty. + +You probably want to use hashlittle(). hashlittle() and hashbig() +hash byte arrays. hashlittle() is is faster than hashbig() on +little-endian machines. Intel and AMD are little-endian machines. +On second thought, you probably want hashlittle2(), which is identical to +hashlittle() except it returns two 32-bit hashes for the price of one. +You could implement hashbig2() if you wanted but I haven't bothered here. + +If you want to find a hash of, say, exactly 7 integers, do + a = i1; b = i2; c = i3; + mix(a,b,c); + a += i4; b += i5; c += i6; + mix(a,b,c); + a += i7; + final(a,b,c); +then use c as the hash value. If you have a variable length array of +4-byte integers to hash, use hashword(). If you have a byte array (like +a character string), use hashlittle(). If you have several byte arrays, or +a mix of things, see the comments above hashlittle(). + +Why is this so big? I read 12 bytes at a time into 3 4-byte integers, +then mix those integers. This is fast (you can do a lot more thorough +mixing with 12*3 instructions on 3 integers than you can with 3 instructions +on 1 byte), but shoehorning those bytes into integers efficiently is messy. +*/ + +#define SELF_TEST 1 + +#include /* defines printf for tests */ +#include /* defines time_t for timings in the test */ +#include "nhash2.h" +//#include /* attempt to define endianness */ +//#ifdef linux +//# include /* attempt to define endianness */ +//#endif + +#define HASH_LITTLE_ENDIAN 1 + +#define hashsize(n) ((uint32_t)1<<(n)) +#define hashmask(n) (hashsize(n)-1) +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. + +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). + +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. + +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. + +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c + +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} + +/* +------------------------------------------------------------------------------- +hashlittle() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + length : the length of the key, counting by bytes + initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Two keys differing by one or two bits will have +totally different hash values. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (uint8_t **)k, do it like this: + for (i=0, h=0; i 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + // printf("AAA %x %d %x %d\n",c,c,c&32767,c&32767); + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : return c; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : return c; + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : return c; /* zero length requires no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; + /* fall through */ + case 11: c+=((uint32_t)k[10])<<16; + /* fall through */ + case 10: c+=((uint32_t)k[9])<<8; + /* fall through */ + case 9 : c+=k[8]; + /* fall through */ + case 8 : b+=((uint32_t)k[7])<<24; + /* fall through */ + case 7 : b+=((uint32_t)k[6])<<16; + /* fall through */ + case 6 : b+=((uint32_t)k[5])<<8; + /* fall through */ + case 5 : b+=k[4]; + /* fall through */ + case 4 : a+=((uint32_t)k[3])<<24; + /* fall through */ + case 3 : a+=((uint32_t)k[2])<<16; + /* fall through */ + case 2 : a+=((uint32_t)k[1])<<8; + /* fall through */ + case 1 : a+=k[0]; + break; + case 0 : return c; + } + } + + final(a,b,c); + return c; +} diff --git a/lib/superfox/qpc/nhash2.h b/lib/superfox/qpc/nhash2.h new file mode 100644 index 000000000..1864a93fc --- /dev/null +++ b/lib/superfox/qpc/nhash2.h @@ -0,0 +1,22 @@ +#ifndef NHASH_H_ +#define NHASH_H_ + +#ifdef Win32 +#include "win_stdint.h" /* defines uint32_t etc */ +#else +#include +#include /* defines uint32_t etc */ +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +uint32_t nhash( const void * key, uint64_t length, uint32_t initval); + //int nhash(const char *key, int length, int initval); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/superfox/qpc/np_qpc.c b/lib/superfox/qpc/np_qpc.c new file mode 100644 index 000000000..d44e544b8 --- /dev/null +++ b/lib/superfox/qpc/np_qpc.c @@ -0,0 +1,329 @@ +// ------------------------------------------------------------------------------ +// np_qpc.h +// Q-Ary Polar Codes encoding/decoding functions +// +// (c) 2024 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy +// ------------------------------------------------------------------------------ +// +// This source is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this source distribution. +// If not, see . + +#include +#include +#include + +#include "dbgprintf.h" +#include "qpc_fwht.h" +#include "np_qpc.h" + +// static constant / functions + +static const float knorm = 1.0f / QPC_Q; + +static float* pdf_conv(float *dst, float* pdf1, float* pdf2) +{ + // convolution between two pdf + + float fwht_pdf1[QPC_Q]; + float fwht_pdf2[QPC_Q]; + int k; + + qpc_fwht(fwht_pdf1, pdf1); + qpc_fwht(fwht_pdf2, pdf2); + + for (k = 0; k < QPC_Q; k++) + fwht_pdf1[k] *= fwht_pdf2[k]; + + qpc_fwht(dst, fwht_pdf1); + + for (k = 0; k < QPC_Q; k++) + dst[k] *= knorm; + + return dst; +} +static void pdfarray_conv(float* dstarray, float* pdf1array, float* pdf2array, int numrows) +{ + int k; + + // convolutions between rows of pdfs + + for (k = 0; k < numrows; k++) { + pdf_conv(dstarray, pdf1array, pdf2array); + dstarray += QPC_Q; + pdf1array += QPC_Q; + pdf2array += QPC_Q; + } + +} + +static float _pdfuniform[QPC_Q]; +static const float* _pdf_uniform1(); +static const float* _pdf_uniform0(); +typedef const float*(*ptr_pdfuniform)(void); + +static ptr_pdfuniform _ptr_pdf_uniform = _pdf_uniform0; + +static const float* _pdf_uniform1() +{ + return _pdfuniform; +}; +static const float* _pdf_uniform0() +{ + // compute uniform pdf once for all + int k; + for (k = 0; k < QPC_Q; k++) + _pdfuniform[k] = knorm; + + // next call to _qpc_pdfuniform + // will be handled directly by _pqc_pdfuniform1 + _ptr_pdf_uniform = _pdf_uniform1; + + return _pdfuniform; +}; +static const float* pdf_uniform() +{ + return _ptr_pdf_uniform(); +} + +static float * pdf_mul(float *dst, float* pdf1, float* pdf2) +{ + int k; + float v; + float norm = 0; + for (k = 0; k < QPC_Q; k++) { + v = pdf1[k] * pdf2[k]; + dst[k] = v; + norm += v; + } + // if norm of the result is not positive + // return in dst a uniform distribution + if (norm <= 0) + memcpy(dst, pdf_uniform(), QPC_Q * sizeof(float)); + else { + norm = 1.0f / norm; + for (k = 0; k < QPC_Q; k++) + dst[k] = dst[k] * norm; + } + + + return dst; +} +static void pdfarray_mul(float* dstarray, float* pdf1array, float* pdf2array, int numrows) +{ + int k; + + // products between rows of pdfs + + for (k = 0; k < numrows; k++) { + pdf_mul(dstarray, pdf1array, pdf2array); + dstarray += QPC_Q; + pdf1array += QPC_Q; + pdf2array += QPC_Q; + } +} + +static float* pdf_convhard(float* dst, const float* pdf, unsigned char hd) +{ + // convolution between a pdf and a hard-decision feedback + + int k; + for (k=0;k pdfmax) { + pdfmax = pdf[k]; + imax = k; + } + + return imax; +} + +// local stack functions --------------------------------------- +static float _qpc_stack[QPC_N * QPC_Q * 2]; +static float* _qpc_stack_base = _qpc_stack; + +static float* _qpc_stack_push(int numfloats) +{ + float* addr = _qpc_stack_base; + _qpc_stack_base += numfloats; + return addr; +} +static void _qpc_stack_pop(int numfloats) +{ + _qpc_stack_base -= numfloats; +} + +// qpc encoder function (internal use) ---------------------------------------------------------- +unsigned char* _qpc_encode(unsigned char* y, unsigned char* x) +{ + // Non recursive polar encoder + // Same architecture of a fast fourier transform + // in which the fft butteflies are replaced by the polar transform + // butterflies + + int k, j, m; + int groups; + int bfypergroup; + int stepbfy; + int stepgroup; + int basegroup; + int basebfy; + + memcpy(y, x, QPC_N); + + for (k = 0; k < QPC_LOG2N; k++) { + groups = 1 << (QPC_LOG2N - 1 - k); + stepbfy = bfypergroup = 1 << k; + stepgroup = stepbfy << 1; + basegroup = 0; + for (j = 0; j < groups; j++) { + basebfy = basegroup; + for (m = 0; m < bfypergroup; m++) { + // polar transform + y[basebfy + stepbfy] = y[basebfy + stepbfy] ^ y[basebfy]; + basebfy = basebfy + 1; + } + basegroup = basegroup + stepgroup; + } + } + + return y; +} + +// qpc polar decoder (internal use )-------------------------------------------------- +void _qpc_decode(unsigned char* xdec, unsigned char* ydec, const float* py, const unsigned char* f, const unsigned char* fsize, const int numrows) +{ + + if (numrows == 1) { + if (fsize[0] == 0) { + // dbgprintf_vector_float("py", py, QPC_Q); + + // frozen symbol + xdec[0] = pdf_max(py); + ydec[0] = f[0]; + } + else { + // fsize = 1 => information symbol + xdec[0] = pdf_max(py); + ydec[0] = xdec[0]; + } + + } + else { + + int k; + int nextrows = numrows >> 1; + int size = nextrows << QPC_LOG2Q; + + // upper block variables + unsigned char* xdech = xdec + nextrows; + unsigned char* ydech = ydec + nextrows; + const unsigned char* fh = f + nextrows; + const unsigned char* fsizeh = fsize + nextrows; + + // Step 1. + // stack and init variables used in the recursion + + float* pyl = _qpc_stack_push(size); + memcpy(pyl, py, size * sizeof(float)); + + float* pyh = _qpc_stack_push(size); + memcpy(pyh, py + size, size * sizeof(float)); + + // Step 2. Recursion on upper block + // Forward pdf convolutions for the upper block + // (place in the lower part of py the convolution of lower and higher blocks) + +// float* pyh = py + size; +// pdfarray_conv(py, pyl, pyh, nextrows); // convolution overwriting the lower block of py which is not needed + + pdfarray_conv(pyh, pyl, pyh, nextrows); + _qpc_decode(xdech, ydech, pyh, fh, fsizeh, nextrows); + + // Step 3. compute pdfs in the lower block + pdfarray_convhard(pyh, py+size, ydech,nextrows); // dst ptr must be different form src ptr + pdfarray_mul(pyl, pyl, pyh, nextrows); + // we don't need pyh anymore + _qpc_stack_pop(size); + + // Step 4. Recursion on the lower block + _qpc_decode(xdec, ydec, pyl, f, fsize, nextrows); + // we don't need pyl anymore + _qpc_stack_pop(size); + + // Step 5. Update backward results + // xdec is already ok, we need just to update ydech + for (k = 0; k < nextrows; k++) + ydech[k] = ydech[k] ^ ydec[k]; + + } + + +} + +// Public encoding/decoding functions ------------------------------------------------ +void qpc_encode(unsigned char* y, const unsigned char* x) +{ + + // map information symbols + int kk, pos; + for (kk = 0; kk < QPC_K; kk++) { + pos = qpccode.xpos[kk]; + qpccode.f[pos] = x[kk]; + } + // do polar encoding + _qpc_encode(y, qpccode.f); +} +void qpc_decode(unsigned char* xdec, unsigned char* ydec, float* py) +{ + int k; + unsigned char x[QPC_N]; + + // set the first py row with know frozen (punctured) symbol + if (qpccode.np < qpccode.n) { + // assume that we punctured only the first output symbol + memset(py, 0, QPC_Q * sizeof(float)); + py[qpccode.f[0]] = 1.0f; + } + + // decode + _qpc_decode(x, ydec, py, qpccode.f, qpccode.fsize, QPC_N); + + // demap information symbols + for (k = 0; k < QPC_K; k++) + xdec[k] = x[qpccode.xpos[k]]; + +} diff --git a/lib/superfox/qpc/np_qpc.h b/lib/superfox/qpc/np_qpc.h new file mode 100644 index 000000000..0fa440e76 --- /dev/null +++ b/lib/superfox/qpc/np_qpc.h @@ -0,0 +1,62 @@ +// ------------------------------------------------------------------------------ +// np_qpc.h +// Q-Ary Polar Codes encoding/decoding functions +// +// (c) 2024 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy +// ------------------------------------------------------------------------------ +// +// This source is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this source distribution. +// If not, see . + + + +#ifndef _np_qpc_h_ +#define _np_qpc_h_ + +#define QPC_LOG2N 7 // log2(codeword length) (not punctured) +#define QPC_N (1< frozen) +} qpccode_ds; + +#ifdef __cplusplus +extern "C" +{ +#endif + + void qpc_encode(unsigned char* y, const unsigned char* x); + void qpc_decode(unsigned char* xdec, unsigned char* ydec, float* py); + + unsigned char* _qpc_encode(unsigned char* y, unsigned char* x); + void _qpc_decode(unsigned char* xdec, unsigned char* ydec, + const float* py, const unsigned char* f, const unsigned char* fsize, + const int numrows); + + extern qpccode_ds qpccode; + +#ifdef __cplusplus +} +#endif + +#endif // _np_qpc_h_ diff --git a/lib/superfox/qpc/np_rnd.c b/lib/superfox/qpc/np_rnd.c new file mode 100644 index 000000000..df2afacc3 --- /dev/null +++ b/lib/superfox/qpc/np_rnd.c @@ -0,0 +1,135 @@ +// ------------------------------------------------------------------------------ +// np_rnd.c +// Functions to generate random numbers with uniform/gaussian probability distributions +// +// (c) 2024 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy +// ------------------------------------------------------------------------------ +// +// This source is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this source distribution. +// If not, see . + +#include "np_rnd.h" + +#if _WIN32 // note the underscore: without it, it's not msdn official! + // Windows (x64 and x86) + #include // required only for GetTickCount(...) + #define K_RAND_MAX UINT_MAX +#elif _SVID_SOURCE || _XOPEN_SOURCE || __unix__ || (defined (__APPLE__) && defined(__MACH__)) /* POSIX or Unix or Apple */ + #include + #define rand_s(x) (*x)=(unsigned int)lrand48() // returns unsigned integers in the range 0..0x7FFFFFFF + #define K_RAND_MAX 0x7FFFFFFF // that's the max number + // generated by lrand48 +#else + #error "No good quality PRNG found" +#endif + + +void np_normrnd_real(float *dst, int nitems, float mean, float stdev) +{ + // for odd or even nitems + + unsigned int r; + float phi=0, u=0; + int set = 0; + float scalephi = (M_2PI / (1.0f + K_RAND_MAX)); + float scaleu = (1.0f / (1.0f + K_RAND_MAX)); + + + while (nitems--) + if (set==1) { + // generate a second normally distributed number + *dst++ = sinf(phi)*u*stdev+mean; + set = 0; + } + else { + // generate a uniform distributed phase phi in the range [0,2*PI) + rand_s((unsigned int*)&r); phi = scalephi * r; + + // generate a uniform distributed random number u in the range (0,1] + rand_s((unsigned int*)&r); u = scaleu * (1.0f + r); + + // generate normally distributed number + u = (float)sqrt(-2.0f * log(u)); + *dst++ = cosf(phi) * u * stdev + mean; + + set=1; + } +} + +void np_normrnd_cpx(float* dst, int nitems, float mean, float stdev) +{ + // as qpc_normrnd_real, but generates always nitems complex numbers (2*nitems reals) + + unsigned int r; + float phi = 0, u = 0; + // JHT int set = 0; + float scalephi = (M_2PI / (1.0f + K_RAND_MAX)); + float scaleu = (1.0f / (1.0f + K_RAND_MAX)); + + while (nitems--) { + // generate a uniform distributed phase phi in the range [0,2*PI) + rand_s((unsigned int*)&r); phi = scalephi * r; + + // generate a uniform distributed random number u in the range (0,1] + rand_s((unsigned int*)&r); u = scaleu * (1.0f + r); + + // generate normally distributed real and imaginary parts + u = (float)sqrt(-2.0f * log(u)); + *dst++ = cosf(phi) * u * stdev + mean; + *dst++ = sinf(phi) * u * stdev + mean; + } +} + +void np_unidrnd(unsigned int* dst, int nitems, unsigned int nsetsize) +{ + // generate uniform unsigned int random numbers in the range [0..nsetsize) + + unsigned int r; + float u; + float scaleu = (1.0f / (1.0f + K_RAND_MAX)); + + while (nitems--) { + rand_s((unsigned int*)&r); u = scaleu * nsetsize * r; + *dst++ = (unsigned int)floorf(u); + } + +} +void np_unidrnd_uc(unsigned char* dst, int nitems, unsigned char nsetsize) +{ + // generate uniform unsigned char random numbers in the range [0..nsetsize) + + unsigned int r; + float u; + float scaleu = (1.0f / (1.0f + K_RAND_MAX)); + + while (nitems--) { + rand_s((unsigned int*)&r); u = scaleu * nsetsize * r; + *dst++ = (unsigned char)floorf(u); + } + +} + +void np_unifrnd(float* dst, int nitems, float fmax) +{ + // generate uniform float random numbers in the range [0..fmax) + + unsigned int r; + float u; + float scaleu = (1.0f / (1.0f + K_RAND_MAX)); + + while (nitems--) { + rand_s((unsigned int*)&r); u = scaleu * fmax * r; + *dst++ = u; + } + +} diff --git a/lib/superfox/qpc/np_rnd.h b/lib/superfox/qpc/np_rnd.h new file mode 100644 index 000000000..47a71a3c4 --- /dev/null +++ b/lib/superfox/qpc/np_rnd.h @@ -0,0 +1,59 @@ +// ------------------------------------------------------------------------------ +// np_rnd.h +// Functions to generate random numbers with uniform/gaussian probability distributions +// +// (c) 2024 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy +// ------------------------------------------------------------------------------ +// +// This source is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this source distribution. +// If not, see . + +#ifndef _np_rnd_h_ +#define _np_rnd_h_ + +#define _CRT_RAND_S +#include + +#define _USE_MATH_DEFINES +#include +#define M_2PI (2.0f*(float)M_PI) + +#ifdef __cplusplus +extern "C" { +#endif + +// generate a random array of real numbers with a gaussian distribution of given mean and stdev +void np_normrnd_real(float *dst, int nitems, float mean, float stdev); + +// generate a random array of nitems complex numbers with a gaussian distribution of given mean and stdev +void np_normrnd_cpx(float* dst, int nitems, float mean, float stdev); + +// generate a random array of nitems unsigned int numbers with uniform distribution +// in the range [0 .. nsetsize) +void np_unidrnd(unsigned int* dst, int nitems, unsigned int nsetsize); + +// generate a random array of nitems unsigned chars numbers with uniform distribution +// in the range [0 .. nsetsize) +void np_unidrnd_uc(unsigned char* dst, int nitems, unsigned char nsetsize); + +// generate a random array of nitems float numbers with uniform distribution +// in the range [0 .. fmax) +void np_unifrnd(float* dst, int nitems, float fmax); + + +#ifdef __cplusplus +} +#endif + +#endif // _np_rnd_h_ + diff --git a/lib/superfox/qpc/qpc_fwht.c b/lib/superfox/qpc/qpc_fwht.c new file mode 100644 index 000000000..774112110 --- /dev/null +++ b/lib/superfox/qpc/qpc_fwht.c @@ -0,0 +1,233 @@ +// ------------------------------------------------------------------------------ +// qpc_fwht.c +// Fast Walsh-Hadamard Transforms for q-ary polar codes +// +// (c) 2024 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy +// ------------------------------------------------------------------------------ +// +// This source is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this source distribution. +// If not, see . + +#include "qpc_fwht.h" + +static void _qpc_sumdiff8_16(float* y, float* t) +{ + y[0] = t[0] + t[16]; + y[16] = t[0] - t[16]; + + y[1] = t[1] + t[17]; + y[17] = t[1] - t[17]; + + y[2] = t[2] + t[18]; + y[18] = t[2] - t[18]; + + y[3] = t[3] + t[19]; + y[19] = t[3] - t[19]; + + y[4] = t[4] + t[20]; + y[20] = t[4] - t[20]; + + y[5] = t[5] + t[21]; + y[21] = t[5] - t[21]; + + y[6] = t[6] + t[22]; + y[22] = t[6] - t[22]; + + y[7] = t[7] + t[23]; + y[23] = t[7] - t[23]; + +} +static void _qpc_sumdiff8_32(float* y, float* t) +{ + y[0] = t[0] + t[32]; + y[32] = t[0] - t[32]; + + y[1] = t[1] + t[33]; + y[33] = t[1] - t[33]; + + y[2] = t[2] + t[34]; + y[34] = t[2] - t[34]; + + y[3] = t[3] + t[35]; + y[35] = t[3] - t[35]; + + y[4] = t[4] + t[36]; + y[36] = t[4] - t[36]; + + y[5] = t[5] + t[37]; + y[37] = t[5] - t[37]; + + y[6] = t[6] + t[38]; + y[38] = t[6] - t[38]; + + y[7] = t[7] + t[39]; + y[39] = t[7] - t[39]; + +} +static void _qpc_sumdiff8_64(float* y, float* t) +{ + y[0] = t[0] + t[64]; + y[64] = t[0] - t[64]; + + y[1] = t[1] + t[65]; + y[65] = t[1] - t[65]; + + y[2] = t[2] + t[66]; + y[66] = t[2] - t[66]; + + y[3] = t[3] + t[67]; + y[67] = t[3] - t[67]; + + y[4] = t[4] + t[68]; + y[68] = t[4] - t[68]; + + y[5] = t[5] + t[69]; + y[69] = t[5] - t[69]; + + y[6] = t[6] + t[70]; + y[70] = t[6] - t[70]; + + y[7] = t[7] + t[71]; + y[71] = t[7] - t[71]; + +} + +float* qpc_fwht8(float* y, float* x) +{ + float t[8]; + + // first stage + y[0] = x[0] + x[1]; + y[1] = x[0] - x[1]; + + y[2] = x[2] + x[3]; + y[3] = x[2] - x[3]; + + y[4] = x[4] + x[5]; + y[5] = x[4] - x[5]; + + y[6] = x[6] + x[7]; + y[7] = x[6] - x[7]; + + // second stage + t[0] = y[0] + y[2]; + t[2] = y[0] - y[2]; + + t[1] = y[1] + y[3]; + t[3] = y[1] - y[3]; + + t[4] = y[4] + y[6]; + t[6] = y[4] - y[6]; + + t[5] = y[5] + y[7]; + t[7] = y[5] - y[7]; + + // third stage + y[0] = t[0] + t[4]; + y[4] = t[0] - t[4]; + + y[1] = t[1] + t[5]; + y[5] = t[1] - t[5]; + + y[2] = t[2] + t[6]; + y[6] = t[2] - t[6]; + + y[3] = t[3] + t[7]; + y[7] = t[3] - t[7]; + + return y; +} +float* qpc_fwht16(float* y, float* x) +{ + float t[16]; + + qpc_fwht8(t, x); + qpc_fwht8(t + 8, x + 8); + + y[0] = t[0] + t[8]; + y[8] = t[0] - t[8]; + + y[1] = t[1] + t[9]; + y[9] = t[1] - t[9]; + + y[2] = t[2] + t[10]; + y[10] = t[2] - t[10]; + + y[3] = t[3] + t[11]; + y[11] = t[3] - t[11]; + + y[4] = t[4] + t[12]; + y[12] = t[4] - t[12]; + + y[5] = t[5] + t[13]; + y[13] = t[5] - t[13]; + + y[6] = t[6] + t[14]; + y[14] = t[6] - t[14]; + + y[7] = t[7] + t[15]; + y[15] = t[7] - t[15]; + + return y; + +} +float* qpc_fwht32(float* y, float* x) +{ + float t[32]; + + qpc_fwht16(t, x); + qpc_fwht16(t + 16, x + 16); + + _qpc_sumdiff8_16(y, t); + _qpc_sumdiff8_16(y + 8, t + 8); + + return y; +} +float* qpc_fwht64(float* y, float* x) +{ + float t[64]; + + qpc_fwht32(t, x); + qpc_fwht32(t + 32, x + 32); + + _qpc_sumdiff8_32(y, t); + _qpc_sumdiff8_32(y + 8, t + 8); + _qpc_sumdiff8_32(y + 16, t + 16); + _qpc_sumdiff8_32(y + 24, t + 24); + + return y; +} +float* qpc_fwht128(float* y, float* x) +{ + float t[128]; + + qpc_fwht64(t, x); + qpc_fwht64(t + 64, x + 64); + + _qpc_sumdiff8_64(y, t); + _qpc_sumdiff8_64(y + 8, t + 8); + _qpc_sumdiff8_64(y + 16, t + 16); + _qpc_sumdiff8_64(y + 24, t + 24); + _qpc_sumdiff8_64(y + 32, t + 32); + _qpc_sumdiff8_64(y + 40, t + 40); + _qpc_sumdiff8_64(y + 48, t + 48); + _qpc_sumdiff8_64(y + 56, t + 56); + + return y; +} + +// functions over pdfs used by the decoder ----------------------------------------------------------- + + + + diff --git a/lib/superfox/qpc/qpc_fwht.h b/lib/superfox/qpc/qpc_fwht.h new file mode 100644 index 000000000..30477a331 --- /dev/null +++ b/lib/superfox/qpc/qpc_fwht.h @@ -0,0 +1,57 @@ +// ------------------------------------------------------------------------------ +// qpc_fwht.h +// Fast Walsh-Hadamard Transforms for q-ary polar codes +// +// (c) 2024 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy +// ------------------------------------------------------------------------------ +// +// This source is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this source distribution. +// If not, see . + +#ifndef _qpc_fwht_h_ +#define _qpc_fwht_h_ + +#include "np_qpc.h" + +#if QPC_LOG2Q==7 +#define qpc_fwht(a,b) qpc_fwht128(a,b) +#endif +#if QPC_LOG2Q==6 +#define qpc_fwht(a,b) qpc_fwht64(a,b) +#endif +#if QPC_LOG2Q==5 +#define qpc_fwht(a,b) qpc_fwht32(a,b) +#endif +#if QPC_LOG2Q==4 +#define qpc_fwht(a,b) qpc_fwht16(a,b) +#endif +// Note that it makes no sense to use fast convolutions +// for transforms that are less than 16 symbols in size. +// For such cases direct convolutions are faster. +#if QPC_LOG2Q==3 +#define qpc_fwht(a,b) qpc_fwht8(a,b) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + float* qpc_fwht8(float* y, float* x); + float* qpc_fwht16(float* y, float* x); + float* qpc_fwht32(float* y, float* x); + float* qpc_fwht64(float* y, float* x); + float* qpc_fwht128(float* y, float* x); +#ifdef __cplusplus +} +#endif + +#endif // _qpc_fwht_h_ diff --git a/lib/superfox/qpc/qpc_main.cpp b/lib/superfox/qpc/qpc_main.cpp new file mode 100644 index 000000000..8e6c59556 --- /dev/null +++ b/lib/superfox/qpc/qpc_main.cpp @@ -0,0 +1,198 @@ + +// ------------------------------------------------------------------------------ +// qpc_main.cpp +// +// Test the WER performance of the QPC (127,50) Q=128 code +// +// (c) 2024 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy +// ------------------------------------------------------------------------------ + +#include +#include +#include + +#include "dbgprintf.h" +#include "qpc_fwht.h" +#include "np_qpc.h" + +// for random numbers generation and NCFSK Rayleigh channel simulation +#include "np_rnd.h" +void qpc_channel(float* yout, unsigned char* y, float EsNo) +{ + // compute channel outputs amplitudes + + int k; + + // generate noise samples ----------------------------------------- + float sigman = 1.0f / sqrtf(2.0f); + np_normrnd_cpx(yout, QPC_N * QPC_Q, 0, sigman); + + // dbgprintf_rows_float("yout", yout, QPC_Q*2, QPC_N); + + // generate rayleigh distributed signal amplitudes ----------------- + float symamps[QPC_N * 2]; + np_normrnd_cpx(symamps, QPC_N, 0, sigman); + + // dbgprintf_vector_float("symamps", symamps, QPC_N*2); + + // normalize signal amps to unity ---------------------------------- + float pwr = 0.0f; + float* psymamps = symamps; + // compute sig power + for (k = 0; k < QPC_N; k++) { + pwr += psymamps[0] * psymamps[0] + psymamps[1] * psymamps[1]; + psymamps += 2; + } + pwr = pwr / QPC_N; + + // normalize to avg EsNo + float norm = sqrtf(EsNo/pwr); + psymamps = symamps; + for (k = 0; k < QPC_N; k++) { + psymamps[0] *= norm; + psymamps[1] *= norm; + psymamps += 2; + } + // dbgprintf_vector_float("symamps norm", symamps, QPC_N * 2); + + // add signal amplitudes to noise ----------------------------------- + float *pyout = yout; + psymamps= symamps; + for (k = 0; k < QPC_N; k++) { + pyout[y[k]<<1] += psymamps[0]; + pyout[(y[k]<<1)+1] += psymamps[1]; + pyout += (QPC_Q*2); + psymamps += 2; + } + + // dbgprintf_rows_float("s+n out", yout, QPC_Q * 2, QPC_N); + + return; +} +void qpc_likelihoods(float* py, float* yout, float EsNo, float No) +{ + // compute symbols likelihoods + // (rayleigh channel) + + int k, j; + + float norm = EsNo / (EsNo + 1) / No; + + // compute likelihoods from energies ---------------------------- + float* pybase; + float* ppyout = yout; + float normpwr; + float normpwrmax; + float pynorm; + + for (k = 0; k < QPC_N; k++) { + + // compute loglikelihoods and largest one from energies + pybase = py + k * QPC_Q; + normpwrmax = 0.0f; + for (j = 0; j < QPC_Q; j++) { + normpwr = norm * (ppyout[0] * ppyout[0] + ppyout[1] * ppyout[1]); + pybase[j] = normpwr; + if (normpwr > normpwrmax) + normpwrmax = normpwr; + ppyout += 2; + } + // subtract largest exponent + pynorm = 0.0f; + for (j = 0; j < QPC_Q; j++) { + pybase[j] = expf(pybase[j] - normpwrmax); + pynorm += pybase[j]; + } + // normalize to probabilities + pynorm = 1.0f / pynorm; + for (j = 0; j < QPC_Q; j++) + pybase[j] = pybase[j] * pynorm; + + } + + return; +} + +int main() +{ + int k; + + int NT = 20000; // number of transmissions to simultate + float EbNodB = 3.7f; // Eb/No to simulate + + + int kc = qpccode.k; + int np = qpccode.np; + float Rc = 1.0f * kc / np; + float Tm = 10.0f; // message duration + float Rb = 1.0f / Tm * (QPC_K - 2) * QPC_LOG2Q; // Bit rate assuming two symbols for CRC + + float EsNodB = EbNodB + 10.0f * log10f(Rc * QPC_LOG2Q); + + static unsigned char xin[QPC_K]; // input information symbols + static unsigned char xdec[QPC_K]; // decoded information symbols + static unsigned char y[QPC_N]; // encoded codeword + static unsigned char ydec[QPC_N]; // decoded codeword + + static float yout[QPC_N * QPC_Q * 2]; // channel complex amplitutes output + static float py[QPC_N * QPC_Q]; // channel output probabilities + + float EsNo = powf(10.0f, EsNodB / 10.0f); + float No = 1.0f; + + //JHT static float we; + static float wer; + + int kk; + + printf("QPC Word Error Rate Test\n"); + printf("Polar Code (%d,%d) Q=%d for the Rayleigh channel\n", qpccode.np, qpccode.k, qpccode.q); + printf("Eb/No = %.1f dB\n", EbNodB); + printf("Es/No = %.1f dB\n", EsNodB); + printf("Tmsg = %.1f s\n", Tm); + printf("Bit Rate = %.1f bit/s\n", Rb); + printf("SNR(2500) = %.1f dB\n\n", EbNodB + 10.0f * log10f(Rb/ 2500)); + + + for (k = 0; k < NT; k++) { + + np_unidrnd_uc(xin, QPC_K, QPC_Q); // generate random information symbols + + qpc_encode(y, xin); // encode + + // compute channel outputs and probabilities + + // Note that if the code is punctured (i.e. N=127) + // the first codeword symbol must not be transmitted over the channel + // Here, in order to avoid useless copies, + // the vector of likelihoods py continues to be a QPC_N*QPC_Q vector of floats. + // The first received symbol likelihoods should start always at offset QPC_Q. + // The first QPC_Q positions of py will be ignored (and set) by the decoder. + qpc_channel(yout, y, EsNo); + + // The decoder can't estimate the EsNo easily + // so we assume that in any case it is 5 dB + float EsNoDec = 3.16; + qpc_likelihoods(py, yout, EsNoDec, No); + + qpc_decode(xdec, ydec, py); // decode + + // count words in errors + for (kk = 0; kk < QPC_K; kk++) + if (xin[kk] ^ xdec[kk]) { + wer += 1; + break; + } + + // show the decoder performance every 200 codewords transmissions + if ((k % 200) == 0 && k>0) { + printf("k=%6d/%6d wer = %8.2E\n", k, NT, wer / k); + fflush(stdout); + } + + } + // show the final result + printf("k=%6d/%6d wer = %8.2E\n", k, NT, wer / k); + printf("\nAll done.\n"); + return 0; +} diff --git a/lib/superfox/qpc/qpc_mod.f90 b/lib/superfox/qpc/qpc_mod.f90 new file mode 100644 index 000000000..931e5b3d1 --- /dev/null +++ b/lib/superfox/qpc/qpc_mod.f90 @@ -0,0 +1,38 @@ +module qpc_mod + interface + integer(c_int32_t) function nhash2 (xin, length, initval) bind(C, & + name="nhash2") + use iso_c_binding, only: c_signed_char, c_int64_t, c_int32_t + integer(c_int64_t), intent(in), value :: length + integer(c_signed_char), intent(in) :: xin(length) + integer(c_int32_t), intent(in), value :: initval + end function nhash2 + end interface + + interface + subroutine qpc_encode(y, xin) bind(C,name="qpc_encode") + use iso_c_binding, only: c_ptr, c_signed_char + integer(c_signed_char), intent(out) :: y(127) + integer(c_signed_char), intent(in) :: xin(50) + end subroutine qpc_encode + end interface + + interface + subroutine qpc_channel(yout, y, EsNo) bind(C,name="qpc_channel") + use iso_c_binding, only: c_float_complex, c_float, c_signed_char + complex(c_float_complex), intent(out) :: yout(128,128) + integer(c_signed_char), intent(out) :: y(127) + real(c_float), intent(in), value :: EsNo + end subroutine qpc_channel + end interface + + interface + subroutine qpc_decode(xdec, ydec, py) bind(C,name="qpc_decode") + use iso_c_binding, only: c_float, c_signed_char + real(c_float), intent(in) :: py(128,128) + integer(c_signed_char), intent(out) :: ydec(127) + integer(c_signed_char), intent(out) :: xdec(50) + end subroutine qpc_decode + end interface + +end module qpc_mod diff --git a/lib/superfox/qpc/qpc_n127k50q128.c b/lib/superfox/qpc/qpc_n127k50q128.c new file mode 100644 index 000000000..3897cc833 --- /dev/null +++ b/lib/superfox/qpc/qpc_n127k50q128.c @@ -0,0 +1,60 @@ +// ------------------------------------------------------------------------------ +// qpc_n127k50q128.c +// +// Defines the parameters of the q-ary polar code (127,50) Q=128 +// for WSJT-X +// ------------------------------------------------------------------------------ +// (c) 2024 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy +// ------------------------------------------------------------------------------ +// +// WARNING: +// This source is NOT free software and it is licensed only for use with WSJT-X. +// No derived work is authorized to use this code without the written +// permission of his author (Nico Palermo / IV3NWV) who owns all the rights +// of this IP (intellectual property). +// +// This file is distributed ONLY for the purpose of documenting and making of +// public domain the encoding scheme used in WSJT-X so that the transmitted +// messages can be decoded by anybody. +// Anyway this does not imply that one could use the following tables in a +// derived work without an explicit and written authorization from his author. +// Any unauthorized use, as for any intellectual property, is simply +// illegal. +// ------------------------------------------------------------------------------- +#include "np_qpc.h" +qpccode_ds qpccode = { + 128, //n + 127, //np + 50, //k + 128, //q +{ + 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 16, 32, 17, 18, 64, 20, + 33, 34, 24, 7, 11, 36, 13, 19, 14, 65, 40, 21, 66, 22, 35, 68, + 25, 48, 37, 26, 72, 15, 38, 28, 41, 67, 23, 80, 42, 69, 49, 96, + 44, 27, 70, 50, 73, 39, 29, 52, 74, 30, 56, 81, 76, 43, 82, 84, + 97, 45, 71, 88, 98, 46, 100, 51, 104, 53, 75, 112, 54, 57, 99, 119, + 92, 77, 58, 117, 59, 83, 106, 31, 85, 108, 115, 116, 122, 125, 124, 91, + 61, 90, 89, 111, 78, 93, 94, 126, 86, 107, 110, 118, 121, 62, 120, 87, +105, 55, 114, 60, 127, 63, 103, 101, 123, 95, 102, 47, 109, 79, 113, 0 +}, +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}, +{ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +} +}; diff --git a/lib/superfox/qpc/qpc_subs.c b/lib/superfox/qpc/qpc_subs.c new file mode 100644 index 000000000..ec5f8e446 --- /dev/null +++ b/lib/superfox/qpc/qpc_subs.c @@ -0,0 +1,115 @@ +// ------------------------------------------------------------------------------ +// qpc_test.cpp +// +// Test the WER performance of the QPC (127,50) Q=128 code +// +// (c) 2024 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy +// ------------------------------------------------------------------------------ + +#include +#include +#include +#include + +#include "dbgprintf.h" +#include "qpc_fwht.h" +#include "np_qpc.h" + +#include "nhash2.h" + +// for random numbers generation and NCFSK Rayleigh channel simulation +#include "np_rnd.h" +void qpc_channel(float* yout, unsigned char* y, float EsNo) +{ + // compute channel outputs amplitudes + + int k; + + // generate noise samples ----------------------------------------- + float sigman = 1.0f / sqrtf(2.0f); + np_normrnd_cpx(yout, QPC_N * QPC_Q, 0, sigman); + + // dbgprintf_rows_float("yout", yout, QPC_Q*2, QPC_N); + + // generate rayleigh distributed signal amplitudes ----------------- + float symamps[QPC_N * 2]; + np_normrnd_cpx(symamps, QPC_N, 0, sigman); + + // dbgprintf_vector_float("symamps", symamps, QPC_N*2); + + // normalize signal amps to unity ---------------------------------- + float pwr = 0.0f; + float* psymamps = symamps; + // compute sig power + for (k = 0; k < QPC_N; k++) { + pwr += psymamps[0] * psymamps[0] + psymamps[1] * psymamps[1]; + psymamps += 2; + } + pwr = pwr / QPC_N; + + // normalize to avg EsNo + float norm = sqrtf(EsNo/pwr); + psymamps = symamps; + for (k = 0; k < QPC_N; k++) { + psymamps[0] *= norm; + psymamps[1] *= norm; + psymamps += 2; + } + // dbgprintf_vector_float("symamps norm", symamps, QPC_N * 2); + + // add signal amplitudes to noise ----------------------------------- + float *pyout = yout; + psymamps= symamps; + for (k = 0; k < QPC_N; k++) { + pyout[y[k]<<1] += psymamps[0]; + pyout[(y[k]<<1)+1] += psymamps[1]; + pyout += (QPC_Q*2); + psymamps += 2; + } + + // dbgprintf_rows_float("s+n out", yout, QPC_Q * 2, QPC_N); + + return; +} +void qpc_likelihoods(float* py, float* yout, float EsNo, float No) +{ + // compute symbols likelihoods + // (rayleigh channel) + + int k, j; + + float norm = EsNo / (EsNo + 1) / No; + + // compute likelihoods from energies ---------------------------- + float* pybase; + float* ppyout = yout; + float normpwr; + float normpwrmax; + float pynorm; + + for (k = 0; k < QPC_N; k++) { + + // compute loglikelihoods and largest one from energies + pybase = py + k * QPC_Q; + normpwrmax = 0.0f; + for (j = 0; j < QPC_Q; j++) { + normpwr = norm * (ppyout[0] * ppyout[0] + ppyout[1] * ppyout[1]); + pybase[j] = normpwr; + if (normpwr > normpwrmax) + normpwrmax = normpwr; + ppyout += 2; + } + // subtract largest exponent + pynorm = 0.0f; + for (j = 0; j < QPC_Q; j++) { + pybase[j] = expf(pybase[j] - normpwrmax); + pynorm += pybase[j]; + } + // normalize to probabilities + pynorm = 1.0f / pynorm; + for (j = 0; j < QPC_Q; j++) + pybase[j] = pybase[j] * pynorm; + } + + return; +} diff --git a/lib/superfox/qpc_decode2.f90 b/lib/superfox/qpc_decode2.f90 new file mode 100644 index 000000000..2a260e021 --- /dev/null +++ b/lib/superfox/qpc_decode2.f90 @@ -0,0 +1,218 @@ +subroutine qpc_decode2(c0,fsync,ftol,xdec,ndepth,dth,damp,crc_ok, & + snrsync,fbest,tbest,snr) + + use qpc_mod + + parameter(NMAX=15*12000,NFT=365,NZ=100) + complex c0(NMAX) !Signal as received + complex c(NMAX) !Signal as received + real py(0:127,0:127) !Probabilities for received synbol values + real py0(0:127,0:127) !Probabilities for strong signal + real pyd(0:127,0:127) !Dithered values for py + real s2(0:127,0:151) !Symbol spectra, including sync + real s3(0:127,0:127) !Synchronized symbol spectra + real No + integer crc_chk,crc_sent + integer*8 n47 + integer idf(NZ),idt(NZ) + integer nseed(33) + integer*1 xdec(0:49) !Decoded message + integer*1 ydec(0:127) !Decoded symbols + logical crc_ok + integer maxdither(8) + integer isync(24) !Symbol numbers for sync tones + data isync/1,2,4,7,11,16,22,29,37,39,42,43,45,48,52,57,63,70,78,80,83, & + 84,86,89/ + data n47/47/,maxdither/20,50,100,200,500,1000,2000,5000/ + data nseed/ & + 321278106, -658879006, 1239150429, -941466001, -698554454, & + 1136210962, 1633585627, 1261915021, -1134191465, -487888229, & + 2131958895, -1429290834, -1802468092, 1801346659, 1966248904, & + 402671397, -1961400750, -1567227835, 1895670987, -286583128, & + -595933665, -1699285543, 1518291336, 1338407128, 838354404, & + -2081343776, -1449416716, 1236537391, -133197638, 337355509, & + -460640480, 1592689606, 0/ + + data idf/0, 0, -1, 0, -1, 1, 0, -1, 1, -2, 0, -1, 1, -2, 2, & + 0, -1, 1, -2, 2, -3, 0, -1, 1, -2, 2, -3, 3, 0, -1, & + 1, -2, 2, -3, 3, -4, 0, -1, 1, -2, 2, -3, 3, -4, 4, & + 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, -1, 1, -2, 2, -3, & + 3, -4, 4, -5, 1, -2, 2, -3, 3, -4, 4, -5, -2, 2, -3, & + 3, -4, 4, -5, 2, -3, 3, -4, 4, -5, -3, 3, -4, 4, -5, & + 3, -4, 4, -5, -4, 4, -5, 4, -5, -5/ + data idt/0 , -1, 0, 1, -1, 0, -2, 1, -1, 0, 2, -2, 1, -1, 0, & + -3, 2, -2, 1, -1, 0, 3, -3, 2, -2, 1, -1, 0, -4, 3, & + -3, 2, -2, 1, -1, 0, 4, -4, 3, -3, 2, -2, 1, -1, 0, & + -5, 4, -4, 3, -3, 2, -2, 1, -1, 0, -5, 4, -4, 3, -3, & + 2, -2, 1, -1, -5, 4, -4, 3, -3, 2, -2, 1, -5, 4, -4, & + 3, -3, 2, -2, -5, 4, -4, 3, -3, 2, -5, 4, -4, 3, -3, & + -5, 4, -4, 3, -5, 4, -4, -5, 4, -5/ + + + fsample=12000.0 + baud=12000.0/1024.0 + nstype=1 + n47=47 + mask21=2**21 - 1 + crc_ok=.false. + + call qpc_sync(c0,fsample,isync,fsync,ftol,f2,t2,snrsync) + f00=1500.0 + f2 + t00=t2 + fbest=f00 + tbest=t00 + maxd=1 + if(ndepth.gt.0) maxd=maxdither(ndepth) + maxft=NZ + if(snrsync.lt.4.0 .or. ndepth.le.0) maxft=1 + do idith=1,maxft + if(idith.ge.2) maxd=1 + deltaf=idf(idith)*0.5 + deltat=idt(idith)*8.0/1024.0 + f=f00+deltaf + t=t00+deltat + fshift=1500.0 - (f+baud) !Shift frequencies down by f + 1 bin + call twkfreq2(c0,c,NMAX,fsample,fshift) + a=1.0 + b=0.0 + do kk=1,4 + if(kk.eq.2) b=0.4 + if(kk.eq.3) b=0.5 + if(kk.eq.4) b=0.6 + call sfox_demod(c,1500.0,t,isync,s2,s3) !Compute s2 and s3 + + if(b.gt.0.0) then + do j=0,127 + call smo121a(s3(:,j),128,a,b) + enddo + endif + call pctile(s3,128*128,50,base3) + s3=s3/base3 + + EsNoDec=3.16 + No=1. + py0=s3 + call qpc_likelihoods2(py,s3,EsNoDec,No) !For weak signals + + call random_seed(put=nseed) + do kkk=1,maxd + if(kkk.eq.1) then + pyd=py0 + else + pyd=0. + if(kkk.gt.2) then + call random_number(pyd) + pyd=2.0*(pyd-0.5) + endif + where(py.gt.dth) pyd=0. !Don't perturb large likelihoods + pyd=py*(1.0 + damp*pyd) !Compute dithered likelihood + endif + do j=0,127 + ss=sum(pyd(:,j)) + if(ss.gt.0.0) then + pyd(:,j)=pyd(:,j)/ss + else + pyd(:,j)=0.0 + endif + enddo + + call qpc_decode(xdec,ydec,pyd) + xdec=xdec(49:0:-1) + crc_chk=iand(nhash2(xdec,n47,571),mask21) !Compute crc_chk + crc_sent=128*128*xdec(47) + 128*xdec(48) + xdec(49) + crc_ok=crc_chk.eq.crc_sent + + if(crc_ok) then + call qpc_snr(s3,ydec,snr) + if(snr.lt.-16.5) crc_ok=.false. +! write(61,3061) idith,kk,kkk,idf(idith),idt(idith),a,b +!3061 format(5i5,2f8.3) + return + endif + enddo !kk: dither of smoothing weights + enddo !kkk: dither of probabilities + enddo !idith: dither of frequency and time + return +end subroutine qpc_decode2 + +subroutine smo121a(x,nz,a,b) + + real x(nz) + fac=1.0/(a+2*b) + x0=x(1) + do i=2,nz-1 + x1=x(i) + x(i)=fac*(a*x(i) + b*(x0+x(i+1))) + x0=x1 + enddo + + return +end subroutine smo121a + +subroutine remove_tone(c0,npts) + + parameter (NFILT=8000) + complex c0(npts) + complex cwindow(15*12000) + complex cref(npts) + complex cfilt(npts) + real window(-NFILT/2:NFILT/2) +! real endcorrection(NFILT/2+1) + real s(npts/4) + integer ipk(1) + logical first + data first/.true./ + save cwindow,first,pi + + if(first) then + pi=4.0*atan(1.0) + fac=1.0/float(npts) + sumw=0.0 + do j=-NFILT/2,NFILT/2 + window(j)=cos(pi*j/NFILT)**2 + sumw=sumw+window(j) + enddo + cwindow=0. + cwindow(1:NFILT+1)=window/sumw + cwindow=cshift(cwindow,NFILT/2+1) + call four2a(cwindow,npts,1,-1,1) + cwindow=cwindow*fac ! frequency domain smoothing filter + first=.false. + endif + + fsample=12000.0 + df=fsample/npts + fac=1.0/npts + cfilt=fac*c0 + call four2a(cfilt,npts,1,-1,1) ! fourier transform of input data + iz=npts/4 + do i=1,iz + s(i)=real(cfilt(i))**2 + aimag(cfilt(i))**2 + enddo + + ia=nint(700.0/df) + ib=nint(800.0/df) + ipk=maxloc(s(ia:ib)) + i0=ipk(1) + ia - 1 + f2=df*i0 + write(*,*) 'remove_tone - frequency: ',f2 + + dt=1.0/fsample + do i=1, npts + arg=2*pi*f2*i*dt + cref(i)=cmplx(cos(arg),sin(arg)) + enddo + cfilt=c0*conjg(cref) ! baseband to be filtered + call four2a(cfilt,npts,1,-1,1) + cfilt=cfilt*cwindow + call four2a(cfilt,npts,1,1,1) + + nframe=50*3456 + do i=1,nframe + cref(i)=cfilt(i)*cref(i) + c0(i)=c0(i)-cref(i) + enddo + + return + +end subroutine remove_tone diff --git a/lib/superfox/qpc_likelihoods2.f90 b/lib/superfox/qpc_likelihoods2.f90 new file mode 100644 index 000000000..70a631814 --- /dev/null +++ b/lib/superfox/qpc_likelihoods2.f90 @@ -0,0 +1,28 @@ +subroutine qpc_likelihoods2(py,s3,EsNo,No) + + integer QQ,QN + parameter(QQ=128,QN=128) + real py(0:QQ-1,0:QN-1) + real s3(0:QQ-1,0:QN-1) + real No,norm,normpwr,normpwrmax + + norm=(EsNo/(EsNo+1.0))/No + +! Compute likelihoods for symbol values, from the symbol power spectra + do k=0,QN-1 + normpwrmax=0. + do j=0,QQ-1 + normpwr=norm*s3(j,k) + py(j,k)=normpwr + normpwrmax=max(normpwr,normpwrmax) + enddo + pynorm=0. + do j=0,QQ-1 + py(j,k)=exp(py(j,k)-normpwrmax) + pynorm=pynorm + py(j,k) + enddo + py(0:QQ-1,k)=py(0:QQ-1,k)/pynorm !Normalize to probabilities + enddo + + return +end subroutine qpc_likelihoods2 diff --git a/lib/superfox/qpc_snr.f90 b/lib/superfox/qpc_snr.f90 new file mode 100644 index 000000000..b2c807f0e --- /dev/null +++ b/lib/superfox/qpc_snr.f90 @@ -0,0 +1,17 @@ +subroutine qpc_snr(s3,y,snr) + + use qpc_mod +! real s2(0:NQ-1,0:151) !Symbol spectra, including sync + real s3(0:127,0:127) !Synchronized symbol spectra + integer*1 y(0:127) !Encoded symbols +! integer isync(24) + + p=0. + do j=1,127 + i=y(j) + p=p + s3(i,j) + enddo + snr=db(p/127.0) - db(127.0) - 4.0 + + return +end subroutine qpc_snr diff --git a/lib/superfox/qpc_sync.f90 b/lib/superfox/qpc_sync.f90 new file mode 100644 index 000000000..ecccc692f --- /dev/null +++ b/lib/superfox/qpc_sync.f90 @@ -0,0 +1,101 @@ +subroutine qpc_sync(crcvd0,fsample,isync,fsync,ftol,f2,t2,snrsync) + + parameter(N9SEC=9*12000,NMAX=15*12000,NDOWN=16,NZ=N9SEC/NDOWN) + complex crcvd0(NMAX) !Signal as received + complex c0(0:N9SEC-1) !For long FFT + complex c1(0:NZ-1) + complex c1sum(0:NZ-1) + complex z + real s(N9SEC/4) + real p(-1125:1125) + integer ipk(1) + integer isync(24) + + baud=12000.0/1024.0 + df2=fsample/N9SEC + fac=1.0/N9SEC + c0=fac*crcvd0(1:N9SEC) + call four2a(c0,N9SEC,1,-1,1) !Forward c2c FFT + iz=N9SEC/4 + do i=1,iz + s(i)=real(c0(i))**2 + aimag(c0(i))**2 + enddo + + do i=1,4 !Smooth the spectrum a bit + call smo121(s,iz) + enddo + + ia=nint((fsync-ftol)/df2) + ib=nint((fsync+ftol)/df2) + ipk=maxloc(s(ia:ib)) + i0=ipk(1) + ia - 1 + f2=df2*i0-750.0 ! f2 is the offset from nominal 750 Hz. + ia=nint(i0-baud/df2) + ib=nint(i0+baud/df2) + s1=0.0 + s0=0.0 + do i=ia,ib + s0=s0+s(i) + s1=s1+(i-i0)*s(i) + enddo + delta=s1/s0 + i0=nint(i0+delta) + f2=i0*df2-750.0 + + c1=0. + ia=nint(i0-baud/df2) + ib=nint(i0+baud/df2) + do i=ia,ib + j=i-i0 + if(j.ge.0) c1(j)=c0(i) + if(j.lt.0) c1(j+NZ)=c0(i) + enddo + call four2a(c1,NZ,1,1,1) !Reverse c2c FFT: back to time domain + + c1sum(0)=c1(0) + do i=1,NZ-1 + c1sum(i)=c1sum(i-1) + c1(i) + enddo + + nspsd=1024/NDOWN + dt=NDOWN/12000.0 + lagmax=1.5/dt + i0=nint(0.5*fsample/NDOWN) !Nominal start time is 0.5 s + pmax=0. + lagpk=0 + do lag=-lagmax,lagmax + sp=0. + do j=1,24 + i1=i0 + (isync(j)-1)*nspsd + lag + i2=i1 + nspsd + if(i1.lt.0 .or. i1.gt.NZ-1) cycle + if(i2.lt.0 .or. i2.gt.NZ-1) cycle + z=c1sum(i2)-c1sum(i1) + sp=sp + real(z)**2 + aimag(z)**2 + enddo + if(sp.gt.pmax) then + pmax=sp + lagpk=lag + endif + p(lag)=sp + enddo + + t2=lagpk*dt + snrsync=0. + sp=0. + sq=0. + nsum=0 + tsym=1024/12000.0 + do lag=-lagmax,lagmax + t=(lag-lagpk)*dt + if(abs(t).lt.tsym) cycle + nsum=nsum+1 + sp=sp + p(lag) + sq=sq + p(lag)*p(lag) + enddo + ave=sp/nsum + rms=sqrt(sq/nsum-ave*ave) + snrsync=(pmax-ave)/rms + + return +end subroutine qpc_sync diff --git a/lib/superfox/sfox_ana.f90 b/lib/superfox/sfox_ana.f90 new file mode 100644 index 000000000..bc9ea6cf1 --- /dev/null +++ b/lib/superfox/sfox_ana.f90 @@ -0,0 +1,17 @@ +subroutine sfox_ana(dd,npts,c0,npts2) + + real dd(npts) !Raw data at 12000 Hz + complex c0(0:npts2-1) !Complex data at 12000 Hz + save + + nfft1=npts + nfft2=nfft1 + fac=2.0/(32767.0*nfft1) + c0(0:npts-1)=fac*dd(1:npts) + call four2a(c0,nfft1,1,-1,1) !Forward c2c FFT + c0(nfft2/2+1:nfft2-1)=0. !Remove negative frequencies + c0(0)=0.5*c0(0) !Scale the DC term to 1/2 + call four2a(c0,nfft2,1,1,1) !Inverse c2c FFT; c0 is analytic sig + + return +end subroutine sfox_ana diff --git a/lib/superfox/sfox_demod.f90 b/lib/superfox/sfox_demod.f90 new file mode 100644 index 000000000..cbb11df71 --- /dev/null +++ b/lib/superfox/sfox_demod.f90 @@ -0,0 +1,79 @@ +subroutine sfox_demod(crcvd,f,t,isync,s2,s3) + + use sfox_mod + complex crcvd(NMAX) !Signal as received + complex c(0:NSPS-1) !Work array, one symbol long + real s2(0:NQ-1,0:151) !Symbol spectra, including sync + real s3(0:NQ-1,0:NN) !Synchronized symbol spectra + integer isync(24) + integer ipk(1) + integer hist1(0:NQ-1),hist2(0:NQ-1) + + j0=nint(12000.0*(t+0.5)) + df=12000.0/NSPS + i0=nint(f/df)-NQ/2 + k2=0 + s2(:,0)=0. !The punctured symbol + s3(:,0)=0. !The punctured symbol + + do n=1,NDS !Loop over all symbols + jb=n*NSPS + j0 + ja=jb-NSPS+1 + k2=k2+1 + if(ja.lt.1 .or. jb.gt.NMAX) cycle + c=crcvd(ja:jb) + call four2a(c,NSPS,1,-1,1) !Compute symbol spectrum + do i=0,NQ-1 + s2(i,k2)=real(c(i0+i))**2 + aimag(c(i0+i))**2 + enddo + enddo + + + call pctile(s2,NQ*151,50,base2) + s2=s2/base2 + + hist1=0 + hist2=0 + do j=0,151 + ipk=maxloc(s2(1:NQ-1,j)) !Find the spectral peak + i=ipk(1)-1 + hist1(i)=hist1(i)+1 + enddo + + hist1(0)=0 !Don't treat sync tone as a birdie + do i=0,123 + hist2(i)=sum(hist1(i:i+3)) + enddo + + ipk=maxloc(hist1) + i1=ipk(1)-1 + m1=maxval(hist1) + ipk=maxloc(hist2) + i2=ipk(1)-1 + m2=maxval(hist2) + if(m1.gt.12) then + do i=0,127 + if(hist1(i).gt.12) then + s2(i,:)=1.0 + endif + enddo + endif + + if(m2.gt.20) then + if(i2.ge.1) i2=i2-1 + if(i2.gt.120) i2=120 + s2(i2:i2+7,:)=1.0 + endif + + k3=0 + do n=1,NDS !Copy symbol spectra from s2 into s3 + if(any(isync(1:NS).eq.n)) cycle !Skip the sync symbols + k3=k3+1 + s3(:,k3)=s2(:,n) + enddo + + call pctile(s3,NQ*NN,50,base3) + s3=s3/base3 + + return +end subroutine sfox_demod diff --git a/lib/superfox/sfox_mod.f90 b/lib/superfox/sfox_mod.f90 new file mode 100644 index 000000000..01fc06f26 --- /dev/null +++ b/lib/superfox/sfox_mod.f90 @@ -0,0 +1,73 @@ +module sfox_mod + + parameter (NMAX=15*12000) !Samples in iwave (180,000) + integer MM,NQ,NN,KK,NS,NDS,NFZ,NSPS,NSYNC,NZ,NFFT1 + real baud,tsym,bw + +contains + subroutine sfox_init(mm0,nn0,kk0,itu,fspread,delay,fsample,ns0) + + character*2 itu + integer isps(54) + integer iloc(1) + data isps/ 896, 960, 972, 980,1000,1008,1024,1029,1050,1080, & + 1120,1125,1134,1152,1176,1200,1215,1225,1250,1260, & + 1280,1296,1323,1344,1350,1372,1400,1440,1458,1470, & + 1500,1512,1536,1568,1575,1600,1620,1680,1701,1715, & + 1728,1750,1764,1792,1800,1875,1890,1920,1944,1960, & + 2000,2016,2025,2048/ + + MM=mm0 !Bits per symbol + NQ=2**MM !Q, number of MFSK tones + NN=nn0 !Codeword length + KK=kk0 !Number of information symbols + NS=ns0 !Number of sync symbols + NDS=NN+NS !Total number of channel symbols + NFZ=3 !First zero + + jsps=nint(12.8*fsample/NDS) + iloc=minloc(abs(isps-jsps)) + NSPS=isps(iloc(1)) !Samples per symbol + NSYNC=NS*NSPS !Samples in sync waveform + NZ=NSPS*NDS !Samples in full Tx waveform + NFFT1=2*NSPS !Length of FFTs for symbol spectra + + baud=fsample/NSPS + tsym=1.0/baud + bw=NQ*baud + + fspread=0.0 + delay=0.0 + if(itu.eq.'LQ') then + fspread=0.5 + delay=0.5 + else if(itu.eq.'LM') then + fspread=1.5 + delay=2.0 + else if(itu.eq.'LD') then + fspread=10.0 + delay=6.0 + else if(itu.eq.'MQ') then + fspread=0.1 + delay=0.5 + else if(itu.eq.'MM') then + fspread=0.5 + delay=1.0 + else if(itu.eq.'MD') then + fspread=1.0 + delay=2.0 + else if(itu.eq.'HQ') then + fspread=0.5 + delay=1.0 + else if(itu.eq.'HM') then + fspread=10.0 + delay=3.0 + else if(itu.eq.'HD') then + fspread=30.0 + delay=7.0 + endif + + return + end subroutine sfox_init + +end module sfox_mod diff --git a/lib/superfox/sfox_prob.f90 b/lib/superfox/sfox_prob.f90 new file mode 100644 index 000000000..c8e9f25c9 --- /dev/null +++ b/lib/superfox/sfox_prob.f90 @@ -0,0 +1,55 @@ +subroutine sfox_prob(s3,rxdat,rxprob,rxdat2,rxprob2) + +! Demodulate the 64-bin spectra for each of 63 symbols in a frame. + +! Parameters +! rxdat most reliable symbol value +! rxdat2 second most likely symbol value +! rxprob probability that rxdat was the transmitted value +! rxprob2 probability that rxdat2 was the transmitted value + + use sfox_mod + implicit real*8 (a-h,o-z) + real*4 s3(0:NQ-1,0:NN-1) + integer rxdat(0:NN-1),rxprob(0:NN-1),rxdat2(0:NN-1),rxprob2(0:NN-1) + + afac=1.1 +! scale=255.999 + scale=2047.999 + +! Compute average spectral value + ave=sum(s3)/(NQ*ND) + i1=1 !Silence warning + i2=1 + +! Compute probabilities for most reliable symbol values + do j=0,NN-1 !Loop over all symbols + s1=-1.e30 + psum=0. + do i=0,NQ-1 !Loop over frequency bins + x=min(afac*s3(i,j)/ave,50.d0) + psum=psum+s3(i,j) + if(s3(i,j).gt.s1) then + s1=s3(i,j) !Find max signal+noise power + i1=i !Find most reliable symbol value + endif + enddo + if(psum.eq.0.0) psum=1.e-6 !Guard against zero signal+noise + + s2=-1.e30 + do i=0,NQ-1 + if(i.ne.i1 .and. s3(i,j).gt.s2) then + s2=s3(i,j) !Second largest signal+noise power + i2=i !Bin number for second largest power + endif + enddo + p1=s1/psum !p1, p2 are symbol metrics for ftrsd + p2=s2/psum + rxdat(j)=i1 + rxdat2(j)=i2 + rxprob(j)=scale*p1 !Scaled probabilities, 0 - 255 + rxprob2(j)=scale*p2 + enddo + + return +end subroutine sfox_prob diff --git a/lib/superfox/sfox_remove_ft8.f90 b/lib/superfox/sfox_remove_ft8.f90 new file mode 100644 index 000000000..ce47ac008 --- /dev/null +++ b/lib/superfox/sfox_remove_ft8.f90 @@ -0,0 +1,229 @@ +subroutine sfox_remove_ft8(dd,npts) + use packjt77 + include 'ft8_params.f90' + parameter (MAXCAND=100) + parameter (NP2=2812) + integer itone(NN) + integer iloc(1), ip(1) + integer icos7(0:6) + integer graymap(0:7) + integer*1 cw(174),apmask(174) + integer*1 message91(91) + integer*1 message77(77) + + character*77 c77 + character*37 msg37 + + real s8(0:7,NN) + real s2(0:7) + real ss(9) + real candidate(3,MAXCAND) + real sbase(NH1) + real dd(npts) + real bmeta(174),bmetd(174) + real llra(174),llrd(174) + complex cd0(0:3199) + complex csymb(32) + complex ctwk(32) + complex cs(0:7,NN) + data icos7/3,1,4,0,6,5,2/ + logical first,newdat + logical one(0:7,0:2) + logical unpk77_success + data first/.true./ + data graymap/0,1,3,2,5,6,4,7/ + save one,first,twopi + + if(first) then + one=.false. + do i=0,7 + do j=0,2 + if(iand(i,2**j).ne.0) one(i,j)=.true. + enddo + enddo + first=.false. + twopi=8.0*atan(1.0) + endif + + fs2=12000.0/NDOWN + dt2=1.0/fs2 + nfa=500 + nfb=2500 + syncmin=1.5 + nfqso=750 + call sync8(dd,npts,nfa,nfb,syncmin,nfqso,MAXCAND,candidate,ncand,sbase) + newdat=.true. + do icand=1,ncand + f1=candidate(1,icand) + xdt=candidate(2,icand) + xbase=10.0**(0.1*(sbase(nint(f1/3.125))-40.0)) + call ft8_downsample(dd,newdat,f1,cd0) + newdat=.false. + i0=nint((xdt+0.5)*fs2) + smax=0.0 + do idt=i0-10,i0+10 + call sync8d(cd0,idt,ctwk,0,sync) + if(sync.gt.smax) then + smax=sync + ibest=idt + endif + enddo + + smax=0.0 + delfbest=0.0 + do ifr=-5,5 !Search over +/- 2.5 Hz + delf=ifr*0.5 + dphi=twopi*delf*dt2 + phi=0.0 + do i=1,32 + ctwk(i)=cmplx(cos(phi),sin(phi)) + phi=mod(phi+dphi,twopi) + enddo + call sync8d(cd0,ibest,ctwk,1,sync) + if( sync .gt. smax ) then + smax=sync + delfbest=delf + endif + enddo + + f1=f1+delfbest !Improved estimate of DF + + call ft8_downsample(dd,newdat,f1,cd0) !Mix f1 to baseband and downsample + + smax=0.0 + do idt=-4,4 !Search over +/- one quarter symbol + call sync8d(cd0,ibest+idt,ctwk,0,sync) + ss(idt+5)=sync + enddo + smax=maxval(ss) + iloc=maxloc(ss) + ibest=iloc(1)-5+ibest + xdt=(ibest-1)*dt2 + sync=smax + + do k=1,NN + i1=ibest+(k-1)*32 + csymb=cmplx(0.0,0.0) + if( i1.ge.0 .and. i1+31 .le. NP2-1 ) csymb=cd0(i1:i1+31) + call four2a(csymb,32,1,-1,1) + cs(0:7,k)=csymb(1:8)/1e3 + s8(0:7,k)=abs(csymb(1:8)) + enddo + +! sync quality check + is1=0 + is2=0 + is3=0 + do k=1,7 + ip=maxloc(s8(:,k)) + if(icos7(k-1).eq.(ip(1)-1)) is1=is1+1 + ip=maxloc(s8(:,k+36)) + if(icos7(k-1).eq.(ip(1)-1)) is2=is2+1 + ip=maxloc(s8(:,k+72)) + if(icos7(k-1).eq.(ip(1)-1)) is3=is3+1 + enddo +! hard sync sum - max is 21 + nsync=is1+is2+is3 + if(nsync .le. 6) then ! bail out + nbadcrc=1 + cycle + endif + + nsym=1 + nt=2**(3*nsym) + do ihalf=1,2 + do k=1,29,nsym + if(ihalf.eq.1) ks=k+7 + if(ihalf.eq.2) ks=k+43 + amax=-1.0 + do i=0,nt-1 + i3=iand(i,7) + s2(i)=abs(cs(graymap(i3),ks)) + enddo + i32=1+(k-1)*3+(ihalf-1)*87 + ibmax=2 + do ib=0,ibmax + bm=maxval(s2(0:nt-1),one(0:nt-1,ibmax-ib)) - & + maxval(s2(0:nt-1),.not.one(0:nt-1,ibmax-ib)) + if(i32+ib .gt.174) cycle + bmeta(i32+ib)=bm + den=max(maxval(s2(0:nt-1),one(0:nt-1,ibmax-ib)), & + maxval(s2(0:nt-1),.not.one(0:nt-1,ibmax-ib))) + if(den.gt.0.0) then + cm=bm/den + else ! erase it + cm=0.0 + endif + bmetd(i32+ib)=cm + enddo + enddo + enddo + + call normalizebmet(bmeta,174) + call normalizebmet(bmetd,174) + + scalefac=2.83 + llra=scalefac*bmeta + llrd=scalefac*bmetd + + cw=0 + dmin=0 + norder=2 + maxosd=-1 + Keff=91 + apmask=0 + message91=0 + cw=0 + call decode174_91(llra,Keff,maxosd,norder,apmask,message91,cw, & + ntype,nharderrors,dmin) + + if(nharderrors.ge.0) then + message77=message91(1:77) + else + cycle + endif + + if(count(cw.eq.0).eq.174) cycle + + write(c77,'(77i1)') message77 + read(c77(72:74),'(b3)') n3 + read(c77(75:77),'(b3)') i3 + if(i3.gt.5 .or. (i3.eq.0.and.n3.gt.6)) cycle + if(i3.eq.0 .and. n3.eq.2) cycle + + call unpack77(c77,1,msg37,unpk77_success) +! write(77,*) 'FT8 interference: ',msg37 + if(.not.unpk77_success) cycle +! Message structure: S7 D29 S7 D29 S7 + itone(1:7)=icos7 + itone(36+1:36+7)=icos7 + itone(NN-6:NN)=icos7 + k=7 + do j=1,ND + i=3*j -2 + k=k+1 + if(j.eq.30) k=k+7 + indx=cw(i)*4 + cw(i+1)*2 + cw(i+2) + itone(k)=graymap(indx) + enddo + call subtractft8(dd,itone,f1,xdt,.true.) +! return + enddo + return +end subroutine sfox_remove_ft8 + + +subroutine normalizebmet(bmet,n) + real bmet(n) + + bmetav=sum(bmet)/real(n) + bmet2av=sum(bmet*bmet)/real(n) + var=bmet2av-bmetav*bmetav + if( var .gt. 0.0 ) then + bmetsig=sqrt(var) + else + bmetsig=sqrt(bmet2av) + endif + bmet=bmet/bmetsig + return +end subroutine normalizebmet diff --git a/lib/superfox/sfox_unpack.f90 b/lib/superfox/sfox_unpack.f90 new file mode 100644 index 000000000..903e92edf --- /dev/null +++ b/lib/superfox/sfox_unpack.f90 @@ -0,0 +1,125 @@ +subroutine sfox_unpack(nutc,x,nsnr,f0,dt0,foxcall,nsignature) + + use packjt77 + integer*1 x(0:49) + integer*8 n58 + logical success + character*336 msgbits + character*22 msg(10) !### only msg(1) is used ??? ### + character*13 foxcall,c13 + character*10 ssignature + character*4 crpt(5),grid4 + character*26 freeTextMsg + character*38 c + logical use_otp + data c/' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/'/ + + ncq=0 + if (nsignature.eq.0) then + use_otp = .FALSE. + else + use_otp = .TRUE. + endif + write(msgbits,1000) x(0:46) +1000 format(47b7.7) + read(msgbits(327:329),'(b6)') i3 !Message type + read(msgbits(1:28),'(b28)') n28 !Standard Fox call + call unpack28(n28,foxcall,success) +! print*,'aa',i3,foxcall + + if(i3.eq.1) then !Compound Fox callsign +! read(msgbits(87:101),'(b15)') n15 +! call unpackgrid(n15,grid4) +! msg(1)='CQ '//trim(foxcall)//' '//grid4 +! write(*,1100) nutc,nsnr,dt0,nint(f0),trim(msg(1)) +! go to 100 + else if(i3.eq.2) then !Up to 4 Hound calls and free text + call unpacktext77(msgbits(161:231),freeTextMsg(1:13)) + call unpacktext77(msgbits(232:302),freeTextMsg(14:26)) + do i=26,1,-1 + if(freeTextMsg(i:i).ne.'.') exit + freeTextMsg(i:i)=' ' + enddo +! print*,'aa1 ',freeTextMsg + write(*,1100) nutc,nsnr,dt0,nint(f0),freeTextMsg +1100 format(i6.6,i4,f5.1,i5,1x,"~",2x,a) + else if(i3.eq.3) then !CQ FoxCall Grid + read(msgbits(1:58),'(b58)') n58 !FoxCall + do i=11,1,-1 + j=mod(n58,38)+1 + foxcall(i:i)=c(j:j) + n58=n58/38 + enddo + foxcall(12:13)=' ' + read(msgbits(59:73),'(b15)') n15 + call unpackgrid(n15,grid4) + msg(1)='CQ '//trim(foxcall)//' '//grid4 + write(*,1100) nutc,nsnr,dt0,nint(f0),trim(msg(1)) + call unpacktext77(msgbits(74:144),freeTextMsg(1:13)) + call unpacktext77(msgbits(145:215),freeTextMsg(14:26)) + do i=26,1,-1 + if(freeTextMsg(i:i).ne.'.') exit + freeTextMsg(i:i)=' ' + enddo + if(len(trim(freeTextMsg)).gt.0) write(*,1100) nutc,nsnr,dt0,& + nint(f0),freeTextMsg + go to 100 + endif + + j=281 + iz=4 !Max number of reports + if(i3.eq.2) j=141 +! print*,'aa2',j,iz + do i=1,iz !Extract the reports + read(msgbits(j:j+4),'(b5)') n + if(n.eq.31) then + crpt(i)='RR73' + else + write(crpt(i),1006) n-18 +1006 format(i3.2) + if(crpt(i)(1:1).eq.' ') crpt(i)(1:1)='+' + endif +! print*,'aa3',i,n,crpt(i) + j=j+5 + enddo + +! Unpack Hound callsigns and format user-level messages: + iz=9 !Max number of hound calls + if(i3.eq.2 .or. i3.eq.3) iz=4 +! print*,'bb',i3,iz +! print*,msgbits + do i=1,iz + j=28*i + 1 + read(msgbits(j:j+27),'(b28)') n28 + call unpack28(n28,c13,success) +! print*,'cc',i,j,n28 + if(n28.eq.0) cycle + msg(i)=trim(c13)//' '//trim(foxcall) + if(msg(i)(1:3).eq.'CQ ') then + ncq=ncq+1 + else + if(i3.eq.2) then +! print*,'dd',i,crpt(i) + msg(i)=trim(msg(i))//' '//crpt(i) + else + if(i.le.5) msg(i)=trim(msg(i))//' RR73' + if(i.gt.5) msg(i)=trim(msg(i))//' '//crpt(i-5) + endif + endif + if(ncq.le.1 .or. msg(i)(1:3).ne.'CQ ') then + write(*,1100) nutc,nsnr,dt0,nint(f0),trim(msg(i)) + endif + enddo + + if(msgbits(306:306).eq.'1' .and. ncq.lt.1) then + write(*,1100) nutc,nsnr,dt0,nint(f0),'CQ '//foxcall + endif + +100 read(msgbits(307:326),'(b20)') nsignature +! print*,'i3:',i3 + if (use_otp) then + write(ssignature,'(I0)') nsignature + write(*,1100) nutc,nsnr,dt0,nint(f0),'$VERIFY$ '//trim(foxcall)//' '//trim(ssignature) + endif + return +end subroutine sfox_unpack diff --git a/lib/superfox/sfrx.f90 b/lib/superfox/sfrx.f90 new file mode 100644 index 000000000..f73ad0d3b --- /dev/null +++ b/lib/superfox/sfrx.f90 @@ -0,0 +1,137 @@ +program sfrx + + use sfox_mod + use julian + use popen_module, only: get_command_as_string + + integer*2 iwave(NMAX) + integer ihdr(11) + integer*8 secday,ntime8,ntime8_chk + integer*1 xdec(0:49) + character*120 fname,cmnd,cresult + character*13 foxcall,foxcall_chk + character*256 ppath + complex c0(NMAX) !Complex form of signal as received + real dd(NMAX) + logical crc_ok + logical use_otp + data secday/86400/ + include 'gtag.f90' + + use_otp = .FALSE. + fsync=750.0 + ftol=100.0 + narg=iargc() + + if(narg.lt.1) then +! print*,'Usage: sfrx [fsync [ftol]] infile [...]' +! print*,'Examples: sfrx 230305_011230.wav' +! print*,' sfrx 775 10 240811_102400.wav' +! print*,'Reads one or more .wav files and calls SuperFox decoder on each.' +! print*,'Defaults: fsync=750, ftol=100' + print '(" Git tag: ",z9)',ntag + go to 999 + endif + + ifile1=1 + call getarg(1,fname) + read(fname,*,err=1) fsync + ifile1=2 + call getarg(2,fname) + read(fname,*,err=1) ftol + ifile1=3 + +1 nf=0 + nd=0 + nv=0 + + fsample=12000.0 + call sfox_init(7,127,50,'no',fspread,delay,fsample,24) + + npts=15*12000 + + do ifile=ifile1,narg + call getarg(ifile,fname) + if(fname.eq.'OTP') then + use_otp = .TRUE. + cycle + endif + open(10,file=trim(fname),status='old',access='stream',err=4) + + go to 5 +4 print*,'Cannot open file ',trim(fname) + go to 999 +5 read(10) ihdr,iwave + close(10) + + nz=len(trim(fname)) + nyymmdd=ihdr(1) + nutc=ihdr(2) + if(fname(nz-3:nz).eq.'.wav') then + read(fname(nz-16:nz-11),*) nyymmdd + read(fname(nz-9:nz-4),*) nutc + endif + if(nyymmdd.eq.-1) then + ntime8=itime8()/30 + ntime8=30*ntime8 + else + iyr=2000+nyymmdd/10000 + imo=mod(nyymmdd/100,100) + iday=mod(nyymmdd,100) + ih=nutc/10000 + im=mod(nutc/100,100) + is=mod(nutc,100) + ntime8=secday*(JD(iyr,imo,iday)-2440588) + 3600*ih + 60*im + is + endif + + dd=iwave + call sfox_remove_ft8(dd,npts) + + call sfox_ana(dd,npts,c0,npts) + + ndepth=3 + dth=0.5 + damp=1.0 + + call qpc_decode2(c0,fsync,ftol, xdec,ndepth,dth,damp,crc_ok, & + snrsync,fbest,tbest,snr) + if(crc_ok) then + nsnr=nint(snr) + if (use_otp) then + nsignature = 1 + else + nsignature = 0 + endif + + call sfox_unpack(nutc,xdec,nsnr,fbest-750.0,tbest,foxcall,nsignature) +! Execute 'foxchk ' to get the correct signature value. + write(cmnd,1102) trim(foxcall),ntime8 +1102 format('foxchk ',a,i12) + call getarg(0,ppath) + lindex=index(ppath,'sfrx')-1 + if (.not.use_otp) then + cmnd=ppath(1:lindex)//cmnd + cresult=get_command_as_string(trim(cmnd)) + read(cresult,1104) foxcall_chk,ntime8_chk,nsignature_chk +1104 format(a11,i13,i10.7) + if(nsignature.eq.nsignature_chk) write(*,1110) trim(foxcall) +1110 format(a,' verified') + if(nsignature.eq.nsignature_chk) nv=nv+1 + endif + nd=nd+1 +! else +! i0=index(fname,'.wav') +! write(60,3060) trim(fname) +!3060 format('cp ',a,' nodecode') + endif + nf=nf+1 + enddo + ncarg=narg + if (use_otp) then + ncarg=ncarg-1 + endif + + if(ncarg.gt.ifile1) write(*,1999) nf,nd,nv +1999 format('nfiles:',i5,' ndecodes:',i5,' nverified:',i5) + +999 end program sfrx diff --git a/lib/superfox/twkfreq2.f90 b/lib/superfox/twkfreq2.f90 new file mode 100644 index 000000000..3cb26f930 --- /dev/null +++ b/lib/superfox/twkfreq2.f90 @@ -0,0 +1,19 @@ +subroutine twkfreq2(c3,c4,npts,fsample,fshift) + +! Adjust frequency of complex waveform + + complex c3(npts) + complex c4(npts) + complex w,wstep + data twopi/6.283185307/ + + w=1.0 + dphi=fshift*twopi/fsample + wstep=cmplx(cos(dphi),sin(dphi)) + do i=1,npts + w=w*wstep + c4(i)=w*c3(i) + enddo + + return +end subroutine twkfreq2