// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011,2012,2013,2014 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 // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // #define DEBUG1 1 /* display debugging info */ // #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 */ /*------------------------------------------------------------------ * * Module: demod_afsk.c * * Purpose: Demodulator for Audio Frequency Shift Keying (AFSK). * * Input: Audio samples from either a file or the "sound card." * * Outputs: Calls hdlc_rec_bit() for each bit demodulated. * *---------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include "direwolf.h" #include "audio.h" //#include "fsk_demod.h" //#include "gen_tone.h" #include "tune.h" #include "fsk_demod_state.h" #include "fsk_gen_filter.h" #include "hdlc_rec.h" #include "textcolor.h" #include "demod_afsk.h" #include "dsp.h" #define MIN(a,b) ((a)<(b)?(a):(b)) #define MAX(a,b) ((a)>(b)?(a):(b)) /* Quick approximation to sqrt(x*x+y*y) */ /* No benefit for regular PC. */ /* Should help with microcomputer platform. */ __attribute__((hot)) static inline float z (float x, float y) { x = fabsf(x); y = fabsf(y); if (x > y) { return (x * .941246 + y * .41); } else { return (y * .941246 + x * .41); } } /* Add sample to buffer and shift the rest down. */ __attribute__((hot)) static inline void push_sample (float val, float *buff, int size) { memmove(buff+1,buff,(size-1)*sizeof(float)); buff[0] = val; } /* FIR filter kernel. */ __attribute__((hot)) static inline float convolve (const float *data, const float *filter, int filter_size) { float sum = 0; int j; for (j=0; j= *ppeak) { *ppeak = in * fast_attack + *ppeak * (1. - fast_attack); } else { *ppeak = in * slow_decay + *ppeak * (1. - slow_decay); } if (in <= *pvalley) { *pvalley = in * fast_attack + *pvalley * (1. - fast_attack); } else { *pvalley = in * slow_decay + *pvalley * (1. - slow_decay); } if (*ppeak > *pvalley) { return ((in - 0.5 * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); } return (0.0); } /*------------------------------------------------------------------ * * Name: demod_afsk_init * * Purpose: Initialization for an AFSK demodulator. * Select appropriate parameters and set up filters. * * Inputs: samples_per_sec * baud * mark_freq * space_freq * * 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[] * * Returns: None. * * Bugs: This doesn't do much error checking so don't give it * anything crazy. * *----------------------------------------------------------------*/ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, int space_freq, char profile, struct demodulator_state_s *D) { int j; memset (D, 0, sizeof(struct demodulator_state_s)); #if DEBUG1 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 if (toupper(profile) == 'F') { if (baud != DEFAULT_BAUD || mark_freq != DEFAULT_MARK_FREQ || space_freq!= DEFAULT_SPACE_FREQ || samples_per_sec != DEFAULT_SAMPLES_PER_SEC) { text_color_set(DW_COLOR_INFO); dw_printf ("Note: Decoder 'F' works only for %d baud, %d/%d tones, %d samples/sec.\n", DEFAULT_BAUD, DEFAULT_MARK_FREQ, DEFAULT_SPACE_FREQ, DEFAULT_SAMPLES_PER_SEC); dw_printf ("Using Decoder 'A' instead.\n"); profile = 'A'; } } if (profile == 'a' || profile == 'A' || profile == 'f' || profile == 'F') { /* Original. 52 taps, truncated bandpass, IIR lowpass */ /* 'F' is the fast version for low end processors. */ /* It is a special case that works only for a particular */ /* baud rate, tone pair, and sampling rate. */ D->filter_len_bits = 1.415; /* 52 @ 44100, 1200 */ D->bp_window = BP_WINDOW_TRUNCATED; D->lpf_use_fir = 0; D->lpf_iir = 0.195; D->lpf_baud = 0; D->agc_fast_attack = 0.250; D->agc_slow_decay = 0.00012; D->hysteresis = 0.005; D->pll_locked_inertia = 0.700; D->pll_searching_inertia = 0.580; } else if (profile == 'b' || profile == 'B') { /* Original bandpass. Use FIR lowpass instead. */ D->filter_len_bits = 1.415; /* 52 @ 44100, 1200 */ D->bp_window = BP_WINDOW_TRUNCATED; D->lpf_use_fir = 1; D->lpf_iir = 0; D->lpf_baud = 1.09; D->agc_fast_attack = 0.370; D->agc_slow_decay = 0.00014; D->hysteresis = 0.003; D->pll_locked_inertia = 0.620; D->pll_searching_inertia = 0.350; } else if (profile == 'c' || profile == 'C') { /* Cosine window, 76 taps for bandpass, FIR lowpass. */ D->filter_len_bits = 2.068; /* 76 @ 44100, 1200 */ D->bp_window = BP_WINDOW_COSINE; D->lpf_use_fir = 1; D->lpf_iir = 0; D->lpf_baud = 1.09; D->agc_fast_attack = 0.495; D->agc_slow_decay = 0.00022; D->hysteresis = 0.005; D->pll_locked_inertia = 0.620; D->pll_searching_inertia = 0.350; } else if (profile == 'd' || profile == 'D') { /* Prefilter, Cosine window, FIR lowpass. Tweeked for 300 baud. */ D->use_prefilter = 1; /* first, a bandpass filter. */ D->prefilter_baud = 0.87; /* Cosine window. */ D->filter_len_bits = 1.857; /* 91 @ 44100/3, 300 */ D->bp_window = BP_WINDOW_COSINE; D->lpf_use_fir = 1; D->lpf_iir = 0; D->lpf_baud = 1.10; D->agc_fast_attack = 0.495; D->agc_slow_decay = 0.00022; D->hysteresis = 0.027; D->pll_locked_inertia = 0.620; D->pll_searching_inertia = 0.350; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid filter profile = %c\n", profile); exit (1); } #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 /* * Calculate constants used for timing. * The audio sample rate must be at least a few times the data rate. */ D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)baud) / ((double)samples_per_sec)); /* * My initial guess at length of filter was about one bit time. * By trial and error, the optimal value was found to somewhat longer. * This was optimized for 44,100 sample rate, 1200 baud, 1200/2200 Hz. * More experimentation is needed for other situations. */ D->ms_filter_size = (int) round( D->filter_len_bits * (float)samples_per_sec / (float)baud ); /* Experiment with other sizes. */ #if defined(TUNE_MS_FILTER_SIZE) D->ms_filter_size = TUNE_MS_FILTER_SIZE; #endif D->lp_filter_size = D->ms_filter_size; assert (D->ms_filter_size >= 4); 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); } /* * For narrow AFSK (e.g. 200 Hz shift), it might be beneficial to * have a bandpass filter before the mark/space detector. * For now, make it the same number of taps for simplicity. */ 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; #if 0 text_color_set(DW_COLOR_DEBUG); dw_printf ("Generating prefilter %.0f to %.0f Hz.\n", f1, f2); #endif f1 = f1 / (float)samples_per_sec; f2 = f2 / (float)samples_per_sec; //gen_bandpass (f1, f2, D->pre_filter, D->ms_filter_size, BP_WINDOW_HAMMING); //gen_bandpass (f1, f2, D->pre_filter, D->ms_filter_size, BP_WINDOW_BLACKMAN); gen_bandpass (f1, f2, D->pre_filter, D->ms_filter_size, BP_WINDOW_COSINE); } /* * 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 for (j=0; jms_filter_size; j++) { float am; float center; float shape = 1; /* Shape is an attempt to smooth out the */ /* abrupt edges in hopes of reducing */ /* overshoot and ringing. */ /* My first thought was to use a cosine shape. */ /* Should investigate Hamming and Blackman */ /* windows mentioned in the literature. */ /* http://en.wikipedia.org/wiki/Window_function */ center = 0.5 * (D->ms_filter_size - 1); am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2 * M_PI); shape = window (D->bp_window, D->ms_filter_size, j); D->m_sin_table[j] = sin(am) * shape; D->m_cos_table[j] = cos(am) * shape; #if DEBUG1 dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->m_sin_table[j], D->m_cos_table[j]) ; #endif } #if DEBUG1 text_color_set(DW_COLOR_DEBUG); dw_printf ("Space\n"); dw_printf (" j shape S sin S cos\n"); #endif for (j=0; jms_filter_size; j++) { float as; float center; float shape = 1; center = 0.5 * (D->ms_filter_size - 1); as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2 * M_PI); shape = window (D->bp_window, D->ms_filter_size, j); D->s_sin_table[j] = sin(as) * shape; D->s_cos_table[j] = cos(as) * shape; #if DEBUG1 dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->s_sin_table[j], D->s_cos_table[j] ) ; #endif } /* Do we want to normalize for unity gain? */ /* * 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... */ if (D->lpf_use_fir) { float fc; fc = baud * D->lpf_baud / (float)samples_per_sec; gen_lowpass (fc, D->lp_filter, D->lp_filter_size, BP_WINDOW_TRUNCATED); } /* * 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. */ #if 0 #ifndef AVOID_FLOATING_POINT failed experiment 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; } 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; } 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; } 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; } #endif #endif } /* fsk_gen_filter */ #if GEN_FFF // Properties of the radio channels. static struct audio_s modem; // Filters will be stored here. static struct demodulator_state_s ds; #define SPARSE 3 static void emit_macro (char *name, int size, float *coeff) { int i; dw_printf ("#define %s(x) \\\n", name); for (i=SPARSE/2; i= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); /* * Filters use last 'filter_size' samples. * * First push the older samples down. * * Finally, put the most recent at the beginning. * * Future project? Can we do better than shifting each time? */ /* Scale to nice number, TODO: range -1.0 to +1.0, not 2. */ fsam = sam / 16384.0; /* * Accumulate measure of the input signal level. */ abs_fsam = fsam >= 0 ? fsam : -fsam; // TODO: move to common code if (abs_fsam > D->lev_peak_acc) { D->lev_peak_acc = abs_fsam; } D->lev_sum_acc += abs_fsam; D->lev_count++; if (D->lev_count >= D->lev_period) { D->lev_prev_peak = D->lev_last_peak; D->lev_last_peak = D->lev_peak_acc; D->lev_peak_acc = 0; D->lev_prev_ave = D->lev_last_ave; D->lev_last_ave = D->lev_sum_acc / D->lev_count; D->lev_sum_acc = 0; D->lev_count = 0; } /* * Optional bandpass filter before the mark/space discriminator. */ if (D->use_prefilter) { float cleaner; push_sample (fsam, D->raw_cb, D->ms_filter_size); cleaner = convolve (D->raw_cb, D->pre_filter, D->ms_filter_size); push_sample (cleaner, D->ms_in_cb, D->ms_filter_size); } else { push_sample (fsam, D->ms_in_cb, D->ms_filter_size); } /* * Next we have bandpass filters for the mark and space tones. * * This takes a lot of computation. * It's not a problem on a typical (Intel x86 based) PC. * Dire Wolf takes only about 2 or 3% of the CPU time. * * It might be too much for a little microcomputer to handle. * * Here we have an optimized case for the default values. */ // TODO: How do we test for profile F here? if (0) { //if (toupper(modem.profiles[chan][subchan]) == toupper(FFF_PROFILE)) { /* ========== Faster for default values on slower processors. ========== */ m_sum1 = CALC_M_SUM1(D->ms_in_cb); m_sum2 = CALC_M_SUM2(D->ms_in_cb); m_amp = z(m_sum1,m_sum2); s_sum1 = CALC_S_SUM1(D->ms_in_cb); s_sum2 = CALC_S_SUM2(D->ms_in_cb); s_amp = z(s_sum1,s_sum2); } else { /* ========== General case to handle all situations. ========== */ /* * 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); /* ========== End of general case. ========== */ } /* * 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. */ if (D->lpf_use_fir) { 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); 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. */ m_amp = D->lpf_iir * m_amp + (1.0 - D->lpf_iir) * D->m_amp_prev; D->m_amp_prev = m_amp; s_amp = D->lpf_iir * s_amp + (1.0 - D->lpf_iir) * D->s_amp_prev; D->s_amp_prev = s_amp; } /* * Which tone is stronger? * * Under real conditions, we find that the higher tone 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 have different amounts of DC bias. * * Try to compensate for this by normalizing them separately with automatic gain * control (AGC). 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. */ /* Fast attack and slow decay. */ /* Numbers were obtained by trial and error from actual */ /* recorded less-than-optimal signals. */ /* See agc.c and fsk_demod_agc.h for more information. */ 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)); /* Demodulator output is difference between response from two filters. */ /* AGC should generally keep this around -1 to +1 range. */ demod_out = m_norm - s_norm; /* Try adding some Hysteresis. */ /* (Not to be confused with Hysteria.) */ if (demod_out > D->hysteresis) { demod_data = 1; } else if (demod_out < (- (D->hysteresis))) { demod_data = 0; } else { demod_data = D->prev_demod_data; } /* * Finally, a PLL is used to sample near the centers of the data bits. * * D->data_clock_pll is a SIGNED 32 bit variable. * When it overflows from a large positive value to a negative value, we * sample a data bit from the demodulated signal. * * Ideally, the the demodulated signal transitions should be near * zero we we sample mid way between the transitions. * * Nudge the PLL by removing some small fraction from the value of * data_clock_pll, pushing it closer to zero. * * This adjustment will never change the sign so it won't cause * any erratic data bit sampling. * * If we adjust it too quickly, the clock will have too much jitter. * If we adjust it too slowly, it will take too long to lock on to a new signal. * * Be a little more agressive about adjusting the PLL * phase when searching for a signal. Don't change it as much when * locked on to a signal. * * I don't think the optimal value will depend on the audio sample rate * because this happens for each transition from the demodulator. */ D->prev_d_c_pll = D->data_clock_pll; D->data_clock_pll += D->pll_step_per_sample; //text_color_set(DW_COLOR_DEBUG); // dw_printf ("prev = %lx, new data clock pll = %lx\n" D->prev_d_c_pll, D->data_clock_pll); if (D->data_clock_pll < 0 && D->prev_d_c_pll > 0) { /* Overflow. */ #if SLICENDICE hdlc_rec_bit_sam (chan, subchan, demod_data, demod_out); #else hdlc_rec_bit (chan, subchan, demod_data, 0, -1); #endif } if (demod_data != D->prev_demod_data) { // Note: Test for this demodulator, not overall for channel. if (hdlc_rec_data_detect_1 (chan, subchan)) { D->data_clock_pll = (int)(D->data_clock_pll * D->pll_locked_inertia); } else { D->data_clock_pll = (int)(D->data_clock_pll * D->pll_searching_inertia); } } #if DEBUG4 if (chan == 0) { if (hdlc_rec_data_detect_1 (chan, subchan)) { char fname[30]; if (demod_log_fp == NULL) { seq++; sprintf (fname, "demod/%04d.csv", seq); if (seq == 1) mkdir ("demod", 0777); demod_log_fp = fopen (fname, "w"); text_color_set(DW_COLOR_DEBUG); dw_printf ("Starting demodulator log file %s\n", fname); fprintf (demod_log_fp, "Audio, Mark, Space, Demod, Data, Clock\n"); } fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f\n", fsam + 3.5, m_norm + 2, s_norm + 2, (m_norm - s_norm) / 2 + 1.5, demod_data ? .9 : .55, (D->data_clock_pll & 0x80000000) ? .1 : .45); } else { if (demod_log_fp != NULL) { fclose (demod_log_fp); demod_log_fp = NULL; } } } #endif /* * Remember demodulator output so we can compare next time. */ D->prev_demod_data = demod_data; } /* end demod_afsk_process_sample */ #endif /* GEN_FFF */ #if 0 /*------------------------------------------------------------------- * * Name: fsk_demod_print_agc * * Purpose: Print information about input signal amplitude. * This will be useful for adjusting transmitter audio levels. * We also want to avoid having an input level so high * that the A/D converter "clips" the signal. * * * Inputs: chan - Audio channel. 0 for left, 1 for right. * * Returns: None * * Descripion: Not sure what to use for final form. * For now display the AGC peaks for both tones. * This will be called at the end of a frame. * * Future: Come up with a sensible scale and add command line option. * Probably makes more sense to return a single number * and let the caller print it. * Just an experiment for now. * *--------------------------------------------------------------------*/ #if 0 void fsk_demod_print_agc (int chan, int subchan) { struct demodulator_state_s *D; assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); D = &demodulator_state[chan][subchan]; dw_printf ("%d\n", (int)((D->lev_last_peak + D->lev_prev_peak)*50)); //dw_printf ("Peak= %.2f, %.2f Ave= %.2f, %.2f AGC M= %.2f / %.2f S= %.2f / %.2f\n", // D->lev_last_peak, D->lev_prev_peak, D->lev_last_ave, D->lev_prev_ave, // D->m_peak, D->m_valley, D->s_peak, D->s_valley); } #endif /* Resulting scale is 0 to almost 100. */ /* Cranking up the input level produces no more than 97 or 98. */ /* We currently produce a message when this goes over 90. */ int fsk_demod_get_audio_level (int chan, int subchan) { struct demodulator_state_s *D; assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); D = &demodulator_state[chan][subchan]; return ( (int) ((D->lev_last_peak + D->lev_prev_peak) * 50 ) ); } #endif /* 0 */ /* end demod_afsk.c */