2016-06-24 15:54:34 -04:00
|
|
|
/*
|
2016-07-02 08:15:41 -04:00
|
|
|
qra64.c
|
|
|
|
Encoding/decoding functions for the QRA64 mode
|
2016-06-24 15:54:34 -04:00
|
|
|
|
|
|
|
(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/>.
|
|
|
|
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
QRA code used in this sowftware release:
|
2016-06-24 15:54:34 -04:00
|
|
|
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
//----------------------------------------------------------------------------
|
2016-06-21 11:07:24 -04:00
|
|
|
|
|
|
|
#include <stdlib.h>
|
2016-07-18 08:42:10 -04:00
|
|
|
#include <string.h>
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-02 08:15:41 -04:00
|
|
|
#include "qra64.h"
|
2016-06-23 05:31:16 -04:00
|
|
|
#include "../qracodes/qracodes.h"
|
|
|
|
#include "../qracodes/qra13_64_64_irr_e.h"
|
|
|
|
#include "../qracodes/pdmath.h"
|
2016-11-14 15:29:00 -05:00
|
|
|
#include "../qracodes/normrnd.h"
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-02 08:15:41 -04:00
|
|
|
// 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
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-02 08:15:41 -04:00
|
|
|
#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
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
// static functions declarations ----------------------------------------------
|
2016-06-21 11:07:24 -04:00
|
|
|
static int calc_crc6(const int *x, int sz);
|
2016-06-24 15:54:34 -04:00
|
|
|
static void ix_mask(float *dst, const float *src, const int *mask,
|
|
|
|
const int *x);
|
2016-11-05 20:53:47 -04:00
|
|
|
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,
|
2016-06-24 15:54:34 -04:00
|
|
|
const int *ap_x);
|
2016-11-05 20:53:47 -04:00
|
|
|
static float qra64_fastfading_estim_noise_std(
|
2016-11-27 10:50:47 -05:00
|
|
|
const float *rxen,
|
2016-11-05 20:53:47 -04:00
|
|
|
const float esnometric,
|
|
|
|
const int submode);
|
2016-11-27 10:50:47 -05:00
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
static void qra64_fastfading_intrinsics(
|
|
|
|
float *pix,
|
2016-11-27 10:50:47 -05:00
|
|
|
const float *rxen,
|
2016-11-05 20:53:47 -04:00
|
|
|
const float *hptr,
|
|
|
|
const int hlen,
|
2016-11-27 10:50:47 -05:00
|
|
|
const float sigma,
|
|
|
|
const float EsNoMetric,
|
2016-11-05 20:53:47 -04:00
|
|
|
const int submode);
|
2016-11-27 10:50:47 -05:00
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
static float qra64_fastfading_msg_esno(
|
|
|
|
const int *ydec,
|
2016-11-27 10:50:47 -05:00
|
|
|
const float *rxen,
|
2016-11-05 20:53:47 -04:00
|
|
|
const float sigma,
|
|
|
|
const float EsNoMetric,
|
|
|
|
const int hlen,
|
|
|
|
const int submode);
|
|
|
|
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
// a-priori information masks for fields in JT65-like msgs --------------------
|
2016-11-27 10:50:47 -05:00
|
|
|
|
|
|
|
// 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
|
2016-06-21 11:07:24 -04:00
|
|
|
#define MASK_CALL1 0xFFFFFFF
|
|
|
|
#define MASK_CALL2 0xFFFFFFF
|
|
|
|
#define MASK_GRIDFULL 0xFFFF
|
2016-11-27 10:50:47 -05:00
|
|
|
#define MASK_GRIDFULL12 0x3FFC
|
2016-06-24 15:54:34 -04:00
|
|
|
#define MASK_GRIDBIT 0x8000 // b[15] is 1 for free text, 0 otherwise
|
2016-11-27 10:50:47 -05:00
|
|
|
#endif
|
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
// ----------------------------------------------------------------------------
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
2016-07-18 08:42:10 -04:00
|
|
|
qra64codec *qra64_init(int flags)
|
2016-06-21 11:07:24 -04:00
|
|
|
{
|
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
// Eb/No value for which we optimize the decoder metric
|
|
|
|
const float EbNodBMetric = 2.8f;
|
|
|
|
const float EbNoMetric = (float)pow(10,EbNodBMetric/10);
|
2016-07-02 08:15:41 -04:00
|
|
|
const float R = 1.0f*(QRA64_KC)/(QRA64_NC);
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-02 08:15:41 -04:00
|
|
|
qra64codec *pcodec = (qra64codec*)malloc(sizeof(qra64codec));
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
if (!pcodec)
|
|
|
|
return 0; // can't allocate memory
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-02 08:15:41 -04:00
|
|
|
pcodec->decEsNoMetric = 1.0f*QRA64_m*R*EbNoMetric;
|
2016-06-24 15:54:34 -04:00
|
|
|
pcodec->apflags = flags;
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-18 08:42:10 -04:00
|
|
|
memset(pcodec->apmsg_set,0,APTYPE_SIZE*sizeof(int));
|
|
|
|
|
|
|
|
if (flags==QRA_NOAP)
|
2016-06-24 15:54:34 -04:00
|
|
|
return pcodec;
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-18 08:42:10 -04:00
|
|
|
// 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);
|
2016-10-07 08:27:22 -04:00
|
|
|
encodemsg_jt65(pcodec->apmask_call1_call2_grid,MASK_CALL1,MASK_CALL2, MASK_GRIDFULL12);
|
2016-07-21 08:38:26 -04:00
|
|
|
encodemsg_jt65(pcodec->apmask_cq_call2, MASK_CQQRZ, MASK_CALL2, MASK_GRIDBIT);
|
2016-10-07 08:27:22 -04:00
|
|
|
encodemsg_jt65(pcodec->apmask_cq_call2_ooo, MASK_CQQRZ, MASK_CALL2, MASK_GRIDFULL12);
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-18 08:42:10 -04:00
|
|
|
return pcodec;
|
|
|
|
}
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-18 08:42:10 -04:00
|
|
|
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]
|
2016-07-21 08:38:26 -04:00
|
|
|
// APTYPE_CQHISCALL set [cq/qrz hiscall ?/blank] and [cq/qrz hiscall grid]
|
2016-07-18 08:42:10 -04:00
|
|
|
// 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;
|
2016-07-21 08:38:26 -04:00
|
|
|
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;
|
2016-07-18 08:42:10 -04:00
|
|
|
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;
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-21 08:38:26 -04:00
|
|
|
if (aptype<APTYPE_CQQRZ || aptype>=APTYPE_SIZE)
|
2016-07-18 08:42:10 -04:00
|
|
|
return;
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-18 08:42:10 -04:00
|
|
|
pcodec->apmsg_set[aptype] = 0; // signal the decoder not to look-up to the specified type
|
2016-06-21 11:07:24 -04:00
|
|
|
}
|
|
|
|
|
2016-07-02 08:15:41 -04:00
|
|
|
void qra64_encode(qra64codec *pcodec, int *y, const int *x)
|
2016-06-21 11:07:24 -04:00
|
|
|
{
|
2016-07-02 08:15:41 -04:00
|
|
|
int encx[QRA64_KC]; // encoder input buffer
|
|
|
|
int ency[QRA64_NC]; // encoder output buffer
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-18 08:42:10 -04:00
|
|
|
int hiscall,mycall,grid;
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-02 08:15:41 -04:00
|
|
|
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
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
// copy codeword to output puncturing the crc symbol
|
2016-07-02 08:15:41 -04:00
|
|
|
memcpy(y,ency,QRA64_K*sizeof(int)); // copy information symbols
|
|
|
|
memcpy(y+QRA64_K,ency+QRA64_KC,QRA64_C*sizeof(int)); // copy parity symbols
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
if (pcodec->apflags!=QRA_AUTOAP)
|
|
|
|
return;
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-07-18 08:42:10 -04:00
|
|
|
// 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
|
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
// look if the msg sent is a std type message (bit15 of grid field = 0)
|
|
|
|
if ((x[9]&0x80)==1)
|
2016-07-18 08:42:10 -04:00
|
|
|
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);
|
2016-06-24 15:54:34 -04:00
|
|
|
} else {
|
2016-07-18 08:42:10 -04:00
|
|
|
// 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);
|
2016-06-24 15:54:34 -04:00
|
|
|
}
|
2016-07-18 08:42:10 -04:00
|
|
|
|
2016-06-21 11:07:24 -04:00
|
|
|
}
|
|
|
|
|
2016-07-18 08:42:10 -04:00
|
|
|
#define EBNO_MIN -10.0f // minimum Eb/No value returned by the decoder (in dB)
|
2016-11-27 10:50:47 -05:00
|
|
|
// AWGN metric decoder
|
2016-07-18 08:42:10 -04:00
|
|
|
int qra64_decode(qra64codec *pcodec, float *ebno, int *x, const float *rxen)
|
2016-06-21 11:07:24 -04:00
|
|
|
{
|
2016-06-24 15:54:34 -04:00
|
|
|
int k;
|
|
|
|
float *srctmp, *dsttmp;
|
2016-07-02 08:15:41 -04:00
|
|
|
float ix[QRA64_NC*QRA64_M]; // (depunctured) intrisic information
|
2016-07-18 08:42:10 -04:00
|
|
|
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
|
2016-06-24 15:54:34 -04:00
|
|
|
int rc;
|
|
|
|
|
2016-07-02 08:15:41 -04:00
|
|
|
if (QRA64_NMSG!=QRA64_CODE.NMSG) // sanity check
|
|
|
|
return -16; // QRA64_NMSG define is wrong
|
2016-06-24 15:54:34 -04:00
|
|
|
|
|
|
|
// compute symbols intrinsic probabilities from received energy observations
|
2016-07-18 08:42:10 -04:00
|
|
|
noisestd = qra_mfskbesselmetric(ix, rxen, QRA64_m, QRA64_N,pcodec->decEsNoMetric);
|
2016-06-24 15:54:34 -04:00
|
|
|
|
|
|
|
// de-puncture observations adding a uniform distribution for the crc symbol
|
|
|
|
|
|
|
|
// move check symbols distributions one symbol towards the end
|
2016-07-02 08:15:41 -04:00
|
|
|
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;
|
2016-06-24 15:54:34 -04:00
|
|
|
}
|
|
|
|
// Initialize crc prob to a uniform distribution
|
2016-07-02 08:15:41 -04:00
|
|
|
pd_init(dsttmp,pd_uniform(QRA64_m),QRA64_M);
|
2016-06-24 15:54:34 -04:00
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
//
|
|
|
|
// Fast-fading / Rayleigh channel metric decoder ----------------------------------------------
|
|
|
|
//
|
|
|
|
// Tables of fading energies coefficients for QRA64 (Ts=6912/12000)
|
2016-11-05 20:53:47 -04:00
|
|
|
// As the fading is assumed to be symmetric around the nominal frequency
|
|
|
|
// only the leftmost and the central coefficient are stored in the tables.
|
2016-11-27 10:50:47 -05:00
|
|
|
// (files have been generated with the Matlab code efgengaussenergy.m and efgenlorentzenergy.m)
|
|
|
|
#include "fadengauss.c"
|
|
|
|
#include "fadenlorentz.c"
|
2016-11-05 20:53:47 -04:00
|
|
|
|
|
|
|
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
|
2016-11-27 10:50:47 -05:00
|
|
|
const float *rxen, // ptr to received symbol energies array
|
2016-11-05 20:53:47 -04:00
|
|
|
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;
|
2016-11-27 10:50:47 -05:00
|
|
|
float EsNoMetric;
|
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
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
|
2016-11-27 10:50:47 -05:00
|
|
|
// 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
|
2016-11-05 20:53:47 -04:00
|
|
|
}
|
|
|
|
else if (fadingModel==1) {
|
2016-11-27 10:50:47 -05:00
|
|
|
// 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
|
2016-11-05 20:53:47 -04:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
|
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
// 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
|
2016-11-27 10:50:47 -05:00
|
|
|
qra64_fastfading_intrinsics(ix, rxen, hptr, hlen, noisestd, EsNoMetric, submode);
|
2016-11-05 20:53:47 -04:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
// Es/N0 --> Eb/N0 conversion
|
2016-11-05 20:53:47 -04:00
|
|
|
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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
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;
|
2016-11-27 10:50:47 -05:00
|
|
|
float sigmasig[65]; // signal standard deviation taps
|
2016-11-05 20:53:47 -04:00
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
float N0, EsN0, Es, sigmanoise;
|
2016-11-05 20:53:47 -04:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
// compute index to most appropriate energy weighting function coefficients
|
2016-11-05 20:53:47 -04:00
|
|
|
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
|
2016-11-27 10:50:47 -05:00
|
|
|
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
|
2016-11-05 20:53:47 -04:00
|
|
|
}
|
|
|
|
else if (fadingModel==1) {
|
|
|
|
// point to lorentzian weighting taps
|
2016-11-27 10:50:47 -05:00
|
|
|
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
|
2016-11-05 20:53:47 -04:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
// compute signal bin sigmas
|
|
|
|
for (n=0;n<hlen;n++)
|
|
|
|
sigmasig[n] = (float)sqrt(hptr[n]*Es/2.0f);
|
2016-11-05 20:53:47 -04:00
|
|
|
|
|
|
|
// Generate gaussian noise iq components
|
|
|
|
normrnd_s(channel_out, bpm*2, 0 , sigmanoise);
|
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
// Add symbols, bin by bin energies
|
2016-11-05 20:53:47 -04:00
|
|
|
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++) {
|
2016-11-27 10:50:47 -05:00
|
|
|
normrnd_s(iq, 2, 0 , sigmasig[j]);
|
2016-11-05 20:53:47 -04:00
|
|
|
*curi++ += iq[0];
|
|
|
|
*curq++ += iq[1];
|
|
|
|
}
|
|
|
|
for (j=hlen-2;j>=0;j--) {
|
2016-11-27 10:50:47 -05:00
|
|
|
normrnd_s(iq, 2, 0 , sigmasig[j]);
|
2016-11-05 20:53:47 -04:00
|
|
|
*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;
|
|
|
|
}
|
2016-11-27 10:50:47 -05:00
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
|
|
|
|
|
|
|
|
// Static functions definitions ----------------------------------------------
|
|
|
|
|
|
|
|
// fast-fading static functions --------------------------------------------------------------
|
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
static float qra64_fastfading_estim_noise_std(const float *rxen, const float esnometric, const int submode)
|
2016-11-05 20:53:47 -04:00
|
|
|
{
|
|
|
|
// 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;
|
2016-11-27 10:50:47 -05:00
|
|
|
for (k=0;k<bpm;k++)
|
2016-11-05 20:53:47 -04:00
|
|
|
sigmaest += rxen[k];
|
2016-11-27 10:50:47 -05:00
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
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,
|
2016-11-27 10:50:47 -05:00
|
|
|
const float *rxen,
|
2016-11-05 20:53:47 -04:00
|
|
|
const float *hptr,
|
|
|
|
const int hlen,
|
2016-11-27 10:50:47 -05:00
|
|
|
const float sigmaest,
|
|
|
|
const float EsNoMetric,
|
2016-11-05 20:53:47 -04:00
|
|
|
const int submode)
|
|
|
|
{
|
|
|
|
|
|
|
|
// For each symbol in a message:
|
|
|
|
// a) Compute tones loglikelihoods as a sum of products between of the expected
|
2016-11-27 10:50:47 -05:00
|
|
|
// energy fading coefficient and received energies.
|
2016-11-05 20:53:47 -04:00
|
|
|
// b) Compute intrinsic symbols probability distributions from symbols loglikelihoods
|
|
|
|
|
|
|
|
int n,k,j, bps, bpt;
|
|
|
|
const float *cursym, *curbin;
|
|
|
|
float *curix;
|
2016-11-27 10:50:47 -05:00
|
|
|
float u, maxloglh, loglh, sumix,hh;
|
|
|
|
float w[65];
|
|
|
|
int hhsz = hlen-1;
|
|
|
|
int hlast = 2*hhsz;
|
|
|
|
float npwrest = 2.0f*sigmaest*sigmaest;
|
2016-11-05 20:53:47 -04:00
|
|
|
|
|
|
|
bpt = 1<<submode; // bins per tone
|
|
|
|
bps = QRA64_M*(2+bpt); // bins per symbol
|
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
u = EsNoMetric;
|
|
|
|
// compute weights from energy tables
|
|
|
|
for (j=0;j<hlen;j++) {
|
|
|
|
hh = hptr[j]*u;
|
|
|
|
w[j] = hh/(1+hh)/npwrest;
|
|
|
|
}
|
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
for (n=0;n<QRA64_N;n++) { // for each symbol in the message
|
2016-11-27 10:50:47 -05:00
|
|
|
cursym = rxen+n*bps + QRA64_M; // point to current symbol nominal bin
|
2016-11-05 20:53:47 -04:00
|
|
|
maxloglh = 0;
|
|
|
|
curix = pix+n*QRA64_M;
|
|
|
|
for (k=0;k<QRA64_M;k++) { // for each tone in the current symbol
|
2016-11-27 10:50:47 -05:00
|
|
|
curbin = cursym + k*bpt -hlen+1;
|
|
|
|
// compute tone loglikelihood (symmetric fir with given weights)
|
2016-11-05 20:53:47 -04:00
|
|
|
loglh = 0.f;
|
2016-11-27 10:50:47 -05:00
|
|
|
for (j=0;j<hhsz;j++)
|
|
|
|
loglh += w[j]*(curbin[j] + curbin[hlast-j]);
|
|
|
|
loglh += w[hhsz]*curbin[hhsz];
|
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
if (loglh>maxloglh) // keep track of the max loglikelihood
|
|
|
|
maxloglh = loglh;
|
|
|
|
curix[k]=loglh;
|
|
|
|
}
|
2016-11-27 10:50:47 -05:00
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
// 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,
|
2016-11-27 10:50:47 -05:00
|
|
|
const float *rxen,
|
2016-11-05 20:53:47 -04:00
|
|
|
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++) {
|
2016-11-27 10:50:47 -05:00
|
|
|
cursym = rxen+n*bps + QRA64_M; // point to n-th symbol amplitudes
|
2016-11-05 20:53:47 -04:00
|
|
|
curtone = cursym+ydec[n]*bpt; // point to decoded tone amplitudes
|
|
|
|
curbin = curtone-hlen+1; // point to first bin amplitude
|
|
|
|
|
|
|
|
// sum bin energies
|
2016-11-27 10:50:47 -05:00
|
|
|
for (j=0;j<tothlen;j++)
|
|
|
|
msgsn += curbin[j];
|
2016-11-05 20:53:47 -04:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
#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
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
|
|
|
|
// Attempt to decode given intrisic information
|
|
|
|
static int qra64_decode_attempts(qra64codec *pcodec, int *xdec, const float *ix)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
// Attempt to decode without a-priori info --------------------------------
|
2016-07-18 08:42:10 -04:00
|
|
|
rc = qra64_do_decode(xdec, ix, NULL, NULL);
|
2016-11-05 20:53:47 -04:00
|
|
|
if (rc>=0)
|
|
|
|
return 0; // successfull decode with AP0
|
2016-07-18 08:42:10 -04:00
|
|
|
else
|
|
|
|
if (pcodec->apflags==QRA_NOAP)
|
|
|
|
// nothing more to do
|
|
|
|
return rc; // rc<0 = unsuccessful decode
|
|
|
|
|
|
|
|
// Here we handle decoding with AP knowledge
|
2016-06-24 15:54:34 -04:00
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
// Attempt to decode CQ calls
|
2016-07-18 08:42:10 -04:00
|
|
|
rc = qra64_do_decode(xdec,ix,pcodec->apmask_cqqrz, pcodec->apmsg_cqqrz);
|
2016-11-27 10:50:47 -05:00
|
|
|
if (rc>=0)
|
|
|
|
return 1; // decoded [cq/qrz ? ?]
|
2016-11-05 20:53:47 -04:00
|
|
|
|
2016-07-18 08:42:10 -04:00
|
|
|
rc = qra64_do_decode(xdec, ix, pcodec->apmask_cqqrz_ooo,
|
|
|
|
pcodec->apmsg_cqqrz);
|
2016-11-27 10:50:47 -05:00
|
|
|
if (rc>=0)
|
|
|
|
// check that ooo really matches
|
|
|
|
if (grid_match(pcodec->apmsg_cqqrz,xdec))
|
|
|
|
return 2; // decoded [cq/qrz ? ooo]
|
2016-07-18 08:42:10 -04:00
|
|
|
|
|
|
|
// 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);
|
2016-11-27 10:50:47 -05:00
|
|
|
if (rc>=0)
|
|
|
|
// check that mycall really matches
|
|
|
|
if (call1_match(pcodec->apmsg_call1,xdec))
|
|
|
|
return 3; // decoded [mycall ? ?]
|
|
|
|
|
2016-07-18 08:42:10 -04:00
|
|
|
rc = qra64_do_decode(xdec, ix, pcodec->apmask_call1_ooo,
|
|
|
|
pcodec->apmsg_call1);
|
2016-11-27 10:50:47 -05:00
|
|
|
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]
|
2016-07-18 08:42:10 -04:00
|
|
|
}
|
|
|
|
|
2016-11-27 10:50:47 -05:00
|
|
|
// attempt to decode [mycall hiscall ?] msgs
|
2016-07-18 08:42:10 -04:00
|
|
|
if (pcodec->apmsg_set[APTYPE_BOTHCALLS]) {
|
|
|
|
rc = qra64_do_decode(xdec, ix, pcodec->apmask_call1_call2,
|
|
|
|
pcodec->apmsg_call1_call2);
|
2016-11-27 10:50:47 -05:00
|
|
|
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 ?]
|
2016-07-18 08:42:10 -04:00
|
|
|
}
|
|
|
|
|
2016-07-21 08:38:26 -04:00
|
|
|
// attempt to decode [? hiscall ?/b] msgs
|
2016-07-18 08:42:10 -04:00
|
|
|
if (pcodec->apmsg_set[APTYPE_HISCALL]) {
|
|
|
|
rc = qra64_do_decode(xdec, ix, pcodec->apmask_call2,
|
|
|
|
pcodec->apmsg_call2);
|
2016-11-27 10:50:47 -05:00
|
|
|
if (rc>=0)
|
|
|
|
// check that hiscall really match
|
|
|
|
if (call2_match(pcodec->apmsg_call2,xdec))
|
|
|
|
return 6; // decoded [? hiscall ?]
|
|
|
|
|
2016-07-18 08:42:10 -04:00
|
|
|
rc = qra64_do_decode(xdec, ix, pcodec->apmask_call2_ooo,
|
|
|
|
pcodec->apmsg_call2);
|
2016-11-27 10:50:47 -05:00
|
|
|
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]
|
2016-07-18 08:42:10 -04:00
|
|
|
}
|
|
|
|
|
2016-07-21 08:38:26 -04:00
|
|
|
// 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);
|
2016-11-27 10:50:47 -05:00
|
|
|
if (rc>=0)
|
|
|
|
// check that hiscall matches
|
|
|
|
if (call2_match(pcodec->apmsg_call2,xdec))
|
|
|
|
return 9; // decoded [cq/qrz hiscall ?]
|
2016-07-21 08:38:26 -04:00
|
|
|
|
|
|
|
rc = qra64_do_decode(xdec, ix, pcodec->apmask_cq_call2_ooo,
|
2016-10-07 08:27:22 -04:00
|
|
|
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
|
2016-11-27 10:50:47 -05:00
|
|
|
if (memcmp(pcodec->apmsg_cq_call2_grid,xdec, QRA64_K*sizeof(int))==0)
|
2016-11-05 20:53:47 -04:00
|
|
|
return 11; // decoded [cq/qrz hiscall grid]
|
2016-11-27 10:50:47 -05:00
|
|
|
}
|
2016-07-21 08:38:26 -04:00
|
|
|
|
|
|
|
rc = qra64_do_decode(xdec, ix, pcodec->apmask_cq_call2_ooo,
|
|
|
|
pcodec->apmsg_cq_call2);
|
2016-10-07 08:27:22 -04:00
|
|
|
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
|
2016-11-27 10:50:47 -05:00
|
|
|
if (memcmp(pcodec->apmsg_cq_call2,xdec, QRA64_K*sizeof(int))==0)
|
2016-11-05 20:53:47 -04:00
|
|
|
return 10; // decoded [cq/qrz hiscall ]
|
|
|
|
}
|
2016-07-21 08:38:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// attempt to decode [mycall hiscall grid]
|
2016-07-18 08:42:10 -04:00
|
|
|
if (pcodec->apmsg_set[APTYPE_FULL]) {
|
|
|
|
rc = qra64_do_decode(xdec, ix, pcodec->apmask_call1_call2_grid,
|
2016-10-07 08:27:22 -04:00
|
|
|
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
|
2016-11-27 10:50:47 -05:00
|
|
|
if (memcmp(pcodec->apmsg_call1_call2_grid,xdec, QRA64_K*sizeof(int))==0)
|
2016-11-05 20:53:47 -04:00
|
|
|
return 8; // decoded [mycall hiscall grid]
|
|
|
|
}
|
2016-07-18 08:42:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// all decoding attempts failed
|
2016-11-27 10:50:47 -05:00
|
|
|
return -1;
|
2016-06-21 11:07:24 -04:00
|
|
|
}
|
|
|
|
|
2016-11-05 20:53:47 -04:00
|
|
|
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
// Decode with given a-priori information
|
2016-07-18 08:42:10 -04:00
|
|
|
static int qra64_do_decode(int *xdec, const float *pix, const int *ap_mask,
|
2016-06-24 15:54:34 -04:00
|
|
|
const int *ap_x)
|
2016-06-21 11:07:24 -04:00
|
|
|
{
|
2016-06-24 15:54:34 -04:00
|
|
|
int rc;
|
|
|
|
const float *ixsrc;
|
2016-07-02 08:15:41 -04:00
|
|
|
float ix_masked[QRA64_NC*QRA64_M]; // Masked intrinsic information
|
|
|
|
float ex[QRA64_NC*QRA64_M]; // Extrinsic information from the decoder
|
2016-06-24 15:54:34 -04:00
|
|
|
|
2016-07-02 08:15:41 -04:00
|
|
|
float v2cmsg[QRA64_NMSG*QRA64_M]; // buffers for the decoder messages
|
|
|
|
float c2vmsg[QRA64_NMSG*QRA64_M];
|
2016-06-24 15:54:34 -04:00
|
|
|
|
|
|
|
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
|
2016-07-02 08:15:41 -04:00
|
|
|
rc = qra_extrinsic(&QRA64_CODE,ex,ixsrc,QRA64_NITER,v2cmsg,c2vmsg);
|
2016-06-24 15:54:34 -04:00
|
|
|
if (rc<0)
|
|
|
|
return -1; // no convergence in given iterations
|
|
|
|
|
|
|
|
// decode
|
2016-07-02 08:15:41 -04:00
|
|
|
qra_mapdecode(&QRA64_CODE,xdec,ex,ixsrc);
|
2016-06-24 15:54:34 -04:00
|
|
|
|
|
|
|
// verify crc
|
2016-07-02 08:15:41 -04:00
|
|
|
if (calc_crc6(xdec,QRA64_K)!=xdec[QRA64_K]) // crc doesn't match (detected error)
|
2016-06-24 15:54:34 -04:00
|
|
|
return -2; // decoding was succesfull but crc doesn't match
|
|
|
|
|
|
|
|
return 0;
|
2016-06-21 11:07:24 -04:00
|
|
|
}
|
2016-11-05 20:53:47 -04:00
|
|
|
|
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
// crc functions --------------------------------------------------------------
|
2016-06-21 11:07:24 -04:00
|
|
|
// 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
|
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
// g(x) = x^6 + x^2 + x + 1 (See: https://users.ece.cmu.edu/~koopman/crc/)
|
2016-06-21 11:07:24 -04:00
|
|
|
// #define CRC6_GEN_POL 0x38 // MSB=a0 LSB=a5. Simulation results are similar
|
|
|
|
|
|
|
|
static int calc_crc6(const int *x, int sz)
|
|
|
|
{
|
2016-06-24 15:54:34 -04:00
|
|
|
// 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;
|
2016-06-21 11:07:24 -04:00
|
|
|
}
|
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
static void ix_mask(float *dst, const float *src, const int *mask,
|
|
|
|
const int *x)
|
2016-06-21 11:07:24 -04:00
|
|
|
{
|
2016-06-24 15:54:34 -04:00
|
|
|
// mask intrinsic information (channel observations) with a priori knowledge
|
2016-06-21 11:07:24 -04:00
|
|
|
|
2016-06-24 15:54:34 -04:00
|
|
|
int k,kk, smask;
|
|
|
|
float *row;
|
|
|
|
|
2016-07-02 08:15:41 -04:00
|
|
|
memcpy(dst,src,(QRA64_NC*QRA64_M)*sizeof(float));
|
2016-06-24 15:54:34 -04:00
|
|
|
|
2016-07-02 08:15:41 -04:00
|
|
|
for (k=0;k<QRA64_K;k++) { // we can mask only information symbols distrib
|
2016-06-24 15:54:34 -04:00
|
|
|
smask = mask[k];
|
2016-07-02 08:15:41 -04:00
|
|
|
row = PD_ROWADDR(dst,QRA64_M,k);
|
2016-06-24 15:54:34 -04:00
|
|
|
if (smask) {
|
2016-07-02 08:15:41 -04:00
|
|
|
for (kk=0;kk<QRA64_M;kk++)
|
2016-06-24 15:54:34 -04:00
|
|
|
if (((kk^x[k])&smask)!=0)
|
|
|
|
*(row+kk) = 0.f;
|
|
|
|
|
2016-07-02 08:15:41 -04:00
|
|
|
pd_norm(row,QRA64_m);
|
2016-06-24 15:54:34 -04:00
|
|
|
}
|
|
|
|
}
|
2016-06-21 11:07:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// encode/decode msgs as done in JT65
|
|
|
|
void encodemsg_jt65(int *y, const int call1, const int call2, const int grid)
|
|
|
|
{
|
2016-06-24 15:54:34 -04:00
|
|
|
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;
|
2016-06-21 11:07:24 -04:00
|
|
|
|
|
|
|
}
|
|
|
|
void decodemsg_jt65(int *call1, int *call2, int *grid, const int *x)
|
|
|
|
{
|
2016-06-24 15:54:34 -04:00
|
|
|
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;
|
2016-06-21 11:07:24 -04:00
|
|
|
}
|