[pskmail] Improved FEC for PSK modes

  • From: John Douyere <vk2eta@xxxxxxxxx>
  • To: pskmail@xxxxxxxxxxxxx
  • Date: Mon, 23 Nov 2009 17:33:36 +1100

Hi all,

I have done more work on the FEC option for BPSK modes:

I added a second Viterbi decoder shifted by one bit and compare the
output of both, then I select the output of the one with the less
errors.

Cpu load has not increased significantly, but re-synchronisation is
now fast (typically one to 3 characters).

I have also changed the way we send the preamble and postamble for
PSK: I pass both through the encoder/decoder as this gives the chance
for the encoder and decoder to be fully synchronized before the frame
data and to flush out the remaining characters at the end of the frame
too. This gives a different sound to the preamble and postamble of
this mode compared to the traditional BPSK modes.

I am still doing hard decoding.

The result seems good based on my tests today. Still not the
robustness of the THOR or MFSK for lighting strikes, but much better
at handling white noise and regular qrm it seems.

In my tests, most of the time I could get better results than the BPSK
mode one speed below, which is a fair comparison in terms of
throughput. And certainly much better for the same BPSK mode without
FEC.

PSK500 with FEC gives good results. I even did some tests mobile and
got good copies when generally the quick fades are not very kind to
the PSK modes.

I feel that version is really worth a try. It could give us a nice
speed downgrade/reliability increase for the automatic mode changes.

Attached are two files: psk.h in the src/include directory and psk.cxx
in the src/psk directory. These should work with any source of Fldigi
from at least 3.13AY onwards.

To access these modes, use the QPSK modes on the Fldigi gui, with the
exception of QPSK31 which is PSK500+FEC. This is of course a temporary
and quick solution since we do not use the QPSK modes in Pskmail.

I will also do some comparisons with the Multipsk implementation of
these FEC modes to see if I am not missing something on the coding
gain.

Please let me know of your results if you can test.

Best regards,

John (VK2ETA)
// ----------------------------------------------------------------------------
// psk.h  --  psk modem
//
// Copyright (C) 2006
//              Dave Freese, W1HKJ
//
// This file is part of fldigi.  Adapted from code contained in gmfsk source 
code 
// distribution.
//
// fldigi 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 2 of the License, or
// (at your option) any later version.
//
// fldigi 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 fldigi; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
// ----------------------------------------------------------------------------

#ifndef _PSK_H
#define _PSK_H

#include "complex.h"
#include "modem.h"
#include "globals.h"
#include "viterbi.h"
#include "filters.h"
#include "pskcoeff.h"
#include "pskvaricode.h"
#include "viewpsk.h"
#include "pskeval.h"

//=====================================================================
#define PskSampleRate   (8000)
#define PipeLen                 (64)

#define SNTHRESHOLD 6.0
#define AFCDECAYSLOW 8

#define NUM_FILTERS 3
#define GOERTZEL 288            //96 x 2 must be an integer value

//=====================================================================

class psk : public modem {
private:
// tx & rx
        int                             symbollen;
        bool                    _qpsk;
        double                  phaseacc;
        complex                 prevsymbol;
        unsigned int    shreg;
//VK2ETA FEC
        unsigned int    shreg2;
// rx variables & functions

        C_FIR_filter    *fir1;
        C_FIR_filter    *fir2;
//      C_FIR_filter    *fir3;
        double                  *fir1c;
        double                  *fir2c;
        Cmovavg                 *snfilt;
        Cmovavg                 *imdfilt;

        double                  I1[NUM_FILTERS];
        double                  I2[NUM_FILTERS];
        double                  Q1[NUM_FILTERS];
        double                  Q2[NUM_FILTERS];
        double                  COEF[NUM_FILTERS];
        double                  m_Energy[NUM_FILTERS];
        int                             m_NCount;
        bool                    imdValid;

        encoder                 *enc;
        viterbi                 *dec;
//VK2ETA BPSK+FEC with 2nd Viterbi decoder for comparison
        viterbi                 *dec2;
        int             fecmet;
        int             fecmet2;
        unsigned char symcur[2];
        unsigned char symP1[2];
        int             c1;
        int             c2;
        int             savedc2;

        double                  phase;
        double                  freqerr;
        int                             bits;
        double                  bitclk;
        double                  syncbuf[16];
        double                  scope_pipe[2*PipeLen];//[PipeLen];
        unsigned int    pipeptr;
        unsigned int    dcdshreg;
//VK2ETA FEC
        unsigned int    dcdshreg2;

        int                     dcd;
        int                             dcdbits;
        complex                 quality;
        int                             acquire;

        viewpsk*                pskviewer;
        pskeval*                evalpsk;

        void                    rx_symbol(complex symbol);
        void                    rx_bit(int bit);
//VK2ETA for debugging
        void                    rx_bit2(int bit);

        void                    rx_qpsk(int bits);
        double                  scopedata[16];
// IMD & s/n variables
        double                  k0, k1, k2;
        double                  I11, I12, I21, I22, I31, I32;
        double                  snratio, s2n, imdratio, imd;
        double                  E1, E2, E3;
        double                  afcmetric;
        
//VK2ETA signal quality
        double                  s2n_ncount;
        double                  s2n_sum;
        double                  s2n_sum2;
//VK2ETA FEC for BPSK modes
        bool                    firstbit;
        int                     lastdoublezero;

        
//      complex thirdorder;
// tx variables & functions
        double                  *tx_shape;
        int                     preamble;
        void                    tx_symbol(int sym);
        void                    tx_bit(int bit);
        void                    tx_char(unsigned char c);
        void                    tx_flush();
        void                    update_syncscope();
        void                    signalquality();
        void                    findsignal();
        void                    phaseafc();
        void                    afc();
        void                    coreafc();

        void                    initSN_IMD();
        void                    resetSN_IMD();
        void                    calcSN_IMD(complex z);
        
public:
        psk(trx_mode mode);
        ~psk();
        void init();
        void rx_init();
        void tx_init(SoundBase *sc);
        void restart();
        int rx_process(const double *buf, int len);
        int tx_process();
        void searchDown();
        void searchUp();
};

#endif
// ----------------------------------------------------------------------------
// psk.cxx  --  psk modem
//
// Copyright (C) 2006
//              Dave Freese, W1HKJ
//
// This file is part of fldigi.  Adapted from code contained in gmfsk 
// source code distribution.
// gmfsk Copyright (C) 2001, 2002, 2003
// Tomi Manninen (oh2bns@xxxxxxx)
//
// fldigi 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 2 of the License, or
// (at your option) any later version.
//
// fldigi 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 fldigi; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, 
// Boston, MA  02111-1307  USA
// --------------------------------------------------------------------


#include <config.h>

#include <stdlib.h>
#include <stdio.h>
#include <iomanip>

#include "psk.h"
#include "main.h"
#include "fl_digi.h"
#include "trx.h"
#include "misc.h"
#include "waterfall.h"
#include "configuration.h"
#include "status.h"
#include "viewpsk.h"
#include "pskeval.h"

extern waterfall *wf;

// Change the following for DCD low pass filter adjustment
#define SQLCOEFF 0.01
#define SQLDECAY 50

//=====================================================================

//#define       K               5
//#define       POLY1   0x17
//#define       POLY2   0x19

//VK2ETA QPSK+FEC
#define K       7
#define POLY1   0x6d
#define POLY2   0x4f

#define         EOT     0x04            //EOT ascii character

char pskmsg[80];
viewpsk *pskviewer = (viewpsk *)0;

void psk::tx_init(SoundBase *sc)
{
        scard = sc;
        phaseacc = 0;
        prevsymbol = complex (1.0, 0.0);
        preamble = dcdbits;
        shreg = 0;
        shreg2 = 0;
        videoText();

//VK2ETA FEC
        symcur[0] = symcur[1] = 0;
        symP1[0] = symP1[1] = 0;

}

void psk::rx_init()
{
        phaseacc = 0;
        prevsymbol      = complex (1.0, 0.0);
        quality         = complex (0.0, 0.0);
        shreg = 0;
        shreg2 = 0;
        dcdshreg = 0;
        dcd = 0;
        bitclk = 0;
        freqerr = 0.0;
        if (mailserver && progdefaults.PSKmailSweetSpot) sigsearch = SIGSEARCH;
        put_MODEstatus(mode);
        resetSN_IMD();
        imdValid = false;
        afcmetric = 0.0;

//VK2ETA FEC
        symcur[0] = symcur[1] = 0;
        symP1[0] = symP1[1] = 0;

}

void psk::restart()
{
        pskviewer->restart(mode);
        evalpsk->setbw(bandwidth);
}

void psk::init()
{
        modem::init();
        restart();
        set_scope_mode(Digiscope::PHASE);
        initSN_IMD();
        snratio = 1.0;
        imdratio = 0.001;
        rx_init();
}

psk::~psk()
{
        if (tx_shape) delete [] tx_shape;
        if (enc) delete enc;
        if (dec) delete dec;
        if (fir1) delete fir1;
        if (fir2) delete fir2;
        if (snfilt) delete snfilt;
        if (imdfilt) delete imdfilt;
        if (::pskviewer == pskviewer)
                ::pskviewer = 0;
        delete pskviewer;
        delete evalpsk;
}

psk::psk(trx_mode pskmode) : modem()
{
        cap = CAP_AFC | CAP_AFC_SR;

        mode = pskmode;

        switch (mode) {
        case MODE_BPSK31:
                symbollen = 256;
                _qpsk = false;
                dcdbits = 32;
                break;
        case MODE_QPSK31:
//VK2ETA FEC test replace qpsk31 by BPSK500 + MFEC
//              symbollen = 256;
//              _qpsk = true;
//              dcdbits = 32;
                symbollen = 16;
                _qpsk = true;
                dcdbits = 512;
//VK2ETA BPSK+FEC               cap |= CAP_REV;
                break;
        case MODE_PSK63:
                symbollen = 128;
                _qpsk = false;
                dcdbits = 64;
                break;
        case MODE_QPSK63:
                symbollen = 128;
                _qpsk = true;
                dcdbits = 64;
//VK2ETA BPSK+FEC               cap |= CAP_REV;
                break;
        case MODE_PSK125:
                symbollen = 64;
                _qpsk = false;
                dcdbits = 128;
                break;
        case MODE_QPSK125:
                symbollen = 64;
                _qpsk = true;
                dcdbits = 128;
//VK2ETA BPSK+FEC               cap |= CAP_REV;
                break;
        case MODE_PSK250:
                symbollen = 32;
                _qpsk = false;
                dcdbits = 256;
                break;
        case MODE_QPSK250:
                symbollen = 32;
                _qpsk = true;
                dcdbits = 256;
//VK2ETA BPSK+FEC               cap |= CAP_REV;
                break;
        case MODE_PSK500:
                symbollen = 16;
                _qpsk = false;
                dcdbits = 512;
                break;
        default:
                mode = MODE_BPSK31;
                symbollen = 256;
                _qpsk = false;
                dcdbits = 32;
        }

        enc = (encoder *)0;
        dec = (viterbi *)0;
//VK2ETA BPSK+FEC - 2nd Viterbi decoder for comparison
        dec2 = (viterbi *)0;

        
// create impulse response for experimental FIR filters
        double fir1c[64];
        double fir2c[64];

        fir1 = new C_FIR_filter();
        fir2 = new C_FIR_filter();

    switch (progdefaults.PSK_filter) {
        case 1:
// use the original gmfsk matched filters
                for (int i = 0; i < 64; i++) {
                        fir1c[i] = gmfir1c[i];
                        fir2c[i] = gmfir2c[i];
                }
                fir1->init(FIRLEN, symbollen / 16, fir1c, fir1c);
                fir2->init(FIRLEN, 1, fir2c, fir2c);
            break;
        case 2:
// creates fir1c matched sin(x)/x filter w hamming
                wsincfilt(fir1c, 1.0 / symbollen, false);
                fir1->init(FIRLEN, symbollen / 16, fir1c, fir1c);
// creates fir2c matched sin(x)/x filter w hamming
                wsincfilt(fir2c, 1.0 / 16.0, false);
                fir2->init(FIRLEN, 1, fir2c, fir2c);
            break;
        case 3:
// creates fir1c matched sin(x)/x filter w hamming
                wsincfilt(fir1c, 1.0 / symbollen, false);
                fir1->init(FIRLEN, symbollen / 16, fir1c, fir1c);
// 1/22 with Hamming window nearly identical to gmfir2c
                wsincfilt(fir2c, 1.0 / 22.0, false);
                fir2->init(FIRLEN, 1, fir2c, fir2c);
            break;
        case 4:
            fir1->init_lowpass (FIRLEN, 16, 1.5 / symbollen);
                wsincfilt(fir2c, 1.5 / 16.0, true);
            fir2->init(FIRLEN, 1, fir2c, fir2c);
        case 0:
        default :
// creates fir1c matched sin(x)/x filter w blackman
                wsincfilt(fir1c, 1.0 / symbollen, true);
                fir1->init(FIRLEN, symbollen / 16, fir1c, fir1c);
// creates fir2c matched sin(x)/x filter w blackman
                wsincfilt(fir2c, 1.0 / 16.0, true);
                fir2->init(FIRLEN, 1, fir2c, fir2c);
    }
    
        snfilt = new Cmovavg(16);
        imdfilt = new Cmovavg(16);

        if (_qpsk) {
                enc = new encoder(K, POLY1, POLY2);
                dec = new viterbi(K, POLY1, POLY2);
//VK2ETA FEC for BPSK. Use a 2nd Viterbi decoder for comparison
                dec->setchunksize(4); 
                dec2 = new viterbi(K, POLY1, POLY2);
                dec2->setchunksize(4); 
                firstbit = true;
        }

        tx_shape = new double[symbollen];

        /* raised cosine shape for the transmitter */
        for ( int i = 0; i < symbollen; i++)
                tx_shape[i] = 0.5 * cos(i * M_PI / symbollen) + 0.5;

        samplerate = PskSampleRate;
        fragmentsize = symbollen;
        bandwidth = samplerate / symbollen;
        snratio = s2n = imdratio = imd = 0;

        if (mailserver && progdefaults.PSKmailSweetSpot)
                sigsearch = SIGSEARCH;
        else
                sigsearch = 0;
        for (int i = 0; i < 16; i++)
                syncbuf[i] = 0.0;
        E1 = E2 = E3 = 0.0;
        acquire = 0;

        evalpsk = new pskeval;
        ::pskviewer = pskviewer = new viewpsk(evalpsk, mode);

        init();
}

//=============================================================================
//=========================== psk31 receive routines ==========================
//=============================================================================

void psk::rx_bit(int bit)
{
        int c;

        char qualityfb[100];
        unsigned i,limit;
        static  double s2n_avg;
        static  double s2n_stddev;

        dcdshreg = (dcdshreg << 2) | bit;

        switch (dcdshreg) {
        case 0xAAAAAAAA:        /* DCD on by preamble */
                dcd = true;
                acquire = 0;
//              quality = complex (1.0, 0.0);
                imdValid = true;
//VK2ETA signal report
                s2n_sum = s2n_sum2 = s2n_ncount = 0;
                break;

        case 0:                 /* DCD off by postamble */
//VK2ETA don't cut too soon, we need to get the rest of the decoding
//              dcd = false;
                dcd = true;

                acquire = 0;
                quality = complex (0.0, 0.0);
                imdValid = false;
                break;
        }
        
        shreg = (shreg << 1) | !!bit;
        if ((shreg & 3) == 0) {
                c = psk_varicode_decode(shreg >> 2);
                if (fecmet >= fecmet2) {
                        if ((c != -1) && (dcd == true)) {
//                              put_rx_char('<');
                                put_rx_char(c);
                        }
                } 
                shreg = 0;
        }
}

void psk::rx_bit2(int bit)
{
        int c;

        dcdshreg2 = (dcdshreg2 << 2) | bit;

        switch (dcdshreg2) {
        case 0xAAAAAAAA:        /* DCD on by preamble */
                dcd = true;
                acquire = 0;
//              quality = complex (1.0, 0.0);
                imdValid = true;
//VK2ETA signal report
                s2n_sum = s2n_sum2 = s2n_ncount = 0;
                break;

        case 0:                 /* DCD off by postamble */
//VK2ETA don't cut too soon, we need to get the rest of the decoding
//              dcd = false;
                dcd = true;

                acquire = 0;
                quality = complex (0.0, 0.0);
                imdValid = false;
                break;
        }

        shreg2 = (shreg2 << 1) | !!bit;
        if ((shreg2 & 3) == 0) {
                c = psk_varicode_decode(shreg2 >> 2);
                if (fecmet < fecmet2) {
                        if ((c != -1) && (dcd == true)) {
//                              put_rx_char('>');
                                put_rx_char(c);
                        }
                } 
                shreg2 = 0;
        }
}

void psk::rx_qpsk(int bits)
{
        int met;

        if (firstbit) {
                symP1[1] = symcur[0] = (bits & 1) ? 255 : 0;
                c2 = dec2->decode(symP1, &met);
                if (c2 != -1) fecmet2 = decayavg(fecmet2, met, 10);
                if (c2 != -1) {
//                      rx_bit2(c2 & 0x80);
//                      rx_bit2(c2 & 0x40);
//                      rx_bit2(c2 & 0x20);
//                      rx_bit2(c2 & 0x10);
                        rx_bit2(c2 & 0x08);
                        rx_bit2(c2 & 0x04);
                        rx_bit2(c2 & 0x02);
                        rx_bit2(c2 & 0x01);
                }
        } else {
                symP1[0] = symcur[1] = (bits & 1) ? 255 : 0;
                c1 = dec->decode(symcur, &met);
                if (c1 != -1) fecmet = decayavg(fecmet, met, 10);
                if (c1 != -1) {
//                      rx_bit(c1 & 0x80);
//                      rx_bit(c1 & 0x40);
//                      rx_bit(c1 & 0x20);
//                      rx_bit(c1 & 0x10);
                        rx_bit(c1 & 0x08);
                        rx_bit(c1 & 0x04);
                        rx_bit(c1 & 0x02);
                        rx_bit(c1 & 0x01);
                }
        }       

        firstbit = ! firstbit;
}

void psk::searchDown()
{
        double srchfreq = frequency - bandwidth * 2;
        double minfreq = bandwidth * 2;
        double spwr, npwr;
        while (srchfreq > minfreq) {
                spwr = wf->powerDensity(srchfreq, bandwidth);
                npwr = wf->powerDensity(srchfreq + bandwidth, bandwidth/2) + 
1e-10;
                if (spwr / npwr > pow(10, progdefaults.ServerACQsn / 10)) {
                        frequency = srchfreq;
                        set_freq(frequency);
                        sigsearch = SIGSEARCH;
                        break;
                }
                srchfreq -= bandwidth;
        }
}

void psk::searchUp()
{
        double srchfreq = frequency + bandwidth * 2;
        double maxfreq = IMAGE_WIDTH - bandwidth * 2;
        double spwr, npwr;
        while (srchfreq < maxfreq) {
                spwr = wf->powerDensity(srchfreq, bandwidth/2);
                npwr = wf->powerDensity(srchfreq - bandwidth, bandwidth/2) + 
1e-10;
                if (spwr / npwr > pow(10, progdefaults.ServerACQsn / 10)) {
                        frequency = srchfreq;
                        set_freq(frequency);
                        sigsearch = SIGSEARCH;
                        break;
                }
                srchfreq += bandwidth;
        }
}

int waitcount = 0;

void psk::findsignal()
{
        int ftest, f1, f2;
        
        if (sigsearch > 0) {
                sigsearch--;
                if (mailserver) { // mail server search algorithm
                        if (progdefaults.PSKmailSweetSpot) {
                                f1 = (int)(progdefaults.ServerCarrier - 
progdefaults.ServerOffset);
                                f2 = (int)(progdefaults.ServerCarrier + 
progdefaults.ServerOffset);
                        } else {
                                f1 = (int)(frequency - 
progdefaults.ServerOffset);
                                f2 = (int)(frequency + 
progdefaults.ServerOffset);
                        }
                        if (evalpsk->sigpeak(ftest, f1, f2) > pow(10, 
progdefaults.ServerACQsn / 10) ) {
                                if (progdefaults.PSKmailSweetSpot) {
                                        if (fabs(ftest - 
progdefaults.ServerCarrier) < progdefaults.ServerOffset) {
                                                frequency = ftest;
                                                set_freq(frequency);
                                                freqerr = 0.0;
                                        } else {
                                                frequency = 
progdefaults.ServerCarrier;
                                                set_freq(frequency);
                                                freqerr = 0.0;
                                        }
                                } else {
                                        frequency = ftest;
                                        set_freq(frequency);
                                        freqerr = 0.0;
                                }
                        } else { // less than the detection threshold
                                if (progdefaults.PSKmailSweetSpot) {
                                        frequency = progdefaults.ServerCarrier;
                                        set_freq(frequency);
                                        sigsearch = SIGSEARCH;
                                }
                        }
                } else { // normal signal search algorithm
                        f1 = (int)(frequency - progdefaults.SearchRange/2);
                        f2 = (int)(frequency + progdefaults.SearchRange/2);
                        if (evalpsk->sigpeak(ftest, f1, f2) > pow(10, 
progdefaults.ACQsn / 10.0) ) {
                                frequency = ftest;
                                set_freq(frequency);
                                freqerr = 0.0;
                                sigsearch = 0;
                                acquire = dcdbits;
                        }
                }
        }
}

void psk::phaseafc()
{
        double error;
        if (afcmetric < 0.05) return;
        
        error = (phase - bits * M_PI / 2.0);
        if (error < -M_PI / 2.0 || error > M_PI / 2.0) return;
        error *= samplerate / (TWOPI * symbollen);
        if (fabs(error) < bandwidth ) {
                freqerr = error / dcdbits;
                frequency -= freqerr;
                if (mailserver) {
                        if (frequency < progdefaults.ServerCarrier - 
progdefaults.ServerAFCrange)
                                frequency = progdefaults.ServerCarrier - 
progdefaults.ServerAFCrange;
                        if (frequency > progdefaults.ServerCarrier + 
progdefaults.ServerAFCrange)
                                frequency = progdefaults.ServerCarrier + 
progdefaults.ServerAFCrange;
                }
                set_freq (frequency);
        }
        if (acquire) acquire--;
}

void psk::afc()
{
        if (!progStatus.afconoff)
                return;
        if (dcd == true || acquire)
                phaseafc();
}


void psk::rx_symbol(complex symbol)
{
        int n;
        phase = (prevsymbol % symbol).arg();
        prevsymbol = symbol;

        if (phase < 0) 
                phase += TWOPI;
//VK2ETA BPSK+FEC
//      if (_qpsk) {
//              bits = ((int) (phase / M_PI_2 + 0.5)) & 3;
//              n = 4;
//      } else {
                bits = (((int) (phase / M_PI + 0.5)) & 1) << 1;
                n = 2;
//      }
// simple low pass filter for quality of signal
        quality.re = decayavg(quality.re, cos(n*phase), SQLDECAY);
        quality.im = decayavg(quality.im, sin(n*phase), SQLDECAY);
        
        metric = 100.0 * quality.norm();

        if (_qpsk) {
                metric = metric * 2;
                if (metric > 100)    metric = 100;
        }

        afcmetric = decayavg(afcmetric, quality.norm(), 50);

        if (metric > progStatus.sldrSquelchValue || progStatus.sqlonoff == 
false) {
                dcd = true;
        } else {
                dcd = false;
//VK2ETA   was it     supposed to be here       instead:        imdValid = 
false;
        }
                imdValid = false;

        set_phase(phase, quality.norm(), dcd);

//VK2ETA FEC   always process, then decide to print characters later
//      if (dcd == true) {

                if (_qpsk)
//VK2ETA (reverse the bit for FEC too                   rx_qpsk(bits);
                        rx_qpsk(!bits);
                else
                        rx_bit(!bits);
//      }
        
}

void psk::signalquality()
{ 

        if (m_Energy[1])
                snratio = snfilt->run(m_Energy[0]/m_Energy[1]);
        else
                snratio = snfilt->run(1.0);

        if (m_Energy[0] && imdValid)
                imdratio = imdfilt->run(m_Energy[2]/m_Energy[0]);
        else
                imdratio = imdfilt->run(0.001);

}

void psk::update_syncscope()
{
        static char msg1[15];
        static char msg2[15];

        display_metric(metric);

        s2n = 10.0*log10( snratio );
        snprintf(msg1, sizeof(msg1), "s/n %2d dB", (int)(floor(s2n))); 
        
        imd = 10.0*log10( imdratio );
        snprintf(msg2, sizeof(msg2), "imd %3d dB", (int)(floor(imd))); 

        if (imdValid) {
                put_Status1(msg1, progdefaults.StatusTimeout, 
progdefaults.StatusDim ? STATUS_DIM : STATUS_CLEAR);
                put_Status2(msg2, progdefaults.StatusTimeout, 
progdefaults.StatusDim ? STATUS_DIM : STATUS_CLEAR);
        }
}

char bitstatus[100];

int psk::rx_process(const double *buf, int len)
{
        double delta;
        complex z, z2;

        if (pskviewer && !bHistory) pskviewer->rx_process(buf, len);
        if (evalpsk) evalpsk->sigdensity();
                
        delta = TWOPI * frequency / samplerate;
        
        while (len-- > 0) {
// Mix with the internal NCO
                z = complex ( *buf * cos(phaseacc), *buf * sin(phaseacc) );

                buf++;
                phaseacc += delta;
                if (phaseacc > M_PI)
                        phaseacc -= TWOPI;

// Filter and downsample 
// by 16 (psk31, qpsk31) 
// by  8 (psk63, qpsk63)
// by  4 (psk125, qpsk125)
// by  2 (psk250, qpsk250)
// first filter
                if (fir1->run( z, z )) { // fir1 returns true every Nth sample
// final filter
                        fir2->run( z, z2 ); // fir2 returns value on every 
sample
                        calcSN_IMD(z);
                        
//                      fir3->run( z, z3);
//                      coreafc(z3);
                                                
                        int idx = (int) bitclk;
                        double sum = 0.0;
                        double ampsum = 0.0;
                        syncbuf[idx] = 0.8 * syncbuf[idx] + 0.2 * z2.mag();
                        
                        for (int i = 0; i < 8; i++) {
                                sum += (syncbuf[i] - syncbuf[i+8]);
                                ampsum += (syncbuf[i] + syncbuf[i+8]);
                        }
// added correction as per PocketDigi
                        sum = (ampsum == 0 ? 0 : sum / ampsum);
                        
                        bitclk -= sum / 5.0;
                        bitclk += 1;
                        
                        if (bitclk < 0) bitclk += 16.0;
                        if (bitclk >= 16.0) {
                                bitclk -= 16.0;
                                rx_symbol(z2);
                                update_syncscope();
                                afc();
                        }
                }
        }
        
        if (sigsearch)
                findsignal();
        else if (mailserver) {
                if (waitcount > 0) {
                        --waitcount;
                        if (waitcount == 0) {
                                if (progdefaults.PSKmailSweetSpot) {
                                        frequency = progdefaults.PSKsweetspot;
                                        set_freq(frequency);
                                }                               
                                sigsearch = SIGSEARCH;
                        }
                }
                else if ( E1/ E2 <= 1.0) { //(snratio <= 1.0) {
                        waitcount = 8;
                        sigsearch = 0;
                }
        }
        return 0;
}

//=====================================================================
// transmit processes
//=====================================================================

void psk::tx_symbol(int sym)
{
        double delta;
        double  ival, qval, shapeA, shapeB;
        complex symbol;

//VK2ETA BPSK+FEC
//      if (_qpsk && !reverse)
//              sym = (4 - sym) & 3;

        /* differential QPSK modulation - top bit flipped */
        switch (sym) {
        case 0:
                symbol = complex (-1.0, 0.0);   // 180 degrees
                break;
        case 1:
                symbol = complex (0.0, -1.0);   // 270 degrees
                break;
        case 2:
                symbol = complex (1.0, 0.0);            //   0 degrees
                break;
        case 3:
                symbol = complex (0.0, 1.0);            //  90 degrees
                break;
        }
        symbol = prevsymbol * symbol;   // complex multiplication

        delta = 2.0 * M_PI * get_txfreq_woffset() / samplerate;

        for (int i = 0; i < symbollen; i++) {
                
                shapeA = tx_shape[i];
                shapeB = (1.0 - shapeA);
                
                ival = shapeA * prevsymbol.real() + shapeB * symbol.real();
                qval = shapeA * prevsymbol.imag() + shapeB * symbol.imag();
                
                outbuf[i] = ival * cos(phaseacc) + qval * sin(phaseacc);
                
                phaseacc += delta;
                if (phaseacc > M_PI)
                        phaseacc -= 2.0 * M_PI;
        }

        ModulateXmtr(outbuf, symbollen);

        prevsymbol = symbol;
}

void psk::tx_bit(int bit)
{
        unsigned int sym;

//VK2ETA PSK+FEC Send encoded bit(s) (result is two bits to send sequencially 
since R = 1/2)
        unsigned int savedsym;

        if (_qpsk) {
                savedsym = enc->encode(bit);
                sym = (savedsym & 1) << 1;
                tx_symbol(sym);
                sym = savedsym & 2;
                tx_symbol(sym);
        } else {
                sym = bit << 1;
                tx_symbol(sym);
        }
}

void psk::tx_char(unsigned char c)
{
        const char *code;

        code = psk_varicode_encode(c);

        while (*code) {
                tx_bit((*code - '0'));
                code++;
        }
        tx_bit(0);
        tx_bit(0);
}

void psk::tx_flush()
{
// flush the encoder (QPSK only)
        if (_qpsk) {
                for (int i = 0; i < dcdbits; i++)
                tx_bit(0);
//VK2ETA FEC : replace unmodulated carrier by an encoded sequence of zeros
        } else {

// DCD off sequence (unmodulated carrier)
        for (int i = 0; i < dcdbits; i++)
                tx_symbol(2);
        }
}

int psk::tx_process()
{
        int c;

        if (preamble > 0) {
                if (_qpsk) {
//VK2ETA FEC prep the encoder with one/zero sequences of bits
                        preamble--;
                        preamble--;
                        tx_bit(1);
                        tx_bit(0);
//VK2ETA FEC Mark start of first character with a double zero
                        if (preamble == 0)  tx_bit(0);
                        return 0;
                } else{
                        preamble--;
                        tx_symbol(0);   /* send phase reversals */
                        return 0;
                }
        }

        c = get_tx_char();

        if (c == 0x03 || stopflag) {
                tx_flush();
                stopflag = false;
                cwid();
                return -1;      /* we're done */
        }

        if (c == -1)
                tx_bit(0);
        else {
                tx_char(c);
                put_echo_char(c);
        }
        return 0;
}

//============================================================================
// psk signal evaluation
// using Goertzel IIR filter
// derived from pskcore by Moe Wheatley, AE4JY
//============================================================================

void psk::initSN_IMD()
{
        for(int i = 0; i < NUM_FILTERS; i++)
        {
                I1[i] = I2[i] = Q1[i] = Q2[i] = 0.0;
                m_Energy[i] = 0.0;
        }
        m_NCount = 0;
        
        COEF[0] = 2.0 * cos(TWOPI * 9 / GOERTZEL);
        COEF[1] = 2.0 * cos(TWOPI * 18 / GOERTZEL);
        COEF[2] = 2.0 * cos(TWOPI  * 27 / GOERTZEL);
}

void psk::resetSN_IMD()
{
        for(int i = 0; i < NUM_FILTERS; i++) {
                I1[i] = I2[i] = Q1[i] = Q2[i] = 0.0;
        }
        m_NCount = 0;
}

//============================================================================
//  This routine calculates the energy in the frequency bands of
//   carrier=F0(15.625), noise=F1(31.25), and 
//   3rd order product=F2(46.875)
//  It is called with complex data samples at 500 Hz.
//============================================================================

void psk::calcSN_IMD(complex z)
{
        int i;
        complex temp;

        for(i = 0; i < NUM_FILTERS; i++) {
                temp.re = I1[i]; temp.im = Q1[i];
                I1[i] = I1[i] * COEF[i]- I2[i] + z.re;
                Q1[i] = Q1[i] * COEF[i]- Q2[i] + z.im;
                I2[i] = temp.re; Q2[i] = temp.im;
        }

        if( ++m_NCount >= GOERTZEL ) {
                m_NCount = 0;
                for(i = 0; i < NUM_FILTERS; i++) {
                        m_Energy[i] =   I1[i]*I1[i] + Q1[i]*Q1[i] 
                                      + I2[i]*I2[i] + Q2[i]*Q2[i] 
                                                  - I1[i]*I2[i]*COEF[i]
                                                  - Q1[i]*Q2[i]*COEF[i];
                        I1[i] = I2[i] = Q1[i] = Q2[i] = 0.0;
                }
                signalquality();
        }
}

Other related posts: