diff --git a/mchf-eclipse/drivers/audio/audio_driver.c b/mchf-eclipse/drivers/audio/audio_driver.c index 4dd242ed8..2b0ce84fd 100644 --- a/mchf-eclipse/drivers/audio/audio_driver.c +++ b/mchf-eclipse/drivers/audio/audio_driver.c @@ -1774,6 +1774,8 @@ static void AudioDriver_RxAgcProcessor(int16_t blockSize) { static ulong i; static ulong agc_delay_inbuf = 0, agc_delay_outbuf = 0; + static float32_t w = 0.0; + static float32_t wold = 0.0; // // AGC function - Look-ahead type by KA7OEI, revised September 2015 to eliminate possible low-order, low-frequency instabilities associated with steady-state signals. @@ -1839,6 +1841,19 @@ static void AudioDriver_RxAgcProcessor(int16_t blockSize) agc_delay_inbuf %= ads.agc_delay_buflen; agc_delay_outbuf %= ads.agc_delay_buflen; + // I have decided to put the DC elimination AFTER the AGC + // because we need the carrier DC for the calmness and functioning of the AGC + // DD4WH 2017-02-08 + if(ts.dmod_mode == DEMOD_AM || ts.dmod_mode == DEMOD_SAM) + { + // eliminate DC in the audio before application of AGC gain + for(i = 0; i < blockSize; i++) + { + w = adb.a_buffer[i] + wold * 0.9999; // yes, I want a superb bass response ;-) + adb.a_buffer[i] = w - wold; + wold = w; + } + } // Now apply pre-calculated AGC values to delayed audio arm_mult_f32(adb.a_buffer, adb.agc_valbuf, adb.a_buffer, blockSize); // do vector multiplication to apply delayed "running" AGC data @@ -2135,31 +2150,7 @@ static void AudioDriver_DemodFM(int16_t blockSize) } } -/* -float32_t fastdcblock_ff(float32_t* input, float32_t* output, int input_size, float32_t last_dc_level) -{ // (c) András Retzler - // taken from libcsdr: https://github.com/simonyiszk/csdr - //this DC block filter does moving average block-by-block. - //this is the most computationally efficient - //input and output buffer is allowed to be the same - //http://www.digitalsignallabs.com/dcblock.pdf - float32_t avg=0.0; - for(int i=0;i 0; j--) - { - a[j] = a[j - 1]; - b[j] = b[j - 1]; - c[j] = c[j - 1]; - d[j] = d[j - 1]; - } - } - - corr[0] = +ai + bq; - corr[1] = -bi + aq; + // Wheatley 2011 cuteSDR & Warren Pratt�s WDSP, 2016 + for(int i = 0; i < blockSize / adb.DF; i++) + { // NCO + sincosf(phs,&Sin,&Cos); + ai = Cos * adb.i_buffer[i]; + bi = Sin * adb.i_buffer[i]; + aq = Cos * adb.q_buffer[i]; + bq = Sin * adb.q_buffer[i]; - switch(ads.sam_sideband) - { - case SAM_SIDEBAND_BOTH: - { - audio = corr[0]; - break; - } - case SAM_SIDEBAND_USB: - { - audio = (ai_ps - bi_ps) + (aq_ps + bq_ps); - break; - } - case SAM_SIDEBAND_LSB: - { - audio = (ai_ps + bi_ps) - (aq_ps - bq_ps); - break; - } - } + if (ads.sam_sideband != SAM_SIDEBAND_BOTH) + { + a[0] = dsI; + b[0] = bi; + c[0] = dsQ; + d[0] = aq; + dsI = ai; + dsQ = bq; + + for (int j = 0; j < SAM_PLL_HILBERT_STAGES; j++) + { + k = 3 * j; + a[k + 3] = adb.c0[j] * (a[k] - a[k + 5]) + a[k + 2]; + b[k + 3] = adb.c1[j] * (b[k] - b[k + 5]) + b[k + 2]; + c[k + 3] = adb.c0[j] * (c[k] - c[k + 5]) + c[k + 2]; + d[k + 3] = adb.c1[j] * (d[k] - d[k + 5]) + d[k + 2]; + } + ai_ps = a[OUT_IDX]; + bi_ps = b[OUT_IDX]; + bq_ps = c[OUT_IDX]; + aq_ps = d[OUT_IDX]; + + for (j = OUT_IDX + 2; j > 0; j--) + { + a[j] = a[j - 1]; + b[j] = b[j - 1]; + c[j] = c[j - 1]; + d[j] = d[j - 1]; + } + } - // "fade leveler", taken from Warren Pratts� WDSP / HPSDR, 2016 - // http://svn.tapr.org/repos_sdr_hpsdr/trunk/W5WC/PowerSDR_HPSDR_mRX_PS/Source/wdsp/ - if(ads.fade_leveler) - { - dc27 = adb.mtauR * dc27 + adb.onem_mtauR * audio; - dc_insert = adb.mtauI * dc_insert + adb.onem_mtauI * corr[0]; - audio = audio + dc_insert - dc27; - } - else - { - // DC Filter - dc27 = adb.mtauR * dc27 + adb.onem_mtauR * audio; - audio = audio - dc27; - } + corr[0] = +ai + bq; + corr[1] = -bi + aq; - adb.a_buffer[i] = audio; + switch(ads.sam_sideband) + { + case SAM_SIDEBAND_BOTH: + { + audio = corr[0]; + break; + } + case SAM_SIDEBAND_USB: + { + audio = (ai_ps - bi_ps) + (aq_ps + bq_ps); + break; + } + case SAM_SIDEBAND_LSB: + { + audio = (ai_ps + bi_ps) - (aq_ps - bq_ps); + break; + } + } - // determine phase error - phzerror = atan2f(corr[1], corr[0]); + // "fade leveler", taken from Warren Pratts� WDSP / HPSDR, 2016 + // http://svn.tapr.org/repos_sdr_hpsdr/trunk/W5WC/PowerSDR_HPSDR_mRX_PS/Source/wdsp/ + if(ads.fade_leveler) + { + dc27 = adb.mtauR * dc27 + adb.onem_mtauR * audio; + dc_insert = adb.mtauI * dc_insert + adb.onem_mtauI * corr[0]; + audio = audio + dc_insert - dc27; + } + else + { + // DC Filter is now put AFTER the AGC in the AGC subroutine + // DC Filter +// dc27 = adb.mtauR * dc27 + adb.onem_mtauR * audio; +// audio = audio - dc27; + } -/* // the following does not work! + adb.a_buffer[i] = audio; + // determine phase error + phzerror = atan2f(corr[1], corr[0]); - // first, calculate "x" and "y" for the arctan2, comparing the vectors of present data with previous data - // -// y corr[1] = -bi + aq; -// x corr[0] = +ai + bq; -// y = (i_prev * adb.q_buffer[i]) - (adb.i_buffer[i] * q_prev); -// x = (i_prev * adb.i_buffer[i]) + (adb.q_buffer[i] * q_prev); - // - // What follows is adapted from "Fixed-Point Atan2 With Self Normalization", public domain code by "Jim Shima". - // The result is "approximate" - but plenty good enough for speech-grade communications! - // - // Do calculation of arc-tangent (with quadrant preservation) of of I and Q channels, comparing with previous sample. - // Because the result is absolute (we are using ratios!) there is no need to apply any sort of amplitude limiting - // - abs_y = fabs(corr[1]) + 2e-16; // prevent us from taking "zero divided by zero" (indeterminate value) by setting this to be ALWAYS at least slightly higher than zero - // - if(corr[0] >= 0) // Quadrant 1 or 4 - { - r = (corr[0] - abs_y) / (corr[0] + abs_y); - phzerror = FM_DEMOD_COEFF1 - FM_DEMOD_COEFF1 * r; - } - else // Quadrant 2 or 3 - { - r = (corr[0] + abs_y) / abs_y - corr[0]; - phzerror = FM_DEMOD_COEFF2 - FM_DEMOD_COEFF1 * r; - } - // - if (corr[1] < 0) // Quadrant 3 or 4 - flip sign - { - phzerror = -phzerror; - } -*/ + del_out = fil_out; + // correct frequency 1st step + omega2 = omega2 + adb.g2 * phzerror; + if (omega2 < adb.omega_min) + { + omega2 = adb.omega_min; + } + else if (omega2 > adb.omega_max) + { + omega2 = adb.omega_max; + } + // correct frequency 2nd step + fil_out = adb.g1 * phzerror + omega2; + phs = phs + del_out; - del_out = fil_out; - // correct frequency 1st step - omega2 = omega2 + adb.g2 * phzerror; - if (omega2 < adb.omega_min) - { - omega2 = adb.omega_min; + // wrap round 2PI, modulus + while (phs >= 2.0 * PI) phs -= (2.0 * PI); + while (phs < 0.0) phs += (2.0 * PI); } - else if (omega2 > adb.omega_max) - { - omega2 = adb.omega_max; + count++; + if(count > 50) // to display the exact carrier frequency that the PLL is tuned to + // in the small frequency display + // we calculate carrier offset here and the display function is + // then called in UiDriver_MainHandler approx. every 40-80ms + { // to make this smoother, a simple lowpass/exponential averager here . . . + carrier = 0.1 * (omega2 * IQ_SAMPLE_RATE) / (adb.DF * 2.0 * PI); + carrier = carrier + 0.9 * lowpass; + ads.carrier_freq_offset = (int)carrier; + count = 0; + lowpass = carrier; } - // correct frequency 2nd step - fil_out = adb.g1 * phzerror + omega2; - phs = phs + del_out; - - // wrap round 2PI, modulus - while (phs >= 2.0 * PI) phs -= (2.0 * PI); - while (phs < 0.0) phs += (2.0 * PI); - } - count++; - if(count > 50) // to display the exact carrier frequency that the PLL is tuned to -// if(0) - // in the small frequency display - // we calculate carrier offset here and the display function is - // then called in UiDriver_MainHandler approx. every 40-80ms - { // to make this smoother, a simple lowpass/exponential averager here . . . - carrier = 0.1 * (omega2 * IQ_SAMPLE_RATE) / (adb.DF * 2.0 * PI); - carrier = carrier + 0.9 * lowpass; - ads.carrier_freq_offset = (int)carrier; - count = 0; - lowpass = carrier; - } break; } } @@ -3332,7 +3283,7 @@ static void AudioDriver_RxProcessor(AudioSample_t * const src, AudioSample_t * c post_agc_gain_scaling = POST_AGC_GAIN_SCALING_DECIMATE_2; } - // Scale audio to according to AGC setting, demodulation mode and required fixed levels and scaling + // Scale audio according to AGC setting, demodulation mode and required fixed levels and scaling float32_t scale_gain; if(dmod_mode == DEMOD_AM) { diff --git a/mchf-eclipse/drivers/ui/menu/ui_menu_structure.c b/mchf-eclipse/drivers/ui/menu/ui_menu_structure.c index 9c5bdf74d..501c4e771 100644 --- a/mchf-eclipse/drivers/ui/menu/ui_menu_structure.c +++ b/mchf-eclipse/drivers/ui/menu/ui_menu_structure.c @@ -64,7 +64,7 @@ const MenuDescriptor baseGroup[] = { MENU_BASE, MENU_ITEM, MENU_SAM_PLL_LOCKING_RANGE, NULL, "SAM PLL locking range", UiMenuDesc("SAM PLL Locking Range in Hz: this determines how far up and down from the carrier frequency of an AM station we can offtune the receiver, so that the PLL will still lock to the carrier.") }, { MENU_BASE, MENU_ITEM, MENU_SAM_PLL_STEP_RESPONSE, NULL, "SAM PLL step response", UiMenuDesc("Step response = Zeta = damping factor of the SAM PLL. Sets the stability and transient response of the PLL. Larger values give faster lock even if you are offtune, but PLL is also more sensitive.") }, { MENU_BASE, MENU_ITEM, MENU_SAM_PLL_BANDWIDTH, NULL, "SAM PLL bandwidth in Hz", UiMenuDesc("Bandwidth of the PLL loop = OmegaN in Hz: smaller bandwidth = more stable lock. FAST LOCK SAM PLL - set Step response and PLL bandwidth to large values [eg. 80 / 350]; DX (SLOW & STABLE) SAM PLL - set Step response and PLL bandwidth to small values [eg. 30 / 100].") }, - { MENU_BASE, MENU_ITEM, MENU_SAM_FADE_LEVELER, NULL, "SAM Fade Leveler", UiMenuDesc("Fade leveler of the SAM demodulator can be switched ON/OFF. If ON, it can be expected to better remove the effect of selective fading.") }, + { MENU_BASE, MENU_ITEM, MENU_SAM_FADE_LEVELER, NULL, "SAM Fade Leveler", UiMenuDesc("Fade leveler (in AM/SAM mode) ON/OFF. Fade leveler is helpful in situations with very fast QSB of the carrier ´flutter´. It is designed to remove the rapidly changing carrier and replace it with a more stable carrier. If there is no QSB on the carrier, there is no change.") }, { MENU_BASE, MENU_ITEM, MENU_FM_MODE_ENABLE, NULL, "FM Mode", UiMenuDesc("Disable appearance of FM mode when pressing Mode button")}, { MENU_BASE, MENU_ITEM, MENU_FM_GEN_SUBAUDIBLE_TONE, NULL, "FM Sub Tone Gen", UiMenuDesc("Enable generation of CTCSS tones during FM transmissions.") }, { MENU_BASE, MENU_ITEM, MENU_FM_DET_SUBAUDIBLE_TONE, NULL, "FM Sub Tone Det", UiMenuDesc("Enable detection of CTCSS tones during FM receive. RX is muted unless tone is detected.") },