diff options
Diffstat (limited to 'src/freedv_api.c')
| -rw-r--r-- | src/freedv_api.c | 1527 |
1 files changed, 1527 insertions, 0 deletions
diff --git a/src/freedv_api.c b/src/freedv_api.c new file mode 100644 index 0000000..516d604 --- /dev/null +++ b/src/freedv_api.c @@ -0,0 +1,1527 @@ +/*---------------------------------------------------------------------------*\ + + 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 <assert.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <math.h> + +#include "fsk.h" +#include "fmfsk.h" +#include "codec2.h" +#include "codec2_fdmdv.h" +#include "fdmdv_internal.h" +#include "varicode.h" +#include "freedv_api.h" +#include "freedv_api_internal.h" +#include "freedv_vhf_framing.h" +#include "comp_prim.h" + +#include "codec2_ofdm.h" +#include "ofdm_internal.h" +#include "mpdecode_core.h" +#include "gp_interleaver.h" +#include "interldpc.h" + +#include "debug_alloc.h" + +#define VERSION 15 /* 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. */ +/* + * 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_2020C, 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) || + FDV_MODE_ACTIVE( FREEDV_MODE_2020C, 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) || + FDV_MODE_ACTIVE( FREEDV_MODE_2020C, 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_2020C, 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) || + FDV_MODE_ACTIVE( FREEDV_MODE_2020C, 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) || + FDV_MODE_ACTIVE( FREEDV_MODE_2020C, 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) || + FDV_MODE_ACTIVE( FREEDV_MODE_2020C, 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) || + FDV_MODE_ACTIVE( FREEDV_MODE_2020C, 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 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) || + (f->mode == FREEDV_MODE_2020C)) { + /* 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)|| + FDV_MODE_ACTIVE( FREEDV_MODE_2020C, 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) || + FDV_MODE_ACTIVE( FREEDV_MODE_2020C, 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) || + FDV_MODE_ACTIVE(FREEDV_MODE_2020C, 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; +} |
