WSJT-X/lib/qra/qra64/qra64.c

1116 lines
35 KiB
C
Raw Normal View History

/*
qra64.c
Encoding/decoding functions for the QRA64 mode
(c) 2016 - Nico Palermo, IV3NWV
-------------------------------------------------------------------------------
qracodes 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.
qracodes 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 qracodes source distribution.
If not, see <http://www.gnu.org/licenses/>.
-----------------------------------------------------------------------------
QRA code used in this sowftware release:
QRA13_64_64_IRR_E: K=13 N=64 Q=64 irregular QRA code (defined in
qra13_64_64_irr_e.h /.c)
Codes with K=13 are designed to include a CRC as the 13th information symbol
and improve the code UER (Undetected Error Rate).
The CRC symbol is not sent along the channel (the codes are punctured) and the
resulting code is a (12,63) code
*/
//----------------------------------------------------------------------------
#include <stdlib.h>
#include <string.h>
#include "qra64.h"
#include "../qracodes/qracodes.h"
#include "../qracodes/qra13_64_64_irr_e.h"
#include "../qracodes/pdmath.h"
#include "../qracodes/normrnd.h"
// Code parameters of the QRA64 mode
#define QRA64_CODE qra_13_64_64_irr_e
#define QRA64_NMSG 218 // Must much value indicated in QRA64_CODE.NMSG
#define QRA64_KC (QRA64_K+1) // Information symbols (crc included)
#define QRA64_NC (QRA64_N+1) // Codeword length (as defined in the code)
#define QRA64_NITER 100 // max number of iterations per decode
// static functions declarations ----------------------------------------------
static int calc_crc6(const int *x, int sz);
static void ix_mask(float *dst, const float *src, const int *mask,
const int *x);
static int qra64_decode_attempts(qra64codec *pcodec, int *xdec, const float *ix);
static int qra64_do_decode(int *x, const float *pix, const int *ap_mask,
const int *ap_x);
static float qra64_fastfading_estim_noise_std(
const float *rxen,
const float esnometric,
const int submode);
static void qra64_fastfading_intrinsics(
float *pix,
const float *rxen,
const float *hptr,
const int hlen,
const float sigma,
const float EsNoMetric,
const int submode);
static float qra64_fastfading_msg_esno(
const int *ydec,
const float *rxen,
const float sigma,
const float EsNoMetric,
const int hlen,
const int submode);
// a-priori information masks for fields in JT65-like msgs --------------------
// when defined limits the AP masks to reduce the false decode rate
#define LIMIT_AP_MASKS
#ifdef LIMIT_AP_MASKS
#define MASK_CQQRZ 0xFFFFFFC
#define MASK_CALL1 0xFFFFFFC
#define MASK_CALL2 0xFFFFFFC
#define MASK_GRIDFULL 0x3FFC
#define MASK_GRIDFULL12 0x3FFC
#define MASK_GRIDBIT 0x8000
#else
#define MASK_CQQRZ 0xFFFFFFC
#define MASK_CALL1 0xFFFFFFF
#define MASK_CALL2 0xFFFFFFF
#define MASK_GRIDFULL 0xFFFF
#define MASK_GRIDFULL12 0x3FFC
#define MASK_GRIDBIT 0x8000 // b[15] is 1 for free text, 0 otherwise
#endif
// ----------------------------------------------------------------------------
qra64codec *qra64_init(int flags)
{
// Eb/No value for which we optimize the decoder metric
const float EbNodBMetric = 2.8f;
const float EbNoMetric = (float)pow(10,EbNodBMetric/10);
const float R = 1.0f*(QRA64_KC)/(QRA64_NC);
qra64codec *pcodec = (qra64codec*)malloc(sizeof(qra64codec));
if (!pcodec)
return 0; // can't allocate memory
pcodec->decEsNoMetric = 1.0f*QRA64_m*R*EbNoMetric;
pcodec->apflags = flags;
memset(pcodec->apmsg_set,0,APTYPE_SIZE*sizeof(int));
if (flags==QRA_NOAP)
return pcodec;
// for QRA_USERAP and QRA_AUTOAP modes we always enable [CQ/QRZ ? ?] mgs look-up.
// encode CQ/QRZ AP messages
// NOTE: Here we handle only CQ and QRZ msgs.
// 'CQ nnn', 'CQ DX' and 'DE' msgs will be handled by the decoder
// as messages with no a-priori knowledge
qra64_apset(pcodec, CALL_CQ, 0, GRID_BLANK, APTYPE_CQQRZ);
// initialize masks for decoding with a-priori information
encodemsg_jt65(pcodec->apmask_cqqrz, MASK_CQQRZ, 0, MASK_GRIDBIT);
encodemsg_jt65(pcodec->apmask_cqqrz_ooo, MASK_CQQRZ, 0, MASK_GRIDFULL);
encodemsg_jt65(pcodec->apmask_call1, MASK_CALL1, 0, MASK_GRIDBIT);
encodemsg_jt65(pcodec->apmask_call1_ooo, MASK_CALL1, 0, MASK_GRIDFULL);
encodemsg_jt65(pcodec->apmask_call2, 0, MASK_CALL2, MASK_GRIDBIT);
encodemsg_jt65(pcodec->apmask_call2_ooo, 0, MASK_CALL2, MASK_GRIDFULL);
encodemsg_jt65(pcodec->apmask_call1_call2, MASK_CALL1,MASK_CALL2, MASK_GRIDBIT);
encodemsg_jt65(pcodec->apmask_call1_call2_grid,MASK_CALL1,MASK_CALL2, MASK_GRIDFULL12);
encodemsg_jt65(pcodec->apmask_cq_call2, MASK_CQQRZ, MASK_CALL2, MASK_GRIDBIT);
encodemsg_jt65(pcodec->apmask_cq_call2_ooo, MASK_CQQRZ, MASK_CALL2, MASK_GRIDFULL12);
return pcodec;
}
void qra64_close(qra64codec *pcodec)
{
free(pcodec);
}
int qra64_apset(qra64codec *pcodec, const int mycall, const int hiscall, const int grid, const int aptype)
{
// Set decoder a-priori knowledge accordingly to the type of the message to look up for
// arguments:
// pcodec = pointer to a qra64codec data structure as returned by qra64_init
// mycall = mycall to look for
// hiscall = hiscall to look for
// grid = grid to look for
// aptype = define and masks the type of AP to be set accordingly to the following:
// APTYPE_CQQRZ set [cq/qrz ? ?/blank]
// APTYPE_MYCALL set [mycall ? ?/blank]
// APTYPE_HISCALL set [? hiscall ?/blank]
// APTYPE_BOTHCALLS set [mycall hiscall ?]
// APTYPE_FULL set [mycall hiscall grid]
// APTYPE_CQHISCALL set [cq/qrz hiscall ?/blank] and [cq/qrz hiscall grid]
// returns:
// 0 on success
// -1 when qra64_init was called with the QRA_NOAP flag
// -2 invalid apytpe
if (pcodec->apflags==QRA_NOAP)
return -1;
switch (aptype) {
case APTYPE_CQQRZ:
encodemsg_jt65(pcodec->apmsg_cqqrz, CALL_CQ, 0, GRID_BLANK);
break;
case APTYPE_MYCALL:
encodemsg_jt65(pcodec->apmsg_call1, mycall, 0, GRID_BLANK);
break;
case APTYPE_HISCALL:
encodemsg_jt65(pcodec->apmsg_call2, 0, hiscall, GRID_BLANK);
break;
case APTYPE_BOTHCALLS:
encodemsg_jt65(pcodec->apmsg_call1_call2, mycall, hiscall, GRID_BLANK);
break;
case APTYPE_FULL:
encodemsg_jt65(pcodec->apmsg_call1_call2_grid, mycall, hiscall, grid);
break;
case APTYPE_CQHISCALL:
encodemsg_jt65(pcodec->apmsg_cq_call2, CALL_CQ, hiscall, GRID_BLANK);
encodemsg_jt65(pcodec->apmsg_cq_call2_grid, CALL_CQ, hiscall, grid);
break;
default:
return -2; // invalid ap type
}
pcodec->apmsg_set[aptype]=1; // signal the decoder to look-up for the specified type
return 0;
}
void qra64_apdisable(qra64codec *pcodec, const int aptype)
{
if (pcodec->apflags==QRA_NOAP)
return;
if (aptype<APTYPE_CQQRZ || aptype>=APTYPE_SIZE)
return;
pcodec->apmsg_set[aptype] = 0; // signal the decoder not to look-up to the specified type
}
void qra64_encode(qra64codec *pcodec, int *y, const int *x)
{
int encx[QRA64_KC]; // encoder input buffer
int ency[QRA64_NC]; // encoder output buffer
int hiscall,mycall,grid;
memcpy(encx,x,QRA64_K*sizeof(int)); // Copy input to encoder buffer
encx[QRA64_K]=calc_crc6(encx,QRA64_K); // Compute and add crc symbol
qra_encode(&QRA64_CODE, ency, encx); // encode msg+crc using given QRA code
// copy codeword to output puncturing the crc symbol
memcpy(y,ency,QRA64_K*sizeof(int)); // copy information symbols
memcpy(y+QRA64_K,ency+QRA64_KC,QRA64_C*sizeof(int)); // copy parity symbols
if (pcodec->apflags!=QRA_AUTOAP)
return;
// Here we handle the QRA_AUTOAP mode --------------------------------------------
// When a [hiscall mycall ?] msg is detected we instruct the decoder
// to look for [mycall hiscall ?] msgs
// otherwise when a [cq mycall ?] msg is sent we reset the APTYPE_BOTHCALLS
// look if the msg sent is a std type message (bit15 of grid field = 0)
if ((x[9]&0x80)==1)
return; // no, it's a text message, nothing to do
// It's a [hiscall mycall grid] message
// We assume that mycall is our call (but we don't check it)
// hiscall the station we are calling or a general call (CQ/QRZ/etc..)
decodemsg_jt65(&hiscall,&mycall,&grid,x);
if ((hiscall>=CALL_CQ && hiscall<=CALL_CQ999) || hiscall==CALL_CQDX ||
hiscall==CALL_DE) {
// tell the decoder to look for msgs directed to us
qra64_apset(pcodec,mycall,0,0,APTYPE_MYCALL);
// We are making a general call and don't know who might reply
// Reset APTYPE_BOTHCALLS so decoder won't look for [mycall hiscall ?] msgs
qra64_apdisable(pcodec,APTYPE_BOTHCALLS);
} else {
// We are replying to someone named hiscall
// Set APTYPE_BOTHCALLS so decoder will try for [mycall hiscall ?] msgs
qra64_apset(pcodec,mycall, hiscall, GRID_BLANK, APTYPE_BOTHCALLS);
}
}
#define EBNO_MIN -10.0f // minimum Eb/No value returned by the decoder (in dB)
// AWGN metric decoder
int qra64_decode(qra64codec *pcodec, float *ebno, int *x, const float *rxen)
{
int k;
float *srctmp, *dsttmp;
float ix[QRA64_NC*QRA64_M]; // (depunctured) intrisic information
int xdec[QRA64_KC]; // decoded message (with crc)
int ydec[QRA64_NC]; // re-encoded message (for snr calculations)
float noisestd; // estimated noise variance
float msge; // estimated message energy
float ebnoval; // estimated Eb/No
int rc;
if (QRA64_NMSG!=QRA64_CODE.NMSG) // sanity check
return -16; // QRA64_NMSG define is wrong
// compute symbols intrinsic probabilities from received energy observations
noisestd = qra_mfskbesselmetric(ix, rxen, QRA64_m, QRA64_N,pcodec->decEsNoMetric);
// de-puncture observations adding a uniform distribution for the crc symbol
// move check symbols distributions one symbol towards the end
dsttmp = PD_ROWADDR(ix,QRA64_M, QRA64_NC-1); //Point to last symbol prob dist
srctmp = dsttmp-QRA64_M; // source is the previous pd
for (k=0;k<QRA64_C;k++) {
pd_init(dsttmp,srctmp,QRA64_M);
dsttmp -=QRA64_M;
srctmp -=QRA64_M;
}
// Initialize crc prob to a uniform distribution
pd_init(dsttmp,pd_uniform(QRA64_m),QRA64_M);
// Try to decode using all AP cases if required
rc = qra64_decode_attempts(pcodec, xdec, ix);
if (rc<0)
return rc; // no success
// successfull decode --------------------------------
// copy decoded message (without crc) to output buffer
memcpy(x,xdec,QRA64_K*sizeof(int));
if (ebno==0) // null pointer indicates we are not interested in the Eb/No estimate
return rc;
// reencode message and estimate Eb/No
qra_encode(&QRA64_CODE, ydec, xdec);
// puncture crc
memmove(ydec+QRA64_K,ydec+QRA64_KC,QRA64_C*sizeof(int));
// compute total power of decoded message
msge = 0;
for (k=0;k<QRA64_N;k++) {
msge +=rxen[ydec[k]]; // add energy of current symbol
rxen+=QRA64_M; // ptr to next symbol
}
// NOTE:
// To make a more accurate Eb/No estimation we should compute the noise variance
// on all the rxen values but the transmitted symbols.
// Noisestd is compute by qra_mfskbesselmetric assuming that
// the signal power is much less than the total noise power in the QRA64_M tones
// but this is true only if the Eb/No is low.
// Here, in order to improve accuracy, we linearize the estimated Eb/No value empirically
// (it gets compressed when it is very high as in this case the noise variance
// is overestimated)
// this would be the exact value if the noisestd were not overestimated at high Eb/No
ebnoval = (0.5f/(QRA64_K*QRA64_m))*msge/(noisestd*noisestd)-1.0f;
// Empirical linearization (to remove the noise variance overestimation)
// the resulting SNR is accurate up to +20 dB (51 dB Eb/No)
if (ebnoval>57.004f)
ebnoval=57.004f;
ebnoval = ebnoval*57.03f/(57.03f-ebnoval);
// compute value in dB
if (ebnoval<=0)
ebnoval = EBNO_MIN; // assume a minimum, positive value
else
ebnoval = 10.0f*(float)log10(ebnoval);
if (ebnoval<EBNO_MIN)
ebnoval = EBNO_MIN;
*ebno = ebnoval;
return rc;
}
//
// Fast-fading / Rayleigh channel metric decoder ----------------------------------------------
//
// Tables of fading energies coefficients for QRA64 (Ts=6912/12000)
// As the fading is assumed to be symmetric around the nominal frequency
// only the leftmost and the central coefficient are stored in the tables.
// (files have been generated with the Matlab code efgengaussenergy.m and efgenlorentzenergy.m)
#include "fadengauss.c"
#include "fadenlorentz.c"
int qra64_decode_fastfading(
qra64codec *pcodec, // ptr to the codec structure
float *ebno, // ptr to where the estimated Eb/No value will be saved
int *x, // ptr to decoded message
const float *rxen, // ptr to received symbol energies array
const int submode, // submode idx (0=QRA64A ... 4=QRA64E)
const float B90, // spread bandwidth (90% fractional energy)
const int fadingModel) // 0=Gaussian 1=Lorentzian fade model
// Decode a QRA64 msg using a fast-fading metric
//
// rxen: The array of the received bin energies
// Bins must be spaced by integer multiples of the symbol rate (1/Ts Hz)
// The array must be an array of total length U = L x N where:
// L: is the number of frequency bins per message symbol (see after)
// N: is the number of symbols in a QRA64 msg (63)
// The number of bins/symbol L depends on the selected submode accordingly to
// the following rule:
// L = (64+64*2^submode+64) = 64*(2+2^submode)
// Tone 0 is always supposed to be at offset 64 in the array.
// The m-th tone nominal frequency is located at offset 64 + m*2^submode (m=0..63)
//
// Submode A: (2^submode = 1)
// L = 64*3 = 196 bins/symbol
// Total length of the energies array: U = 192*63 = 12096 floats
//
// Submode B: (2^submode = 2)
// L = 64*4 = 256 bins/symbol
// Total length of the energies array: U = 256*63 = 16128 floats
//
// Submode C: (2^submode = 4)
// L = 64*6 = 384 bins/symbol
// Total length of the energies array: U = 384*63 = 24192 floats
//
// Submode D: (2^submode = 8)
// L = 64*10 = 640 bins/symbol
// Total length of the energies array: U = 640*63 = 40320 floats
//
// Submode E: (2^submode = 16)
// L = 64*18 = 1152 bins/symbol
// Total length of the energies array: U = 1152*63 = 72576 floats
//
// Note: The rxen array is modified and reused for internal calculations.
//
//
// B90: spread fading bandwidth in Hz (90% fractional average energy)
//
// B90 should be in the range 1 Hz ... 238 Hz
// The value passed to the call is rounded to the closest value among the
// 64 available values:
// B = 1.09^k Hz, with k=0,1,...,63
//
// I.e. B90=27 Hz will be approximated in this way:
// k = rnd(log(27)/log(1.09)) = 38
// B90 = 1.09^k = 1.09^38 = 26.4 Hz
//
// For any input value the maximum rounding error is not larger than +/- 5%
//
{
int k;
float *srctmp, *dsttmp;
float ix[QRA64_NC*QRA64_M]; // (depunctured) intrisic information
int xdec[QRA64_KC]; // decoded message (with crc)
int ydec[QRA64_NC]; // re-encoded message (for snr calculations)
float noisestd; // estimated noise std
float esno,ebnoval; // estimated Eb/No
float tempf;
float EsNoMetric;
int rc;
int hidx, hlen;
const float *hptr;
if (QRA64_NMSG!=QRA64_CODE.NMSG)
return -16; // QRA64_NMSG define is wrong
if (submode<0 || submode>4)
return -17; // invalid submode
if (B90<1.0f || B90>238.0f)
return -18; // B90 out of range
// compute index to most appropriate amplitude weighting function coefficients
hidx = (int)(log((float)B90)/log(1.09f) - 0.499f);
if (hidx<0 || hidx > 64)
return -19; // index of weighting function out of range
if (fadingModel==0) { // gaussian fading model
// point to gaussian energy weighting taps
hlen = glen_tab_gauss[hidx]; // hlen = (L+1)/2 (where L=(odd) number of taps of w fun)
hptr = gptr_tab_gauss[hidx]; // pointer to the first (L+1)/2 coefficients of w fun
}
else if (fadingModel==1) {
// point to lorentzian energy weighting taps
hlen = glen_tab_lorentz[hidx]; // hlen = (L+1)/2 (where L=(odd) number of taps of w fun)
hptr = gptr_tab_lorentz[hidx]; // pointer to the first (L+1)/2 coefficients of w fun
}
else
return -20; // invalid fading model index
// compute (euristically) the optimal decoder metric accordingly the given spread amount
// We assume that the decoder threshold is:
// Es/No(dB) = Es/No(AWGN)(dB) + 8*log(B90)/log(240)(dB)
// that's to say, at the maximum Doppler spread bandwidth (240 Hz) there's a ~8 dB Es/No degradation
// over the AWGN case
tempf = 8.0f*(float)log((float)B90)/(float)log(240.0f);
EsNoMetric = pcodec->decEsNoMetric*(float)pow(10.0f,tempf/10.0f);
// Step 1 -----------------------------------------------------------------------------------
// Evaluate the noise stdev from the received energies at nominal tone frequencies
noisestd = qra64_fastfading_estim_noise_std(rxen, EsNoMetric, submode);
// Step 2 -----------------------------------------------------------------------------------
// Compute message symbols probability distributions
qra64_fastfading_intrinsics(ix, rxen, hptr, hlen, noisestd, EsNoMetric, submode);
// Step 3 ---------------------------------------------------------------------------
// De-puncture observations adding a uniform distribution for the crc symbol
// Move check symbols distributions one symbol towards the end
dsttmp = PD_ROWADDR(ix,QRA64_M, QRA64_NC-1); //Point to last symbol prob dist
srctmp = dsttmp-QRA64_M; // source is the previous pd
for (k=0;k<QRA64_C;k++) {
pd_init(dsttmp,srctmp,QRA64_M);
dsttmp -=QRA64_M;
srctmp -=QRA64_M;
}
// Initialize crc prob to a uniform distribution
pd_init(dsttmp,pd_uniform(QRA64_m),QRA64_M);
// Step 4 ---------------------------------------------------------------------------
// Attempt to decode
rc = qra64_decode_attempts(pcodec, xdec, ix);
if (rc<0)
return rc; // no success
// copy decoded message (without crc) to output buffer
memcpy(x,xdec,QRA64_K*sizeof(int));
// Step 5 ----------------------------------------------------------------------------
// Estimate the message Eb/No
if (ebno==0) // null pointer indicates we are not interested in the Eb/No estimate
return rc;
// reencode message to estimate Eb/No
qra_encode(&QRA64_CODE, ydec, xdec);
// puncture crc
memmove(ydec+QRA64_K,ydec+QRA64_KC,QRA64_C*sizeof(int));
// compute Es/N0 of decoded message
esno = qra64_fastfading_msg_esno(ydec,rxen,noisestd, EsNoMetric, hlen,submode);
// as the weigthing function include about 90% of the energy
// we could compute the unbiased esno with:
// esno = esno/0.9;
// Es/N0 --> Eb/N0 conversion
ebnoval = 1.0f/(1.0f*QRA64_K/QRA64_N*QRA64_m)*esno;
// compute value in dB
if (ebnoval<=0)
ebnoval = EBNO_MIN; // assume a minimum, positive value
else
ebnoval = 10.0f*(float)log10(ebnoval);
if (ebnoval<EBNO_MIN)
ebnoval = EBNO_MIN;
*ebno = ebnoval;
return rc;
}
int qra64_fastfading_channel(float **rxen, const int *xmsg, const int submode, const float EbN0dB, const float B90, const int fadingModel)
{
// Simulate transmission over a fading channel and non coherent detection
// Set rxen to point to an array of bin energies formatted as required
// by the (fast-fading) decoding routine
// returns 0 on success or negative values on error conditions
static float *channel_out = NULL;
static int channel_submode = -1;
int bpt = (1<<submode); // bins per tone
int bps = QRA64_M*(2+bpt); // total number of bins per symbols
int bpm = bps*QRA64_N; // total number of bins in a message
int n,j,hidx, hlen;
const float *hptr;
float *cursym,*curtone;
float iq[2];
float *curi, *curq;
float sigmasig[65]; // signal standard deviation taps
float N0, EsN0, Es, sigmanoise;
if (rxen==NULL)
return -1; // rxen must be a non-null ptr
// allocate output buffer if not yet done or if submode changed
if (channel_out==NULL || submode!=channel_submode) {
// unallocate previous buffer
if (channel_out)
free(channel_out);
// allocate new buffer
// we allocate twice the mem so that we can store/compute complex amplitudes
channel_out = (float*)malloc(bpm*sizeof(float)*2);
if (channel_out==NULL)
return -2; // error allocating memory
channel_submode = submode;
}
if (B90<1.0f || B90>238.0f)
return -18; // B90 out of range
// compute index to most appropriate energy weighting function coefficients
hidx = (int)(log((float)B90)/log(1.09f) - 0.499f);
if (hidx<0 || hidx > 64)
return -19; // index of weighting function out of range
if (fadingModel==0) { // gaussian fading model
// point to gaussian weighting taps
hlen = glen_tab_gauss[hidx]; // hlen = (L+1)/2 (where L=(odd) number of taps of w fun)
hptr = gptr_tab_gauss[hidx]; // pointer to the first (L+1)/2 coefficients of w fun
}
else if (fadingModel==1) {
// point to lorentzian weighting taps
hlen = glen_tab_lorentz[hidx]; // hlen = (L+1)/2 (where L=(odd) number of taps of w fun)
hptr = gptr_tab_lorentz[hidx]; // pointer to the first (L+1)/2 coefficients of w fun
}
else
return -20; // invalid fading model index
// Compute the unfaded tone amplitudes from the Eb/No value passed to the call
N0 = 1.0f; // assume unitary noise PSD
sigmanoise = (float)sqrt(N0/2);
EsN0 = (float)pow(10.0f,EbN0dB/10.0f)*QRA64_m*QRA64_K/QRA64_N; // Es/No = m*R*Eb/No
Es = EsN0*N0;
// compute signal bin sigmas
for (n=0;n<hlen;n++)
sigmasig[n] = (float)sqrt(hptr[n]*Es/2.0f);
// Generate gaussian noise iq components
normrnd_s(channel_out, bpm*2, 0 , sigmanoise);
// Add symbols, bin by bin energies
for (n=0;n<QRA64_N;n++) {
cursym = channel_out+n*bps + QRA64_M; // point to n-th symbol
curtone = cursym+xmsg[n]*bpt; // point to encoded tone
curi = curtone-hlen+1; // point to real part of first bin
curq = curtone-hlen+1+bpm; // point to imag part of first bin
// generate Rayleigh faded bins with given average energy and add to noise
for (j=0;j<hlen;j++) {
normrnd_s(iq, 2, 0 , sigmasig[j]);
*curi++ += iq[0];
*curq++ += iq[1];
}
for (j=hlen-2;j>=0;j--) {
normrnd_s(iq, 2, 0 , sigmasig[j]);
*curi++ += iq[0];
*curq++ += iq[1];
}
}
// compute total bin energies (S+N) and store in first half of buffer
curi = channel_out;
curq = channel_out+bpm;
for (n=0;n<bpm;n++)
channel_out[n] = curi[n]*curi[n] + curq[n]*curq[n];
// set rxen to point to the channel output energies
*rxen = channel_out;
return 0;
}
// Static functions definitions ----------------------------------------------
// fast-fading static functions --------------------------------------------------------------
static float qra64_fastfading_estim_noise_std(const float *rxen, const float esnometric, const int submode)
{
// estimate the noise standard deviation from nominal frequency symbol bins
// transform energies to amplitudes
// rxen = message symbols energies (overwritten with symbols amplitudes)
// esnometric = Es/No at nominal frequency bin for which we compute the decoder metric
// submode = submode used (0=A...4=E)
int bpt = (1<<submode); // bins per tone
int bps = QRA64_M*(2+bpt); // total number of bins per symbols
int bpm = bps*QRA64_N; // total number of bins in a message
int k;
float sigmaest;
// estimate noise std
sigmaest = 0;
for (k=0;k<bpm;k++)
sigmaest += rxen[k];
sigmaest = sigmaest/bpm;
sigmaest = (float)sqrt(sigmaest/(1.0f+esnometric/bps)/2.0f);
// Note: sigma is overestimated by the (unknown) factor sqrt((1+esno(true)/bps)/(1+esnometric/bps))
return sigmaest;
}
static void qra64_fastfading_intrinsics(
float *pix,
const float *rxen,
const float *hptr,
const int hlen,
const float sigmaest,
const float EsNoMetric,
const int submode)
{
// For each symbol in a message:
// a) Compute tones loglikelihoods as a sum of products between of the expected
// energy fading coefficient and received energies.
// b) Compute intrinsic symbols probability distributions from symbols loglikelihoods
int n,k,j, bps, bpt;
const float *cursym, *curbin;
float *curix;
float u, maxloglh, loglh, sumix,hh;
float w[65];
int hhsz = hlen-1;
int hlast = 2*hhsz;
float npwrest = 2.0f*sigmaest*sigmaest;
bpt = 1<<submode; // bins per tone
bps = QRA64_M*(2+bpt); // bins per symbol
u = EsNoMetric;
// compute weights from energy tables
for (j=0;j<hlen;j++) {
hh = hptr[j]*u;
w[j] = hh/(1+hh)/npwrest;
}
for (n=0;n<QRA64_N;n++) { // for each symbol in the message
cursym = rxen+n*bps + QRA64_M; // point to current symbol nominal bin
maxloglh = 0;
curix = pix+n*QRA64_M;
for (k=0;k<QRA64_M;k++) { // for each tone in the current symbol
curbin = cursym + k*bpt -hlen+1;
// compute tone loglikelihood (symmetric fir with given weights)
loglh = 0.f;
for (j=0;j<hhsz;j++)
loglh += w[j]*(curbin[j] + curbin[hlast-j]);
loglh += w[hhsz]*curbin[hhsz];
if (loglh>maxloglh) // keep track of the max loglikelihood
maxloglh = loglh;
curix[k]=loglh;
}
// scale to likelihoods
sumix = 0.f;
for (k=0;k<QRA64_M;k++) {
u = (float)exp(curix[k]-maxloglh);
curix[k]=u;
sumix +=u;
}
// scale to probabilities
sumix = 1.0f/sumix;
for (k=0;k<QRA64_M;k++)
curix[k] = curix[k]*sumix;
}
}
static float qra64_fastfading_msg_esno(
const int *ydec,
const float *rxen,
const float sigma,
const float EsNoMetric,
const int hlen,
const int submode)
{
// Estimate msg Es/N0
int n,j, bps, bpt;
const float *cursym, *curtone, *curbin;
float u, msgsn,esno;
int tothlen = 2*hlen-1;
bpt = 1<<submode; // bins per tone
bps = QRA64_M*(2+bpt); // bins per symbol
msgsn = 0;
for (n=0;n<QRA64_N;n++) {
cursym = rxen+n*bps + QRA64_M; // point to n-th symbol amplitudes
curtone = cursym+ydec[n]*bpt; // point to decoded tone amplitudes
curbin = curtone-hlen+1; // point to first bin amplitude
// sum bin energies
for (j=0;j<tothlen;j++)
msgsn += curbin[j];
}
msgsn = msgsn/(QRA64_N*tothlen); // avg msg energy per bin (noise included)
// as sigma is overestimated (sigmatrue = sigma*sqrt((1+EsNoMetric/bps)/(1+EsNo/bps))
// we have: msgsn = (1+x/hlen)/(1+x/bps)*2*sigma^2*(1+EsnoMetric/bps), where x = Es/N0(true)
//
// we can then write:
// u = msgsn/2.0f/(sigma*sigma)/(1.0f+EsNoMetric/bps);
// (1+x/hlen)/(1+x/bps) = u
u = msgsn/(2.0f*sigma*sigma)/(1.0f+EsNoMetric/bps);
// check u>1
if (u<1)
return 0.f;
// check u<bps/tot hlen
if (u>(bps/tothlen))
return 10000.f;
// solve for Es/No
esno = (u-1.0f)/(1.0f/tothlen-u/bps);
return esno;
}
#ifdef LIMIT_AP_MASKS
static int call1_match(const int *papmsg, const int *pdec)
{
// assumes MASK_CALL1 = 0xFFFFFFC
int u = papmsg[4]^pdec[4];
return (u&0x3C)==0;
}
static int call2_match(const int *papmsg, const int *pdec)
{
// assumes MASK_CALL2 = 0xFFFFFFC
int u = papmsg[9]^pdec[9];
return (u&0x30)==0;
}
static int grid_match(const int *papmsg, const int *pdec)
{
// assumes MASK_GRIDFULL = 0x3FFC
int u = papmsg[11]^pdec[11];
int rc = (u&0x03)==0;
u = papmsg[9]^pdec[9];
return (u&0x0C)==0 && rc;
}
#else
#define call1_match(a,b) (1)
#define call2_match(a,b) (1)
#define grid_match(a,b) (1)
#endif
// Attempt to decode given intrisic information
static int qra64_decode_attempts(qra64codec *pcodec, int *xdec, const float *ix)
{
int rc;
// Attempt to decode without a-priori info --------------------------------
rc = qra64_do_decode(xdec, ix, NULL, NULL);
if (rc>=0)
return 0; // successfull decode with AP0
else
if (pcodec->apflags==QRA_NOAP)
// nothing more to do
return rc; // rc<0 = unsuccessful decode
// Here we handle decoding with AP knowledge
// Attempt to decode CQ calls
rc = qra64_do_decode(xdec,ix,pcodec->apmask_cqqrz, pcodec->apmsg_cqqrz);
if (rc>=0)
return 1; // decoded [cq/qrz ? ?]
rc = qra64_do_decode(xdec, ix, pcodec->apmask_cqqrz_ooo,
pcodec->apmsg_cqqrz);
if (rc>=0)
// check that ooo really matches
if (grid_match(pcodec->apmsg_cqqrz,xdec))
return 2; // decoded [cq/qrz ? ooo]
// attempt to decode calls directed to us
if (pcodec->apmsg_set[APTYPE_MYCALL]) {
rc = qra64_do_decode(xdec, ix, pcodec->apmask_call1,
pcodec->apmsg_call1);
if (rc>=0)
// check that mycall really matches
if (call1_match(pcodec->apmsg_call1,xdec))
return 3; // decoded [mycall ? ?]
rc = qra64_do_decode(xdec, ix, pcodec->apmask_call1_ooo,
pcodec->apmsg_call1);
if (rc>=0)
// check that mycall and ooo really match
if (call1_match(pcodec->apmsg_call1,xdec) &&
grid_match(pcodec->apmsg_call1,xdec))
return 4; // decoded [mycall ? ooo]
}
// attempt to decode [mycall hiscall ?] msgs
if (pcodec->apmsg_set[APTYPE_BOTHCALLS]) {
rc = qra64_do_decode(xdec, ix, pcodec->apmask_call1_call2,
pcodec->apmsg_call1_call2);
if (rc>=0)
// check that mycall and hiscall really match
if (call1_match(pcodec->apmsg_call1_call2,xdec) &&
call2_match(pcodec->apmsg_call1_call2,xdec))
return 5; // decoded [mycall srccall ?]
}
// attempt to decode [? hiscall ?/b] msgs
if (pcodec->apmsg_set[APTYPE_HISCALL]) {
rc = qra64_do_decode(xdec, ix, pcodec->apmask_call2,
pcodec->apmsg_call2);
if (rc>=0)
// check that hiscall really match
if (call2_match(pcodec->apmsg_call2,xdec))
return 6; // decoded [? hiscall ?]
rc = qra64_do_decode(xdec, ix, pcodec->apmask_call2_ooo,
pcodec->apmsg_call2);
if (rc>=0)
// check that hiscall and ooo match
if (call2_match(pcodec->apmsg_call2,xdec) &&
grid_match(pcodec->apmsg_call2,xdec))
return 7; // decoded [? hiscall ooo]
}
// attempt to decode [cq/qrz hiscall ?/b/grid] msgs
if (pcodec->apmsg_set[APTYPE_CQHISCALL]) {
rc = qra64_do_decode(xdec, ix, pcodec->apmask_cq_call2,
pcodec->apmsg_cq_call2);
if (rc>=0)
// check that hiscall matches
if (call2_match(pcodec->apmsg_call2,xdec))
return 9; // decoded [cq/qrz hiscall ?]
rc = qra64_do_decode(xdec, ix, pcodec->apmask_cq_call2_ooo,
pcodec->apmsg_cq_call2_grid);
if (rc>=0) {
// Full AP mask need special handling
// To minimize false decodes we check the decoded message
// with what passed in the ap_set call
if (memcmp(pcodec->apmsg_cq_call2_grid,xdec, QRA64_K*sizeof(int))==0)
return 11; // decoded [cq/qrz hiscall grid]
}
rc = qra64_do_decode(xdec, ix, pcodec->apmask_cq_call2_ooo,
pcodec->apmsg_cq_call2);
if (rc>=0) {
// Full AP mask need special handling
// To minimize false decodes we check the decoded message
// with what passed in the ap_set call
if (memcmp(pcodec->apmsg_cq_call2,xdec, QRA64_K*sizeof(int))==0)
return 10; // decoded [cq/qrz hiscall ]
}
}
// attempt to decode [mycall hiscall grid]
if (pcodec->apmsg_set[APTYPE_FULL]) {
rc = qra64_do_decode(xdec, ix, pcodec->apmask_call1_call2_grid,
pcodec->apmsg_call1_call2_grid);
if (rc>=0) {
// Full AP mask need special handling
// All the three msg fields were given.
// To minimize false decodes we check the decoded message
// with what passed in the ap_set call
if (memcmp(pcodec->apmsg_call1_call2_grid,xdec, QRA64_K*sizeof(int))==0)
return 8; // decoded [mycall hiscall grid]
}
}
// all decoding attempts failed
return -1;
}
// Decode with given a-priori information
static int qra64_do_decode(int *xdec, const float *pix, const int *ap_mask,
const int *ap_x)
{
int rc;
const float *ixsrc;
float ix_masked[QRA64_NC*QRA64_M]; // Masked intrinsic information
float ex[QRA64_NC*QRA64_M]; // Extrinsic information from the decoder
float v2cmsg[QRA64_NMSG*QRA64_M]; // buffers for the decoder messages
float c2vmsg[QRA64_NMSG*QRA64_M];
if (ap_mask==NULL) { // no a-priori information
ixsrc = pix; // intrinsic source is what passed as argument
} else {
// a-priori information provided
// mask channel observations with a-priori
ix_mask(ix_masked,pix,ap_mask,ap_x);
ixsrc = ix_masked; // intrinsic source is the masked version
}
// run the decoding algorithm
rc = qra_extrinsic(&QRA64_CODE,ex,ixsrc,QRA64_NITER,v2cmsg,c2vmsg);
if (rc<0)
return -1; // no convergence in given iterations
// decode
qra_mapdecode(&QRA64_CODE,xdec,ex,ixsrc);
// verify crc
if (calc_crc6(xdec,QRA64_K)!=xdec[QRA64_K]) // crc doesn't match (detected error)
return -2; // decoding was succesfull but crc doesn't match
return 0;
}
// crc functions --------------------------------------------------------------
// crc-6 generator polynomial
// g(x) = x^6 + a5*x^5 + ... + a1*x + a0
// g(x) = x^6 + x + 1
#define CRC6_GEN_POL 0x30 // MSB=a0 LSB=a5
// g(x) = x^6 + x^2 + x + 1 (See: https://users.ece.cmu.edu/~koopman/crc/)
// #define CRC6_GEN_POL 0x38 // MSB=a0 LSB=a5. Simulation results are similar
static int calc_crc6(const int *x, int sz)
{
// todo: compute it faster using a look up table
int k,j,t,sr = 0;
for (k=0;k<sz;k++) {
t = x[k];
for (j=0;j<6;j++) {
if ((t^sr)&0x01)
sr = (sr>>1) ^ CRC6_GEN_POL;
else
sr = (sr>>1);
t>>=1;
}
}
return sr;
}
static void ix_mask(float *dst, const float *src, const int *mask,
const int *x)
{
// mask intrinsic information (channel observations) with a priori knowledge
int k,kk, smask;
float *row;
memcpy(dst,src,(QRA64_NC*QRA64_M)*sizeof(float));
for (k=0;k<QRA64_K;k++) { // we can mask only information symbols distrib
smask = mask[k];
row = PD_ROWADDR(dst,QRA64_M,k);
if (smask) {
for (kk=0;kk<QRA64_M;kk++)
if (((kk^x[k])&smask)!=0)
*(row+kk) = 0.f;
pd_norm(row,QRA64_m);
}
}
}
// encode/decode msgs as done in JT65
void encodemsg_jt65(int *y, const int call1, const int call2, const int grid)
{
y[0]= (call1>>22)&0x3F;
y[1]= (call1>>16)&0x3F;
y[2]= (call1>>10)&0x3F;
y[3]= (call1>>4)&0x3F;
y[4]= (call1<<2)&0x3F;
y[4] |= (call2>>26)&0x3F;
y[5]= (call2>>20)&0x3F;
y[6]= (call2>>14)&0x3F;
y[7]= (call2>>8)&0x3F;
y[8]= (call2>>2)&0x3F;
y[9]= (call2<<4)&0x3F;
y[9] |= (grid>>12)&0x3F;
y[10]= (grid>>6)&0x3F;
y[11]= (grid)&0x3F;
}
void decodemsg_jt65(int *call1, int *call2, int *grid, const int *x)
{
int nc1, nc2, ng;
nc1 = x[4]>>2;
nc1 |= x[3]<<4;
nc1 |= x[2]<<10;
nc1 |= x[1]<<16;
nc1 |= x[0]<<22;
nc2 = x[9]>>4;
nc2 |= x[8]<<2;
nc2 |= x[7]<<8;
nc2 |= x[6]<<14;
nc2 |= x[5]<<20;
nc2 |= (x[4]&0x03)<<26;
ng = x[11];
ng |= x[10]<<6;
ng |= (x[9]&0x0F)<<12;
*call1 = nc1;
*call2 = nc2;
*grid = ng;
}