From 9922f176b248e2cd3196f95db3c0bc9940fa561f Mon Sep 17 00:00:00 2001 From: wb2osz Date: Fri, 27 Nov 2020 21:25:35 -0500 Subject: [PATCH] New AFSK demodulators. 'A' uses mark and space filters but simpler and cleaner than earlier attempts. New 'B' uses a different technique where the demodulated signal is proportional to the frequency. --- src/atest.c | 18 +- src/config.c | 2 +- src/demod.c | 67 +-- src/demod_9600.c | 2 +- src/demod_afsk.c | 894 ++++++++++++++++++----------------- src/demod_psk.c | 2 +- src/dsp.c | 134 ++++-- src/dsp.h | 6 +- src/fsk_demod_agc.h | 2 - src/fsk_demod_state.h | 199 +++++--- test/scripts/check-modem1200 | 6 +- test/scripts/check-modem300 | 6 +- 12 files changed, 754 insertions(+), 584 deletions(-) delete mode 100644 src/fsk_demod_agc.h diff --git a/src/atest.c b/src/atest.c index 5c197759..7e8f03bb 100644 --- a/src/atest.c +++ b/src/atest.c @@ -236,7 +236,7 @@ int main (int argc, char *argv[]) my_audio_config.achan[channel].space_freq = DEFAULT_SPACE_FREQ; my_audio_config.achan[channel].baud = DEFAULT_BAUD; - strlcpy (my_audio_config.achan[channel].profiles, "E", sizeof(my_audio_config.achan[channel].profiles)); + strlcpy (my_audio_config.achan[channel].profiles, "A", sizeof(my_audio_config.achan[channel].profiles)); my_audio_config.achan[channel].num_freq = 1; my_audio_config.achan[channel].offset = 0; @@ -430,19 +430,21 @@ int main (int argc, char *argv[]) /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ /* that need to be kept in sync. Maybe it could be a common function someday. */ - if (my_audio_config.achan[0].baud == 100) { + if (my_audio_config.achan[0].baud == 100) { // What was this for? my_audio_config.achan[0].modem_type = MODEM_AFSK; my_audio_config.achan[0].mark_freq = 1615; my_audio_config.achan[0].space_freq = 1785; - strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles)); + //strlcpy (my_audio_config.achan[0].profiles, "A", sizeof(my_audio_config.achan[0].profiles)); } - else if (my_audio_config.achan[0].baud < 600) { + else if (my_audio_config.achan[0].baud < 600) { // e.g. HF SSB packet my_audio_config.achan[0].modem_type = MODEM_AFSK; my_audio_config.achan[0].mark_freq = 1600; my_audio_config.achan[0].space_freq = 1800; - strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles)); + // Previously we had a "D" which was fine tuned for 300 bps. + // In v1.7, it's not clear if we should use "B" or just stick with "A". + //strlcpy (my_audio_config.achan[0].profiles, "B", sizeof(my_audio_config.achan[0].profiles)); } - else if (my_audio_config.achan[0].baud < 1800) { + else if (my_audio_config.achan[0].baud < 1800) { // common 1200 my_audio_config.achan[0].modem_type = MODEM_AFSK; my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ; my_audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ; @@ -460,7 +462,7 @@ int main (int argc, char *argv[]) my_audio_config.achan[0].space_freq = 0; strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); } - else if (my_audio_config.achan[0].baud == 12345) { + else if (my_audio_config.achan[0].baud == 12345) { // Hack for different use of 9600 my_audio_config.achan[0].modem_type = MODEM_AIS; my_audio_config.achan[0].baud = 9600; my_audio_config.achan[0].mark_freq = 0; @@ -473,7 +475,7 @@ int main (int argc, char *argv[]) // Will make more precise in afsk demod init. my_audio_config.achan[0].mark_freq = 2083; // Actually 2083.3 - logic 1. my_audio_config.achan[0].space_freq = 1563; // Actually 1562.5 - logic 0. - strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles)); + strlcpy (my_audio_config.achan[0].profiles, "A", sizeof(my_audio_config.achan[0].profiles)); } else { my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE; diff --git a/src/config.c b/src/config.c index 08f71a3a..93b92a14 100644 --- a/src/config.c +++ b/src/config.c @@ -1337,7 +1337,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, // Will make more precise in afsk demod init. p_audio_config->achan[channel].mark_freq = 2083; // Actually 2083.3 - logic 1. p_audio_config->achan[channel].space_freq = 1563; // Actually 1562.5 - logic 0. - // ? strlcpy (p_audio_config->achan[channel].profiles, "D", sizeof(p_audio_config->achan[channel].profiles)); + // ? strlcpy (p_audio_config->achan[channel].profiles, "A", sizeof(p_audio_config->achan[channel].profiles)); } else { p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; diff --git a/src/demod.c b/src/demod.c index 281367bc..3f032b41 100644 --- a/src/demod.c +++ b/src/demod.c @@ -198,46 +198,57 @@ int demod_init (struct audio_s *pa) assert (num_letters == (int)(strlen(just_letters))); /* - * Pick a good default demodulator if none specified. + * Pick a good default demodulator if none specified. + * Previously, we had "D" optimized for 300 bps. + * Gone in 1.7 so it is always "A+". */ if (num_letters == 0) { + strlcpy (just_letters, "A", sizeof(just_letters)); + num_letters = strlen(just_letters); - if (save_audio_config_p->achan[chan].baud < 600) { - - /* This has been optimized for 300 baud. */ - - strlcpy (just_letters, "D", sizeof(just_letters)); + if (have_plus != -1) have_plus = 1; // Add as default for version 1.2 + // If not explicitly turned off. + } - } - else { +/* + * Special case for ARM. + * The higher end ARM chips have loads of power but many people + * are using a single core Pi Zero or similar. + * (I'm still using a model 1 for my digipeater/IGate!) + * Decreasing CPU requirement has a negligible impact on decoding performance. + * + * atest -PA- 01_Track_1.wav --> 1002 packets decoded. + * atest -PA- -D3 01_Track_1.wav --> 997 packets decoded. + * + * Someone concerned about 1/2 of one percent difference can add "-D 1" + */ #if __arm__ - /* We probably don't have a lot of CPU power available. */ - /* Previously we would use F if possible otherwise fall back to A. */ - - /* In version 1.2, new default is E+ /3. */ - strlcpy (just_letters, "E", sizeof(just_letters)); // version 1.2 now E. - if (have_plus != -1) have_plus = 1; // Add as default for version 1.2 - // If not explicitly turned off. - if (save_audio_config_p->achan[chan].decimate == 0) { - if (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) { - save_audio_config_p->achan[chan].decimate = 3; - } - } -#else - strlcpy (just_letters, "E", sizeof(just_letters)); // version 1.2 changed C to E. - if (have_plus != -1) have_plus = 1; // Add as default for version 1.2 - // If not explicitly turned off. -#endif + if (save_audio_config_p->achan[chan].decimate == 0) { + if (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) { + save_audio_config_p->achan[chan].decimate = 3; } - num_letters = 1; } +#endif + +/* + * Number of filter taps is proportional to number of audio samples in a "symbol" duration. + * These can get extremely large for low speeds, e.g. 300 baud. + * In this case, increase the decimation ration. Crude approximation. Could be improved. + */ + if (save_audio_config_p->achan[chan].decimate == 0 && + save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000 && + save_audio_config_p->achan[chan].baud < 600) { + // Avoid enormous number of filter taps. + + save_audio_config_p->achan[chan].decimate = 3; + } - assert (num_letters == (int)(strlen(just_letters))); /* * Put it back together again. */ + assert (num_letters == (int)(strlen(just_letters))); /* At this point, have_plus can have 3 values: */ /* 1 = turned on, either explicitly or by applied default */ @@ -286,7 +297,7 @@ int demod_init (struct audio_s *pa) if (save_audio_config_p->achan[chan].decimate == 0) { save_audio_config_p->achan[chan].decimate = 1; - if (strchr (just_letters, 'D') != NULL && save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) { + if (strchr (just_letters, 'B') != NULL && save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) { save_audio_config_p->achan[chan].decimate = 3; } } diff --git a/src/demod_9600.c b/src/demod_9600.c index 2f989830..ef45e4ce 100644 --- a/src/demod_9600.c +++ b/src/demod_9600.c @@ -221,7 +221,7 @@ void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, st //dw_printf ("demod_9600_init: call gen_lowpass(fc=%.2f, , size=%d, )\n", fc, D->lp_filter_size); - (void)gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window, 0); + gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window); /* Version 1.2: Experiment with different slicing levels. */ diff --git a/src/demod_afsk.c b/src/demod_afsk.c index 7a007d1f..c34d8bca 100644 --- a/src/demod_afsk.c +++ b/src/demod_afsk.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2020 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -23,8 +23,7 @@ // #define DEBUG3 1 /* print carrier detect changes. */ // #define DEBUG4 1 /* capture AFSK demodulator output to log files */ - -// #define DEBUG5 1 /* capture 9600 output to log files */ + /* Can be used to make nice plots. */ /*------------------------------------------------------------------ @@ -62,15 +61,32 @@ #define MIN(a,b) ((a)<(b)?(a):(b)) #define MAX(a,b) ((a)>(b)?(a):(b)) +#define TUNE(envvar,param,name,fmt) { \ + char *e = getenv(envvar); \ + if (e != NULL) { \ + param = atof(e); \ + text_color_set (DW_COLOR_ERROR); \ + dw_printf ("TUNE: " name " = " fmt "\n", param); \ + } } + + +// Cosine table indexed by unsigned byte. +static float fcos256_table[256]; + +#define fcos256(x) (fcos256_table[((x)>>24)&0xff]) +#define fsin256(x) (fcos256_table[(((x)>>24)-64)&0xff]) -/* Quick approximation to sqrt(x*x+y*y) */ +static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct demodulator_state_s *D, float amplitude); + + +/* Quick approximation to sqrt(x*x + y*y) */ /* No benefit for regular PC. */ -/* Should help with microcomputer platform. */ +/* Might help with microcomputer platform??? */ -#if 0 // not using anymore __attribute__((hot)) __attribute__((always_inline)) -static inline float z (float x, float y) +static inline float fast_hypot(float x, float y) { +#if 0 x = fabsf(x); y = fabsf(y); @@ -80,8 +96,11 @@ static inline float z (float x, float y) else { return (y * .941246f + x * .41f); } -} +#else + return (hypotf(x,y)); #endif +} + /* Add sample to buffer and shift the rest down. */ @@ -96,22 +115,31 @@ static inline void push_sample (float val, float *buff, int size) /* FIR filter kernel. */ __attribute__((hot)) __attribute__((always_inline)) -static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size) +static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_taps) { float sum = 0.0f; int j; - //#pragma GCC ivdep // ignored until gcc 4.9 - for (j=0; j *ppeak) x = *ppeak; // experiment: clip to envelope? + if (x < *pvalley) x = *pvalley; +#endif if (*ppeak > *pvalley) { - return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); + + return ((x - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); // my original AGC + + //return (( x - 0.5f * (*ppeak + *pvalley )) * ( *ppeak - *pvalley )); // see note below. + //return (x - 0.5f * (*ppeak + *pvalley)); // not as good either. } return (0.0f); } +// K6JQ pointed me to this wonderful article: +// Improved Automatic Threshold Correction Methods for FSK by Kok Chen, W7AY. +// http://www.w7ay.net/site/Technical/ATC/index.html +// +// The stated problem is a little different, selective fading for HF RTTY, but the +// general idea is the similar: Compensating for imbalance of the two tones. +// +// The stronger tone probably has a better S/N ratio so we apply a larger +// weight to it. Effectively it is comparing power rather than amplitude. +// This is the optimal method from the article referenced. +// +// Interesting idea but it did not work as well as the original AGC in this case. +// For VHF FM we are not dealing with rapid deep selective fading of one tone. +// Instead we have an imbalance which is the same for the whole frame. +// It might be interesting to try this with HF SSB packet which is much like RTTY. +// +// I use the term valley rather than noise floor. +// After a little algebra, it looks remarkably similar to the function above. +// +// return (( x - valley ) * ( peak - valley ) - 0.5f * ( peak - valley ) * ( peak - valley )); +// return (( x - valley ) - 0.5f * ( peak - valley )) * ( peak - valley )); +// return (( x - 0.5f * (peak + valley )) * ( peak - valley )); + + + /* * for multi-slicer experiment. */ @@ -162,11 +224,7 @@ static inline float agc (float in, float fast_attack, float slow_decay, float *p * * D - Pointer to demodulator state for given channel. * - * Outputs: D->ms_filter_size - * D->m_sin_table[] - * D->m_cos_table[] - * D->s_sin_table[] - * D->s_cos_table[] + * Outputs: * * Returns: None. * @@ -180,6 +238,10 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, { int j; + + for (j = 0; j < 256; j++) { + fcos256_table[j] = cosf((float)j * 2.0f * (float)M_PI / 256.0f); + } memset (D, 0, sizeof(struct demodulator_state_s)); D->num_slicers = 1; @@ -188,122 +250,156 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, dw_printf ("demod_afsk_init (rate=%d, baud=%d, mark=%d, space=%d, profile=%c\n", samples_per_sec, baud, mark_freq, space_freq, profile); #endif - -#ifdef TUNE_PROFILE - profile = TUNE_PROFILE; -#endif - + D->profile = profile; - D->profile = profile; // so we know whether to take fast path later. + switch (D->profile) { - switch (profile) { + case 'A': // Official name + case 'E': // For compatibility during transition - case 'D': + D->profile = 'A'; - /* Prefilter, Cosine window, FIR lowpass. Tweeked for 300 baud. */ + /* New in version 1.7 */ + /* This is a simpler version of what has been used all along. */ + /* Rather than convolving each sample with a pre-computed mark and */ + /* space filter, we have two free running local oscillators. */ + /* Also see if we can do better with a Root Raised Cosine filter */ + /* which supposedly reduces intersymbol interference. */ D->use_prefilter = 1; /* first, a bandpass filter. */ - D->prefilter_baud = 0.87; - D->pre_filter_len_bits = 1.857; - D->pre_window = BP_WINDOW_COSINE; - D->ms_filter_len_bits = 1.857; /* 91 @ 44100/3, 300 */ - D->ms_window = BP_WINDOW_COSINE; - - //D->bp_window = BP_WINDOW_COSINE; - - D->lpf_use_fir = 1; - D->lpf_baud = 1.10; - D->lp_filter_len_bits = D->ms_filter_len_bits; - D->lp_window = BP_WINDOW_TRUNCATED; + if (baud > 600) { + D->prefilter_baud = 0.155; + // Low cutoff below mark, high cutoff above space + // as fraction of the symbol rate. + // Intuitively you might expect this to be about + // half the symbol rate, e.g. 600 Hz outside + // the two tones of interest for 1200 baud. + // It turns out that narrower is better. + + D->pre_filter_len_sym = 383 * 1200. / 44100.; // about 8 symbols + D->pre_window = BP_WINDOW_TRUNCATED; + } + else { + D->prefilter_baud = 0.87; // TOTO: fine tune + D->pre_filter_len_sym = 1.857; + D->pre_window = BP_WINDOW_COSINE; + } + + // Local oscillators for Mark and Space tones. + + D->u.afsk.m_osc_phase = 0; + D->u.afsk.m_osc_delta = round ( pow(2., 32.) * (double)mark_freq / (double)samples_per_sec ); + + D->u.afsk.s_osc_phase = 0; + D->u.afsk.s_osc_delta = round ( pow(2., 32.) * (double)space_freq / (double)samples_per_sec ); + + D->u.afsk.use_rrc = 1; + TUNE("TUNE_USE_RRC", D->u.afsk.use_rrc, "use_rrc", "%d") + + if (D->u.afsk.use_rrc) { + D->u.afsk.rrc_width_sym = 2.80; + D->u.afsk.rrc_rolloff = 0.20; + } + else { + D->lpf_baud = 0.14; + D->lp_filter_width_sym = 1.388; + D->lp_window = BP_WINDOW_TRUNCATED; + } - D->agc_fast_attack = 0.495; - D->agc_slow_decay = 0.00022; - D->hysteresis = 0.027; + D->agc_fast_attack = 0.820; + D->agc_slow_decay = 0.000214; + D->agc_fast_attack = 0.45; + D->agc_slow_decay = 0.000195; + D->agc_fast_attack = 0.70; + D->agc_slow_decay = 0.000090; - D->pll_locked_inertia = 0.620; - D->pll_searching_inertia = 0.350; + D->pll_locked_inertia = 0.74; + D->pll_searching_inertia = 0.50; break; - case 'F': // removed obsolete. treat as E for now. - case 'E': + case 'B': // official name + case 'D': // backward compatibility - /* 1200 baud - Started out similar to C but add prefilter. */ - /* Version 1.2 */ - /* Enhancements: */ - /* + Add prefilter. Previously used for 300 baud D, but not 1200. */ - /* + Prefilter length now independent of M/S filters. */ - /* + Lowpass filter length now independent of M/S filters. */ - /* + Allow mixed window types. */ + D->profile = 'B'; - //D->bp_window = BP_WINDOW_COSINE; /* The name says BP but it is used for all of them. */ + // Experiment for version 1.7. + // Up to this point, I've always used separate mark and space + // filters and compared the amplitudes. + // Another technique for an FM demodulator is to mix with + // the center frequency and look for the rate of change of the phase. D->use_prefilter = 1; /* first, a bandpass filter. */ - D->prefilter_baud = 0.23; - D->pre_filter_len_bits = 156 * 1200. / 44100.; - D->pre_window = BP_WINDOW_TRUNCATED; - - D->ms_filter_len_bits = 74 * 1200. / 44100.; - D->ms_window = BP_WINDOW_COSINE; - D->lpf_use_fir = 1; - D->lpf_baud = 1.18; - D->lp_filter_len_bits = 63 * 1200. / 44100.; - D->lp_window = BP_WINDOW_TRUNCATED; - - //D->agc_fast_attack = 0.300; - //D->agc_slow_decay = 0.000185; - D->agc_fast_attack = 0.820; - D->agc_slow_decay = 0.000214; - D->hysteresis = 0.01; + if (baud > 600) { + D->prefilter_baud = 0.19; + // Low cutoff below mark, high cutoff above space + // as fraction of the symbol rate. + // Intuitively you might expect this to be about + // half the symbol rate, e.g. 600 Hz outside + // the two tones of interest for 1200 baud. + // It turns out that narrower is better. + + D->pre_filter_len_sym = 8.163; // Filter length in symbol times. + D->pre_window = BP_WINDOW_TRUNCATED; + } + else { + D->prefilter_baud = 0.87; // TOTO: fine tune + D->pre_filter_len_sym = 1.857; + D->pre_window = BP_WINDOW_COSINE; + } + + // Local oscillator for Center frequency. + + D->u.afsk.c_osc_phase = 0; + D->u.afsk.c_osc_delta = round ( pow(2., 32.) * 0.5 * (mark_freq + space_freq) / (double)samples_per_sec ); + + D->u.afsk.use_rrc = 1; + TUNE("TUNE_USE_RRC", D->u.afsk.use_rrc, "use_rrc", "%d") + + if (D->u.afsk.use_rrc) { + D->u.afsk.rrc_width_sym = 2.00; + D->u.afsk.rrc_rolloff = 0.40; + } + else { + D->lpf_baud = 0.5; + D->lp_filter_width_sym = 1.714286; // 63 * 1200. / 44100.; + D->lp_window = BP_WINDOW_TRUNCATED; + } + + // For scaling phase shift into normallized -1 to +1 range for mark and space. + D->u.afsk.normalize_rpsam = 1.0 / (0.5 * abs(mark_freq - space_freq) * 2 * M_PI / samples_per_sec); - //D->pll_locked_inertia = 0.57; - //D->pll_searching_inertia = 0.33; D->pll_locked_inertia = 0.74; D->pll_searching_inertia = 0.50; + + D->alevel_mark_peak = -1; // FIXME: disable display + D->alevel_space_peak = -1; break; default: text_color_set(DW_COLOR_ERROR); - dw_printf ("Invalid filter profile = %c\n", profile); + dw_printf ("Invalid AFSK demodulator profile = %c\n", profile); exit (1); } -#ifdef TUNE_PRE_WINDOW - D->pre_window = TUNE_PRE_WINDOW; -#endif -#ifdef TUNE_MS_WINDOW - D->ms_window = TUNE_MS_WINDOW; -#endif -#ifdef TUNE_MS2_WINDOW - D->ms2_window = TUNE_MS2_WINDOW; -#endif -#ifdef TUNE_LP_WINDOW - D->lp_window = TUNE_LP_WINDOW; -#endif + TUNE("TUNE_PRE_BAUD", D->prefilter_baud, "prefilter_baud", "%.3f") + TUNE("TUNE_PRE_WINDOW", D->pre_window, "pre_window", "%d") + + TUNE("TUNE_LPF_BAUD", D->lpf_baud, "lpf_baud", "%.3f") + TUNE("TUNE_LP_WINDOW", D->lp_window, "lp_window", "%d") + + TUNE("TUNE_RRC_ROLLOFF", D->u.afsk.rrc_rolloff, "rrc_rolloff", "%.2f") + TUNE("TUNE_RRC_WIDTH_SYM", D->u.afsk.rrc_width_sym, "rrc_width_sym", "%.2f") + + TUNE("TUNE_AGC_FAST", D->agc_fast_attack, "agc_fast_attack", "%.3f") + TUNE("TUNE_AGC_SLOW", D->agc_slow_decay, "agc_slow_decay", "%.6f") + + TUNE("TUNE_PLL_LOCKED", D->pll_locked_inertia, "pll_locked_inertia", "%.2f") + TUNE("TUNE_PLL_SEARCHING", D->pll_searching_inertia, "pll_searching_inertia", "%.2f") -#if defined(TUNE_AGC_FAST) && defined(TUNE_AGC_SLOW) - D->agc_fast_attack = TUNE_AGC_FAST; - D->agc_slow_decay = TUNE_AGC_SLOW; -#endif -#ifdef TUNE_HYST - D->hysteresis = TUNE_HYST; -#endif -#if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING) - D->pll_locked_inertia = TUNE_PLL_LOCKED; - D->pll_searching_inertia = TUNE_PLL_SEARCHING; -#endif -#ifdef TUNE_LPF_BAUD - D->lpf_baud = TUNE_LPF_BAUD; -#endif -#ifdef TUNE_PRE_BAUD - D->prefilter_baud = TUNE_PRE_BAUD; -#endif -#ifdef TUNE_LP_DELAY_FRACT - D->lp_delay_fract = TUNE_LP_DELAY_FRACT; -#endif /* * Calculate constants used for timing. @@ -321,77 +417,28 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)baud) / ((double)samples_per_sec)); } -/* - * Convert number of bit times to number of taps. - */ - - D->pre_filter_size = (int) round( D->pre_filter_len_bits * (float)samples_per_sec / (float)baud ); - D->ms_filter_size = (int) round( D->ms_filter_len_bits * (float)samples_per_sec / (float)baud ); - D->lp_filter_size = (int) round( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud ); - -/* Experiment with other sizes. */ - -#ifdef TUNE_PRE_FILTER_SIZE - D->pre_filter_size = TUNE_PRE_FILTER_SIZE; -#endif -#ifdef TUNE_MS_FILTER_SIZE - D->ms_filter_size = TUNE_MS_FILTER_SIZE; -#endif -#ifdef TUNE_LP_FILTER_SIZE - D->lp_filter_size = TUNE_LP_FILTER_SIZE; -#endif - - //assert (D->pre_filter_size >= 4); - assert (D->ms_filter_size >= 4); - //assert (D->lp_filter_size >= 4); - - if (D->pre_filter_size > MAX_FILTER_SIZE) - { - text_color_set (DW_COLOR_ERROR); - dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size); - dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); - dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", - MAX_FILTER_SIZE); - exit (1); - } - - if (D->ms_filter_size > MAX_FILTER_SIZE) - { - text_color_set (DW_COLOR_ERROR); - dw_printf ("Calculated filter size of %d is too large.\n", D->ms_filter_size); - dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); - dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", - MAX_FILTER_SIZE); - exit (1); - } - - - - if (D->lp_filter_size > MAX_FILTER_SIZE) - { - text_color_set (DW_COLOR_ERROR); - dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size); - dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); - dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", - MAX_FILTER_SIZE); - exit (1); - } - /* * Optionally apply a bandpass ("pre") filter to attenuate * frequencies outside the range of interest. - * This was first used for the "D" profile for 300 baud - * which uses narrow shift. We expect it to have significant - * benefit for a narrow shift. - * In version 1.2, we will also try it with 1200 baud "E" as - * an experiment to see how much it actually helps. */ if (D->use_prefilter) { - float f1, f2; - f1 = MIN(mark_freq,space_freq) - D->prefilter_baud * baud; - f2 = MAX(mark_freq,space_freq) + D->prefilter_baud * baud; + // odd number is a little better + D->pre_filter_taps = ((int)( D->pre_filter_len_sym * (float)samples_per_sec / (float)baud )) | 1; + + TUNE("TUNE_PRE_FILTER_TAPS", D->pre_filter_taps, "pre_filter_taps", "%d") + + if (D->pre_filter_taps > MAX_FILTER_SIZE) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Calculated pre filter size of %d is too large.\n", D->pre_filter_taps); + dw_printf ("Decrease the audio sample rate or increase the decimation factor or\n"); + dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", MAX_FILTER_SIZE); + D->pre_filter_taps = (MAX_FILTER_SIZE - 1) | 1; + } + + float f1 = MIN(mark_freq,space_freq) - D->prefilter_baud * baud; + float f2 = MAX(mark_freq,space_freq) + D->prefilter_baud * baud; #if 0 text_color_set(DW_COLOR_DEBUG); dw_printf ("Generating prefilter %.0f to %.0f Hz.\n", f1, f2); @@ -399,121 +446,67 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, f1 = f1 / (float)samples_per_sec; f2 = f2 / (float)samples_per_sec; - gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->pre_window); + gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_taps, D->pre_window); } -/* - * Filters for detecting mark and space tones. - */ - -#if DEBUG1 - text_color_set(DW_COLOR_DEBUG); - dw_printf ("%s: \n", __FILE__); - dw_printf ("%d baud, %d samples_per_sec\n", baud, samples_per_sec); - dw_printf ("AFSK %d & %d Hz\n", mark_freq, space_freq); - dw_printf ("spll_step_per_sample = %d = 0x%08x\n", D->pll_step_per_sample, D->pll_step_per_sample); - dw_printf ("D->ms_filter_size = %d = 0x%08x\n", D->ms_filter_size, D->ms_filter_size); - dw_printf ("\n"); - dw_printf ("Mark\n"); - dw_printf (" j shape M sin M cos \n"); -#endif - - - gen_ms (mark_freq, samples_per_sec, D->m_sin_table, D->m_cos_table, D->ms_filter_size, D->ms_window); - -#if DEBUG1 - text_color_set(DW_COLOR_DEBUG); - - dw_printf ("Space\n"); - dw_printf (" j shape S sin S cos\n"); -#endif - - gen_ms (space_freq, samples_per_sec, D->s_sin_table, D->s_cos_table, D->ms_filter_size, D->ms_window); - /* * Now the lowpass filter. - * I thought we'd want a cutoff of about 0.5 the baud rate - * but it turns out about 1.1x is better. Still investigating... + * In version 1.7 a Root Raised Cosine filter is added as an alternative + * to the generic low pass filter. + * In both cases, lp_filter and lp_filter_taps are used but the + * contents will be generated differently. Later code does not care. */ + if (D->u.afsk.use_rrc) { - if (D->lpf_use_fir) { - float fc; - fc = baud * D->lpf_baud / (float)samples_per_sec; - D->lp_filter_delay = gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window, D->lp_delay_fract); - } - else { - // D->lp_filter_delay = - // Only needed for looking back and I don't expect to use IIR in that case. - } + assert (D->u.afsk.rrc_width_sym >= 1 && D->u.afsk.rrc_width_sym <= 16); + assert (D->u.afsk.rrc_rolloff >= 0. && D->u.afsk.rrc_rolloff <= 1.); -/* - * A non-whole number of cycles results in a DC bias. - * Let's see if it helps to take it out. - * Actually makes things worse: 20 fewer decoded. - * Might want to try again after EXPERIMENTC. - */ + D->lp_filter_taps = ((int) (D->u.afsk.rrc_width_sym * (float)samples_per_sec / baud)) | 1; // odd works better -#if 0 -#ifndef AVOID_FLOATING_POINT + TUNE("TUNE_LP_FILTER_TAPS", D->lp_filter_taps, "lp_filter_taps (RRC)", "%d") -failed experiment + if (D->lp_filter_taps > MAX_FILTER_SIZE) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Calculated RRC low pass filter size of %d is too large.\n", D->lp_filter_taps); + dw_printf ("Decrease the audio sample rate or increase the decimation factor or\n"); + dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", MAX_FILTER_SIZE); + D->lp_filter_taps = (MAX_FILTER_SIZE - 1) | 1; + } - dc_bias = 0; - for (j=0; jms_filter_size; j++) { - dc_bias += D->m_sin_table[j]; - } - for (j=0; jms_filter_size; j++) { - D->m_sin_table[j] -= dc_bias / D->ms_filter_size; + assert (D->lp_filter_taps > 8 && D->lp_filter_taps <= MAX_FILTER_SIZE); + (void)gen_rrc_lowpass (D->lp_filter, D->lp_filter_taps, D->u.afsk.rrc_rolloff, (float)samples_per_sec / baud); } + else { + D->lp_filter_taps = (int) round( D->lp_filter_width_sym * (float)samples_per_sec / (float)baud ); - dc_bias = 0; - for (j=0; jms_filter_size; j++) { - dc_bias += D->m_cos_table[j]; - } - for (j=0; jms_filter_size; j++) { - D->m_cos_table[j] -= dc_bias / D->ms_filter_size; - } + TUNE("TUNE_LP_FILTER_TAPS", D->lp_filter_taps, "lp_filter_taps (FIR)", "%d") + if (D->lp_filter_taps > MAX_FILTER_SIZE) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Calculated FIR low pass filter size of %d is too large.\n", D->lp_filter_taps); + dw_printf ("Decrease the audio sample rate or increase the decimation factor or\n"); + dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", MAX_FILTER_SIZE); + D->lp_filter_taps = (MAX_FILTER_SIZE - 1) | 1; + } - dc_bias = 0; - for (j=0; jms_filter_size; j++) { - dc_bias += D->s_sin_table[j]; - } - for (j=0; jms_filter_size; j++) { - D->s_sin_table[j] -= dc_bias / D->ms_filter_size; - } + assert (D->lp_filter_taps > 8 && D->lp_filter_taps <= MAX_FILTER_SIZE); - dc_bias = 0; - for (j=0; jms_filter_size; j++) { - dc_bias += D->s_cos_table[j]; - } - for (j=0; jms_filter_size; j++) { - D->s_cos_table[j] -= dc_bias / D->ms_filter_size; + float fc = baud * D->lpf_baud / (float)samples_per_sec; + gen_lowpass (fc, D->lp_filter, D->lp_filter_taps, D->lp_window); } -#endif -#endif /* - * In version 1.2 we try another experiment. - * Try using multiple slicing points instead of the traditional AGC. + * Starting with version 1.2 + * try using multiple slicing points instead of the traditional AGC. */ - space_gain[0] = MIN_G; float step = powf(10.0, log10f(MAX_G/MIN_G) / (MAX_SUBCHANS-1)); for (j=1; j= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); /* - * Filters use last 'filter_size' samples. + * Filters use last 'filter_taps' samples. * * First push the older samples down. * @@ -588,187 +612,182 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat * Future project? Can we do better than shifting each time? */ - /* Scale to nice number, TODO: range -1.0 to +1.0, not 2. */ + /* Scale to nice number. */ - fsam = sam / 16384.0f; + float fsam = (float)sam / 16384.0f; - //abs_fsam = fsam >= 0.0f ? fsam : -fsam; + switch (D->profile) { + case 'E': + default: + case 'A': { + /* ========== New in Version 1.7 ========== */ -/* - * Optional bandpass filter before the mark/space discriminator. - */ + // Cleaner & simpler than earlier 'A' thru 'E' -// FIXME: calculate how much we really need. + if (D->use_prefilter) { + push_sample (fsam, D->raw_cb, D->pre_filter_taps); + fsam = convolve (D->raw_cb, D->pre_filter, D->pre_filter_taps); + } - int extra = 0; + push_sample (fsam * fcos256(D->u.afsk.m_osc_phase), D->u.afsk.m_I_raw, D->lp_filter_taps); + push_sample (fsam * fsin256(D->u.afsk.m_osc_phase), D->u.afsk.m_Q_raw, D->lp_filter_taps); + D->u.afsk.m_osc_phase += D->u.afsk.m_osc_delta; - if (D->use_prefilter) { - float cleaner; + push_sample (fsam * fcos256(D->u.afsk.s_osc_phase), D->u.afsk.s_I_raw, D->lp_filter_taps); + push_sample (fsam * fsin256(D->u.afsk.s_osc_phase), D->u.afsk.s_Q_raw, D->lp_filter_taps); + D->u.afsk.s_osc_phase += D->u.afsk.s_osc_delta; - push_sample (fsam, D->raw_cb, D->pre_filter_size); - cleaner = convolve (D->raw_cb, D->pre_filter, D->pre_filter_size); - push_sample (cleaner, D->ms_in_cb, D->ms_filter_size + extra); - } - else { - push_sample (fsam, D->ms_in_cb, D->ms_filter_size + extra); - } + float m_I = convolve (D->u.afsk.m_I_raw, D->lp_filter, D->lp_filter_taps); + float m_Q = convolve (D->u.afsk.m_Q_raw, D->lp_filter, D->lp_filter_taps); + float m_amp = fast_hypot(m_I, m_Q); -/* - * Next we have bandpass filters for the mark and space tones. - */ + float s_I = convolve (D->u.afsk.s_I_raw, D->lp_filter, D->lp_filter_taps); + float s_Q = convolve (D->u.afsk.s_Q_raw, D->lp_filter, D->lp_filter_taps); + float s_amp = fast_hypot(s_I, s_Q); /* - * find amplitude of "Mark" tone. - */ - m_sum1 = convolve (D->ms_in_cb, D->m_sin_table, D->ms_filter_size); - m_sum2 = convolve (D->ms_in_cb, D->m_cos_table, D->ms_filter_size); - - m_amp = sqrtf(m_sum1 * m_sum1 + m_sum2 * m_sum2); - -/* - * Find amplitude of "Space" tone. - */ - s_sum1 = convolve (D->ms_in_cb, D->s_sin_table, D->ms_filter_size); - s_sum2 = convolve (D->ms_in_cb, D->s_cos_table, D->ms_filter_size); - - s_amp = sqrtf(s_sum1 * s_sum1 + s_sum2 * s_sum2); - - -/* - * Apply some low pass filtering BEFORE the AGC to remove - * overshoot, ringing, and other bad stuff. - * - * A simple IIR filter is faster but FIR produces better results. - * - * It is a balancing act between removing high frequency components - * from the tone dectection while letting the data thru. + * Capture the mark and space peak amplitudes for display. + * It uses fast attack and slow decay to get an idea of the + * overall amplitude. */ + if (m_amp >= D->alevel_mark_peak) { + D->alevel_mark_peak = m_amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack); + } + else { + D->alevel_mark_peak = m_amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay); + } + + if (s_amp >= D->alevel_space_peak) { + D->alevel_space_peak = s_amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack); + } + else { + D->alevel_space_peak = s_amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay); + } + + if (D->num_slicers <= 1) { + + // Which tone is stonger? That's simple with an ideal signal. + // However, we don't see too many ideal signals. + // Due to mismatching pre-emphasis and de-emphasis, the two + // tones will often have greatly different amplitudes so we use + // automatic gain control (AGC) to scale each to the same range + // before comparing. + // This is probably over complicated and could be combined with + // the signal amplitude measurement, above. + // It works so let's move along to other topics. + + float m_norm = agc (m_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley)); + float s_norm = agc (s_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->s_peak), &(D->s_valley)); + + // The normalized values should be around -0.5 to +0.5 so the difference + // should work out to be around -1 to +1. + // This is important because nudge_pll uses the demod_out amplitude to assign + // a quality or confidence score to the symbol. + + float demod_out = m_norm - s_norm; + + // Tested and it looks good. Range of about -1 to +1. + //printf ("JWL DEBUG demod A with agc = %6.2f\n", demod_out); + + nudge_pll (chan, subchan, 0, demod_out, D, 1.0); + + } + else { + // Multiple slice case. + // Rather than trying to find the best threshold location, use multiple + // slicer thresholds in parallel. + // The best slicing point will vary from packet to packet but should + // remain abount the same or a given packet. + + // We are not performing the AGC step here but still want the envelope + // for caluculating the confidence level (or quality) of the sample. + + (void) agc (m_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley)); + (void) agc (s_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->s_peak), &(D->s_valley)); + + for (int slice=0; slicenum_slicers; slice++) { + float demod_out = m_amp - s_amp * space_gain[slice]; + float amp = 0.5f * (D->m_peak - D->m_valley + (D->s_peak - D->s_valley) * space_gain[slice]); + if (amp < 0.0000001f) amp = 1; // avoid divide by zero with no signal. + + // Tested and it looks good. Range of about -1 to +1 relative to amp. + // Biased one way or the other depending on the space gain. + //printf ("JWL DEBUG demod A with slicer %d: %6.2f / %6.2f = %6.2f\n", slice, demod_out, amp, demod_out/amp); + + nudge_pll (chan, subchan, slice, demod_out, D, amp); + } + } + } + break; - if (D->lpf_use_fir) { + case 'D': + case 'B': { + /* ========== Version 1.7 Experiment ========== */ - push_sample (m_amp, D->m_amp_cb, D->lp_filter_size); - m_amp = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size); + // New - Convert frequency to a value proportional to frequency. - push_sample (s_amp, D->s_amp_cb, D->lp_filter_size); - s_amp = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size); - } - else { - - /* Original, but faster, IIR. */ + if (D->use_prefilter) { + push_sample (fsam, D->raw_cb, D->pre_filter_taps); + fsam = convolve (D->raw_cb, D->pre_filter, D->pre_filter_taps); + } - m_amp = D->lpf_iir * m_amp + (1.0f - D->lpf_iir) * D->m_amp_prev; - D->m_amp_prev = m_amp; + push_sample (fsam * fcos256(D->u.afsk.c_osc_phase), D->u.afsk.c_I_raw, D->lp_filter_taps); + push_sample (fsam * fsin256(D->u.afsk.c_osc_phase), D->u.afsk.c_Q_raw, D->lp_filter_taps); + D->u.afsk.c_osc_phase += D->u.afsk.c_osc_delta; - s_amp = D->lpf_iir * s_amp + (1.0f - D->lpf_iir) * D->s_amp_prev; - D->s_amp_prev = s_amp; - } + float c_I = convolve (D->u.afsk.c_I_raw, D->lp_filter, D->lp_filter_taps); + float c_Q = convolve (D->u.afsk.c_Q_raw, D->lp_filter, D->lp_filter_taps); -/* - * Version 1.2: Try new approach to capturing the amplitude for display. - * This is same as the AGC above without the normalization step. - * We want decay to be substantially slower to get a longer - * range idea of the received audio. - */ + float phase = atan2f (c_Q, c_I); + float rate = phase - D->u.afsk.prev_phase; + if (rate > M_PI) rate -= 2 * M_PI; + else if (rate < -M_PI) rate += 2 * M_PI; + D->u.afsk.prev_phase = phase; - if (m_amp >= D->alevel_mark_peak) { - D->alevel_mark_peak = m_amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack); - } - else { - D->alevel_mark_peak = m_amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay); - } + // Rate is radians per audio sample interval or something like that. + // Scale scale that into -1 to +1 for expected tones. - if (s_amp >= D->alevel_space_peak) { - D->alevel_space_peak = s_amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack); - } - else { - D->alevel_space_peak = s_amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay); - } + float norm_rate = rate * D->u.afsk.normalize_rpsam; + // We really don't have mark and space amplitudes available in this case. -/* - * Which tone is stronger? - * - * In an ideal world, simply compare. In my first naive attempt, that - * worked perfectly with perfect signals. In the real world, we don't - * have too many perfect signals. - * - * Here is an excellent explanation: - * http://www.febo.com/packet/layer-one/transmit.html - * - * Under real conditions, we find that the higher tone usually has a - * considerably smaller amplitude due to the passband characteristics - * of the transmitter and receiver. To make matters worse, it - * varies considerably from one station to another. - * - * The two filters also have different amounts of DC bias. - * - * My solution was to apply automatic gain control (AGC) to the mark and space - * levels. This works by looking at the minimum and maximum outputs - * for each filter and scaling the results to be roughly in the -0.5 to +0.5 range. - * Results were excellent after tweaking the attack and decay times. - * - * 4X6IZ took a different approach. See QEX Jul-Aug 2012. - * - * He ran two different demodulators in parallel. One of them boosted the higher - * frequency tone by 6 dB. Any duplicates were removed. This produced similar results. - * He also used a bandpass filter before the mark/space filters. - * I haven't tried this combination yet for 1200 baud. - * - * First, let's take a look at Track 1 of the TNC test CD. Here the receiver - * has a flat response. We find the mark/space strength ratios very from 0.53 to 1.38 - * with a median of 0.81. This in in line with expections because most - * transmitters add pre-emphasis to boost the higher audio frequencies. - * Track 2 should more closely resemble what comes out of the speaker on a typical - * transceiver. Here we see a ratio from 1.73 to 3.81 with a median of 2.48. - * - * This is similar to my observations of local signals, from the speaker. - * The amplitude ratio varies from 1.48 to 3.41 with a median of 2.70. - * - * Rather than only two filters, let's try slicing the data in more places. - */ + if (D->num_slicers <= 1) { - /* Fast attack and slow decay. */ - /* Numbers were obtained by trial and error from actual */ - /* recorded less-than-optimal signals. */ + float demod_out = norm_rate; + // Tested and it looks good. Range roughly -1 to +1. + //printf ("JWL DEBUG demod B single = %6.2f\n", demod_out); - /* See fsk_demod_agc.h for more information. */ + nudge_pll (chan, subchan, 0, demod_out, D, 1.0); - m_norm = agc (m_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley)); - s_norm = agc (s_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->s_peak), &(D->s_valley)); + } + else { - if (D->num_slicers <= 1) { + // This would be useful for HF SSB where a tuning error + // would shift the frequency. Multiple slicing points would + // then compensate for differences in transmit/receive frequencies. + // + // Where should we set the thresholds? + // I'm thinking something like: + // -.5 -.375 -.25 -.125 0 .125 .25 .375 .5 + // + // Assuming a 300 Hz shift, this would put slicing thresholds up + // to +-75 Hz from the center. - /* Normal case of one demodulator to one HDLC decoder. */ - /* Demodulator output is difference between response from two filters. */ - /* AGC should generally keep this around -1 to +1 range. */ + for (int slice=0; slicenum_slicers; slice++) { - demod_out = m_norm - s_norm; + float offset = -0.5 + slice * (1. / (D->num_slicers - 1)); + float demod_out = norm_rate + offset; - /* Try adding some Hysteresis. */ - /* (Not to be confused with Hysteria.) */ + //printf ("JWL DEBUG demod B slice %d, offset = %6.3f, demod_out = %6.2f\n", slice, offset, demod_out); - if (demod_out > D->hysteresis) { - demod_data = 1; - } - else if (demod_out < (- (D->hysteresis))) { - demod_data = 0; - } - else { - demod_data = D->slicer[subchan].prev_demod_data; + nudge_pll (chan, subchan, slice, demod_out, D, 1.0); + } } - nudge_pll (chan, subchan, 0, demod_data, D); - } - else { - int slice; - - for (slice=0; slicenum_slicers; slice++) { - demod_data = m_amp > s_amp * space_gain[slice]; - nudge_pll (chan, subchan, slice, demod_data, D); } + break; } - - + #if DEBUG4 if (chan == 0) { @@ -805,9 +824,6 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat } /* end demod_afsk_process_sample */ -__attribute__((hot)) -inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D) -{ /* * Finally, a PLL is used to sample near the centers of the data bits. @@ -839,6 +855,9 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, * because this happens for each transition from the demodulator. */ +__attribute__((hot)) +static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct demodulator_state_s *D, float amplitude) +{ D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; // Perform the add as unsigned to avoid signed overflow error. @@ -850,12 +869,19 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll > 0) { /* Overflow - this is where we sample. */ - hdlc_rec_bit (chan, subchan, slice, demod_data, 0, -1); + // Assign it a confidence level or quality, 0 to 100, based on the amplitude. + // Those very close to 0 are suspect. We'll get back to this later. + + int quality = fabsf(demod_out) * 100.0f / amplitude; + if (quality > 100) quality = 100; + + hdlc_rec_bit (chan, subchan, slice, demod_out > 0, 0, quality); pll_dcd_each_symbol2 (D, chan, subchan, slice); } // Transitions nudge the DPLL phase toward the incoming signal. + int demod_data = demod_out > 0; if (demod_data != D->slicer[slice].prev_demod_data) { pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll); diff --git a/src/demod_psk.c b/src/demod_psk.c index f01ee217..b953addf 100644 --- a/src/demod_psk.c +++ b/src/demod_psk.c @@ -501,7 +501,7 @@ void demod_psk_init (enum modem_t modem_type, enum v26_e v26_alt, int samples_pe */ float fc = correct_baud * D->u.psk.lpf_baud / (float)samples_per_sec; - gen_lowpass (fc, D->u.psk.lp_filter, D->u.psk.lp_filter_taps, D->u.psk.lp_window, 0); + gen_lowpass (fc, D->u.psk.lp_filter, D->u.psk.lp_filter_taps, D->u.psk.lp_window); /* * No point in having multiple numbers for signal level. diff --git a/src/dsp.c b/src/dsp.c index 6ba70949..4a5f4a88 100644 --- a/src/dsp.c +++ b/src/dsp.c @@ -43,7 +43,6 @@ #include "dsp.h" -//#include "fsk_demod_agc.h" /* for M_FILTER_SIZE, etc. */ #define MIN(a,b) ((a)<(b)?(a):(b)) #define MAX(a,b) ((a)>(b)?(a):(b)) @@ -127,7 +126,7 @@ float window (bp_window_t type, int size, int j) *----------------------------------------------------------------*/ -int gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype, float lp_delay_fract) +void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype) { int j; float G; @@ -175,54 +174,7 @@ int gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype, lp_filter[j] = lp_filter[j] / G; } - -// Calculate the signal delay. -// If a signal at level 0 steps to level 1, this is the time that it would -// take for the output to reach 0.5. -// -// Examples: -// -// Filter has one tap with value of 1.0. -// Output is immediate so I would call this delay of 0. -// -// Filter coefficients: 0.2, 0.2, 0.2, 0.2, 0.2 -// "1" inputs Out -// 1 0.2 -// 2 0.4 -// 3 0.6 -// -// In this case, the output does not change immediately. -// It takes two more samples to reach the half way point -// so it has a delay of 2. - - float sum = 0; - int delay = 0; - - if (lp_delay_fract == 0) lp_delay_fract = 0.5; - - for (j=0; j lp_delay_fract) { - delay = j; - break; - } - } - -#if DEBUG1 - dw_printf ("Low Pass Delay = %d samples\n", delay) ; -#endif - -// Hmmm. This might have been wasted effort. The result is always half the number of taps. - - if (delay < 2 || delay > filter_size - 2) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Internal error, %s %d, delay %d for size %d\n", __func__, __LINE__, delay, filter_size); - } - - return (delay); + return; } /* end gen_lowpass */ @@ -369,4 +321,86 @@ void gen_ms (int fc, int sps, float *sin_table, float *cos_table, int filter_siz } /* end gen_ms */ + + + +/*------------------------------------------------------------------ + * + * Name: rrc + * + * Purpose: Root Raised Cosine function. + * Why do they call it that? + * It's mostly the sinc function with cos windowing to taper off edges faster. + * + * Inputs: t - Time in units of symbol duration. + * i.e. The centers of two adjacent symbols would differ by 1. + * + * a - Roll off factor, between 0 and 1. + * + * Returns: Basically the sinc (sin(x)/x) function with edges decreasing faster. + * Should be 1 for t = 0 and 0 at all other integer values of t. + * + *----------------------------------------------------------------*/ + +__attribute__((const)) +float rrc (float t, float a) +{ + float sinc, window, result; + + if (t > -0.001 && t < 0.001) { + sinc = 1; + } + else { + sinc = sinf(M_PI * t) / (M_PI * t); + } + + if (fabsf(a * t) > 0.499 && fabsf(a * t) < 0.501) { + window = M_PI / 4; + } + else { + window = cos(M_PI * a * t) / ( 1 - powf(2 * a * t, 2)); + // This made nicer looking waveforms for generating signal. + //window = cos(M_PI * a * t); + // Do we want to let it go negative? + // I think this would happen when a > 0.5 / (filter width in symbol times) + if (window < 0) { + //printf ("'a' is too large for range of 't'.\n"); + //window = 0; + } + } + + result = sinc * window; + +#if DEBUGRRC + // t should vary from - to + half of filter size in symbols. + // Result should be 1 at t=0 and 0 at all other integer values of t. + + printf ("%.3f, %.3f, %.3f, %.3f\n", t, sinc, window, result); +#endif + return (result); +} + +// The Root Raised Cosine (RRC) low pass filter is suppposed to minimize Intersymbol Interference (ISI). + +void gen_rrc_lowpass (float *pfilter, int filter_taps, float rolloff, float samples_per_symbol) +{ + int k; + float t; + + for (k = 0; k < filter_taps; k++) { + t = (k - ((filter_taps - 1.0) / 2.0)) / samples_per_symbol; + pfilter[k] = rrc (t, rolloff); + } + + // Scale it for unity gain. + + t = 0; + for (k = 0; k < filter_taps; k++) { + t += pfilter[k]; + } + for (k = 0; k < filter_taps; k++) { + pfilter[k] = pfilter[k] / t; + } +} + /* end dsp.c */ diff --git a/src/dsp.h b/src/dsp.h index 5d5b882d..e0dbd248 100644 --- a/src/dsp.h +++ b/src/dsp.h @@ -5,9 +5,13 @@ float window (bp_window_t type, int size, int j); -int gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype, float lp_delay_fract); +void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype); void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype); void gen_ms (int fc, int samples_per_sec, float *sin_table, float *cos_table, int filter_size, int wtype); + +__attribute__((const)) float rrc (float t, float a); + +void gen_rrc_lowpass (float *pfilter, int filter_taps, float rolloff, float samples_per_symbol); diff --git a/src/fsk_demod_agc.h b/src/fsk_demod_agc.h deleted file mode 100644 index 95c80794..00000000 --- a/src/fsk_demod_agc.h +++ /dev/null @@ -1,2 +0,0 @@ -#define TUNE_MS_FILTER_SIZE 140 -#define TUNE_PRE_BAUD 1.080 diff --git a/src/fsk_demod_state.h b/src/fsk_demod_state.h index f7b56502..33f7901d 100644 --- a/src/fsk_demod_state.h +++ b/src/fsk_demod_state.h @@ -20,6 +20,31 @@ typedef enum bp_window_e { BP_WINDOW_TRUNCATED, BP_WINDOW_BLACKMAN, BP_WINDOW_FLATTOP } bp_window_t; +// Experimental low pass filter to detect DC bias or low frequency changes. +// IIR behaves like an analog R-C filter. +// Intuitively, it seems like FIR would be better because it is based on a finite history. +// However, it would require MANY taps and a LOT of computation for a low frequency. +// We can use a little trick here to keep a running average. +// This would be equivalent to convolving with an array of all 1 values. +// That would eliminate the need to multiply. +// We can also eliminate the need to add them all up each time by keeping a running total. +// Add a sample to the total when putting it in our array of recent samples. +// Subtract it from the total when it gets pushed off the end. +// We can also eliminate the need to shift them all down by using a circular buffer. + +#define CIC_LEN_MAX 4000 + +typedef struct cic_s { + int len; // Number of elements used. + // Might want to dynamically allocate. + short in[CIC_LEN_MAX]; // Samples coming in. + int sum; // Running sum. + int inext; // Next position to fill. +} cic_t; + + +#define MAX_FILTER_SIZE 404 /* 401 is needed for profile A, 300 baud & 44100. Revisit someday. */ + struct demodulator_state_s { @@ -39,30 +64,12 @@ struct demodulator_state_s // Data is sampled when it overflows. - int ms_filter_size; /* Size of mark & space filters, in audio samples. */ - /* Started off as a guess of one bit length */ - /* but about 2 bit times turned out to be better. */ - /* Currently using same size for any prefilter. */ - - -#define MAX_FILTER_SIZE 320 /* 304 is needed for profile C, 300 baud & 44100. */ - -/* - * Filter length for Mark & Space in bit times. - * e.g. 1 means 1/1200 second for 1200 baud. - */ - float ms_filter_len_bits; - float lp_delay_fract; - /* * Window type for the various filters. */ - bp_window_t pre_window; - bp_window_t ms_window; bp_window_t lp_window; - /* * Alternate Low pass filters. * First is arbitrary number for quick IIR. @@ -78,16 +85,13 @@ struct demodulator_state_s /* In practice, it turned out a little larger */ /* for profiles B, C, D. */ - float lp_filter_len_bits; /* Length in number of bit times. */ + float lp_filter_width_sym; /* Length in number of symbol times. */ - int lp_filter_size; /* Size of Low Pass filter, in audio samples. */ - /* Previously it was always the same as the M/S */ - /* filters but in version 1.2 it's now independent. */ +#define lp_filter_len_bits lp_filter_width_sym // FIXME: temp hack - int lp_filter_delay; /* Number of samples that the low pass filter */ - /* delays the signal. */ - - /* New in 1.6. */ + int lp_filter_taps; /* Size of Low Pass filter, in audio samples. */ + +#define lp_filter_size lp_filter_taps // FIXME: temp hack /* @@ -111,6 +115,7 @@ struct demodulator_state_s /* * Phase Locked Loop (PLL) inertia. * Larger number means less influence by signal transitions. + * It is more resistant to change when locked on to a signal. */ float pll_locked_inertia; float pll_searching_inertia; @@ -129,23 +134,17 @@ struct demodulator_state_s /* lower = min(1600,1800) - 0.5 * 300 = 1450 */ /* upper = max(1600,1800) + 0.5 * 300 = 1950 */ - float pre_filter_len_bits; /* Length in number of bit times. */ + float pre_filter_len_sym; // Length in number of symbol times. +#define pre_filter_len_bits pre_filter_len_sym // temp until all references changed. - int pre_filter_size; /* Size of pre filter, in audio samples. */ - - float pre_filter[MAX_FILTER_SIZE] __attribute__((aligned(16))); + bp_window_t pre_window; // Window type for filter shaping. + int pre_filter_taps; // Calculated number of filter taps. +#define pre_filter_size pre_filter_taps // temp until all references changed. -/* - * Kernel for the mark and space detection filters. - */ - - float m_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); - float m_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); - - float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); - float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); + float pre_filter[MAX_FILTER_SIZE] __attribute__((aligned(16))); + float raw_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); // audio in, need better name. /* * The rest are continuously updated. @@ -154,11 +153,6 @@ struct demodulator_state_s unsigned int lo_phase; /* Local oscillator for PSK. */ -/* - * Most recent raw audio samples, before/after prefiltering. - */ - float raw_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); - /* * Use half of the AGC code to get a measure of input audio amplitude. * These use "quick" attack and "sluggish" decay while the @@ -170,24 +164,14 @@ struct demodulator_state_s float alevel_mark_peak; float alevel_space_peak; -/* - * Input to the mark/space detector. - * Could be prefiltered or raw audio. - */ - float ms_in_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); - /* * Outputs from the mark and space amplitude detection, * used as inputs to the FIR lowpass filters. * Kernel for the lowpass filters. */ - float m_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); - float s_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); - float lp_filter[MAX_FILTER_SIZE] __attribute__((aligned(16))); - float m_peak, s_peak; float m_valley, s_valley; float m_amp_prev, s_amp_prev; @@ -266,6 +250,113 @@ struct demodulator_state_s union { +////////////////////////////////////////////////////////////////////////////////// +// // +// AFSK only - new method in 1.7 // +// // +////////////////////////////////////////////////////////////////////////////////// + + + struct afsk_only_s { + + unsigned int m_osc_phase; // Phase for Mark local oscillator. + unsigned int m_osc_delta; // How much to change for each audio sample. + + unsigned int s_osc_phase; // Phase for Space local oscillator. + unsigned int s_osc_delta; // How much to change for each audio sample. + + unsigned int c_osc_phase; // Phase for Center frequency local oscillator. + unsigned int c_osc_delta; // How much to change for each audio sample. + + // Need two mixers for profile "A". + + float m_I_raw[MAX_FILTER_SIZE] __attribute__((aligned(16))); + float m_Q_raw[MAX_FILTER_SIZE] __attribute__((aligned(16))); + + float s_I_raw[MAX_FILTER_SIZE] __attribute__((aligned(16))); + float s_Q_raw[MAX_FILTER_SIZE] __attribute__((aligned(16))); + + // Only need one mixer for profile "B". Reuse the same storage? + +//#define c_I_raw m_I_raw +//#define c_Q_raw m_Q_raw + float c_I_raw[MAX_FILTER_SIZE] __attribute__((aligned(16))); + float c_Q_raw[MAX_FILTER_SIZE] __attribute__((aligned(16))); + + int use_rrc; // Use RRC rather than generic low pass. + + float rrc_width_sym; /* Width of RRC filter in number of symbols. */ + + float rrc_rolloff; /* Rolloff factor for RRC. Between 0 and 1. */ + + float prev_phase; // To see phase shift between samples for FM demod. + + float normalize_rpsam; // Normalize to -1 to +1 for expected tones. + + } afsk; + +////////////////////////////////////////////////////////////////////////////////// +// // +// Baseband only, AKA G3RUH // +// // +////////////////////////////////////////////////////////////////////////////////// + + + struct bb_only_s { + + float rrc_width_sym; /* Width of RRC filter in number of symbols. */ + + float rrc_rolloff; /* Rolloff factor for RRC. Between 0 and 1. */ + + int rrc_filter_taps; // Number of elements used in the next two. + +// FIXME: TODO: reevaluate max size needed. + + float audio_in[MAX_FILTER_SIZE] __attribute__((aligned(16))); // Audio samples in. + +// FIXME: use lp_filter + float rrc_filter[MAX_FILTER_SIZE] __attribute__((aligned(16))); // RRC Low pass filter. + + float lp_1_iir_param; // very low pass filters to get DC offset. + float lp_1_out; + + float lp_2_iir_param; + float lp_2_out; + + float agc_1_fast_attack; // Signal envelope detection. + float agc_1_slow_decay; + float agc_1_peak; + float agc_1_valley; + + float agc_2_fast_attack; + float agc_2_slow_decay; + float agc_2_peak; + float agc_2_valley; + + float agc_3_fast_attack; + float agc_3_slow_decay; + float agc_3_peak; + float agc_3_valley; + + // CIC low pass filters to detect DC bias or low frequency changes. + // IIR behaves like an analog R-C filter. + // Intuitively, it seems like FIR would be better because it is based on a finite history. + // However, it would require MANY taps and a LOT of computation for a low frequency. + // We can use a little trick here to keep a running average. + // This would be equivalent to convolving with an array of all 1 values. + // That would eliminate the need to multiply. + // We can also eliminate the need to add them all up each time by keeping a running total. + // Add a sample to the total when putting it in our array of recent samples. + // Subtract it from the total when it gets pushed off the end. + // We can also eliminate the need to shift them all down by using a circular buffer. + // This only works with integers because float would have cummulated round off errors. + + cic_t cic_center1; + cic_t cic_above; + cic_t cic_below; + + } bb; + ////////////////////////////////////////////////////////////////////////////////// // // // PSK only. // diff --git a/test/scripts/check-modem1200 b/test/scripts/check-modem1200 index 2b975871..0c79af98 100755 --- a/test/scripts/check-modem1200 +++ b/test/scripts/check-modem1200 @@ -1,5 +1,7 @@ @CUSTOM_SHELL_SHABANG@ @GEN_PACKETS_BIN@ -n 100 -o test12.wav -@ATEST_BIN@ -F0 -PE -L64 -G72 test12.wav -@ATEST_BIN@ -F1 -PE -L70 -G75 test12.wav +@ATEST_BIN@ -F0 -PA -L66 -G72 test12.wav +@ATEST_BIN@ -F1 -PA -L72 -G78 test12.wav +@ATEST_BIN@ -F0 -PB -L66 -G72 test12.wav +@ATEST_BIN@ -F1 -PB -L70 -G76 test12.wav \ No newline at end of file diff --git a/test/scripts/check-modem300 b/test/scripts/check-modem300 index da37dd2b..6d6e6432 100755 --- a/test/scripts/check-modem300 +++ b/test/scripts/check-modem300 @@ -1,5 +1,7 @@ @CUSTOM_SHELL_SHABANG@ @GEN_PACKETS_BIN@ -B300 -n 100 -o test3.wav -@ATEST_BIN@ -B300 -F0 -L68 -G69 test3.wav -@ATEST_BIN@ -B300 -F1 -L71 -G75 test3.wav +@ATEST_BIN@ -B300 -PA -F0 -L65 -G71 test3.wav +@ATEST_BIN@ -B300 -PA -F1 -L69 -G75 test3.wav +@ATEST_BIN@ -B300 -PB -F0 -L69 -G75 test3.wav +@ATEST_BIN@ -B300 -PB -F1 -L73 -G79 test3.wav