/*---------------------------------------------------------------------------*\

  FILE........: freedv_api.c
  AUTHOR......: David Rowe
  DATE CREATED: August 2014

  Library of API functions that implement the FreeDV API, useful for
  embedding FreeDV in other programs.  Please see:

  1. README_freedv.md
  2. Notes on function use in this file
  3. Simple demo programs in the "demo" directory
  4. The full featured command line freedv_tx.c and freedv_rx.c programs

\*---------------------------------------------------------------------------*/

/*
  Copyright (C) 2014 David Rowe

  All rights reserved.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License version 2.1, as
  published by the Free Software Foundation.  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 Lesser General Public License
  along with this program; if not, see <http://www.gnu.org/licenses/>.
*/

#include "freedv_api.h"

#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "codec2.h"
#include "codec2_fdmdv.h"
#include "codec2_ofdm.h"
#include "comp_prim.h"
#include "debug_alloc.h"
#include "fdmdv_internal.h"
#include "fmfsk.h"
#include "freedv_api_internal.h"
#include "freedv_vhf_framing.h"
#include "fsk.h"
#include "gp_interleaver.h"
#include "interldpc.h"
#include "mpdecode_core.h"
#include "ofdm_internal.h"
#include "varicode.h"

/* The API version number.  The first version is 10.  Increment if the API
   changes in a way that would require changes by the API user. */

#define VERSION 15

/*
  Version 10   Initial version August 2, 2015.

  Version 11   September 2015
               Added: freedv_zero_total_bit_errors(), freedv_get_sync()
               Changed all input and output sample rates to 8000 sps.  Rates
               for FREEDV_MODE_700 and 700B were 7500.

  Version 12   August 2018 Added OFDM configuration switch structure

  Version 13   November 2019 Removed 700 and 700B modes

  Version 14   May 2020 Number of returned speech samples can vary,  use
               freedv_get_n_max_speech_samples() to allocate buffers.

  Version 15   December 2022 Removing rarely used DPSK support which is not
               needed given fast fading modes
 */

char *ofdm_statemode[] = {"search", "trial", "synced"};

char *rx_sync_flags_to_text[] = {"----", "---T", "--S-", "--ST", "-B--", "-B-T",
                                 "-BS-", "-BST", "E---", "E--T", "E-S-", "E-ST",
                                 "EB--", "EB-T", "EBS-", "EBST"};

/*---------------------------------------------------------------------------* \

  FUNCTION....: freedv_open
  AUTHOR......: David Rowe
  DATE CREATED: 3 August 2014

  Call this first to initialise.  Returns NULL if initialisation
  fails. If a malloc() or calloc() fails in general asserts() will
  fire.

\*---------------------------------------------------------------------------*/

struct freedv *freedv_open(int mode) {
  // defaults for those modes that support the use of adv
  struct freedv_advanced adv = {0, 2, 100, 8000, 1000, 200, "H_256_512_4"};
  return freedv_open_advanced(mode, &adv);
}

struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv) {
  struct freedv *f;

  assert(FREEDV_PEAK == OFDM_PEAK);
  assert(FREEDV_VARICODE_MAX_BITS == VARICODE_MAX_BITS);

  if ((FDV_MODE_ACTIVE(FREEDV_MODE_1600, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_700C, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_700D, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_700E, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_2400A, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_2400B, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_800XA, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_2020, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_2020B, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_DATAC0, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, mode) ||
       FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode)) == false)
    return NULL;

  /* set everything to zero just in case */
  f = (struct freedv *)CALLOC(1, sizeof(struct freedv));
  if (f == NULL) return NULL;

  f->mode = mode;

  if (FDV_MODE_ACTIVE(FREEDV_MODE_1600, mode)) freedv_1600_open(f);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700C, mode)) freedv_700c_open(f);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700D, mode))
    freedv_ofdm_voice_open(f, "700D");
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700E, mode))
    freedv_ofdm_voice_open(f, "700E");
#ifdef __LPCNET__
  if (FDV_MODE_ACTIVE(FREEDV_MODE_2020, mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2020B, mode))
    freedv_2020x_open(f);
#endif
  if (FDV_MODE_ACTIVE(FREEDV_MODE_2400A, mode)) freedv_2400a_open(f);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_2400B, mode)) freedv_2400b_open(f);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_800XA, mode)) freedv_800xa_open(f);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, mode)) freedv_fsk_ldpc_open(f, adv);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC0, mode)) freedv_ofdm_data_open(f);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, mode)) freedv_ofdm_data_open(f);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, mode)) freedv_ofdm_data_open(f);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, mode)) freedv_ofdm_data_open(f);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode)) freedv_ofdm_data_open(f);

  varicode_decode_init(&f->varicode_dec_states, 1);

  return f;
}

/*---------------------------------------------------------------------------*\

  FUNCTION....: freedv_close
  AUTHOR......: David Rowe
  DATE CREATED: 3 August 2014

  Call to shut down a freedv instance and free memory.

\*---------------------------------------------------------------------------*/

void freedv_close(struct freedv *freedv) {
  assert(freedv != NULL);

  FREE(freedv->tx_payload_bits);
  FREE(freedv->rx_payload_bits);
  if (freedv->codec2) codec2_destroy(freedv->codec2);

  if (FDV_MODE_ACTIVE(FREEDV_MODE_1600, freedv->mode)) {
    FREE(freedv->fdmdv_bits);
    FREE(freedv->fdmdv_tx_bits);
    FREE(freedv->fdmdv_rx_bits);
    fdmdv_destroy(freedv->fdmdv);
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_700C, freedv->mode)) {
    cohpsk_destroy(freedv->cohpsk);
    quisk_filt_destroy(freedv->ptFilter8000to7500);
    FREE(freedv->ptFilter8000to7500);
    quisk_filt_destroy(freedv->ptFilter7500to8000);
    FREE(freedv->ptFilter7500to8000);
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_700D, freedv->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_700E, freedv->mode)) {
    FREE(freedv->rx_syms);
    FREE(freedv->rx_amps);
    FREE(freedv->ldpc);
    ofdm_destroy(freedv->ofdm);
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_2020, freedv->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2020B, freedv->mode)) {
    FREE(freedv->codeword_symbols);
    FREE(freedv->codeword_amps);
    FREE(freedv->ldpc);
    FREE(freedv->passthrough_2020);
    ofdm_destroy(freedv->ofdm);
#ifdef __LPCNET__
    lpcnet_freedv_destroy(freedv->lpcnet);
#endif
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_2400A, freedv->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_800XA, freedv->mode)) {
    fsk_destroy(freedv->fsk);
    fvhff_destroy_deframer(freedv->deframer);
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_2400B, freedv->mode)) {
    fmfsk_destroy(freedv->fmfsk);
    fvhff_destroy_deframer(freedv->deframer);
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, freedv->mode)) {
    fsk_destroy(freedv->fsk);
    FREE(freedv->ldpc);
    FREE(freedv->frame_llr);
    FREE(freedv->twoframes_llr);
    FREE(freedv->twoframes_hard);
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC0, freedv->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, freedv->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, freedv->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, freedv->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, freedv->mode)) {
    FREE(freedv->rx_syms);
    FREE(freedv->rx_amps);
    FREE(freedv->ldpc);
    ofdm_destroy(freedv->ofdm);
  }

  FREE(freedv);
}

/* helper function, unpacked bits are much easier to work with inside the modem
 */

static void codec2_encode_upacked(struct freedv *f, uint8_t unpacked_bits[],
                                  short speech_in[]) {
  int n_packed = (f->bits_per_codec_frame + 7) / 8;
  uint8_t packed_codec_bits[n_packed];

  codec2_encode(f->codec2, packed_codec_bits, speech_in);
  freedv_unpack(unpacked_bits, packed_codec_bits, f->bits_per_codec_frame);
}

static int is_ofdm_mode(struct freedv *f) {
  return FDV_MODE_ACTIVE(FREEDV_MODE_2020, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_2020B, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_700D, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_700E, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_DATAC0, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode);
}

static int is_ofdm_data_mode(struct freedv *f) {
  return FDV_MODE_ACTIVE(FREEDV_MODE_DATAC0, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode);
}

/*---------------------------------------------------------------------------*\

  FUNCTION....: freedv_tx
  AUTHOR......: David Rowe
  DATE CREATED: 3 August 2014

  Takes a frame of input speech samples, encodes and modulates them to
  produce a frame of modem samples that can be sent to the
  transmitter.  See demo/freedv_700d_tx.c for an example.

  speech_in[] is sampled at freedv_get_speech_sample_rate() Hz, and
  the user must supply exactly freedv_get_n_speech_samples(). The peak
  level should be between +/- 16384 and +/- 32767.

  The modem signal mod_out[] is sampled at
  freedv_get_modem_sample_rate() and is always exactly
  freedv_get_n_nom_modem_samples() long.  mod_out[] will be scaled
  such that the peak level is around +/-16384.

  mod_out[] has a higher RMS power than SSB with the same peak level.
  In other words, the crest factor or peak to average power ratio is
  lower than typical SSB voice.  Ensure your transmitter is capable of
  continuous high RMS power operation, or consider reducing Tx power.

\*---------------------------------------------------------------------------*/

/* real-valued short output */

void freedv_tx(struct freedv *f, short mod_out[], short speech_in[]) {
  assert(f != NULL);
  COMP tx_fdm[f->n_nom_modem_samples];
  int i;

  /* FSK and MEFSK/FMFSK modems work only on real samples. It's simpler to just
   * stick them in the real sample tx/rx functions than to add a comp->real
   * converter to comptx */

  if ((FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode)) ||
      (FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode)) ||
      (FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode))) {
    /* 800XA has two codec frames per modem frame */
    if (FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode)) {
      codec2_encode(f->codec2, &f->tx_payload_bits[0], &speech_in[0]);
      codec2_encode(f->codec2, &f->tx_payload_bits[4], &speech_in[320]);
    } else {
      codec2_encode(f->codec2, f->tx_payload_bits, speech_in);
    }
    freedv_tx_fsk_voice(f, mod_out);
  } else {
    freedv_comptx(f, tx_fdm, speech_in);
    for (i = 0; i < f->n_nom_modem_samples; i++) mod_out[i] = tx_fdm[i].real;
  }
}

/* complex float output version of freedv_tx() */

void freedv_comptx(struct freedv *f, COMP mod_out[], short speech_in[]) {
  assert(f != NULL);

  assert(FDV_MODE_ACTIVE(FREEDV_MODE_1600, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_700C, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_700D, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_700E, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_2020, f->mode) ||
         FDV_MODE_ACTIVE(FREEDV_MODE_2020B, f->mode));

  if (FDV_MODE_ACTIVE(FREEDV_MODE_1600, f->mode)) {
    codec2_encode_upacked(f, f->tx_payload_bits, speech_in);
    freedv_comptx_fdmdv_1600(f, mod_out);
  }

  /* all these modes need to pack a bunch of codec frames into one modem frame
   * ... */

  if (FDV_MODE_ACTIVE(FREEDV_MODE_700C, f->mode)) {
    for (int j = 0; j < f->n_codec_frames; j++) {
      codec2_encode_upacked(f, f->tx_payload_bits + j * f->bits_per_codec_frame,
                            speech_in);
      speech_in += codec2_samples_per_frame(f->codec2);
    }
    freedv_comptx_700c(f, mod_out);
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_700D, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_700E, f->mode)) {
    /* buffer up bits until we get enough encoded bits for interleaver */

    for (int j = 0; j < f->n_codec_frames; j++) {
      int offset = j * f->bits_per_codec_frame;
      codec2_encode_upacked(f, f->tx_payload_bits + offset, speech_in);
      speech_in += codec2_samples_per_frame(f->codec2);
    }

    freedv_comptx_ofdm(f, mod_out);
  }

#ifdef __LPCNET__
  if (FDV_MODE_ACTIVE(FREEDV_MODE_2020, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2020B, f->mode)) {
    /* buffer up bits until we get enough encoded bits for interleaver */

    for (int j = 0; j < f->n_codec_frames; j++) {
      int offset = j * f->bits_per_codec_frame;
      lpcnet_enc(f->lpcnet, speech_in, (char *)f->tx_payload_bits + offset);
      speech_in += lpcnet_samples_per_frame(f->lpcnet);
    }

    freedv_comptx_2020(f, mod_out);
  }
#endif

  /* 2400 A and B are handled by the real-mode TX */
  if (FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode)) {
    codec2_encode(f->codec2, f->tx_payload_bits, speech_in);
    freedv_comptx_fsk_voice(f, mod_out);
  }
}

/* pack bits */
void freedv_pack(uint8_t *bytes, uint8_t *bits, int nbits) {
  memset(bytes, 0, (nbits + 7) / 8);
  int bit = 7, byte = 0;
  for (int i = 0; i < nbits; i++) {
    bytes[byte] |= bits[i] << bit;
    bit--;
    if (bit < 0) {
      bit = 7;
      byte++;
    }
  }
}

/* unpack bits, MSB first */
void freedv_unpack(uint8_t *bits, uint8_t *bytes, int nbits) {
  int bit = 7, byte = 0;
  for (int i = 0; i < nbits; i++) {
    bits[i] = (bytes[byte] >> bit) & 0x1;
    bit--;
    if (bit < 0) {
      bit = 7;
      byte++;
    }
  }
}

/* compute the CRC16 of a frame of unpacked bits */
unsigned short freedv_crc16_unpacked(unsigned char unpacked_bits[], int nbits) {
  assert((nbits % 8) == 0);
  int nbytes = nbits / 8;
  uint8_t packed_bytes[nbytes];
  freedv_pack(packed_bytes, unpacked_bits, nbits);
  return freedv_gen_crc16(packed_bytes, nbytes);
}

/* Return non-zero if CRC16 of a frame of unpacked bits is correct */
int freedv_check_crc16_unpacked(unsigned char unpacked_bits[], int nbits) {
  assert((nbits % 8) == 0);
  int nbytes = nbits / 8;
  uint8_t packed_bytes[nbytes];
  freedv_pack(packed_bytes, unpacked_bits, nbits);
  uint16_t tx_crc16 =
      (packed_bytes[nbytes - 2] << 8) | packed_bytes[nbytes - 1];
  uint16_t rx_crc16 = freedv_crc16_unpacked(unpacked_bits, nbits - 16);
  return tx_crc16 == rx_crc16;
}

/* send raw frames of bytes, or speech data that was compressed externally,
 * complex float output */
void freedv_rawdatacomptx(struct freedv *f, COMP mod_out[],
                          unsigned char *packed_payload_bits) {
  assert(f != NULL);

  freedv_unpack(f->tx_payload_bits, packed_payload_bits,
                f->bits_per_modem_frame);

  if (FDV_MODE_ACTIVE(FREEDV_MODE_1600, f->mode))
    freedv_comptx_fdmdv_1600(f, mod_out);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700C, f->mode))
    freedv_comptx_700c(f, mod_out);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700D, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC0, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode))
    freedv_comptx_ofdm(f, mod_out);

  if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, f->mode)) {
    freedv_tx_fsk_ldpc_data(f, mod_out);
  }
}

/* send raw frames of bytes, or speech data that was compressed externally, real
 * short output */
void freedv_rawdatatx(struct freedv *f, short mod_out[],
                      unsigned char *packed_payload_bits) {
  assert(f != NULL);
  COMP mod_out_comp[f->n_nat_modem_samples];

  /* Some FSK modes used packed bits, and coincidentally support real samples
   * natively */
  if (FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode)) {
    freedv_codec_frames_from_rawdata(f, f->tx_payload_bits,
                                     packed_payload_bits);
    freedv_tx_fsk_voice(f, mod_out);
    return; /* output is already real */
  }

  freedv_rawdatacomptx(f, mod_out_comp, packed_payload_bits);

  /* convert complex to real */
  for (int i = 0; i < f->n_nat_modem_samples; i++)
    mod_out[i] = mod_out_comp[i].real;
}

int freedv_rawdatapreamblecomptx(struct freedv *f, COMP mod_out[]) {
  assert(f != NULL);
  int npreamble_samples = 0;

  if (f->mode == FREEDV_MODE_FSK_LDPC) {
    struct FSK *fsk = f->fsk;

    int npreamble_symbols = 50 * (fsk->mode >> 1);
    int npreamble_bits = npreamble_symbols * (fsk->mode >> 1);
    npreamble_samples = fsk->Ts * npreamble_symbols;
    // fprintf(stderr, "npreamble_symbols: %d npreamble_bits: %d
    // npreamble_samples: %d Nbits: %d N: %d\n", npreamble_symbols,
    // npreamble_bits, npreamble_samples, fsk->Nbits, fsk->N);

    assert(npreamble_samples <
           f->n_nom_modem_samples); /* caller probably using an array of this
                                       size */
    freedv_tx_fsk_ldpc_data_preamble(f, mod_out, npreamble_bits,
                                     npreamble_samples);
  } else if (is_ofdm_data_mode(f)) {
    struct OFDM *ofdm = f->ofdm;
    complex float *tx_preamble = (complex float *)mod_out;
    memcpy(tx_preamble, ofdm->tx_preamble,
           sizeof(COMP) * ofdm->samplesperframe);
    ofdm_hilbert_clipper(ofdm, tx_preamble, ofdm->samplesperframe);
    npreamble_samples = ofdm->samplesperframe;
  }

  return npreamble_samples;
}

int freedv_rawdatapreambletx(struct freedv *f, short mod_out[]) {
  assert(f != NULL);
  COMP mod_out_comp[f->n_nat_modem_samples];

  int npreamble_samples = freedv_rawdatapreamblecomptx(f, mod_out_comp);
  assert(npreamble_samples <= f->n_nat_modem_samples);

  /* convert complex to real */
  for (int i = 0; i < npreamble_samples; i++) mod_out[i] = mod_out_comp[i].real;

  return npreamble_samples;
}

int freedv_rawdatapostamblecomptx(struct freedv *f, COMP mod_out[]) {
  assert(f != NULL);
  int npostamble_samples = 0;

  if (is_ofdm_data_mode(f)) {
    struct OFDM *ofdm = f->ofdm;
    complex float *tx_postamble = (complex float *)mod_out;
    memcpy(tx_postamble, ofdm->tx_postamble,
           sizeof(COMP) * ofdm->samplesperframe);
    ofdm_hilbert_clipper(ofdm, tx_postamble, ofdm->samplesperframe);
    npostamble_samples = ofdm->samplesperframe;
  }

  return npostamble_samples;
}

int freedv_rawdatapostambletx(struct freedv *f, short mod_out[]) {
  assert(f != NULL);
  COMP mod_out_comp[f->n_nat_modem_samples];

  int npostamble_samples = freedv_rawdatapostamblecomptx(f, mod_out_comp);
  assert(npostamble_samples <= f->n_nat_modem_samples);

  /* convert complex to real */
  for (int i = 0; i < npostamble_samples; i++)
    mod_out[i] = mod_out_comp[i].real;

  return npostamble_samples;
}

/* VHF packet data tx function */
void freedv_datatx(struct freedv *f, short mod_out[]) {
  assert(f != NULL);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode)) {
    freedv_tx_fsk_data(f, mod_out);
  }
}

/* VHF packet data: returns how many tx frames are queued up but not sent yet */
int freedv_data_ntxframes(struct freedv *f) {
  assert(f != NULL);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode)) {
    if (f->deframer->fdc)
      return freedv_data_get_n_tx_frames(f->deframer->fdc, 8);
  } else if (FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode)) {
    if (f->deframer->fdc)
      return freedv_data_get_n_tx_frames(f->deframer->fdc, 6);
  }
  return 0;
}

int freedv_nin(struct freedv *f) {
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700C, f->mode))
    // For mode 700C, the input rate is 8000 sps, but the modem rate is 7500 sps
    // For mode 700C, we request a larger number of Rx samples that will be
    // decimated to f->nin samples
    return (16 * f->nin + f->ptFilter8000to7500->decim_index) / 15;
  else
    return f->nin;
}

int freedv_codec_frames_from_rawdata(struct freedv *f,
                                     unsigned char *codec_frames,
                                     unsigned char *rawdata) {
  int cbit = 7;
  int cbyte = 0;
  int rbit = 7;
  int rbyte = 0;
  int modem_bits = freedv_get_bits_per_modem_frame(f);
  int codec_bits = freedv_get_bits_per_codec_frame(f);
  int nr_cbits = 0;
  int i;

  codec_frames[0] = 0;
  for (i = 0; i < modem_bits; i++) {
    codec_frames[cbyte] |= ((rawdata[rbyte] >> rbit) & 1) << cbit;

    rbit--;
    if (rbit < 0) {
      rbit = 7;
      rbyte++;
    }

    cbit--;
    if (cbit < 0) {
      cbit = 7;
      cbyte++;
      codec_frames[cbyte] = 0;
    }
    nr_cbits++;
    if (nr_cbits == codec_bits) {
      if (cbit) {
        cbyte++;
        codec_frames[cbyte] = 0;
      }
      cbit = 7;
      nr_cbits = 0;
    }
  }
  return f->n_codec_frames;
}

int freedv_rawdata_from_codec_frames(struct freedv *f, unsigned char *rawdata,
                                     unsigned char *codec_frames) {
  int cbit = 7;
  int cbyte = 0;
  int rbit = 7;
  int rbyte = 0;
  int modem_bits = freedv_get_bits_per_modem_frame(f);
  int codec_bits = freedv_get_bits_per_codec_frame(f);
  int nr_cbits = 0;
  int i;

  rawdata[rbyte] = 0;
  for (i = 0; i < modem_bits; i++) {
    rawdata[rbyte] |= ((codec_frames[cbyte] >> cbit) & 1) << rbit;

    rbit--;
    if (rbit < 0) {
      rbit = 7;
      rbyte++;
      rawdata[rbyte] = 0;
    }

    cbit--;
    if (cbit < 0) {
      cbit = 7;
      cbyte++;
    }

    nr_cbits++;
    if (nr_cbits == codec_bits) {
      if (cbit) cbyte++;
      cbit = 7;
      nr_cbits = 0;
    }
  }
  return f->n_codec_frames;
}

/*---------------------------------------------------------------------------*\

  FUNCTION....: freedv_rx
  AUTHOR......: David Rowe
  DATE CREATED: 3 August 2014

  Takes samples from the radio receiver and decodes
  them, producing a frame of decoded speech samples.  See
  demo/freedv_700d_rx.c for an example.

  demod_in[] is an array of received samples sampled at
  freedv_get_modem_sample_rate().  To account for difference in the
  transmit and receive sample clock frequencies, the number of
  demod_in[] samples is time varying. You MUST call freedv_nin()
  BEFORE EACH call to freedv_rx() and pass exactly that many samples
  to this function:

  short demod_in[freedv_get_n_max_modem_samples(f)];
  short speech_out[freedv_get_n_max_speech_samples(f)];

  nin = freedv_nin(f);  // num input samples for first read
  while(fread(demod_in, sizeof(short), nin, fin) == nin) {
      nout = freedv_rx(f, speech_out, demod_in);
      fwrite(speech_out, sizeof(short), nout, fout);
      nin = freedv_nin(f); // num input samples for next read
  }

  To help set your buffer sizes, The maximum value of freedv_nin() is
  freedv_get_n_max_modem_samples().

  freedv_rx() returns the number of output speech samples available in
  speech_out[], which is sampled at freedv_get_speech_sample_rate().
  You should ALWAYS check the return value of freedv_rx(), and read
  EXACTLY that number of speech samples from speech_out[].

  Not every call to freedv_rx will return speech samples; in some
  modes several modem frames are processed before speech samples are
  returned.  When squelch is active, zero samples may be returned.

  The peak level of demod_in[] is not critical, as the demod works
  well over a wide range of amplitude scaling.  However avoid clipping
  (overload, or samples pinned to +/- 32767).  speech_out[] will peak
  at just less than +/-32767.

  When squelch is disabled, this function echoes the demod_in[]
  samples to speech_out[].  This allows the user to listen to the
  channel, which is useful for tuning FreeDV signals or reception of
  non-FreeDV signals.

\*---------------------------------------------------------------------------*/

int freedv_rx(struct freedv *f, short speech_out[], short demod_in[]) {
  assert(f != NULL);
  int i;
  int nin = freedv_nin(f);
  f->nin_prev = nin;

  assert(nin <= f->n_max_modem_samples);

  /* FSK Rx happens in real floats, so convert to those and call their demod
   * here */
  if (FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode)) {
    float rx_float[f->n_max_modem_samples];
    for (i = 0; i < nin; i++) {
      rx_float[i] = ((float)demod_in[i]);
    }
    return freedv_floatrx(f, speech_out, rx_float);
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_1600, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_700C, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2020, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2020B, f->mode)) {
    float gain = 1.0f;

    assert(nin <= f->n_max_modem_samples);
    COMP rx_fdm[f->n_max_modem_samples];

    for (i = 0; i < nin; i++) {
      rx_fdm[i].real = gain * (float)demod_in[i];
      rx_fdm[i].imag = 0.0f;
    }
    return freedv_comprx(f, speech_out, rx_fdm);
  }

  /* special low memory version for 700D, to help with stm32 port */
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700D, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_700E, f->mode)) {
    float gain = 2.0f; /* keep levels the same as Octave simulations and C unit
                          tests for real signals */
    return freedv_shortrx(f, speech_out, demod_in, gain);
  }

  assert(1); /* should never get here */
  return 0;
}

/* complex sample input version of freedv_rx() */

int freedv_comprx(struct freedv *f, short speech_out[], COMP demod_in[]) {
  assert(f != NULL);
  assert(f->nin <= f->n_max_modem_samples);
  int rx_status = 0;
  f->nin_prev = freedv_nin(f);

  if (FDV_MODE_ACTIVE(FREEDV_MODE_1600, f->mode)) {
    rx_status = freedv_comprx_fdmdv_1600(f, demod_in);
  }
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700C, f->mode)) {
    rx_status = freedv_comprx_700c(f, demod_in);
  }

  if ((FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode)) ||
      (FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode)) ||
      (FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode))) {
    rx_status = freedv_comprx_fsk(f, demod_in);
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_700D, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_700E, f->mode)) {
    rx_status =
        freedv_comp_short_rx_ofdm(f, (void *)demod_in, 0, 2.0f);  // was 1.0 ??
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_2020, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2020B, f->mode)) {
#ifdef __LPCNET__
    rx_status = freedv_comprx_2020(f, demod_in);
#endif
  }

  short demod_in_short[f->nin_prev];

  for (int i = 0; i < f->nin_prev; i++) demod_in_short[i] = demod_in[i].real;

  return freedv_bits_to_speech(f, speech_out, demod_in_short, rx_status);
}

/* memory efficient real short version - just for 700D on the SM1000 */

int freedv_shortrx(struct freedv *f, short speech_out[], short demod_in[],
                   float gain) {
  assert(f != NULL);
  int rx_status = 0;
  f->nin_prev = f->nin;

  // At this stage short interface only supported for 700D, to help
  // memory requirements on stm32
  assert((f->mode == FREEDV_MODE_700D) || (f->mode == FREEDV_MODE_700E));
  assert(f->nin <= f->n_max_modem_samples);

  if (FDV_MODE_ACTIVE(FREEDV_MODE_700D, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_700E, f->mode)) {
    rx_status = freedv_comp_short_rx_ofdm(f, (void *)demod_in, 1, gain);
  }

  return freedv_bits_to_speech(f, speech_out, demod_in, rx_status);
}

/* helper function, unpacked bits are much easier to work with inside the modem
 */

static void codec2_decode_upacked(struct freedv *f, short speech_out[],
                                  uint8_t unpacked_bits[]) {
  int n_packed = (f->bits_per_codec_frame + 7) / 8;
  uint8_t packed_codec_bits[n_packed];

  freedv_pack(packed_codec_bits, unpacked_bits, f->bits_per_codec_frame);
  codec2_decode(f->codec2, speech_out, packed_codec_bits);
}

/*---------------------------------------------------------------------------* \

  FUNCTION....: freedv_rx_bits_to_speech
  AUTHOR......: David Rowe
  DATE CREATED: May 2020

  The *_rx functions takes off air samples, demodulates and (for some
  modes) FEC decodes, giving us a frame of bits.

  This function captures a lot of tricky logic that has been distilled
  through experience:

  There may not be a frame of bits returned on every call freedv_*rx* call.
  When there are valid bits we need to run the speech decoder.
  We may not have demod sync, so various pass through options may happen
  with the input samples.
  We may squelch based on SNR.
  Need to handle various codecs, and varying number of codec frames per modem
  frame.
  Squelch audio if test frames are being sent.
  Determine how many speech samples to return, which will vary if in
  sync/out of sync Work with real and complex inputs (complex wrapper)
  Attenuate audio on pass through.
  Deal with 700D first frame burble, and different sync states from OFDM
  modes like 700D.
  Output no samples if squelched, we assume it's OK for the audio sink to run
  dry.
  A FIFO is required on output to smooth sample flow to audio sink.
  Don't decode when we are sending test frames

\*---------------------------------------------------------------------------*/

int freedv_bits_to_speech(struct freedv *f, short speech_out[],
                          short demod_in[], int rx_status) {
  int nout = 0;
  int decode_speech = 0;
  if ((rx_status & FREEDV_RX_SYNC) == 0) {
    if (!f->squelch_en) {
      /* pass through received samples so we can hear what's going on, e.g.
       * during tuning */

      if ((f->mode == FREEDV_MODE_2020) || (f->mode == FREEDV_MODE_2020B)) {
        /* 8kHz modem sample rate but 16 kHz speech sample
           rate, so we need to resample */
        nout = 2 * f->nin_prev;
        assert(nout <= freedv_get_n_max_speech_samples(f));
        float tmp[nout];
        for (int i = 0; i < nout / 2; i++)
          f->passthrough_2020[FDMDV_OS_TAPS_16K + i] = demod_in[i];
        fdmdv_8_to_16(tmp, &f->passthrough_2020[FDMDV_OS_TAPS_16K], nout / 2);
        for (int i = 0; i < nout; i++)
          speech_out[i] = f->passthrough_gain * tmp[i];
      } else {
        /* Speech and modem rates might be different */
        int rate_factor = f->modem_sample_rate / f->speech_sample_rate;
        nout = f->nin_prev / rate_factor;
        for (int i = 0; i < nout; i++)
          speech_out[i] = f->passthrough_gain * demod_in[i * rate_factor];
      }
    }
  }

  if ((rx_status & FREEDV_RX_SYNC) && (rx_status & FREEDV_RX_BITS) &&
      !f->test_frames) {
    /* following logic is tricky so spell it out clearly, see table
       in: https://github.com/drowe67/codec2/pull/111 */

    if (!f->squelch_en) {
      decode_speech = 1;
    } else {
      /* squelch is enabled */

      /* anti-burble case - don't decode on trial sync unless the
         frame has no bit errors.  This prevents short lived trial
         sync cases generating random bursts of audio */
      if (rx_status & FREEDV_RX_TRIAL_SYNC) {
        if ((rx_status & FREEDV_RX_BIT_ERRORS) == 0) decode_speech = 1;
      } else {
        /* sync is solid - decode even through fades as there is still some
         * speech info there */
        if (f->snr_est > f->snr_squelch_thresh) decode_speech = 1;
      }
    }
  }

  if (decode_speech) {
    if (FDV_MODE_ACTIVE(FREEDV_MODE_2020, f->mode) ||
        FDV_MODE_ACTIVE(FREEDV_MODE_2020B, f->mode)) {
#ifdef __LPCNET__
      /* LPCNet decoder */

      int bits_per_codec_frame = lpcnet_bits_per_frame(f->lpcnet);
      int data_bits_per_frame = f->ldpc->data_bits_per_frame;
      int frames = data_bits_per_frame / bits_per_codec_frame;

      nout = f->n_speech_samples;
      for (int i = 0; i < frames; i++) {
        lpcnet_dec(f->lpcnet,
                   (char *)f->rx_payload_bits + i * bits_per_codec_frame,
                   speech_out);
        /* ear protection: on frames with errors and clipping, reduce level by
         * 12dB */
        if (rx_status & FREEDV_RX_BIT_ERRORS) {
          int max = 0.0;
          for (int j = 0; j < lpcnet_samples_per_frame(f->lpcnet); j++)
            if (abs(speech_out[j]) > max) max = abs(speech_out[j]);
          if (max == 32767)
            for (int j = 0; j < lpcnet_samples_per_frame(f->lpcnet); j++)
              speech_out[j] *= 0.25;
        }

        speech_out += lpcnet_samples_per_frame(f->lpcnet);
      }

#endif
    } else {
      /* codec 2 decoder */

      if (FDV_MODE_ACTIVE(FREEDV_MODE_700D, f->mode) ||
          FDV_MODE_ACTIVE(FREEDV_MODE_700E, f->mode)) {
        nout = f->n_speech_samples;
        for (int i = 0; i < f->n_codec_frames; i++) {
          codec2_decode_upacked(
              f, speech_out, f->rx_payload_bits + i * f->bits_per_codec_frame);
          speech_out += codec2_samples_per_frame(f->codec2);
        }
      } else {
        /* non-interleaved Codec 2 modes */

        nout = f->n_speech_samples;
        if ((FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode)) ||
            (FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode)))
          codec2_decode(f->codec2, speech_out, f->rx_payload_bits);
        else if (FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode)) {
          codec2_decode(f->codec2, &speech_out[0], &f->rx_payload_bits[0]);
          codec2_decode(f->codec2, &speech_out[320], &f->rx_payload_bits[4]);
        } else {
          for (int i = 0; i < f->n_codec_frames; i++) {
            codec2_decode_upacked(
                f, speech_out,
                f->rx_payload_bits + i * f->bits_per_codec_frame);
            speech_out += codec2_samples_per_frame(f->codec2);
          }
        }
      }
    }
  }

  if (f->verbose == 3) {
    fprintf(stderr, "    sqen: %d nout: %d decsp: %d\n", f->squelch_en, nout,
            decode_speech);
  }

  f->rx_status = rx_status;
  assert(nout <= freedv_get_n_max_speech_samples(f));
  return nout;
}

/* a way to receive raw frames of bytes, or speech data that will be
 * decompressed externally */
int freedv_rawdatarx(struct freedv *f, unsigned char *packed_payload_bits,
                     short demod_in[]) {
  assert(f != NULL);
  int nin = freedv_nin(f);
  assert(nin <= f->n_max_modem_samples);
  COMP demod_in_comp[f->n_max_modem_samples];

  for (int i = 0; i < nin; i++) {
    demod_in_comp[i].real = (float)demod_in[i];
    demod_in_comp[i].imag = 0.0;
  }

  return freedv_rawdatacomprx(f, packed_payload_bits, demod_in_comp);
}

/* a way to receive raw frames of bytes, or speech data that will be
 * decompressed externally */
int freedv_rawdatacomprx(struct freedv *f, unsigned char *packed_payload_bits,
                         COMP demod_in[]) {
  assert(f != NULL);
  int ret = 0;
  int rx_status = 0;

  /* FSK modes used packed bits internally */
  if (FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode)) {
    rx_status = freedv_comprx_fsk(f, demod_in);
    f->rx_status = rx_status;
    if (rx_status & FREEDV_RX_BITS) {
      ret = (freedv_get_bits_per_modem_frame(f) + 7) / 8;
      freedv_rawdata_from_codec_frames(f, packed_payload_bits,
                                       f->rx_payload_bits);
    }
    return ret;
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_1600, f->mode))
    rx_status = freedv_comprx_fdmdv_1600(f, demod_in);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700C, f->mode))
    rx_status = freedv_comprx_700c(f, demod_in);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700D, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC0, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode))
    rx_status = freedv_comp_short_rx_ofdm(f, (void *)demod_in, 0, 1.0f);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, f->mode)) {
    rx_status = freedv_rx_fsk_ldpc_data(f, demod_in);
  }

  if (rx_status & FREEDV_RX_BITS) {
    ret = (f->bits_per_modem_frame + 7) / 8;
    freedv_pack(packed_payload_bits, f->rx_payload_bits,
                f->bits_per_modem_frame);
  }

  /* might want to check this for errors, e.g. if reliable data is important */
  f->rx_status = rx_status;

  return ret;
}

/*---------------------------------------------------------------------------* \

  FUNCTION....: freedv_get_version
  AUTHOR......: Jim Ahlstrom
  DATE CREATED: 28 July 2015

  Return the version of the FreeDV API.  This is meant to help API
  users determine when incompatible changes have occurred.

\*---------------------------------------------------------------------------*/

int freedv_get_version(void) { return VERSION; }

/*---------------------------------------------------------------------------* \

  FUNCTION....: freedv_get_hash
  AUTHOR......: David Rowe
  DATE CREATED: July 2020

  Return the a string with the Git hash of the repo used to build this code.

\*---------------------------------------------------------------------------*/

static char git_hash[] = GIT_HASH;
char *freedv_get_hash(void) { return git_hash; }

/*---------------------------------------------------------------------------*\

  FUNCTION....: freedv_set_callback_txt
  AUTHOR......: Jim Ahlstrom
  DATE CREATED: 28 July 2015

  Set the callback functions and the callback state pointer that will
  be used for the aux txt channel.  The freedv_callback_rx is a
  function pointer that will be called to return received characters.
  The freedv_callback_tx is a function pointer that will be called to
  send transmitted characters.  The callback state is a user-defined
  void pointer that will be passed to the callback functions.  Any or
  all can be NULL, and the default is all NULL.

  The function signatures are:
    void receive_char(void *callback_state, char c);
    char transmit_char(void *callback_state);

\*---------------------------------------------------------------------------*/

void freedv_set_callback_txt(struct freedv *f, freedv_callback_rx rx,
                             freedv_callback_tx tx, void *state) {
  if (FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode) == false) {
    f->freedv_put_next_rx_char = rx;
    f->freedv_get_next_tx_char = tx;
    f->callback_state = state;
  }
}

/*---------------------------------------------------------------------------*\

  FUNCTION....: freedv_set_callback_txt_sym
  AUTHOR......: Mooneer Salem
  DATE CREATED: 19 August 2021

  Set the callback functions and the callback state pointer that will
  be used to provide the raw symbols for the aux txt channel. The
  freedv_callback_rx_sym is a function pointer that will be called to
  return received symbols. The callback state is a user-defined
  void pointer that will be passed to the callback function.  Any or
  all can be NULL, and the default is all NULL.

  The function signature is:
    void receive_sym(void *callback_state, COMP sym, COMP amp);

  Note: Active for OFDM modes only (700D/E, 2020).
\*---------------------------------------------------------------------------*/

void freedv_set_callback_txt_sym(struct freedv *f, freedv_callback_rx_sym rx,
                                 void *state) {
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700D, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_700E, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2020, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2020B, f->mode)) {
    f->freedv_put_next_rx_symbol = rx;
    f->callback_state_sym = state;
  }
}

/*---------------------------------------------------------------------------*\

  FUNCTION....: freedv_set_callback_protocol
  AUTHOR......: Brady OBrien
  DATE CREATED: 21 February 2016

  VHF packet data function.

  Set the callback functions and callback pointer that will be used
  for the protocol data channel. freedv_callback_protorx will be
  called when a frame containing protocol data
  arrives. freedv_callback_prototx will be called when a frame
  containing protocol information is being generated. Protocol
  information is intended to be used to develop protocols and fancy
  features atop VHF freedv, much like those present in DMR.  Protocol
  bits are to be passed in an msb-first char array The number of
  protocol bits are findable with freedv_get_protocol_bits

\*---------------------------------------------------------------------------*/

void freedv_set_callback_protocol(struct freedv *f, freedv_callback_protorx rx,
                                  freedv_callback_prototx tx,
                                  void *callback_state) {
  if (FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode) == false) {
    f->freedv_put_next_proto = rx;
    f->freedv_get_next_proto = tx;
    f->proto_callback_state = callback_state;
  }
}

/*---------------------------------------------------------------------------*\

  FUNCTION....: freedv_set_callback_datarx / freedv_set_callback_datatx
  AUTHOR......: Jeroen Vreeken
  DATE CREATED: 04 March 2016

  VHF packet data function.

  Set the callback functions and callback pointer that will be used
  for the data channel. freedv_callback_datarx will be called when a
  packet has been successfully received. freedv_callback_data_tx will
  be called when transmission of a new packet can begin.  If the
  returned size of the datatx callback is zero the data frame is still
  generated, but will contain only a header update.

\*---------------------------------------------------------------------------*/

void freedv_set_callback_data(struct freedv *f, freedv_callback_datarx datarx,
                              freedv_callback_datatx datatx,
                              void *callback_state) {
  if ((FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode)) ||
      (FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode)) ||
      (FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode))) {
    if (!f->deframer->fdc) f->deframer->fdc = freedv_data_channel_create();
    if (!f->deframer->fdc) return;

    freedv_data_set_cb_rx(f->deframer->fdc, datarx, callback_state);
    freedv_data_set_cb_tx(f->deframer->fdc, datatx, callback_state);
  }
}

/*---------------------------------------------------------------------------*\

  FUNCTION....: freedv_set_data_header
  AUTHOR......: Jeroen Vreeken
  DATE CREATED: 04 March 2016

  VHF packet data function.

  Set the data header for the data channel.  Header compression will
  be used whenever packets from this header are sent.  The header will
  also be used for fill packets when a data frame is requested without
  a packet available.

\*---------------------------------------------------------------------------*/

void freedv_set_data_header(struct freedv *f, unsigned char *header) {
  if ((FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode)) ||
      (FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode)) ||
      (FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode))) {
    if (!f->deframer->fdc) f->deframer->fdc = freedv_data_channel_create();
    if (!f->deframer->fdc) return;

    freedv_data_set_header(f->deframer->fdc, header);
  }
}

/*---------------------------------------------------------------------------*\

  FUNCTION....: freedv_get_modem_stats
  AUTHOR......: Jim Ahlstrom
  DATE CREATED: 28 July 2015

  Return data from the modem.  The arguments are pointers to the data
  items.  The pointers can be NULL if the data item is not wanted.

\*---------------------------------------------------------------------------*/

void freedv_get_modem_stats(struct freedv *f, int *sync, float *snr_est) {
  if (FDV_MODE_ACTIVE(FREEDV_MODE_1600, f->mode))
    fdmdv_get_demod_stats(f->fdmdv, &f->stats);
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700C, f->mode))
    cohpsk_get_demod_stats(f->cohpsk, &f->stats);
  if (sync) *sync = f->sync;
  if (snr_est) *snr_est = f->snr_est;
}

/*---------------------------------------------------------------------------*\

  FUNCTIONS...: freedv_set_*
  AUTHOR......: Jim Ahlstrom
  DATE CREATED: 28 July 2015

  Set some parameters used by FreeDV.  It is possible to write a macro
  using ## for this, but I wasn't sure it would be 100% portable.

\*---------------------------------------------------------------------------*/

void freedv_set_test_frames(struct freedv *f, int val) { f->test_frames = val; }
void freedv_set_test_frames_diversity(struct freedv *f, int val) {
  f->test_frames_diversity = val;
}
void freedv_set_squelch_en(struct freedv *f, bool val) { f->squelch_en = val; }
void freedv_set_total_bit_errors(struct freedv *f, int val) {
  f->total_bit_errors = val;
}
void freedv_set_total_bits(struct freedv *f, int val) { f->total_bits = val; }
void freedv_set_total_bit_errors_coded(struct freedv *f, int val) {
  f->total_bit_errors_coded = val;
}
void freedv_set_total_bits_coded(struct freedv *f, int val) {
  f->total_bits_coded = val;
}
void freedv_set_total_packet_errors(struct freedv *f, int val) {
  f->total_packet_errors = val;
}
void freedv_set_total_packets(struct freedv *f, int val) {
  f->total_packets = val;
}
void freedv_set_varicode_code_num(struct freedv *f, int val) {
  varicode_set_code_num(&f->varicode_dec_states, val);
}
void freedv_set_ext_vco(struct freedv *f, int val) { f->ext_vco = val; }
void freedv_set_snr_squelch_thresh(struct freedv *f, float val) {
  f->snr_squelch_thresh = val;
}
void freedv_set_tx_amp(struct freedv *f, float amp) { f->tx_amp = amp; }
void freedv_passthrough_gain(struct freedv *f, float g) {
  f->passthrough_gain = g;
}

/* supported by 700C, 700D, 700E */

void freedv_set_clip(struct freedv *f, bool val) {
  f->clip_en = val;
  if (is_ofdm_mode(f)) {
    f->ofdm->clip_en = val;
    /* really should have BPF if we clip */
    if (val) ofdm_set_tx_bpf(f->ofdm, true);
  }
}

/* Band Pass Filter to cleanup OFDM tx waveform, only supported by some modes */

void freedv_set_tx_bpf(struct freedv *f, int val) {
  if (is_ofdm_mode(f)) {
    ofdm_set_tx_bpf(f->ofdm, val);
  }
}

void freedv_set_phase_est_bandwidth_mode(struct freedv *f, int val) {
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700D, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2020, f->mode)) {
    ofdm_set_phase_est_bandwidth_mode(f->ofdm, val);
  }
}

// For those FreeDV modes using the codec 2 700C vocoder 700C/D/E/800XA
void freedv_set_eq(struct freedv *f, bool val) {
  if (f->codec2 != NULL) {
    codec2_700c_eq(f->codec2, val);
  }
}

void freedv_set_verbose(struct freedv *f, int verbosity) {
  f->verbose = verbosity;
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700C, f->mode)) {
    cohpsk_set_verbose(f->cohpsk, f->verbose);
  }
  if (is_ofdm_mode(f)) {
    ofdm_set_verbose(f->ofdm, f->verbose - 1);
  }
}

void freedv_set_callback_error_pattern(struct freedv *f,
                                       freedv_calback_error_pattern cb,
                                       void *state) {
  f->freedv_put_error_pattern = cb;
  f->error_pattern_callback_state = state;
}

void freedv_set_carrier_ampl(struct freedv *f, int c, float ampl) {
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700C, f->mode)) {
    cohpsk_set_carrier_ampl(f->cohpsk, c, ampl);
  }
}

/*---------------------------------------------------------------------------* \

  FUNCTIONS...: freedv_set_sync
  AUTHOR......: David Rowe
  DATE CREATED: May 2018

  Extended control of sync state machines for OFDM modes.

  Ensure this is called in the same thread as freedv_rx().

\*---------------------------------------------------------------------------*/

void freedv_set_sync(struct freedv *freedv, int sync_cmd) {
  assert(freedv != NULL);

  if (freedv->ofdm != NULL) {
    ofdm_set_sync(freedv->ofdm, sync_cmd);
  }
}

// this also selects burst mode
void freedv_set_frames_per_burst(struct freedv *freedv, int framesperburst) {
  assert(freedv != NULL);
  if (freedv->ofdm != NULL) {
    // change of nomenclature as we cross into the OFDM modem layer. In the
    // OFDM modem we have packets that contain multiple "modem frames"
    ofdm_set_packets_per_burst(freedv->ofdm, framesperburst);
  }
}

struct FSK *freedv_get_fsk(struct freedv *f) {
  return f->fsk;
}

/*---------------------------------------------------------------------------*\

  FUNCTIONS...: freedv_get_*
  AUTHOR......: Jim Ahlstrom
  DATE CREATED: 28 July 2015

  Get some parameters from FreeDV.

\*---------------------------------------------------------------------------*/

int freedv_get_protocol_bits(struct freedv *f) { return f->n_protocol_bits; }
int freedv_get_mode(struct freedv *f) { return f->mode; }
int freedv_get_test_frames(struct freedv *f) { return f->test_frames; }
int freedv_get_speech_sample_rate(struct freedv *f) {
  return f->speech_sample_rate;
}
int freedv_get_n_speech_samples(struct freedv *f) {
  return f->n_speech_samples;
}
int freedv_get_modem_sample_rate(struct freedv *f) {
  return f->modem_sample_rate;
}
int freedv_get_modem_symbol_rate(struct freedv *f) {
  return f->modem_symbol_rate;
}
int freedv_get_n_max_modem_samples(struct freedv *f) {
  return f->n_max_modem_samples;
}
int freedv_get_n_nom_modem_samples(struct freedv *f) {
  return f->n_nom_modem_samples;
}
int freedv_get_n_tx_modem_samples(struct freedv *f) {
  return f->n_nat_modem_samples;
}
int freedv_get_total_bits(struct freedv *f) { return f->total_bits; }
int freedv_get_total_bit_errors(struct freedv *f) {
  return f->total_bit_errors;
}
int freedv_get_total_bits_coded(struct freedv *f) {
  return f->total_bits_coded;
}
int freedv_get_total_bit_errors_coded(struct freedv *f) {
  return f->total_bit_errors_coded;
}
int freedv_get_total_packets(struct freedv *f) { return f->total_packets; }
int freedv_get_total_packet_errors(struct freedv *f) {
  return f->total_packet_errors;
}
int freedv_get_sync(struct freedv *f) { return f->sync; }
struct CODEC2 *freedv_get_codec2(struct freedv *f) {
  return f->codec2;
}
int freedv_get_bits_per_codec_frame(struct freedv *f) {
  return f->bits_per_codec_frame;
}
int freedv_get_bits_per_modem_frame(struct freedv *f) {
  return f->bits_per_modem_frame;
}
int freedv_get_rx_status(struct freedv *f) { return f->rx_status; }
void freedv_get_fsk_S_and_N(struct freedv *f, float *S, float *N) {
  *S = f->fsk_S[0];
  *N = f->fsk_N[0];
}

/*---------------------------------------------------------------------------*\

  FUNCTIONS...: freedv_set_tuning_range
  AUTHOR......: Simon Lang - DJ2LS
  DATE CREATED: 18 feb 2022
  DEFAULT.....: fmin: -50.0Hz fmax: 50.0Hz
  DESCRIPTION.:

  |<---fmin - | rx centre frequency | + fmax--->|

  Useful for handling frequency offsets,
  e.g. caused by an imprecise VFO, the trade off is more CPU power is required.

\*---------------------------------------------------------------------------*/
int freedv_set_tuning_range(struct freedv *freedv, float val_fmin,
                            float val_fmax) {
  if (is_ofdm_data_mode(freedv) &&
      (strcmp(freedv->ofdm->data_mode, "burst") == 0)) {
    freedv->ofdm->fmin = val_fmin;
    freedv->ofdm->fmax = val_fmax;
    return 1;
  } else {
    return 0;
  }
}

int freedv_get_n_max_speech_samples(struct freedv *f) {
  /* When "passing through" demod samples to the speech output
     (e.g. no sync and squelch off) f->nin bounces around with
     timing variations.  So we may return
     freedv_get_n_max_modem_samples() via the speech_output[]
     array */
  int max_output_passthrough_samples;
  if (FDV_MODE_ACTIVE(FREEDV_MODE_2020, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_2020B, f->mode))
    // In 2020 we oversample the input modem samples from 8->16 kHz
    max_output_passthrough_samples = 2 * freedv_get_n_max_modem_samples(f);
  else
    max_output_passthrough_samples = freedv_get_n_max_modem_samples(f);

  if (max_output_passthrough_samples > f->n_speech_samples)
    return max_output_passthrough_samples;
  else
    return f->n_speech_samples;
}

// Now dummy obsolete call
int freedv_get_sync_interleaver(struct freedv *f) { return 1; }

int freedv_get_sz_error_pattern(struct freedv *f) {
  if (FDV_MODE_ACTIVE(FREEDV_MODE_700C, f->mode)) {
    /* if diversity disabled callback sends error pattern for upper and lower
     * carriers */
    return f->sz_error_pattern * (2 - f->test_frames_diversity);
  } else {
    return f->sz_error_pattern;
  }
}

// Get modem status, scatter/eye diagram for plotting, other goodies
void freedv_get_modem_extended_stats(struct freedv *f,
                                     struct MODEM_STATS *stats) {
  if (FDV_MODE_ACTIVE(FREEDV_MODE_1600, f->mode))
    fdmdv_get_demod_stats(f->fdmdv, stats);

  if (FDV_MODE_ACTIVE(FREEDV_MODE_2400A, f->mode) ||
      FDV_MODE_ACTIVE(FREEDV_MODE_800XA, f->mode)) {
    fsk_get_demod_stats(f->fsk,
                        stats); /* eye diagram samples, clock offset etc */
    stats->snr_est =
        f->snr_est; /* estimated when fsk_demod() called in freedv_fsk.c */
    stats->sync = f->sync; /* sync indicator comes from framing layer */
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_2400B, f->mode)) {
    fmfsk_get_demod_stats(f->fmfsk, stats);
    stats->snr_est = f->snr_est;
    stats->sync = f->sync;
  }

  if (FDV_MODE_ACTIVE(FREEDV_MODE_700C, f->mode)) {
    cohpsk_get_demod_stats(f->cohpsk, stats);
  }

  if (is_ofdm_mode(f)) {
    // OFDM modem stats updated when demod runs, so copy last update
    // We need to avoid over writing the FFT states which are updated by a
    // different function
    // TODO we need a better design here: Issue #182
#ifndef __EMBEDDED__
    size_t ncopy = (void *)stats->rx_eye - (void *)stats;
    memcpy(stats, &f->stats, ncopy);
#endif
    stats->snr_est = f->snr_est;
    stats->sync = f->sync;
  }
}

int freedv_get_n_tx_preamble_modem_samples(struct freedv *f) {
  if (f->mode == FREEDV_MODE_FSK_LDPC) {
    struct FSK *fsk = f->fsk;
    int npreamble_symbols = 50 * (fsk->mode >> 1);
    return fsk->Ts * npreamble_symbols;
  } else if (is_ofdm_data_mode(f)) {
    return f->ofdm->samplesperframe;
  }

  return 0;
}

int freedv_get_n_tx_postamble_modem_samples(struct freedv *f) {
  if (is_ofdm_data_mode(f)) {
    return f->ofdm->samplesperframe;
  }

  return 0;
}

// from
// http://stackoverflow.com/questions/10564491/function-to-calculate-a-crc16-checksum

unsigned short freedv_gen_crc16(unsigned char *data_p, int length) {
  unsigned char x;
  unsigned short crc = 0xFFFF;

  while (length--) {
    x = crc >> 8 ^ *data_p++;
    x ^= x >> 4;
    crc = (crc << 8) ^ ((unsigned short)(x << 12)) ^
          ((unsigned short)(x << 5)) ^ ((unsigned short)x);
  }

  return crc;
}
