/*
* High Speed modem to transfer data in a 2,7kHz SSB channel
* =========================================================
* Author: DJ0ABR
*
*   (c) DJ0ABR
*   www.dj0abr.de
*
*   This program is free software; you can redistribute it and/or modify
*   it under the terms of the GNU General Public License as published by
*   the Free Software Foundation; either version 2 of the License, or
*   (at your option) any later version.
*
*   This program is distributed in the hope that it will be useful,
*   but WITHOUT ANY WARRANTY; without even the implied warranty of
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*   GNU General Public License for more details.
*
*   You should have received a copy of the GNU General Public License
*   along with this program; if not, write to the Free Software
*   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* 
* liquid_if.c ... functions using liquid-dsp
* 
* liquid-dsp must be previously installed by running ./liquid-dsp-install (under linux)
* 
*/

#include "hsmodem.h"

void modulator(uint8_t sym_in);
void init_demodulator();
void close_demodulator();
void init_modulator();
void close_modulator();

void init_dsp()
{
    printf("init DSP\n");
    init_modulator();
    io_fifo_clear(io_pbidx);
    io_fifo_clear(io_capidx);
    init_demodulator();
}

void close_dsp()
{
    close_modulator();
    close_demodulator();
}

modulation_scheme getMod()
{
    if (bitsPerSymbol == 1)
        return LIQUID_MODEM_BPSK;
    if(bitsPerSymbol == 2)
        return LIQUID_MODEM_QPSK;
    if (bitsPerSymbol == 3)
        return LIQUID_MODEM_APSK8;

    return LIQUID_MODEM_QPSK;
}

// =========== MODULATOR ==================================================

// modem objects
modem mod = NULL;

// NCOs for mixing baseband <-> 1500 Hz
#define FREQUENCY           1500            
int      type        = LIQUID_NCO;                  // nco type
nco_crcf upnco = NULL;

// TX-Interpolator Filter Parameters
// 44100 input rate for 2205 Sym/s = 20
// change for other rates
firinterp_crcf TX_interpolator = NULL;
unsigned int k_SampPerSymb =            20;         // 44100 / (4410/2)
unsigned int m_filterDelay_Symbols =    15;         // not too short for good filter
float        beta_excessBW  =           0.2f;      // filter excess bandwidth factor
float        tau_FracSymbOffset   =     -0.2f;      // fractional symbol offset

void init_modulator()
{
    close_dsp();
    printf("init TX modulator\n");
    
    k_SampPerSymb = txinterpolfactor;
    
    // create modulator
    mod = modem_create(getMod());
    
    // create NCO for upmixing to 1500 Hz
    float RADIANS_PER_SAMPLE   = ((2.0f*(float)M_PI*(float)FREQUENCY)/(float)caprate);

    upnco = nco_crcf_create(LIQUID_NCO);
    nco_crcf_set_phase(upnco, 0.0f);
    nco_crcf_set_frequency(upnco, RADIANS_PER_SAMPLE);
    
    // TX: Interpolator Filter
    // compute delay
    while (tau_FracSymbOffset < 0) tau_FracSymbOffset += 1.0f;  // ensure positive tau
    float g = k_SampPerSymb*tau_FracSymbOffset;                 // number of samples offset
    int ds=(int)floorf(g);               // additional symbol delay
    float dt = (g - (float)ds);     // fractional sample offset
    // force dt to be in [0.5,0.5]
    if (dt > 0.5f) 
    {                
        dt -= 1.0f;
        ds++;
    }
    
    // calculate filter coeffs
    unsigned int h_len_NumFilterCoeefs = 2 * k_SampPerSymb * m_filterDelay_Symbols + 1;
    float h[4000];
    if (h_len_NumFilterCoeefs >= 4000)
    {
        printf("h in h_len_NumFilterCoeefs too small, need %d\n", h_len_NumFilterCoeefs);
        return;
    }
    liquid_firdes_prototype(    LIQUID_FIRFILT_RRC,
                                k_SampPerSymb,
                                m_filterDelay_Symbols,
                                beta_excessBW,
                                dt,
                                h);
    // create the filter
    TX_interpolator = firinterp_crcf_create(k_SampPerSymb,h,h_len_NumFilterCoeefs);
    
    printf("DSP created\n");
    return;
}

void close_modulator()
{
    if(mod != NULL) modem_destroy(mod);
    if(upnco != NULL) nco_crcf_destroy(upnco);
    if(TX_interpolator != NULL) firinterp_crcf_destroy(TX_interpolator);
    mod = NULL;
    upnco = NULL;
    TX_interpolator = NULL;
}

// d ... symbols to send
// len ... number of symbols in d
void sendToModulator(uint8_t *d, int len)
{
    if(upnco == NULL) return;
    
    int symanz = len * 8 / bitsPerSymbol;
    uint8_t syms[10000]; 
    if (symanz >= 10000)
    {
        printf("syms in symanz too small\n");
        return;
    }

    if (bitsPerSymbol == 1)
        convertBytesToSyms_BPSK(d, syms, len);
    else if (bitsPerSymbol == 2)
        convertBytesToSyms_QPSK(d, syms, len);
    else if (bitsPerSymbol == 3)
        convertBytesToSyms_8PSK(d, syms, len);

    for(int i=0; i<symanz; i++)
    {
        // remove gray code
        // this adds gray code, liquid adds it again which removes it
        if(bitsPerSymbol == 2) syms[i] ^= (syms[i]>>1);
        
        modulator(syms[i]);
    }
}

// call for every symbol
// modulates, filters and upmixes symbols and send it to soundcard
void modulator(uint8_t sym_in)
{
    liquid_float_complex sample;
    modem_modulate(mod, sym_in, &sample);
    
    //printf("TX ================= sample: %f + i%f\n", sample.real, sample.imag);
    
    // interpolate by k_SampPerSymb
    liquid_float_complex y[400];
    if (k_SampPerSymb >= 400)
    {
        printf("y in k_SampPerSymb too small, need %d\n", k_SampPerSymb);
        return;
    }

    firinterp_crcf_execute(TX_interpolator, sample, y);

    for(unsigned int i=0; i<k_SampPerSymb; i++)
    {
        // move sample to 1,5kHz carrier
        nco_crcf_step(upnco);

        liquid_float_complex c;
        nco_crcf_mix_up(upnco,y[i],&c);
        float usb = c.real + c.imag;
    
        // adapt speed to soundcard samplerate
        int fs;
        int to = 0;
        while(keeprunning)
        {
            fs = io_fifo_freespace(io_pbidx);
            // wait until there is space in fifo
            if(fs > 10) break;
            sleep_ms(10);
            if (++to >= 400) break; // give up after 4s 
        }

        if (marker)
            usb += singleFrequency() / 4.0f;

        // reduce volume and send to soundcard
        usb *= 0.1f;
        kmaudio_playsamples(io_pbidx, &usb, 1, pbvol);
    }
}

// =========== DEMODULATOR =============================

nco_crcf dnnco = NULL;
firdecim_crcf decim = NULL;

// decimator parameters
unsigned int m_predec = 8;  // filter delay
float As_predec = 40.0f;    // stop-band att 

// symtrack parameters
int          ftype_st       = LIQUID_FIRFILT_RRC;
unsigned int k_st = 4;       // samples per symbol
unsigned int m_st           = 7;       // filter delay (symbols)
float        beta_st        = beta_excessBW;//0.30f;   // filter excess bandwidth factor
float        bandwidth_st   = 0.9f;  // loop filter bandwidth

uint8_t maxLevel = 0;   // maximum RXlevel over the last x samples in %
uint8_t maxTXLevel = 0; // maximum TXlevel over the last x samples in %

float radians_per_sample = ((2.0f * (float)M_PI * (float)FREQUENCY) / (float)caprate);
float last_radians_per_sample = 0;

SYMTRACK *km_symtrack = NULL;

void init_demodulator()
{
    printf("init RX demodulator\n");
    
    // downmixer oscillator
    radians_per_sample = ((2.0f*(float)M_PI*(float)FREQUENCY)/(float)caprate);
    
    dnnco = nco_crcf_create(LIQUID_NCO);
    nco_crcf_set_phase(dnnco, 0.0f);
    nco_crcf_set_frequency(dnnco, radians_per_sample);
    
    // create pre-decimator
    decim = firdecim_crcf_create_kaiser(rxPreInterpolfactor, m_predec, As_predec);
    firdecim_crcf_set_scale(decim, 1.0f/(float)rxPreInterpolfactor);

    // create symbol tracking synchronizer
    km_symtrack = km_symtrack_cccf_create(ftype_st, k_st, m_st, beta_st, getMod());
    km_symtrack_cccf_set_bandwidth(km_symtrack, bandwidth_st);
}

void close_demodulator()
{
    if(decim != NULL) firdecim_crcf_destroy(decim);
    decim = NULL;
    if (dnnco != NULL) nco_crcf_destroy(dnnco);
    dnnco = NULL;
    if (km_symtrack != NULL) km_symtrack_cccf_destroy(km_symtrack);
    km_symtrack = NULL;
}

void resetModem()
{
    //printf("Reset Symtrack\n");
    if (km_symtrack == NULL) return;
    km_symtrack_cccf_reset(km_symtrack,0xff);
}

// called for Audio-Samples (FFT)
void make_FFTdata(float f)
{
    // send IQ data to FFT for waterfall calculation
    int fftlen = 0;
    uint16_t* fft = make_waterfall(f, &fftlen);
    if (fft != NULL)
    {
        uint8_t txpl[10000];
        if (fftlen > (10000 * 2 + 1))
        {
            printf("FFTdata: txpl too small !!!\n");
            return;
        }

        int bidx = 0;
        txpl[bidx++] = 4;    // type 4: FFT data follows

        int us = 0;
        if (speedmode < 10)
        {
            // Bytes in Fifo
            int bus = io_fifo_usedspace(io_pbidx);
            // Payloads in fifo
            us = bus / (txinterpolfactor * UDPBLOCKLEN * 8 / bitsPerSymbol);
            //printf("bytes:%d blocks:%d\n", bus, us);
        }
        if (speedmode == 10)
        {
            // RTTY
            us = io_fifo_usedspace(io_pbidx);
        }

        if (us > 255 || ann_running == 1) us = 255;
        txpl[bidx++] = us;    // usage of TX fifo

        us = io_fifo_usedspace(io_capidx);
        us /= 400;
        if (us > 255) us = 255;
        txpl[bidx++] = us;    // usage of RX fifo

        txpl[bidx++] = rxlevel_deteced; // RX level present
        txpl[bidx++] = rx_in_sync;

        txpl[bidx++] = kmaudio_maxlevel(io_capidx);
        txpl[bidx++] = kmaudio_maxlevel(io_pbidx);

        txpl[bidx++] = rtty_frequency >> 8;     // rtty qrg by autosync
        txpl[bidx++] = rtty_frequency & 0xff;

        for (int i = 0; i < fftlen; i++)
        {
            txpl[bidx++] = fft[i] >> 8;
            txpl[bidx++] = fft[i] & 0xff;
        }

        sendUDP(appIP, UdpDataPort_ModemToApp, txpl, bidx);
    }
}

#define CONSTPOINTS 400
int demodulator()
{
static liquid_float_complex ccol[500];
static int ccol_idx = 0;
static int16_t const_re[CONSTPOINTS];
static int16_t const_im[CONSTPOINTS];
static int const_idx = 0;

    if(dnnco == NULL) return 0;

    // get available received samples
    float farr[1100];
    int ret = kmaudio_readsamples(io_capidx, farr, 1000, capvol,0);
    if(ret == 0) return 0;

    //measure_speed_bps(ret);

    for (int fanz = 0; fanz < ret; fanz++)
    {
        float f = farr[fanz];
        if (VoiceAudioMode == VOICEMODE_LISTENAUDIOIN)
        {
            kmaudio_playsamples(voice_pbidx, &f, 1,lsvol);
        }

        make_FFTdata(f * 100);

        // if user changed frequency
        if (radians_per_sample != last_radians_per_sample)
        {
            //printf("new QRG. Rad:%f\n", radians_per_sample);
            nco_crcf_set_frequency(dnnco, radians_per_sample);
            last_radians_per_sample = radians_per_sample;
        }

        // downconvert 1,5kHz into baseband, still at soundcard sample rate
        nco_crcf_step(dnnco);

        liquid_float_complex in;
        in.real = f;
        in.imag = f;
        liquid_float_complex c;
        nco_crcf_mix_down(dnnco, in, &c);

        // c is the actual sample, converted to complex and shifted to baseband

        // this is the first decimator. We need to collect rxPreInterpolfactor number of samples
        // then call execute which will give us one decimated sample
        ccol[ccol_idx++] = c;
        if (ccol_idx < rxPreInterpolfactor) continue;
        ccol_idx = 0;

        // we have rxPreInterpolfactor samples in ccol
        liquid_float_complex y;
        firdecim_crcf_execute(decim, ccol, &y);
        // the output of the pre decimator is exactly one sample in y

        unsigned int num_symbols_sync;
        liquid_float_complex syms;
        unsigned int nsym_out;   // demodulated output symbol
        km_symtrack_execute(km_symtrack, y, &syms, &num_symbols_sync, &nsym_out);

        if (num_symbols_sync > 1) printf("symtrack_cccf_execute %d output symbols ???\n", num_symbols_sync);
        if (num_symbols_sync != 0)
        {
            measure_speed_syms(1); // do NOT remove, used for speed display in GUI

            // try to unpack and extract a complete frame
            uint8_t symb = nsym_out;
            if (bitsPerSymbol == 2) symb ^= (symb >> 1);
            GRdata_rxdata(&symb, 1, NULL);

            // send the data "as is" to app for Constellation Diagram
            // collect values until an UDP frame is full
            const_re[const_idx] = (int16_t)(syms.real * 15000.0f);
            const_im[const_idx] = (int16_t)(syms.imag * 15000.0f);
            if (++const_idx >= CONSTPOINTS)
            {
                uint8_t txpl[CONSTPOINTS * sizeof(int16_t) * 2 + 1];
                int idx = 0;
                txpl[idx++] = 5;    // type 5: IQ data follows

                for (int i = 0; i < CONSTPOINTS; i++)
                {
                    txpl[idx++] = (uint8_t)(const_re[i] >> 8);
                    txpl[idx++] = (uint8_t)(const_re[i]);
                    txpl[idx++] = (uint8_t)(const_im[i] >> 8);
                    txpl[idx++] = (uint8_t)(const_im[i]);
                }

                sendUDP(appIP, UdpDataPort_ModemToApp, txpl, sizeof(txpl));

                const_idx = 0;
            }
        }
    }
            
    return 1;
}