/* -*- mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8 -*- */ #include "uhsdr_board.h" #include "audio_management.h" #include "math.h" #include "audio_driver.h" #include "softdds.h" #include "fm_subaudible_tone_table.h" #include "radio_management.h" /** * Calculate the ALC Decay for the Tx Voice Compressor */ void AudioManagement_CalcALCDecay() { // calculate ALC decay (release) time constant - this needs to be moved to its own function (and the one in "ui_menu.c") ads.alc_decay = pow10f(-((((float32_t)ts.alc_decay_var)+35.0)/10.0)); } // make sure the frequencies below match the order of iq_freq_enum_t ! #define AUDIO_M_IQ_ADJUST_INIT(freqVal) { .freq = freqVal , .adj = { .rx = { IQ_BALANCE_OFF, IQ_BALANCE_OFF }, .tx = { { IQ_BALANCE_OFF, IQ_BALANCE_OFF }, { IQ_BALANCE_OFF,IQ_BALANCE_OFF } } } }, freq_adjust_point_t iq_adjust[IQ_FREQ_NUM+1] = { AUDIO_M_IQ_ADJUST_INIT( 3600000) AUDIO_M_IQ_ADJUST_INIT(14100000) AUDIO_M_IQ_ADJUST_INIT(21100000) AUDIO_M_IQ_ADJUST_INIT(28100000) AUDIO_M_IQ_ADJUST_INIT(29650000) AUDIO_M_IQ_ADJUST_INIT(52000000) AUDIO_M_IQ_ADJUST_INIT( 1900000) AUDIO_M_IQ_ADJUST_INIT( 7100000) AUDIO_M_IQ_ADJUST_INIT(10100000) AUDIO_M_IQ_ADJUST_INIT(18100000) AUDIO_M_IQ_ADJUST_INIT(24900000) // this must be last AUDIO_M_IQ_ADJUST_INIT(0) }; static int32_t AudioManagement_GetBalanceValFromStruct(freq_adjust_point_t* point, iq_adjust_params_t what) { int32_t retval = IQ_BALANCE_OFF; switch(what) { case IQ_RX_GAIN: retval = point->adj.rx.gain; break; case IQ_RX_PHASE: retval = point->adj.rx.phase; break; case IQ_TX_TRANS_ON_GAIN: retval = point->adj.tx[IQ_TRANS_ON].gain; break; case IQ_TX_TRANS_OFF_GAIN: retval = point->adj.tx[IQ_TRANS_OFF].gain; break; case IQ_TX_TRANS_ON_PHASE: retval = point->adj.tx[IQ_TRANS_ON].phase; break; case IQ_TX_TRANS_OFF_PHASE: retval = point->adj.tx[IQ_TRANS_OFF].phase; break; } return retval; } /** * * @param freq for which the two closest points shall be found * @return idx of lower frequency point, please note that the higher frequency point may be the stop value */ static uint32_t AudioManagement_GetNextValid(freq_adjust_point_t* points, uint32_t idx, iq_adjust_params_t what) { if(!(ts.expflags2 & EXPFLAGS2_IMPROV_IQ_ADJ)) // regular I/Q adj. { // while (points[idx].freq != 0) while (idx <= 5) { int32_t val = AudioManagement_GetBalanceValFromStruct(&points[idx], what); if (val != IQ_BALANCE_OFF) { break; } idx++; } } else // advance I/Q adj. { uint8_t iq_adv_adj_order[] = {6, 0, 7, 8, 1, 9, 2, 10, 3, 4, 5}; uint8_t i = 0; while (i <= 10) { if(idx == iq_adv_adj_order[i]) { break; } i++; } int32_t val; while (i <= 10) { val = AudioManagement_GetBalanceValFromStruct(&points[(iq_adv_adj_order[i])], what); if (val != IQ_BALANCE_OFF) { break; } i++; } idx = iq_adv_adj_order[i]; } return idx; } static void AudioManagement_FindFreqRange(freq_adjust_point_t* points, uint32_t freq, iq_adjust_params_t what, float32_t* adj_low_ptr, float32_t* adj_high_ptr, float32_t* freq_low_ptr, float32_t* freq_high_ptr ) { // AudioManagement_GetBalanceValFromStruct(&points[idx], what); uint32_t idx = AudioManagement_GetNextValid(points, 0, what); uint32_t previous_idx = idx; uint32_t before_previous_idx = idx; uint32_t valid_points = 0; bool found_match = false; if(!(ts.expflags2 & EXPFLAGS2_IMPROV_IQ_ADJ)) { // searching for the frist valid calibration point just higher than this frequency // while (points[idx].freq != 0) while (idx <= 5) { valid_points++; if (freq < points[idx].freq) { // we found one valid calibration point which is higher. // now we need to see if there is a second point if possible. // if the idx is not equal previous_idx, there was a valid lower frequency calibration point // which is nice otherwise we try to find one more higher point if (previous_idx == idx) { // we are lower than the lowest entry, now check if there is one more valid calibration point idx = AudioManagement_GetNextValid(points, idx+1, what); // if (points[idx].freq == 0) // stop entry if (idx == 5) // stop entry { // no more valid points, too bad idx = previous_idx; // we keep both points identical then } } found_match = true; break; } before_previous_idx = previous_idx; previous_idx = idx; idx = AudioManagement_GetNextValid(points, idx+1, what); } } else // Advanced I/Q just. We assume that advanced alignment is only needed by advanced users. We work on all alignment points. { uint8_t iq_adv_adj_order[] = {6, 0, 7, 8, 1, 9, 2, 10, 3, 4, 5}; uint8_t i; uint8_t previous_i = 0; if(freq <= points[(iq_adv_adj_order[0])].freq) { i = previous_i = 0; } else if(freq >= points[(iq_adv_adj_order[10])].freq) { i = previous_i = 10; } else { i = 1; while (i <= 9) { if(freq == points[(iq_adv_adj_order[i])].freq) { previous_i = i; break; } else if(freq < points[(iq_adv_adj_order[i])].freq) { previous_i = (i - 1); break; } i++; } } previous_idx = iq_adv_adj_order[previous_i]; idx = iq_adv_adj_order[i]; } if(!(ts.expflags2 & EXPFLAGS2_IMPROV_IQ_ADJ)) { if (found_match == false) { if (valid_points > 1) { // we had two or more valid points, but all are below or equal the frequency // just use the last two valid points, idx is not a valid point but the stop entry idx = previous_idx; previous_idx = before_previous_idx; } else { // if we had not a single valid calibration point, too bad // idx is same as previous_idx and both point to the last "stop" entry // nothing to be done now, but we handle it like the next case, makes no difference // we had only one valid point, and that must be in previous_idx // no problem, we use it for all frequencies idx = previous_idx; } } } *adj_high_ptr = AudioManagement_GetBalanceValFromStruct(&points[idx], what); *adj_low_ptr = AudioManagement_GetBalanceValFromStruct(&points[previous_idx], what); *freq_high_ptr = points[idx].freq; *freq_low_ptr = points[previous_idx].freq; } static float AudioManagement_CalcAdjustInFreqRangeHelperNew(freq_adjust_point_t* points, iq_adjust_params_t what, float32_t freq, float32_t scaling) { float32_t adj_low, adj_high; float32_t freq_low, freq_high; AudioManagement_FindFreqRange(points, freq, what, &adj_low, &adj_high, &freq_low, &freq_high); if (adj_high == IQ_BALANCE_OFF) { if (adj_low == IQ_BALANCE_OFF) { // no data available, set both to zero adj_high = adj_low = 0.0; } else { // we use the low value for both adj_high = adj_low; } } else if (adj_low == IQ_BALANCE_OFF) { // we use the high value for both adj_low = adj_high; } float32_t adj_delta = (freq_high != freq_low)? (adj_high - adj_low) / (freq_high - freq_low) * (freq - freq_low) : 0; return (adj_delta + adj_low)/scaling; // get current gain adjustment setting USB and other modes } static void AudioManagement_CalcIqGainAdjustVarHelper(volatile iq_float_t* var, float32_t adj) { var->i = 1 + adj; var->q = 1 - adj; } void AudioManagement_CalcIqPhaseGainAdjust(float freq) { // // the phase adjustment is done by mixing a little bit of I into Q or vice versa // this is justified because the phase shift between two signals of equal frequency can // be regulated by adjusting the amplitudes of the two signals! ads.iq_phase_balance_rx = AudioManagement_CalcAdjustInFreqRangeHelperNew( iq_adjust, IQ_RX_PHASE, freq, SCALING_FACTOR_IQ_PHASE_ADJUST); // please note that the RX adjustments for gain are negative // and the adjustments for TX (in the function AudioManagement_CalcTxIqGainAdj) are positive float32_t adj_i_rx = -AudioManagement_CalcAdjustInFreqRangeHelperNew( iq_adjust, IQ_RX_GAIN, freq, SCALING_FACTOR_IQ_AMPLITUDE_ADJUST); AudioManagement_CalcIqGainAdjustVarHelper(&ts.rx_adj_gain_var,adj_i_rx); ads.iq_phase_balance_tx[IQ_TRANS_ON] = AudioManagement_CalcAdjustInFreqRangeHelperNew( iq_adjust, IQ_TX_TRANS_ON_PHASE, freq, SCALING_FACTOR_IQ_PHASE_ADJUST); ads.iq_phase_balance_tx[IQ_TRANS_OFF] = AudioManagement_CalcAdjustInFreqRangeHelperNew( iq_adjust, IQ_TX_TRANS_OFF_PHASE, freq, SCALING_FACTOR_IQ_PHASE_ADJUST); AudioManagement_CalcIqGainAdjustVarHelper(&ts.tx_adj_gain_var[IQ_TRANS_ON],AudioManagement_CalcAdjustInFreqRangeHelperNew( iq_adjust, IQ_TX_TRANS_ON_GAIN, freq, SCALING_FACTOR_IQ_AMPLITUDE_ADJUST)); AudioManagement_CalcIqGainAdjustVarHelper( &ts.tx_adj_gain_var[IQ_TRANS_OFF], AudioManagement_CalcAdjustInFreqRangeHelperNew( iq_adjust, IQ_TX_TRANS_OFF_GAIN, freq, SCALING_FACTOR_IQ_AMPLITUDE_ADJUST)); } //*---------------------------------------------------------------------------- typedef struct AlcParams_s { uint32_t tx_postfilt_gain; uint32_t alc_decay; } AlcParams; static const AlcParams alc_params[] = { { 1, 15}, { 2, 12}, { 4, 10}, { 6, 9}, { 7, 8}, { 8, 7}, { 10, 6}, { 12, 5}, { 15, 4}, { 17, 3}, { 20, 2}, { 25, 1}, { 25, 0}, }; /** * @brief Set TX audio compression settings (gain and ALC decay rate) based on user setting */ void AudioManagement_CalcTxCompLevel() { int16_t tx_comp_level = ts.tx_comp_level; if (TX_AUDIO_COMPRESSION_MIN < tx_comp_level && tx_comp_level < TX_AUDIO_COMPRESSION_SV) { ts.alc_tx_postfilt_gain_var = alc_params[ts.tx_comp_level].tx_postfilt_gain; // restore "pristine" EEPROM values ts.alc_decay_var = alc_params[ts.tx_comp_level].alc_decay; } else if (tx_comp_level == TX_AUDIO_COMPRESSION_SV) // get the speech compressor setting { // read saved values from EEPROM ts.alc_tx_postfilt_gain_var = ts.alc_tx_postfilt_gain; // restore "pristine" EEPROM values ts.alc_decay_var = ts.alc_decay; } else { ts.alc_tx_postfilt_gain_var = 4; ts.alc_decay_var = 10; } AudioManagement_CalcALCDecay(); } #include "fm_subaudible_tone_table.h" // //*---------------------------------------------------------------------------- //* Function Name : UiCalcSubaudibleGenFreq //* Object : Calculate frequency word for subaudible tone generation [KA7OEI October, 2015] //* Input Parameters : //* Output Parameters : //* Functions called : //*---------------------------------------------------------------------------- void AudioManagement_CalcSubaudibleGenFreq(float32_t freq) { ads.fm_conf.subaudible_tone_gen_freq = freq; // look up tone frequency (in Hz) softdds_setFreqDDS(&ads.fm_conf.subaudible_tone_dds, ads.fm_conf.subaudible_tone_gen_freq,ts.samp_rate,false); } #define FM_GOERTZEL_HIGH 1.04 // ratio of "high" detect frequency with respect to center #define FM_GOERTZEL_LOW 0.95 // ratio of "low" detect frequency with respect to center /** * @brief Calculate frequency word for subaudible tone, call after change of detection frequency [KA7OEI October, 2015] */ void AudioManagement_CalcSubaudibleDetFreq(float32_t freq) { const uint32_t size = AUDIO_BLOCK_SIZE; ads.fm_conf.subaudible_tone_det_freq = freq; // look up tone frequency (in Hz) if (freq > 0) { // Calculate Goertzel terms for tone detector(s) AudioFilter_CalcGoertzel(&ads.fm_conf.goertzel[FM_HIGH], ads.fm_conf.subaudible_tone_det_freq, FM_SUBAUDIBLE_GOERTZEL_WINDOW*size,FM_GOERTZEL_HIGH, IQ_SAMPLE_RATE); AudioFilter_CalcGoertzel(&ads.fm_conf.goertzel[FM_LOW], ads.fm_conf.subaudible_tone_det_freq, FM_SUBAUDIBLE_GOERTZEL_WINDOW*size,FM_GOERTZEL_LOW, IQ_SAMPLE_RATE); AudioFilter_CalcGoertzel(&ads.fm_conf.goertzel[FM_CTR], ads.fm_conf.subaudible_tone_det_freq, FM_SUBAUDIBLE_GOERTZEL_WINDOW*size,1.0, IQ_SAMPLE_RATE); } } uint32_t fm_tone_burst_freq[FM_TONE_BURST_MAX+1] = { 0, 1750, 2135 }; // // //*---------------------------------------------------------------------------- //* Function Name : UiLoadToneBurstMode //* Object : Load tone burst mode [KA7OEI October, 2015] //* Input Parameters : //* Output Parameters : //* Functions called : //*---------------------------------------------------------------------------- void AudioManagement_LoadToneBurstMode() { uint16_t frequency = 0; if (ts.fm_tone_burst_mode <= FM_TONE_BURST_MAX) { frequency = fm_tone_burst_freq[ts.fm_tone_burst_mode]; } softdds_setFreqDDS(&ads.fm_conf.tone_burst_dds, frequency, ts.samp_rate, false); } /** * @brief Generates the sound for a given beep frequency, call after change of beep freq or before beep [KA7OEI October, 2015] */ void AudioManagement_KeyBeepPrepare() { softdds_setFreqDDS(&ads.beep, ts.beep_frequency,ts.samp_rate,false); float32_t calc = (float)(ts.beep_loudness-1); // range 0-20 calc /= 2; // range 0-10 calc *= calc; // range 0-100 calc += 3; // range 3-103 ads.beep_loudness_factor = calc / 400; // range from 0.0075 to 0.2575 - multiplied by DDS output } /** * @brief Tell audio driver to make beeping sound [KA7OEI October, 2015] */ void AudioManagement_KeyBeep() { if((ts.flags2 & FLAGS2_KEY_BEEP_ENABLE) && (ads.beep.step > 0)) // is beep enabled and frequency non-zero? { ads.beep.acc = 0; // force reset of accumulator to start at zero to minimize "click" caused by an abrupt voltage transition at startup ts.beep_timing = BEEP_DURATION * (IQ_INTERRUPT_FREQ / 100); // set duration of beep and activate it } } void AudioManagement_SetSidetoneForDemodMode(uint8_t dmod_mode, bool tune_mode) { float tonefreq[2] = {0.0, 0.0}; switch(dmod_mode) { case DEMOD_CW: tonefreq[0] = tune_mode?CW_SIDETONE_FREQ_DEFAULT:ts.cw_sidetone_freq; break; case DEMOD_DIGI: if (ts.digital_mode == DigitalMode_RTTY || ts.digital_mode == DigitalMode_BPSK) { tonefreq[0] = tune_mode?CW_SIDETONE_FREQ_DEFAULT:ts.cw_sidetone_freq; } break; default: tonefreq[0] = tune_mode?SSB_TUNE_FREQ:0.0; if (tune_mode && ts.tune_tone_mode == TUNE_TONE_TWO) { // ARRL Standard is 700Hz and 1900Hz // so I temporarily changed this to SSB_TUNE_FREQ + 1200, DD4WH 2016_07_14 // --> TWO_TONE = 750Hz and 1950Hz // tonefreq[1] = tune_mode?(SSB_TUNE_FREQ+600):0.0; tonefreq[1] = SSB_TUNE_FREQ+1200; } } softdds_configRunIQ(tonefreq,ts.samp_rate,0); }