UHSDR/UHSDR-active-devel/mchf-eclipse/drivers/audio/freedv_uhsdr.c
2022-08-24 08:39:13 +02:00

436 lines
14 KiB
C
Executable File

/* -*- mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8 -*- */
/************************************************************************************
** **
** mcHF QRP Transceiver **
** K Atanassov - M0NKA 2014 **
** **
**---------------------------------------------------------------------------------**
** **
** File name: **
** Description: **
** Last Modified: **
** Licence: GNU GPLv3 **
************************************************************************************/
#include "ui_driver.h"
#include "freedv_uhsdr.h"
#include "ui_lcd_layouts.h"
#include "profiling.h"
#include "ui_lcd_hy28.h"
#include "radio_management.h"
#if defined(USE_FREEDV) || defined(USE_ALTERNATE_NR)
MultiModeBuffer_t __MCHF_SPECIALMEM mmb;
#endif
#ifdef USE_FREEDV
#include "freedv_api.h"
freedv_conf_t freedv_conf;
struct freedv *f_FREEDV;
RingBuffer_DefineExtMem(fdv_demod_rb,sizeof(mmb.fdv_demod_buff)/sizeof(fdv_demod_rb_item_t), mmb.fdv_demod_buff)
RingBuffer_DefineExtMem(fdv_iq_rb,sizeof(mmb.fdv_iq_buff)/sizeof(fdv_iq_rb_item_t), mmb.fdv_iq_buff)
#define FDV_AUDIO_MEM_SIZE ((FDV_BUFFER_SIZE*2)+IQ_BLOCK_SIZE)
__MCHF_SPECIALMEM fdv_audio_rb_item_t fdv_audio_rb_mem[FDV_AUDIO_MEM_SIZE];
RingBuffer_DefineExtMem(fdv_audio_rb, FDV_AUDIO_MEM_SIZE, fdv_audio_rb_mem)
typedef struct {
int32_t start;
int32_t offset;
int32_t count;
} flex_buffer;
static uint16_t freedv_display_x_offset;
/**
* Returns the internal UHSDR configuration value in the value range for display and use with the freedv_api
* @param freedv_conf_p
* @return SNR in the range of -100 to 99
*/
int32_t FreeDV_Get_Squelch_SNR(freedv_conf_t* freedv_conf_p)
{
return (int32_t)(freedv_conf_p->squelch_snr_thresh) + FDV_SQUELCH_OFFSET;
}
/**
* Sets the internal UHSDR configuration value in the permitted value range
* @param freedv_conf_p
* @param squelch_snr SNR in the range of -100 to 99 , -100 represents OFF
*/
void FreeDV_Set_Squelch_SNR(freedv_conf_t* freedv_conf_p, int8_t squelch_snr)
{
int32_t internal_snr = squelch_snr - FDV_SQUELCH_OFFSET;
if (internal_snr > FDV_SQUELCH_MAX)
{
internal_snr = FDV_SQUELCH_MAX;
}
else if (internal_snr < FDV_SQUELCH_OFF)
{
internal_snr = FDV_SQUELCH_OFF;
}
freedv_conf_p->squelch_snr_thresh = internal_snr;
}
int FreeDV_Is_Squelch_Enable(freedv_conf_t* freedv_conf_p)
{
return freedv_conf_p->squelch_snr_thresh != FDV_SQUELCH_OFF;
}
void FreeDV_Squelch_Update(freedv_conf_t* freedv_conf_p)
{
UNUSED(freedv_conf_p);
freedv_set_squelch_en(f_FREEDV, FreeDV_Is_Squelch_Enable(&freedv_conf));
freedv_set_snr_squelch_thresh(f_FREEDV, FreeDV_Get_Squelch_SNR(&freedv_conf));
}
static void FreeDv_DisplayBer(void)
{
int ber = 0;
char ber_string[12];
ber = 1000*freedv_get_total_bit_errors(f_FREEDV)/freedv_get_total_bits(f_FREEDV);
snprintf(ber_string,12,"0.%03d",ber); //calculate and display the bit error rate
UiLcdHy28_PrintText(ts.Layout->FREEDV_BER.x + freedv_display_x_offset,ts.Layout->FREEDV_BER.y, ber_string,Yellow,Black, ts.Layout->FREEDV_FONT);
}
static void FreeDv_DisplaySnr(void)
{
static float SNR = 1;
float SNR_est;
char SNR_string[12];
int sync;
freedv_get_modem_stats(f_FREEDV,&sync,&SNR_est);
SNR = 0.95*SNR + 0.05 * SNR_est; //some averaging to keep it more calm
int32_t SNR_Int = roundf(SNR);
if (SNR_Int > 99)
{
SNR_Int = 99;
}
else if (SNR_Int < -99)
{
SNR_Int = -99;
}
uint32_t clr_fg;
if (FreeDV_Is_Squelch_Enable(&freedv_conf))
{
clr_fg = SNR_Int >= FreeDV_Get_Squelch_SNR(&freedv_conf)? Green:Red;
}
else
{
clr_fg = Yellow;
}
snprintf(SNR_string,12,"%-2ld",SNR_Int); //Display the current SNR and round it up to the next int
UiLcdHy28_PrintText(ts.Layout->FREEDV_SNR.x + freedv_display_x_offset, ts.Layout->FREEDV_SNR.y ,SNR_string,clr_fg,Black, ts.Layout->FREEDV_FONT);
}
void FreeDv_DisplayClear()
{
// UiLcdHy28_PrintText(ts.Layout->FREEDV_SNR.x, ts.Layout->FREEDV_SNR.y," ",Yellow,Black,ts.Layout->FREEDV_FONT);
// UiLcdHy28_PrintText(ts.Layout->FREEDV_BER.x, ts.Layout->FREEDV_BER.y," ",Yellow,Black,ts.Layout->FREEDV_FONT);
UiLcdHy28_PrintText(ts.Layout->FREEDV_SNR.x, ts.Layout->FREEDV_SNR.y," ",Yellow,Black,ts.Layout->FREEDV_FONT); //SNR=00
UiLcdHy28_PrintText(ts.Layout->FREEDV_BER.x, ts.Layout->FREEDV_BER.y," ",Yellow,Black,ts.Layout->FREEDV_FONT); //BER=0.000 (max 9 chars)
UiDriver_TextMsgClear();
}
void FreeDv_DisplayPrepare()
{
UiDriver_TextMsgClear();
freedv_display_x_offset = UiLcdHy28_TextWidth("SNR=", ts.Layout->FREEDV_FONT);
UiLcdHy28_PrintText(ts.Layout->FREEDV_SNR.x, ts.Layout->FREEDV_SNR.y,"SNR=",Yellow,Black, ts.Layout->FREEDV_FONT);
UiLcdHy28_PrintText(ts.Layout->FREEDV_BER.x, ts.Layout->FREEDV_BER.y,"BER=",Yellow,Black, ts.Layout->FREEDV_FONT);
}
void FreeDv_DisplayUpdate()
{
FreeDv_DisplayBer();
FreeDv_DisplaySnr();
UiDriver_TextMsgDisplay();
}
void FreeDv_HandleFreeDv()
{
// Freedv DL2FW
// static FDV_Out_Buffer FDV_TX_out_im_buff;
static bool tx_was_here = false;
static bool rx_was_here = true;
// we are always called, so check if there is FreeDV active
if (ts.digital_mode == DigitalMode_FreeDV)
{
if ((ts.txrx_mode == TRX_MODE_TX)
&& RingBuffer_GetData(&fdv_audio_rb) >= freedv_get_n_speech_samples(f_FREEDV)
&& RingBuffer_GetRoom(&fdv_iq_rb) >= freedv_get_n_nom_modem_samples(f_FREEDV))
{
// ...and if we are transmitting and samples from dv_tx_processor are ready
if (tx_was_here == false)
{
RingBuffer_ClearGetTail(&fdv_audio_rb);
RingBuffer_ClearPutHead(&fdv_iq_rb);
tx_was_here = true;
rx_was_here = false;
}
COMP iq_buffer[freedv_get_n_nom_modem_samples(f_FREEDV)];
int16_t audio_buffer[freedv_get_n_speech_samples(f_FREEDV)];
RingBuffer_GetSamples(&fdv_audio_rb, &audio_buffer, freedv_get_n_speech_samples(f_FREEDV));
profileTimedEventStart(7);
freedv_comptx(f_FREEDV,
iq_buffer,
audio_buffer); // start the encoding process
profileTimedEventStop(7);
for (int idx = 0; idx < freedv_get_n_nom_modem_samples(f_FREEDV); idx++)
{
fdv_iq_rb_item_t sample;
sample.real = iq_buffer[idx].real;
sample.imag = iq_buffer[idx].imag;
RingBuffer_PutSamples(&fdv_iq_rb, &sample, 1);
}
}
else if ((ts.txrx_mode == TRX_MODE_RX))
{
if (rx_was_here == false)
{
RingBuffer_ClearGetTail(&fdv_demod_rb);
RingBuffer_ClearPutHead(&fdv_audio_rb);
freedv_set_total_bit_errors(f_FREEDV,0); //reset ber calculation after coming from TX
freedv_set_total_bits(f_FREEDV,0);
rx_was_here = true; // this is used to clear buffers when going into TX
tx_was_here = false;
}
// these buffers are large enough to hold the requested/provided amount of data for freedv_comprx
// these are larger than the FDV_BUFFER_SIZE since some more bytes may be asked for.
#ifdef USE_SIMPLE_FREEDV_FILTERS
#define input_rb fdv_iq_rb
#define input_rb_item_t fdv_iq_rb_item_t
#define FREEDV_RX_FUNC freedv_comprx
#define FREEDV_RX_INPUT_T COMP
#else
#define input_rb fdv_demod_rb
#define input_rb_item_t fdv_demod_rb_item_t
#define FREEDV_RX_FUNC freedv_rx
#define FREEDV_RX_INPUT_T int16_t
#endif
FREEDV_RX_INPUT_T input_buffer[freedv_get_n_max_modem_samples(f_FREEDV)];
int16_t audio_buffer[freedv_get_n_max_modem_samples(f_FREEDV)];
// while makes this highest prio
// if may give more responsiveness but can cause interrupted reception
while (RingBuffer_GetData(&input_rb) >= freedv_nin(f_FREEDV)
&& RingBuffer_GetRoom(&fdv_audio_rb) >= freedv_nin(f_FREEDV))
{
// MchfBoard_GreenLed(LED_STATE_OFF);
// if we arrive here the rx_buffer is full enough and will be consumed now.
#ifdef USE_SIMPLE_FREEDV_FILTERS
for (int idx = 0; idx < freedv_nin(f_FREEDV); idx++ )
{
input_rb_item_t sample;
RingBuffer_GetSamples(&input_rb, &sample, 1);
input_buffer[idx].real = sample.real;
input_buffer[idx].imag = sample.imag;
}
#else
RingBuffer_GetSamples(&input_rb, &input_buffer, freedv_nin(f_FREEDV));
#endif
int count = FREEDV_RX_FUNC(f_FREEDV, audio_buffer, input_buffer); // run the decoding process
if (freedv_get_sync(f_FREEDV) != 0)
{
RingBuffer_PutSamples(&fdv_audio_rb, &audio_buffer, count);
}
}
}
}
else
{
if (tx_was_here == true || rx_was_here == true)
{
tx_was_here = false;
rx_was_here = false;
RingBuffer_ClearGetTail(&fdv_iq_rb);
RingBuffer_ClearGetTail(&fdv_audio_rb);
}
}
}
// FreeDV txt test - will be out of here
typedef struct {
char tx_str[80];
char *ptx_str;
} my_callback_state_t;
static my_callback_state_t my_cb_state;
static char my_get_next_tx_char(void *callback_state) {
my_callback_state_t* pstate = (my_callback_state_t*)callback_state;
char c = *pstate->ptx_str++;
if (*pstate->ptx_str == 0) {
pstate->ptx_str = pstate->tx_str;
}
return c;
}
static void my_put_next_rx_char(void *callback_state, char ch)
{
UNUSED(callback_state);
UiDriver_TextMsgPutChar(ch);
}
freedv_mode_desc_t freedv_modes[] =
{
{ "1600", "FD1600", FREEDV_MODE_1600 },
#ifdef USE_FREEDV_700D
{ "700D", "FD700D", FREEDV_MODE_700D },
#endif
};
const uint8_t freedv_modes_num = sizeof(freedv_modes)/sizeof(freedv_modes[0]);
// FreeDV txt test - will be out of here
void FreeDV_Init()
{
// Freedv Test DL2FW
// move this to ui_configuration;
FreeDV_Set_Squelch_SNR(&freedv_conf,-2);
freedv_conf.mode = 0; // 0 = 1600, 1 = 700D
FreeDV_SetMode(freedv_conf.mode, true);
}
/**
*
* @return true if mode was activated, false if old mode is still active
*/
int32_t FreeDV_SetMode(uint8_t fdv_mode, int32_t firstTime)
{
bool retval = true;
// turn off rx audio processing to be able to disable and reenable FreeDV processing
ads.af_disabled++;
// normal init starts
if (fdv_mode >= freedv_modes_num)
{
fdv_mode = 0;
}
// we don't switch if TX is currently active
if (firstTime == false)
{
retval = (ts.txrx_mode == TRX_MODE_RX);
if (retval == true && f_FREEDV != NULL && freedv_conf.mode != fdv_mode)
{
freedv_close(f_FREEDV);
f_FREEDV = NULL;
}
}
// only if all went well AND we have not yet initialized the mode,
// we are going to run the mode, so if same mode is being set, nothing happens
// really
if (retval && f_FREEDV == NULL)
{
f_FREEDV = freedv_open(freedv_modes[fdv_mode].freedv_id);
retval = f_FREEDV != NULL;
if (retval)
{
sprintf(my_cb_state.tx_str, ts.special_functions_enabled == 1 ? FREEDV_TX_DF8OE_MESSAGE : FREEDV_TX_MESSAGE);
my_cb_state.ptx_str = my_cb_state.tx_str;
freedv_set_callback_txt(f_FREEDV, &my_put_next_rx_char, &my_get_next_tx_char, &my_cb_state);
if (FreeDV_Is_Squelch_Enable(&freedv_conf))
{
freedv_set_squelch_en(f_FREEDV,1);
freedv_set_snr_squelch_thresh(f_FREEDV, FreeDV_Get_Squelch_SNR(&freedv_conf));
}
else
{
freedv_set_squelch_en(f_FREEDV,0);
}
freedv_set_tx_bpf(f_FREEDV, 0);
}
}
if (retval)
{
freedv_conf.mode = fdv_mode;
}
// turn on rx audio processing, FreeDV is ready for work
ads.af_disabled--;
return retval;
}
int32_t FreeDV_Iq_Get_FrameLen()
{
return freedv_get_n_nom_modem_samples(f_FREEDV);
}
int32_t FreeDV_Audio_Get_FrameLen()
{
return freedv_get_n_speech_samples(f_FREEDV);
}
#ifdef DEBUG_FREEDV
void FreeDV_Test()
{
ts.digital_mode = DigitalMode_FreeDV;
ts.txrx_mode = TRX_MODE_RX;
while (1)
{
for (int idx = 0; idx < FREEDV_TEST_BUFFER_FRAME_SIZE*FREEDV_TEST_BUFFER_FRAME_COUNT; idx++)
{
int16_t sample[2];
sample[0] = test_buffer[idx].real;
sample[1] = test_buffer[idx].imag;
RingBuffer_PutSamples(&fdv_iq_rb,&sample,2);
FreeDv_HandleFreeDv();
RingBuffer_GetSamples(&fdv_audio_rb,sample,1);
}
}
}
#endif
#endif