diff --git a/CHANGES.md b/CHANGES.md index 355cffd..8e5b024 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,15 @@ # Revision History # +---------- + +## Version 1.3 -- Development snapshot H -- November 2015 ## + +### New Feature: ### + +- New experimental demodulator. More details later. + + ---------- ## Version 1.3 -- Development snapshot G -- November 2015 ## diff --git a/Makefile.win b/Makefile.win index a12ede8..133392e 100644 --- a/Makefile.win +++ b/Makefile.win @@ -238,6 +238,7 @@ strlcat.o : misc/strlcat.c check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest check-modem1200 check-modem300 check-modem9600 # Can we encode and decode at popular data rates? +# Verify that single bit fixup increases the count. check-modem1200 : gen_packets atest gen_packets -n 100 -o test1.wav @@ -361,14 +362,13 @@ demod_9600.o : tune.h demod_afsk.o : tune.h -testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c fsk_demod_agc.h \ +testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.o fsk_demod_agc.h \ hdlc_rec.o hdlc_rec2.o multi_modem.o \ rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o latlong.o symbols.o textcolor.o telemetry.o \ - regex.a misc.a \ - + dwgpsnmea.o dwgps.o serial_port.o tt_text.o regex.a misc.a rm -f atest.exe $(CC) $(CFLAGS) -o atest $^ - ./atest -P E ../02_Track_2.wav | grep "packets decoded in" >atest.out + ./atest -P GGG- -F 0 ../02_Track_2.wav | grep "packets decoded in" >atest.out echo " " > tune.h diff --git a/aprs_tt.c b/aprs_tt.c index 9352f09..5db3f6b 100644 --- a/aprs_tt.c +++ b/aprs_tt.c @@ -1561,7 +1561,7 @@ static void raw_tt_data_to_app (int chan, char *msg) alevel.mark = -2; alevel.space = -2; - dlq_append (DLQ_REC_FRAME, chan, -1, pp, alevel, RETRY_NONE, "tt"); + dlq_append (DLQ_REC_FRAME, chan, -1, 0, pp, alevel, RETRY_NONE, "tt"); } else { text_color_set(DW_COLOR_ERROR); diff --git a/atest.c b/atest.c index c869698..6ba5cc8 100644 --- a/atest.c +++ b/atest.c @@ -167,6 +167,10 @@ static void usage (void); static int decode_only = 0; /* Set to 0 or 1 to decode only one channel. 2 for both. */ +static int sample_number = -1; /* Sample number from the file. */ + /* Incremented only for channel 0. */ + /* Use to print timestamp, relative to beginning */ + /* of file, when frame was decoded. */ int main (int argc, char *argv[]) { @@ -495,6 +499,8 @@ int main (int argc, char *argv[]) if (audio_sample >= 256 * 256) e_o_f = 1; + if (c == 0) sample_number++; + if (decode_only == 0 && c != 0) continue; if (decode_only == 1 && c != 1) continue; @@ -572,16 +578,16 @@ int audio_get (int a) void rdq_append (rrbb_t rrbb) { - int chan; + int chan, subchan, slice; alevel_t alevel; - int subchan; chan = rrbb_get_chan(rrbb); subchan = rrbb_get_subchan(rrbb); + slice = rrbb_get_slice(rrbb); alevel = rrbb_get_audio_level(rrbb); - hdlc_rec2_try_to_fix_later (rrbb, chan, subchan, alevel); + hdlc_rec2_try_to_fix_later (rrbb, chan, subchan, slice, alevel); rrbb_delete (rrbb); } @@ -590,7 +596,7 @@ void rdq_append (rrbb_t rrbb) * This is called when we have a good frame. */ -void dlq_append (dlq_type_t type, int chan, int subchan, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) +void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) { char stemp[500]; @@ -627,6 +633,15 @@ void dlq_append (dlq_type_t type, int chan, int subchan, packet_t pp, alevel_t a text_color_set(DW_COLOR_DEBUG); dw_printf ("\n"); dw_printf("DECODED[%d] ", packets_decoded ); + + /* Insert time stamp relative to start of file. */ + + double sec = (double)sample_number / my_audio_config.adev[0].samples_per_sec; + int min = (int)(sec / 60.); + sec -= min * 60; + + dw_printf ("%d:%07.4f ", min, sec); + if (h != AX25_SOURCE) { dw_printf ("Digipeater "); } @@ -641,27 +656,38 @@ void dlq_append (dlq_type_t type, int chan, int subchan, packet_t pp, alevel_t a #endif -#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H) - int j; - - for (j=0; j 1 && my_audio_config.achan[chan].num_slicers == 1) { + dw_printf ("[%d.%d] ", chan, subchan); + } + else if (my_audio_config.achan[chan].num_subchan == 1 && my_audio_config.achan[chan].num_slicers > 1) { + dw_printf ("[%d.%d] ", chan, slice); + } + else if (my_audio_config.achan[chan].num_subchan > 1 && my_audio_config.achan[chan].num_slicers > 1) { + dw_printf ("[%d.%d.%d] ", chan, subchan, slice); + } + else { dw_printf ("[%d] ", chan); } @@ -671,7 +697,7 @@ void dlq_append (dlq_type_t type, int chan, int subchan, packet_t pp, alevel_t a ax25_delete (pp); -} /* end app_process_rec_packet */ +} /* end fake dlq_append */ void ptt_set (int ot, int chan, int ptt_signal) diff --git a/audio.h b/audio.h index c493eef..fb79d69 100644 --- a/audio.h +++ b/audio.h @@ -41,19 +41,12 @@ enum audio_in_type_e { typedef enum retry_e { RETRY_NONE=0, - RETRY_SWAP_SINGLE=1, - RETRY_SWAP_DOUBLE=2, - RETRY_SWAP_TRIPLE=3, - RETRY_REMOVE_SINGLE=4, - RETRY_REMOVE_DOUBLE=5, - RETRY_REMOVE_TRIPLE=6, - RETRY_INSERT_SINGLE=7, - RETRY_INSERT_DOUBLE=8, - RETRY_SWAP_TWO_SEP=9, - RETRY_SWAP_MANY=10, - RETRY_REMOVE_MANY=11, - RETRY_REMOVE_TWO_SEP=12, - RETRY_MAX = 13} retry_t; + RETRY_INVERT_SINGLE=1, + RETRY_INVERT_DOUBLE=2, + RETRY_INVERT_TRIPLE=3, + RETRY_INVERT_TWO_SEP=4, + RETRY_MAX = 5} retry_t; + typedef enum sanity_e { SANITY_APRS, SANITY_AX25, SANITY_NONE } sanity_t; @@ -126,6 +119,9 @@ struct audio_s { int decimate; /* Reduce AFSK sample rate by this factor to */ /* decrease computational requirements. */ + int interleave; /* If > 1, interleave samples among multiple decoders. */ + /* Quick hack for experiment. */ + int mark_freq; /* Two tones for AFSK modulation, in Hz. */ int space_freq; /* Standard tones are 1200 and 2200 for 1200 baud. */ @@ -140,19 +136,13 @@ struct audio_s { int offset; /* Spacing between filter frequencies. */ - /* Next two are derived from 3 above by demod_init. */ + int num_slicers; /* Number of different threshold points to decide */ + /* between mark or space. */ - int num_demod; /* Number of different demodulators (filters). */ - /* Previously this was same as num_subchan but we add */ - /* a new variation in version 1.2 where a single modem */ - /* can feed multiple slicers and HDLC decoders. */ + /* This is derived from above by demod_init. */ - /* num_slicers could be added to be more general but */ - /* for the intial experiment, we can just examine profiles. */ + int num_subchan; /* Total number of modems for each channel. */ - int num_subchan; /* Total number of modems / hdlc decoders for each channel. */ - /* Potentially it could be product of strlen(profiles) * num_freq. */ - /* Currently can't use both at once. */ /* These are for dealing with imperfect frames. */ @@ -267,7 +257,7 @@ struct audio_s { #define DEFAULT_BITS_PER_SAMPLE 16 -#define DEFAULT_FIX_BITS RETRY_SWAP_SINGLE +#define DEFAULT_FIX_BITS RETRY_INVERT_SINGLE /* * Standard for AFSK on VHF FM. diff --git a/beacon.c b/beacon.c index 8bcca4a..bbfe644 100644 --- a/beacon.c +++ b/beacon.c @@ -914,7 +914,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) /* Simulated reception. */ memset (&alevel, 0xff, sizeof(alevel)); - dlq_append (DLQ_REC_FRAME, g_misc_config_p->beacon[j].sendto_chan, 0, pp, alevel, 0, ""); + dlq_append (DLQ_REC_FRAME, g_misc_config_p->beacon[j].sendto_chan, 0, 0, pp, alevel, 0, ""); break; } } diff --git a/config.c b/config.c index 2cd172f..1dfd160 100644 --- a/config.c +++ b/config.c @@ -1390,15 +1390,27 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } n = atoi(t); - if (n >= RETRY_NONE && n <= RETRY_REMOVE_TWO_SEP) { + if (n >= RETRY_NONE && n < RETRY_MAX) { // MAX is actually last valid +1 p_audio_config->achan[channel].fix_bits = (retry_t)n; } else { p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS; text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Invalid value for FIX_BITS. Using %d.\n", - line, p_audio_config->achan[channel].fix_bits); - } + dw_printf ("Line %d: Invalid value %d for FIX_BITS. Using default of %d.\n", + line, n, p_audio_config->achan[channel].fix_bits); + } + + if (p_audio_config->achan[channel].fix_bits > RETRY_INVERT_SINGLE) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Line %d: Using a FIX_BITS value greater than %d is not recommended for normal operation.\n", + line, RETRY_INVERT_SINGLE); + } + + if (p_audio_config->achan[channel].fix_bits >= RETRY_INVERT_TWO_SEP) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Line %d: Using a FIX_BITS value of %d will waste a lot of CPU power and produce wrong results.\n", + line, RETRY_INVERT_TWO_SEP); + } t = split(NULL,0); while (t != NULL) { diff --git a/demod.c b/demod.c index 8560e71..a4944af 100644 --- a/demod.c +++ b/demod.c @@ -119,6 +119,21 @@ int demod_init (struct audio_s *pa) int num_letters; int have_plus; + /* + * These are derived from config file parameters. + * + * num_subchan is number of demodulators. + * This can be increased by: + * Multiple frequencies. + * Multiple letters (not sure if I will continue this). + * New interleaved decoders. + * + * num_slicers is set to max by the "+" option. + */ + + save_audio_config_p->achan[chan].num_subchan = 1; + save_audio_config_p->achan[chan].num_slicers = 1; + switch (save_audio_config_p->achan[chan].modem_type) { case MODEM_OFF: @@ -234,8 +249,7 @@ int demod_init (struct audio_s *pa) /* These can be increased later for the multi-frequency case. */ save_audio_config_p->achan[chan].num_subchan = num_letters; - save_audio_config_p->achan[chan].num_demod = num_letters; - + save_audio_config_p->achan[chan].num_slicers = 1; /* * Some error checking - Can use only one of these: @@ -245,24 +259,10 @@ int demod_init (struct audio_s *pa) * - Multiple frequencies. */ - if (have_plus && num_letters > 1) { - - text_color_set(DW_COLOR_ERROR); - dw_printf ("Channel %d: Demodulator + option can't be combined with multiple letters.\n", chan); - - strlcpy (save_audio_config_p->achan[chan].profiles, "C+", sizeof(save_audio_config_p->achan[chan].profiles)); // Reduce to one letter. - num_letters = 1; - save_audio_config_p->achan[chan].num_demod = 1; - save_audio_config_p->achan[chan].num_subchan = 1; // Will be set higher later. - save_audio_config_p->achan[chan].num_freq = 1; - } - if (have_plus && save_audio_config_p->achan[chan].num_freq > 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Channel %d: Demodulator + option can't be combined with multiple frequencies.\n", chan); - - save_audio_config_p->achan[chan].num_demod = 1; save_audio_config_p->achan[chan].num_subchan = 1; // Will be set higher later. save_audio_config_p->achan[chan].num_freq = 1; } @@ -302,6 +302,7 @@ int demod_init (struct audio_s *pa) * We have 3 cases to consider. */ +// TODO1.3: revisit this logic now that it is less restrictive. if (num_letters > 1) { int d; @@ -311,17 +312,58 @@ int demod_init (struct audio_s *pa) * Each one corresponds to a demodulator and subchannel. * * An interesting experiment but probably not too useful. - * Can't have multiple frequency pairs or the + option. + * Can't have multiple frequency pairs. + * In version 1.3 this can be combined with the + option. */ save_audio_config_p->achan[chan].num_subchan = num_letters; - save_audio_config_p->achan[chan].num_demod = num_letters; +/* + * Quick hack with special case for another experiment. + * Do this in a more general way if it turns out to be useful. + */ + save_audio_config_p->achan[chan].interleave = 1; + if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EE") == 0) { + save_audio_config_p->achan[chan].interleave = 2; + save_audio_config_p->achan[chan].decimate = 1; + } + else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEE") == 0) { + save_audio_config_p->achan[chan].interleave = 3; + save_audio_config_p->achan[chan].decimate = 1; + } + else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEEE") == 0) { + save_audio_config_p->achan[chan].interleave = 4; + save_audio_config_p->achan[chan].decimate = 1; + } + else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEEEE") == 0) { + save_audio_config_p->achan[chan].interleave = 5; + save_audio_config_p->achan[chan].decimate = 1; + } + else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GG") == 0) { + save_audio_config_p->achan[chan].interleave = 2; + save_audio_config_p->achan[chan].decimate = 1; + } + else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGG") == 0) { + save_audio_config_p->achan[chan].interleave = 3; + save_audio_config_p->achan[chan].decimate = 1; + } + else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGG+") == 0) { + save_audio_config_p->achan[chan].interleave = 3; + save_audio_config_p->achan[chan].decimate = 1; + } + else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGGG") == 0) { + save_audio_config_p->achan[chan].interleave = 4; + save_audio_config_p->achan[chan].decimate = 1; + } + else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGGGG") == 0) { + save_audio_config_p->achan[chan].interleave = 5; + save_audio_config_p->achan[chan].decimate = 1; + } - if (save_audio_config_p->achan[chan].num_demod != num_letters) { + if (save_audio_config_p->achan[chan].num_subchan != num_letters) { text_color_set(DW_COLOR_ERROR); - dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_demod(%d) != strlen(\"%s\")\n", - __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_demod, save_audio_config_p->achan[chan].profiles); + dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_subchan(%d) != strlen(\"%s\")\n", + __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_subchan, save_audio_config_p->achan[chan].profiles); } if (save_audio_config_p->achan[chan].num_freq != 1) { @@ -329,9 +371,8 @@ int demod_init (struct audio_s *pa) dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != 1\n", __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq); } - - for (d = 0; d < save_audio_config_p->achan[chan].num_demod; d++) { + for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) { int mark, space; assert (d >= 0 && d < MAX_SUBCHANS); @@ -342,18 +383,26 @@ int demod_init (struct audio_s *pa) mark = save_audio_config_p->achan[chan].mark_freq; space = save_audio_config_p->achan[chan].space_freq; - if (save_audio_config_p->achan[chan].num_demod != 1) { + if (save_audio_config_p->achan[chan].num_subchan != 1) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %d.%d: %c %d & %d\n", chan, d, profile, mark, space); } - - demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, + + demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / (save_audio_config_p->achan[chan].decimate * save_audio_config_p->achan[chan].interleave), save_audio_config_p->achan[chan].baud, mark, space, profile, D); + if (have_plus) { + /* I'm not happy about putting this hack here. */ + /* should pass in as a parameter rather than adding on later. */ + + save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; + D->num_slicers = MAX_SLICERS; + } + /* For siginal level reporting, we want a longer term view. */ // TODO: Should probably move this into the init functions. @@ -364,7 +413,7 @@ int demod_init (struct audio_s *pa) else if (have_plus) { /* - * PLUS - which implies we have only one letter and one frequency pair. + * PLUS - which (formerly) implies we have only one letter and one frequency pair. * * One demodulator feeds multiple slicers, each a subchannel. */ @@ -381,12 +430,11 @@ int demod_init (struct audio_s *pa) __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq); } - if (save_audio_config_p->achan[chan].num_demod != save_audio_config_p->achan[chan].num_demod) { + if (save_audio_config_p->achan[chan].num_freq != save_audio_config_p->achan[chan].num_subchan) { text_color_set(DW_COLOR_ERROR); - dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != num_demod(%d)\n", - __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq, save_audio_config_p->achan[chan].num_demod); + dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != num_subchan(%d)\n", + __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq, save_audio_config_p->achan[chan].num_subchan); } - struct demodulator_state_s *D; D = &demodulator_state[chan][0]; @@ -394,8 +442,7 @@ int demod_init (struct audio_s *pa) /* I'm not happy about putting this hack here. */ /* This belongs in demod_afsk_init but it doesn't have access to the audio config. */ - save_audio_config_p->achan[chan].num_demod = 1; - save_audio_config_p->achan[chan].num_subchan = MAX_SUBCHANS; + save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, save_audio_config_p->achan[chan].baud, @@ -404,10 +451,13 @@ int demod_init (struct audio_s *pa) save_audio_config_p->achan[chan].profiles[0], D); - /* I'm not happy about putting this hack here. */ - /* should pass in as a parameter rather than adding on later. */ + if (have_plus) { + /* I'm not happy about putting this hack here. */ + /* should pass in as a parameter rather than adding on later. */ - D->num_slicers = MAX_SUBCHANS; + save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; + D->num_slicers = MAX_SLICERS; + } /* For siginal level reporting, we want a longer term view. */ @@ -427,7 +477,6 @@ int demod_init (struct audio_s *pa) __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].profiles); } - save_audio_config_p->achan[chan].num_demod = save_audio_config_p->achan[chan].num_freq; save_audio_config_p->achan[chan].num_subchan = save_audio_config_p->achan[chan].num_freq; for (d = 0; d < save_audio_config_p->achan[chan].num_freq; d++) { @@ -455,6 +504,14 @@ int demod_init (struct audio_s *pa) profile, D); + if (have_plus) { + /* I'm not happy about putting this hack here. */ + /* should pass in as a parameter rather than adding on later. */ + + save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; + D->num_slicers = MAX_SLICERS; + } + /* For siginal level reporting, we want a longer term view. */ D->quick_attack = D->agc_fast_attack * 0.2; @@ -495,15 +552,14 @@ int demod_init (struct audio_s *pa) D = &demodulator_state[chan][0]; // first subchannel save_audio_config_p->achan[chan].num_subchan = 1; - save_audio_config_p->achan[chan].num_demod = 1; + save_audio_config_p->achan[chan].num_slicers = 1; if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) { /* I'm not happy about putting this hack here. */ /* This belongs in demod_9600_init but it doesn't have access to the audio config. */ - save_audio_config_p->achan[chan].num_demod = 1; - save_audio_config_p->achan[chan].num_subchan = MAX_SUBCHANS; + save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; } demod_9600_init (UPSAMPLE * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, D); @@ -513,7 +569,8 @@ int demod_init (struct audio_s *pa) /* I'm not happy about putting this hack here. */ /* should pass in as a parameter rather than adding on later. */ - D->num_slicers = MAX_SUBCHANS; + save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; + D->num_slicers = MAX_SLICERS; } /* For siginal level reporting, we want a longer term view. */ diff --git a/demod_9600.c b/demod_9600.c index e4ebfa6..62faa01 100644 --- a/demod_9600.c +++ b/demod_9600.c @@ -145,6 +145,7 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s int j; memset (D, 0, sizeof(struct demodulator_state_s)); + D->num_slicers = 1; //dw_printf ("demod_9600_init(rate=%d, baud=%d, D ptr)\n", samples_per_sec, baud); @@ -259,7 +260,7 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s * *--------------------------------------------------------------------*/ -static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator_state_s *D); +static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D); __attribute__((hot)) void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D) @@ -377,19 +378,16 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D /* AGC should generally keep this around -1 to +1 range. */ demod_data = demod_out > 0; - - nudge_pll (chan, subchan, demod_data, D); + nudge_pll (chan, subchan, 0, demod_data, D); } else { - int s; - - assert (subchan == 0); + int slice; /* Multiple slicers each feeding its own HDLC decoder. */ - for (s=0; snum_slicers; s++) { - demod_data = demod_out > slice_point[s]; - nudge_pll (chan, s, demod_data, D); + for (slice=0; slicenum_slicers; slice++) { + demod_data = demod_out > slice_point[slice]; + nudge_pll (chan, subchan, slice, demod_data, D); } } @@ -397,7 +395,7 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D __attribute__((hot)) -static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator_state_s *D) +static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D) { /* @@ -425,10 +423,11 @@ static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator * This was optimized for 1200 baud AFSK. There might be some opportunity * for improvement here. */ - D->slicer[subchan].prev_d_c_pll = D->slicer[subchan].data_clock_pll; - D->slicer[subchan].data_clock_pll += D->pll_step_per_sample; - if (D->slicer[subchan].data_clock_pll < 0 && D->slicer[subchan].prev_d_c_pll > 0) { + D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; + D->slicer[slice].data_clock_pll += D->pll_step_per_sample; + + if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll > 0) { /* Overflow. */ @@ -443,20 +442,20 @@ static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator // Warning: 'descram' set but not used. // It's used in conditional debug code below. // descram = - descramble (demod_data, &(D->slicer[subchan].lfsr)); + descramble (demod_data, &(D->slicer[slice].lfsr)); - hdlc_rec_bit (chan, subchan, demod_data, 1, D->slicer[subchan].lfsr); + hdlc_rec_bit (chan, subchan, slice, demod_data, 1, D->slicer[slice].lfsr); } - if (demod_data != D->slicer[subchan].prev_demod_data) { + if (demod_data != D->slicer[slice].prev_demod_data) { // Note: Test for this demodulator, not overall for channel. - if (hdlc_rec_gathering (chan, subchan)) { - D->slicer[subchan].data_clock_pll = (int)(D->slicer[subchan].data_clock_pll * D->pll_locked_inertia); + if (hdlc_rec_gathering (chan, subchan, slice)) { + D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia); } else { - D->slicer[subchan].data_clock_pll = (int)(D->slicer[subchan].data_clock_pll * D->pll_searching_inertia); + D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia); } } @@ -464,7 +463,7 @@ static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator #if DEBUG5 //if (chan == 0) { - if (hdlc_rec_gathering (chan,subchan)) { + if (hdlc_rec_gathering (chan,subchan,slice)) { char fname[30]; @@ -507,7 +506,7 @@ static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator * Remember demodulator output (pre-descrambling) so we can compare next time * for the DPLL sync. */ - D->slicer[subchan].prev_demod_data = demod_data; + D->slicer[slice].prev_demod_data = demod_data; } /* end nudge_pll */ diff --git a/demod_afsk.c b/demod_afsk.c index 725dcb1..7167f90 100644 --- a/demod_afsk.c +++ b/demod_afsk.c @@ -198,6 +198,7 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, int j; memset (D, 0, sizeof(struct demodulator_state_s)); + D->num_slicers = 1; #if DEBUG1 dw_printf ("demod_afsk_init (rate=%d, baud=%d, mark=%d, space=%d, profile=%c\n", @@ -332,7 +333,7 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, case 'E': /* 1200 baud - Started out similar to C but add prefilter. */ - /* Version 1.2 - EXPERIMENTAL - Needs more fine tuning. */ + /* Version 1.2 */ /* Enhancements: */ /* + Add prefilter. Previously used for 300 baud D, but not 1200. */ /* + Prefilter length now independent of M/S filters. */ @@ -366,6 +367,34 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, D->pll_searching_inertia = 0.50; break; + case 'G': + + /* 1200 baud - Started out same as E but add 3 way interleave. */ + /* Version 1.3 - EXPERIMENTAL - Needs more fine tuning. */ + + //D->bp_window = BP_WINDOW_COSINE; /* The name says BP but it is used for all of them. */ + + D->use_prefilter = 1; /* first, a bandpass filter. */ + D->prefilter_baud = 0.15; + D->pre_filter_len_bits = 128 * 1200. / (44100. / 3.); + D->pre_window = BP_WINDOW_TRUNCATED; + + D->ms_filter_len_bits = 25 * 1200. / (44100. / 3.); + D->ms_window = BP_WINDOW_COSINE; + + D->lpf_use_fir = 1; + D->lpf_baud = 1.16; + D->lp_filter_len_bits = 21 * 1200. / (44100. / 3.); + D->lp_window = BP_WINDOW_TRUNCATED; + + D->agc_fast_attack = 0.130; + D->agc_slow_decay = 0.00013; + D->hysteresis = 0.01; + + D->pll_locked_inertia = 0.73; + D->pll_searching_inertia = 0.64; + break; + default: text_color_set(DW_COLOR_ERROR); @@ -719,8 +748,8 @@ int main () modem.achan[0].mark_freq = DEFAULT_MARK_FREQ; modem.achan[0].space_freq = DEFAULT_SPACE_FREQ; modem.achan[0].baud = DEFAULT_BAUD; - modem.achan[0].num_demod = 1; modem.achan[0].num_subchan = 1; + modem.achan[0].num_slicers = 1; demod_afsk_init (modem.adev[0].samples_per_sec, modem.achan[0].baud, @@ -792,7 +821,7 @@ int main () * *--------------------------------------------------------------------*/ -static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator_state_s *D); +static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D); __attribute__((hot)) void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D) @@ -1023,26 +1052,18 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat else { demod_data = D->slicer[subchan].prev_demod_data; } - - nudge_pll (chan, subchan, demod_data, D); + nudge_pll (chan, subchan, 0, demod_data, D); } else { - int s; + int slice; - assert (subchan == 0); - - /* "G" profile with one demodulator and multiple slicers */ - /* each feeding its own HDLC decoder. */ - - for (s=0; snum_slicers; s++) { - demod_data = m_amp > s_amp * space_gain[s]; - nudge_pll (chan, s, demod_data, D); + for (slice=0; slicenum_slicers; slice++) { + demod_data = m_amp > s_amp * space_gain[slice]; + nudge_pll (chan, subchan, slice, demod_data, D); } } - - #if DEBUG4 if (chan == 0) { @@ -1080,12 +1101,15 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat __attribute__((hot)) -static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator_state_s *D) +static void inline 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. * + * D points to a demodulator for a channel/subchannel pair so we don't + * have to keep recalculating it. + * * 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. @@ -1109,33 +1133,34 @@ static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator * I don't think the optimal value will depend on the audio sample rate * because this happens for each transition from the demodulator. */ - D->slicer[subchan].prev_d_c_pll = D->slicer[subchan].data_clock_pll; - D->slicer[subchan].data_clock_pll += D->pll_step_per_sample; + + D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; + D->slicer[slice].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->slicer[subchan].data_clock_pll < 0 && D->slicer[subchan].prev_d_c_pll > 0) { + if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll > 0) { /* Overflow. */ - hdlc_rec_bit (chan, subchan, demod_data, 0, -1); + hdlc_rec_bit (chan, subchan, slice, demod_data, 0, -1); } - if (demod_data != D->slicer[subchan].prev_demod_data) { + if (demod_data != D->slicer[slice].prev_demod_data) { - if (hdlc_rec_gathering (chan, subchan)) { - D->slicer[subchan].data_clock_pll = (int)(D->slicer[subchan].data_clock_pll * D->pll_locked_inertia); + if (hdlc_rec_gathering (chan, subchan, slice)) { + D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia); } else { - D->slicer[subchan].data_clock_pll = (int)(D->slicer[subchan].data_clock_pll * D->pll_searching_inertia); + D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia); } } /* * Remember demodulator output so we can compare next time. */ - D->slicer[subchan].prev_demod_data = demod_data; + D->slicer[slice].prev_demod_data = demod_data; } /* end nudge_pll */ diff --git a/direwolf.c b/direwolf.c index 3220402..835a8ef 100644 --- a/direwolf.c +++ b/direwolf.c @@ -231,7 +231,7 @@ int main (int argc, char *argv[]) text_color_init(t_opt); text_color_set(DW_COLOR_INFO); //dw_printf ("Dire Wolf version %d.%d (%s) Beta Test\n", MAJOR_VERSION, MINOR_VERSION, __DATE__); - dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "G", __DATE__); + dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "H", __DATE__); //dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); #if defined(ENABLE_GPSD) // later or hamlib ... @@ -721,6 +721,7 @@ int main (int argc, char *argv[]) * Inputs: chan - Audio channel number, 0 or 1. * subchan - Which modem caught it. * Special case -1 for DTMF decoder. + * slice - Slicer which caught it. * pp - Packet handle. * alevel - Audio level, range of 0 - 100. * (Special case, use negative to skip @@ -737,8 +738,7 @@ int main (int argc, char *argv[]) // TODO: Use only one printf per line so output doesn't get jumbled up with stuff from other threads. - -void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) +void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) { char stemp[500]; @@ -751,6 +751,7 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t alevel assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= -1 && subchan < MAX_SUBCHANS); + assert (slice >= 0 && slice < MAX_SLICERS); assert (pp != NULL); // 1.1J+ strlcpy (display_retries, "", sizeof(display_retries)); @@ -848,9 +849,16 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t alevel else { text_color_set(DW_COLOR_DEBUG); } - if (audio_config.achan[chan].num_subchan > 1) { + + if (audio_config.achan[chan].num_subchan > 1 && audio_config.achan[chan].num_slicers == 1) { dw_printf ("[%d.%d] ", chan, subchan); } + else if (audio_config.achan[chan].num_subchan == 1 && audio_config.achan[chan].num_slicers > 1) { + dw_printf ("[%d.%d] ", chan, slice); + } + else if (audio_config.achan[chan].num_subchan > 1 && audio_config.achan[chan].num_slicers > 1) { + dw_printf ("[%d.%d.%d] ", chan, subchan, slice); + } else { dw_printf ("[%d] ", chan); } diff --git a/direwolf.h b/direwolf.h index 90dae23..2a547f9 100644 --- a/direwolf.h +++ b/direwolf.h @@ -48,6 +48,17 @@ #define MAX_SUBCHANS 9 +/* + * Each one of these can have multiple slicers, at + * different levels, to compensate for different + * amplitudes of the AFSK tones. + * Intially used same number as subchannels but + * we could probably trim this down a little + * without impacting performance. + */ + +#define MAX_SLICERS 9 + #if __WIN32__ #include diff --git a/dlq.c b/dlq.c index c08294b..d628035 100644 --- a/dlq.c +++ b/dlq.c @@ -64,14 +64,15 @@ struct dlq_item_s { /* Special case, -1 means DTMF decoder. */ /* Maybe we should have a different type in this case? */ + int slice; /* Winning slicer. */ + packet_t pp; /* Pointer to frame structure. */ - alevel_t alevel; /* Audio level. */ + alevel_t alevel; /* Audio level. */ retry_t retries; /* Effort expended to get a valid CRC. */ - char spectrum[MAX_SUBCHANS+1]; /* "Spectrum" display for multi-decoders. */ - + char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; /* "Spectrum" display for multi-decoders. */ }; @@ -206,6 +207,8 @@ void dlq_init (void) * subchan - Which modem caught it. * Special case -1 for APRStt gateway. * + * slice - Which slice we picked. + * * pp - Address of packet object. * Caller should NOT make any references to * it after this point because it could @@ -231,7 +234,7 @@ void dlq_init (void) * *--------------------------------------------------------------------*/ -void dlq_append (dlq_type_t type, int chan, int subchan, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) +void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) { struct dlq_item_s *pnew; @@ -271,6 +274,7 @@ void dlq_append (dlq_type_t type, int chan, int subchan, packet_t pp, alevel_t a pnew->nextp = NULL; pnew->type = type; pnew->chan = chan; + pnew->slice = slice; pnew->subchan = subchan; pnew->pp = pp; pnew->alevel = alevel; @@ -514,7 +518,8 @@ void dlq_wait_while_empty (void) * Outputs: type - type of queue entry. * * chan - channel of received frame. - * subchan - which modem caught it. + * subchan - which demodulator caught it. + * slice - which slicer caught it. * * pp - pointer to packet object when type is DLQ_REC_FRAME. * Caller should destroy it with ax25_delete when finished with it. @@ -524,7 +529,8 @@ void dlq_wait_while_empty (void) * *--------------------------------------------------------------------*/ -int dlq_remove (dlq_type_t *type, int *chan, int *subchan, packet_t *pp, alevel_t *alevel, retry_t *retries, char *spectrum, size_t spectrumsize) + +int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t *pp, alevel_t *alevel, retry_t *retries, char *spectrum, size_t spectrumsize) { struct dlq_item_s *phead; @@ -557,6 +563,7 @@ int dlq_remove (dlq_type_t *type, int *chan, int *subchan, packet_t *pp, alevel_ *type = -1; *chan = -1; *subchan = -1; + *slice = -1; *pp = NULL; memset (alevel, 0xff, sizeof(*alevel)); @@ -573,6 +580,7 @@ int dlq_remove (dlq_type_t *type, int *chan, int *subchan, packet_t *pp, alevel_ *type = phead->type; *chan = phead->chan; *subchan = phead->subchan; + *slice = phead->slice; *pp = phead->pp; *alevel = phead->alevel; *retries = phead->retries; diff --git a/dlq.h b/dlq.h index 089f8e7..acfbce3 100644 --- a/dlq.h +++ b/dlq.h @@ -18,12 +18,11 @@ void dlq_init (void); typedef enum dlq_type_e {DLQ_REC_FRAME} dlq_type_t; -void dlq_append (dlq_type_t type, int chan, int subchan, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum); +void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum); void dlq_wait_while_empty (void); -int dlq_remove (dlq_type_t *type, int *chan, int *subchan, packet_t *pp, alevel_t *alevel, retry_t *retries, char *spectrum, size_t spectrumsize); - +int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t *pp, alevel_t *alevel, retry_t *retries, char *spectrum, size_t spectrumsize); #endif diff --git a/doc/Raspberry-Pi-SDR-IGate.pdf b/doc/Raspberry-Pi-SDR-IGate.pdf index 75a6239..68037bd 100644 Binary files a/doc/Raspberry-Pi-SDR-IGate.pdf and b/doc/Raspberry-Pi-SDR-IGate.pdf differ diff --git a/doc/User-Guide.pdf b/doc/User-Guide.pdf index 913a38d..b8b43e6 100644 Binary files a/doc/User-Guide.pdf and b/doc/User-Guide.pdf differ diff --git a/dtmf.c b/dtmf.c index 5f1e0fc..3479244 100644 --- a/dtmf.c +++ b/dtmf.c @@ -273,7 +273,7 @@ char dtmf_sample (int c, float input) // Update Data Carrier Detect Indicator. - dcd_change (c, MAX_SUBCHANS, decoded != ' '); + dcd_change (c, MAX_SUBCHANS, 0, decoded != ' '); /* Reset timeout timer. */ if (decoded != ' ') { diff --git a/fsk_demod_state.h b/fsk_demod_state.h index 9185da6..74a723c 100644 --- a/fsk_demod_state.h +++ b/fsk_demod_state.h @@ -180,7 +180,28 @@ struct demodulator_state_s * Each slicer has its own PLL and HDLC decoder. */ -#if 1 +/* + * Version 1.3: Clean up subchan vs. slicer. + * + * Originally some number of CHANNELS (originally 2, later 6) + * which can have multiple parallel demodulators called SUB-CHANNELS. + * This was originally for staggered frequencies for HF SSB. + * It can also be used for multiple demodulators with the same + * frequency but other differing parameters. + * Each subchannel has its own demodulator and HDLC decoder. + * + * In version 1.2 we added multiple SLICERS. + * The data structure, here, has multiple slicers per + * demodulator (subchannel). Due to fuzzy thinking or + * expediency, the multiple slicers got mapped into subchannels. + * This means we can't use both multiple decoders and + * multiple slicers at the same time. + * + * Clean this up in 1.3 and keep the concepts separate. + * This means adding a third variable many places + * we are passing around the origin. + * + */ struct { signed int data_clock_pll; // PLL for data clock recovery. @@ -197,25 +218,13 @@ struct demodulator_state_s int lfsr; // Descrambler shift register. - } slicer [MAX_SUBCHANS]; - -#else - signed int data_clock_pll; // PLL for data clock recovery. - // It is incremented by pll_step_per_sample - // for each audio sample. - - signed int prev_d_c_pll; // Previous value of above, before - // incrementing, to detect overflows. - - int prev_demod_data; // Previous data bit detected. - // Used to look for transitions. -#endif - - + } slicer [MAX_SLICERS]; // Actual number in use is num_slicers. + // Should be in range 1 .. MAX_SLICERS, /* * Special for Rino decoder only. * One for each possible signal polarity. + * The project showed promise but fell by the wayside. */ #if 0 diff --git a/hdlc_rec.c b/hdlc_rec.c index 975c26c..cf932d4 100644 --- a/hdlc_rec.c +++ b/hdlc_rec.c @@ -29,6 +29,7 @@ #include #include +#include #include "direwolf.h" #include "demod.h" @@ -105,13 +106,11 @@ struct hdlc_state_s { }; - -static struct hdlc_state_s hdlc_state[MAX_CHANS][MAX_SUBCHANS]; +static struct hdlc_state_s hdlc_state[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS]; static int num_subchan[MAX_CHANS]; //TODO1.2 use ptr rather than copy. -static int composite_dcd[MAX_CHANS]; - +static int composite_dcd[MAX_CHANS][MAX_SUBCHANS+1]; /*********************************************************************************** @@ -128,7 +127,7 @@ static int was_init = 0; void hdlc_rec_init (struct audio_s *pa) { - int j, k; + int ch, sub, slice; struct hdlc_state_s *H; //text_color_set(DW_COLOR_DEBUG); @@ -136,34 +135,33 @@ void hdlc_rec_init (struct audio_s *pa) assert (pa != NULL); - for (j=0; jachan[j].valid) { + if (pa->achan[ch].valid) { - num_subchan[j] = pa->achan[j].num_subchan; + num_subchan[ch] = pa->achan[ch].num_subchan; - assert (num_subchan[j] >= 1 && num_subchan[j] <= MAX_SUBCHANS); + assert (num_subchan[ch] >= 1 && num_subchan[ch] <= MAX_SUBCHANS); - for (k=0; kprev_raw = 0; - H->lfsr = 0; - H->prev_descram = 0; - H->pat_det = 0; - H->flag4_det = 0; - H->olen = -1; - H->frame_len = 0; - H->data_detect = 0; - // TODO: wasteful if not needed. - H->rrbb = rrbb_new(j, k, pa->achan[j].modem_type == MODEM_SCRAMBLE, H->lfsr, H->prev_descram); + H = &hdlc_state[ch][sub][slice]; + + H->olen = -1; + + // TODO: FIX13 wasteful if not needed. + // Should loop on number of slicers, not max. + + H->rrbb = rrbb_new(ch, sub, slice, pa->achan[ch].modem_type == MODEM_SCRAMBLE, H->lfsr, H->prev_descram); + } } } } - hdlc_rec2_init (pa); was_init = 1; } @@ -178,14 +176,16 @@ void hdlc_rec_init (struct audio_s *pa) * * Inputs: chan - Channel number. * - * subchan - This allows multiple decoders per channel. + * subchan - This allows multiple demodulators per channel. + * + * slice - Allows multiple slicers per demodulator (subchannel). * * raw - One bit from the demodulator. * should be 0 or 1. * * is_scrambled - Is the data scrambled? * - * descram_state - Current descrambler state. + * descram_state - Current descrambler state. (not used - remove) * * * Description: This is called once for each received bit. @@ -196,9 +196,7 @@ void hdlc_rec_init (struct audio_s *pa) // TODO: int not_used_remove - -void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int not_used_remove) - +void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, int not_used_remove) { int dbit; /* Data bit after undoing NRZI. */ @@ -210,10 +208,12 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int not_use assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); + assert (slice >= 0 && slice < MAX_SLICERS); + /* - * Different state information for each channel. + * Different state information for each channel / subchannel / slice. */ - H = &hdlc_state[chan][subchan]; + H = &hdlc_state[chan][subchan][slice]; /* * Using NRZI encoding, @@ -284,7 +284,7 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int not_use if ( ! H->data_detect) { H->data_detect = 1; - dcd_change (chan, subchan, 1); + dcd_change (chan, subchan, slice, 1); } } //else if (H->flag4_det == 0x7e000000) { @@ -293,7 +293,7 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int not_use if ( ! H->data_detect) { H->data_detect = 1; - dcd_change (chan, subchan, 1); + dcd_change (chan, subchan, slice, 1); } } @@ -308,7 +308,7 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int not_use if ( H->data_detect ) { H->data_detect = 0; - dcd_change (chan, subchan, 0); + dcd_change (chan, subchan, slice, 0); } } @@ -372,7 +372,7 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int not_use if (actual_fcs == expected_fcs) { alevel_t alevel = demod_get_audio_level (chan, subchan); - multi_modem_process_rec_frame (chan, subchan, H->frame_buf, H->frame_len - 2, alevel, RETRY_NONE); /* len-2 to remove FCS. */ + multi_modem_process_rec_frame (chan, subchan, slice, H->frame_buf, H->frame_len - 2, alevel, RETRY_NONE); /* len-2 to remove FCS. */ } else { @@ -401,7 +401,8 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int not_use rrbb_set_audio_level (H->rrbb, alevel); hdlc_rec2_block (H->rrbb); /* Now owned by someone else who will free it. */ - H->rrbb = rrbb_new (chan, subchan, is_scrambled, H->lfsr, H->prev_descram); /* Allocate a new one. */ + + H->rrbb = rrbb_new (chan, subchan, slice, is_scrambled, H->lfsr, H->prev_descram); /* Allocate a new one. */ } else { rrbb_clear (H->rrbb, is_scrambled, H->lfsr, H->prev_descram); @@ -514,86 +515,81 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int not_use * * Inputs: chan * subchan + * slice * * Returns: True if we are currently gathering bits. * In this case we want the PLL to have more inertia. * - * Discussion: Originally I used the data carrier detect. - * Later, it seemed like the we should be using "olen>=0" instead. - * - * Seems to make no difference for Track 1 and the original - * way was a hair better for Track 2. + * Discussion: This simply returns the data carrier detect state. + * A couple other variations were tried but turned out to + * be slightly worse. * *--------------------------------------------------------------------*/ -int hdlc_rec_gathering (int chan, int subchan) +int hdlc_rec_gathering (int chan, int subchan, int slice) { assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); + assert (slice >= 0 && slice < MAX_SLICERS); // Counts from Track 1 & Track 2 // data_detect 992 988 // olen>=0 992 985 // OR-ed 992 985 - - return ( hdlc_state[chan][subchan].data_detect ); - - //return ( hdlc_state[chan][subchan].olen >= 0); - - //return ( hdlc_state[chan][subchan].data_detect || hdlc_state[chan][subchan].olen >= 0 ); + return ( hdlc_state[chan][subchan][slice].data_detect ); } /* end hdlc_rec_gathering */ - - /*------------------------------------------------------------------- * * Name: dcd_change * - * Purpose: Combine DCD states of all subchannels into an overall + * Purpose: Combine DCD states of all subchannels/ into an overall * state for the channel. * * Inputs: chan * * subchan 0 to MAX_SUBCHANS-1 for HDLC. - * MAX_SUBCHANS for DTMF decoder. + * SPECIAL CASE --> MAX_SUBCHANS for DTMF decoder. + * + * slice slicer number, 0 .. MAX_SLICERS - 1. * * state 1 for active, 0 for not. * - * Returns: None. Use ??? to retrieve result. + * Returns: None. Use hdlc_rec_data_detect_any to retrieve result. * - * Description: DCD for the channel is active if ANY of the subchannels - * is active. Update the DCD indicator. + * Description: DCD for the channel is active if ANY of the subchannels/slices + * are active. Update the DCD indicator. * * version 1.3: Add DTMF detection into the final result. * This is now called from dtmf.c too. * *--------------------------------------------------------------------*/ - -void dcd_change (int chan, int subchan, int state) +void dcd_change (int chan, int subchan, int slice, int state) { int old, new; assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan <= MAX_SUBCHANS); + assert (slice >= 0 && slice < MAX_SLICERS); assert (state == 0 || state == 1); #if DEBUG3 text_color_set(DW_COLOR_DEBUG); - dw_printf ("DCD %d.%d = %d \n", chan, subchan, state); + dw_printf ("DCD %d.%d.%d = %d \n", chan, subchan, slice, state); #endif old = hdlc_rec_data_detect_any(chan); if (state) { - composite_dcd[chan] |= (1 << subchan); + composite_dcd[chan][subchan] |= (1 << slice); } else { - composite_dcd[chan] &= ~ (1 << subchan); + composite_dcd[chan][subchan] &= ~ (1 << slice); } new = hdlc_rec_data_detect_any(chan); @@ -634,13 +630,17 @@ void dcd_change (int chan, int subchan, int state) int hdlc_rec_data_detect_any (int chan) { + int sc; assert (chan >= 0 && chan < MAX_CHANS); - return (composite_dcd[chan] != 0); + for (sc = 0; sc < num_subchan[chan]; sc++) { + if (composite_dcd[chan][sc] != 0) + return (1); + } + return (0); } /* end hdlc_rec_data_detect_any */ - /* end hdlc_rec.c */ diff --git a/hdlc_rec.h b/hdlc_rec.h index 3f390c2..69b60a9 100644 --- a/hdlc_rec.h +++ b/hdlc_rec.h @@ -5,9 +5,7 @@ void hdlc_rec_init (struct audio_s *pa); - -void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram_state); - +void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, int descram_state); /* Provided elsewhere to process a complete frame. */ @@ -18,11 +16,10 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram /* Similar to, but not exactly the same as, data carrier detect. */ /* We use this to influence the PLL inertia. */ -int hdlc_rec_gathering (int chan, int subchan); - +int hdlc_rec_gathering (int chan, int subchan, int slice); /* Transmit needs to know when someone else is transmitting. */ -void dcd_change (int chan, int subchan, int state); +void dcd_change (int chan, int subchan, int slice, int state); int hdlc_rec_data_detect_any (int chan); diff --git a/hdlc_rec2.c b/hdlc_rec2.c index 31a0bf6..374fa98 100644 --- a/hdlc_rec2.c +++ b/hdlc_rec2.c @@ -76,6 +76,12 @@ * It was necessary to retain more initial state information after * the start flag octet. * + * Version 1.3: Took out all of the "insert" and "remove" cases because they + * offer no benenfit. + * + * Took out the delayed processing and just do it realtime. + * Changed SWAP to INVERT because it is more descriptive. + * *******************************************************************************/ #include @@ -152,11 +158,11 @@ struct hdlc_state_s { }; -#define MAX_RETRY_SWAP_BITS 24 /* Maximum number of contiguous bits to swap */ -#define MAX_RETRY_REMOVE_SEPARATED_BITS 24 /* Maximum number of contiguous bits to remove */ -static int try_decode (rrbb_t block, int chan, int subchan, alevel_t alevel, retry_conf_t retry_conf, int passall); -static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, alevel_t alevel); +static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel, retry_conf_t retry_conf, int passall); + +static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel); + static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped, enum sanity_e sanity_test); @@ -173,7 +179,7 @@ static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped, enu * Level of effort to recover from * a bad FCS on the frame. * 0 = no effort - * 1 = try fixing a single bit + * 1 = try inverting a single bit * 2... = more techniques... * * enum sanity_e sanity_test; @@ -222,6 +228,7 @@ void hdlc_rec2_block (rrbb_t block) { int chan = rrbb_get_chan(block); int subchan = rrbb_get_subchan(block); + int slice = rrbb_get_slice(block); alevel_t alevel = rrbb_get_audio_level(block); retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits; int passall = save_audio_config_p->achan[chan].passall; @@ -245,11 +252,8 @@ void hdlc_rec2_block (rrbb_t block) retry_cfg.retry = RETRY_NONE; retry_cfg.u_bits.contig.nr_bits = 0; retry_cfg.u_bits.contig.bit_idx = 0; - /* Prepare the decoded bits in an array for faster processing - *(at cost of memory but 1 or 2 kbytes is nothing compared to processing time ) */ - rrbb_compute_bits(block); - ok = try_decode (block, chan, subchan, alevel, retry_cfg, passall & (fix_bits == RETRY_NONE)); + ok = try_decode (block, chan, subchan, slice, alevel, retry_cfg, passall & (fix_bits == RETRY_NONE)); if (ok) { #if DEBUG text_color_set(DW_COLOR_INFO); @@ -261,29 +265,19 @@ void hdlc_rec2_block (rrbb_t block) /* * Not successful with frame in orginal form. - * Try the quick techniques with time proportional to the frame length. - */ - if (try_to_fix_quick_now (block, chan, subchan, alevel)) { + * See if we can "fix" it. + */ + if (try_to_fix_quick_now (block, chan, subchan, slice, alevel)) { rrbb_delete (block); return; } -/* - * Not successful with the quick fix up attempts. - * Do we want to try the more aggressive techniques where processing - * time is proportional to the square of length? - * Rather than doing it now, we throw it in a queue for processing - * by a different thread. - */ - if (fix_bits >= RETRY_SWAP_TWO_SEP) { - rdq_append (block); - } - else if (passall) { + if (passall) { /* Exhausted all desired fix up attempts. */ /* Let thru even with bad CRC. Of course, it still */ /* needs to be a minimum number of whole octets. */ - ok = try_decode (block, chan, subchan, alevel, retry_cfg, 1); + ok = try_decode (block, chan, subchan, slice, alevel, retry_cfg, 1); rrbb_delete (block); } else { @@ -307,7 +301,7 @@ void hdlc_rec2_block (rrbb_t block) * Global In: configuration fix_bits - Maximum level of fix up to attempt. * * RETRY_NONE (0) - Don't try any. - * RETRY_SWAP_SINGLE (1) - Try inverting single bits. + * RETRY_INVERT_SINGLE (1) - Try inverting single bits. * etc. * * configuration passall - Let it thru with bad CRC after exhausting @@ -318,15 +312,19 @@ void hdlc_rec2_block (rrbb_t block) * processing step. * 0 for failure. Caller might continue with more aggressive attempts. * - * Description: Some of the attempted fix up techniques are quick. + * Original: Some of the attempted fix up techniques are quick. * We will attempt them immediately after receiving the frame. * Others, that take time order N**2, will be done in a later section. * * Version 1.2: Now works properly for G3RUH type scrambling. * + * Version 1.3: Removed the extra cases that didn't help. + * The separated bit case is now handled immediately instead of + * being thrown in a queue for later processing. + * ***********************************************************************************/ -static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, alevel_t alevel) +static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel) { int ok; int len, i,j; @@ -340,9 +338,9 @@ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, alevel_t a /* Will modify only contiguous bits*/ retry_cfg.mode = RETRY_MODE_CONTIGUOUS; /* - * Try fixing one bit. + * Try inverting one bit. */ - if (fix_bits < RETRY_SWAP_SINGLE) { + if (fix_bits < RETRY_INVERT_SINGLE) { /* Stop before single bit fix up. */ @@ -350,13 +348,13 @@ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, alevel_t a } /* Try to swap one bit */ retry_cfg.type = RETRY_TYPE_SWAP; - retry_cfg.retry = RETRY_SWAP_SINGLE; + retry_cfg.retry = RETRY_INVERT_SINGLE; retry_cfg.u_bits.contig.nr_bits = 1; for (i=0; iachan[chan].fix_bits; - int passall = save_audio_config_p->achan[chan].passall; -#if DEBUG_LATER - double tstart, tend; -#endif - retry_conf_t retry_cfg; - len = rrbb_get_len(block); - - - - if (fix_bits < RETRY_SWAP_TWO_SEP) { - goto failure; - } - - - retry_cfg.mode = RETRY_MODE_SEPARATED; /* * Two non-adjacent ("separated") single bits. - * It chews up a lot of CPU time. Test takes 4 times longer to run. + * It chews up a lot of CPU time. Usual test takes 4 times longer to run. * - * Ran up to xx seconds (TODO check again with optimized code) seconds for 1040 bits before giving up . * Processing time is order N squared so time goes up rapidly with larger frames. */ + if (fix_bits < RETRY_INVERT_TWO_SEP) { + return 0; + } + + retry_cfg.mode = RETRY_MODE_SEPARATED; retry_cfg.type = RETRY_TYPE_SWAP; - retry_cfg.retry = RETRY_SWAP_TWO_SEP; + retry_cfg.retry = RETRY_INVERT_TWO_SEP; retry_cfg.u_bits.sep.bit_idx_c = -1; #ifdef DEBUG_LATER @@ -623,7 +437,7 @@ int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, alevel_t al ok = 0; for (j=i+2; jachan[chan].fix_bits; + int passall = save_audio_config_p->achan[chan].passall; #if DEBUG_LATER - tend = dtime_now(); - text_color_set(DW_COLOR_ERROR); - dw_printf ("*** No luck flipping TWO SEPARATED bits of %d *** %.3f sec.\n", len, tend-tstart); -#endif - - if (fix_bits < RETRY_SWAP_MANY) { - goto failure; - } - /* Try to swap many contiguous bits */ - retry_cfg.mode = RETRY_MODE_CONTIGUOUS; - retry_cfg.type = RETRY_TYPE_SWAP; - retry_cfg.retry = RETRY_SWAP_MANY; - -#ifdef DEBUG_LATER - tstart = dtime_now(); - dw_printf ("*** Try swapping many BITS %d bits\n", len); + double tstart, tend; #endif + retry_conf_t retry_cfg; len = rrbb_get_len(block); - for (i=0; icomputed_data[ind]; -} /*********************************************************************************** * @@ -835,20 +542,12 @@ inline static unsigned int get_bit (const rrbb_t b,const unsigned int ind) * retry_conf - Controls changes that will be attempted to get a good CRC. * * retry: - * Level of effort to recover from A bad FCS on the frame. - * RETRY_NONE=0, - * RETRY_SWAP_SINGLE=1, - * RETRY_SWAP_DOUBLE=2, - * RETRY_SWAP_TRIPLE=3, - * RETRY_REMOVE_SINGLE=4, - * RETRY_REMOVE_DOUBLE=5, - * RETRY_REMOVE_TRIPLE=6, - * RETRY_INSERT_SINGLE=7, - * RETRY_INSERT_DOUBLE=8, - * RETRY_SWAP_TWO_SEP=9, - * RETRY_SWAP_MANY=10, - * RETRY_REMOVE_MANY=11, - * RETRY_REMOVE_TWO_SEP=12, + * Level of effort to recover from a bad FCS on the frame. + * RETRY_NONE = 0 + * RETRY_INVERT_SINGLE = 1 + * RETRY_INVERT_DOUBLE = 2 + * RETRY_INVERT_TRIPLE = 3 + * RETRY_INVERT_TWO_SEP = 4 * * mode: RETRY_MODE_CONTIGUOUS - change adjacent bits. * contig.bit_idx - first bit position @@ -861,8 +560,6 @@ inline static unsigned int get_bit (const rrbb_t b,const unsigned int ind) * * type: RETRY_TYPE_NONE - Make no changes. * RETRY_TYPE_SWAP - Try inverting. - * RETRY_TYPE_REMOVE - Try removing. - * RETRY_TYPE_INSERT - Try inserting. * * passall - All it thru even with bad CRC. * Valid only when no changes make. i.e. @@ -873,8 +570,7 @@ inline static unsigned int get_bit (const rrbb_t b,const unsigned int ind) * ***********************************************************************************/ - -static int try_decode (rrbb_t block, int chan, int subchan, alevel_t alevel, retry_conf_t retry_conf, int passall) +static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel, retry_conf_t retry_conf, int passall) { struct hdlc_state_s H; int blen; /* Block length in bits. */ @@ -891,7 +587,7 @@ static int try_decode (rrbb_t block, int chan, int subchan, alevel_t alevel, ret H.is_scrambled = rrbb_get_is_scrambled (block); H.prev_descram = rrbb_get_prev_descram (block); H.lfsr = rrbb_get_descram_state (block); - H.prev_raw = get_bit (block, 0); /* Actually last bit of the */ + H.prev_raw = rrbb_get_bit (block, 0); /* Actually last bit of the */ /* opening flag so we can derive the */ /* first data bit. */ @@ -911,9 +607,6 @@ static int try_decode (rrbb_t block, int chan, int subchan, alevel_t alevel, ret H.frame_len = 0; blen = rrbb_get_len(block); - /* Prepare space for the inserted bits in contiguous mode (separated mode for insert is not supported yet) */ - if (retry_conf.type == RETRY_TYPE_INSERT && retry_conf.mode == RETRY_MODE_CONTIGUOUS) - blen+=retry_conf.u_bits.contig.nr_bits; #if DEBUGx text_color_set(DW_COLOR_DEBUG); @@ -922,43 +615,16 @@ static int try_decode (rrbb_t block, int chan, int subchan, alevel_t alevel, ret #endif for (i=1; i= bit_idx + nr_bits) - raw = get_bit (block, i-nr_bits); - /* Else if this is a bit to insert, calculate the value of the bit from insert_value */ - else if (is_contig_bit_modified(i, retry_conf)) { - raw = (retry_conf.insert_value >> (i-bit_idx)) & 1; -/* dw_printf ("raw is %d for i %d bit_idx %d insert_value %d\n", - raw, i, bit_idx, retry_conf.insert_value);*/ - /* Else use the original bit value from the buffer */ - } else { - /* Already set before */ - } - /* If in swap mode */ - } else if (retry_conf_type == RETRY_TYPE_SWAP) { + + if (retry_conf_type == RETRY_TYPE_SWAP) { /* If this is the bit to swap */ if (is_contig_bit_modified(i, retry_conf)) raw = ! raw; @@ -1097,8 +763,7 @@ static int try_decode (rrbb_t block, int chan, int subchan, alevel_t alevel, ret assert (rrbb_get_chan(block) == chan); assert (rrbb_get_subchan(block) == subchan); - - multi_modem_process_rec_frame (chan, subchan, H.frame_buf, H.frame_len - 2, alevel, retry_conf.retry); /* len-2 to remove FCS. */ + multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, retry_conf.retry); /* len-2 to remove FCS. */ return 1; /* success */ } else if (passall) { @@ -1107,7 +772,7 @@ static int try_decode (rrbb_t block, int chan, int subchan, alevel_t alevel, ret //text_color_set(DW_COLOR_ERROR); //dw_printf ("ATTEMPTING PASSALL PROCESSING\n"); - multi_modem_process_rec_frame (chan, subchan, H.frame_buf, H.frame_len - 2, alevel, RETRY_MAX); /* len-2 to remove FCS. */ + multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, RETRY_MAX); /* len-2 to remove FCS. */ return 1; /* success */ } else { @@ -1186,7 +851,9 @@ end: * * Returns: 1 if it passes the sanity test. * - * Description: + * Description: This is NOT a validity check. + * We don't know if modifying the frame fixed the problem or made it worse. + * We can only test if it looks reasonable. * ***********************************************************************************/ diff --git a/hdlc_rec2.h b/hdlc_rec2.h index ccc95ee..bb04bd7 100644 --- a/hdlc_rec2.h +++ b/hdlc_rec2.h @@ -17,9 +17,7 @@ typedef enum retry_mode_e { typedef enum retry_type_e { RETRY_TYPE_NONE=0, - RETRY_TYPE_SWAP=1, - RETRY_TYPE_REMOVE=2, - RETRY_TYPE_INSERT=3} retry_type_t; + RETRY_TYPE_SWAP=1 } retry_type_t; typedef struct retry_conf_s { retry_t retry; @@ -52,15 +50,7 @@ static const char * retry_text[] = { "SINGLE", "DOUBLE", "TRIPLE", - "REMOVE_SINGLE", - "REMOVE_DOUBLE", - "REMOVE_TRIPLE", - "INSERT_SINGLE", - "INSERT_DOUBLE", "TWO_SEP", - "MANY", - "REMOVE_MANY", - "REMOVE_SEP", "PASSALL" }; #endif @@ -68,11 +58,10 @@ void hdlc_rec2_init (struct audio_s *audio_config_p); void hdlc_rec2_block (rrbb_t block); -int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, alevel_t alevel); +int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel); /* Provided by the top level application to process a complete frame. */ -void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t level, retry_t retries, char *spectrum); - +void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t level, retry_t retries, char *spectrum); #endif diff --git a/multi_modem.c b/multi_modem.c index 7236de5..ac6122a 100644 --- a/multi_modem.c +++ b/multi_modem.c @@ -98,23 +98,15 @@ static struct audio_s *save_audio_config_p; // Candidates for further processing. static struct { - packet_t packet_p; alevel_t alevel; retry_t retries; int age; unsigned int crc; int score; +} candidate[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS]; -} candidate[MAX_CHANS][MAX_SUBCHANS]; -#define MAX_STORED_CRC 256 -typedef struct crc_s { - struct crc_s* nextp; /* Next pointer to maintain a queue. */ - unsigned int crc; -} *crc_t; - -static crc_t crc_queue_of_last_to_app[MAX_CHANS]; #define PROCESS_AFTER_BITS 2 @@ -163,12 +155,14 @@ void multi_modem_init (struct audio_s *pa) save_audio_config_p->achan[chan].baud = DEFAULT_BAUD; } process_age[chan] = PROCESS_AFTER_BITS * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].baud; - crc_queue_of_last_to_app[chan] = NULL; + //crc_queue_of_last_to_app[chan] = NULL; } } } +#if 0 + //Add a crc to the end of the queue and returns the numbers of CRC stored in the queue int crc_queue_append (unsigned int crc, unsigned int chan) { crc_t plast; @@ -257,6 +251,7 @@ unsigned char is_crc_in_queue(unsigned int chan, unsigned int crc) { } return 0; } +#endif /* if 0 */ /*------------------------------------------------------------------------------ * @@ -281,7 +276,12 @@ unsigned char is_crc_in_queue(unsigned int chan, unsigned int crc) { * slicers, using different levels, each with its own HDLC decoder. * We now have a separate variable, num_demod, which could be 1 * while num_subchan is larger. - * + * + * Version 1.3: Go back to num_subchan with single meaning of number of demodulators. + * We now have separate independent variable, num_slicers, for the + * mark/space imbalance compensation. + * num_demod, while probably more descriptive, should not exist anymore. + * *------------------------------------------------------------------------------*/ @@ -290,25 +290,48 @@ void multi_modem_process_sample (int chan, int audio_sample) { int d; int subchan; + static int i = 0; /* for interleaving among multiple demodulators. */ + +// TODO: temp debug, remove this. + + assert (save_audio_config_p->achan[chan].num_subchan > 0 && save_audio_config_p->achan[chan].num_subchan <= MAX_SUBCHANS); + assert (save_audio_config_p->achan[chan].num_slicers > 0 && save_audio_config_p->achan[chan].num_slicers <= MAX_SLICERS); + /* Formerly one loop. */ /* 1.2: We can feed one demodulator but end up with multiple outputs. */ - for (d = 0; d < save_audio_config_p->achan[chan].num_demod; d++) { + if (save_audio_config_p->achan[chan].interleave > 1) { - demod_process_sample(chan, d, audio_sample); +// TODO: temp debug, remove this. + + assert (save_audio_config_p->achan[chan].interleave == save_audio_config_p->achan[chan].num_subchan); + demod_process_sample(chan, i, audio_sample); + i++; + if (i >= save_audio_config_p->achan[chan].interleave) i = 0; + } + else { + /* Send same thing to all. */ + for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) { + demod_process_sample(chan, d, audio_sample); + } } for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) { + int slice; - if (candidate[chan][subchan].packet_p != NULL) { - candidate[chan][subchan].age++; - if (candidate[chan][subchan].age > process_age[chan]) { - pick_best_candidate (chan); + for (slice = 0; slice < save_audio_config_p->achan[chan].num_slicers; slice++) { + + if (candidate[chan][subchan][slice].packet_p != NULL) { + candidate[chan][subchan][slice].age++; + if (candidate[chan][subchan][slice].age > process_age[chan]) { + pick_best_candidate (chan); + } } - } - }} + } + } +} @@ -320,7 +343,8 @@ void multi_modem_process_sample (int chan, int audio_sample) * FCS and acceptable size. * * Inputs: chan - Audio channel number, 0 or 1. - * subchan - Which modem/decoder found it. + * subchan - Which modem found it. + * slice - Which slice found it. * fbuf - Pointer to first byte in HDLC frame. * flen - Number of bytes excluding the FCS. * alevel - Audio level, range of 0 - 100. @@ -414,73 +438,31 @@ void multi_modem_process_sample (int chan, int audio_sample) than one. */ -void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries) -{ +void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries) +{ packet_t pp; assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); + assert (slice >= 0 && slice < MAX_SUBCHANS); pp = ax25_from_frame (fbuf, flen, alevel); if (pp == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Unexpected internal problem, %s %d\n", __FILE__, __LINE__); return; /* oops! why would it fail? */ } /* - * If single modem/deocder, push it thru and forget about all this foolishness. + * If only one demodulator/slicer, push it thru and forget about all this foolishness. */ - if (save_audio_config_p->achan[chan].num_subchan == 1) { - dlq_append (DLQ_REC_FRAME, chan, subchan, pp, alevel, retries, ""); - return; - } + if (save_audio_config_p->achan[chan].num_subchan == 1 && + save_audio_config_p->achan[chan].num_slicers == 1) { -/* - * Special handing for two separated bit errors. - * See description earlier. - * - * Not combined with others to find the best score. - * Either pass it along or drop if duplicate. - */ - - if (retries >= RETRY_SWAP_TWO_SEP) { - int mycrc; - char spectrum[MAX_SUBCHANS+1]; - int dropped = 0; - - memset (spectrum, 0, sizeof(spectrum)); - memset (spectrum, '_', (size_t)save_audio_config_p->achan[chan].num_subchan); - spectrum[subchan] = '.'; - - mycrc = ax25_m_m_crc(pp); -/* Smetimes recovered packet is not the latest one send to the app: - * It can be a packet sent to the app before the latest one because of the processing time ... - * So we check if the crc of current packet has already been received in the queue of others crc - */ - dropped = is_crc_in_queue(chan, mycrc); - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("\n%s\n%d.%d: ptr=%p, retry=%d, age=, crc=%04x, score= , dropped =%d\n", - spectrum, chan, subchan, pp, (int)retries, mycrc,dropped); -#endif - if (dropped) { - /* Same as last one. Drop it. */ - ax25_delete (pp); -#if DEBUG - dw_printf ("Drop duplicate.\n"); -#endif - return; - } - -#if DEBUG - dw_printf ("Send the best one along.\n"); -#endif - dlq_append (DLQ_REC_FRAME, chan, subchan, pp, alevel, retries, spectrum); - if (crc_queue_append(mycrc, chan) > MAX_STORED_CRC) - crc_queue_remove(chan); + dlq_append (DLQ_REC_FRAME, chan, subchan, slice, pp, alevel, retries, ""); return; } @@ -488,17 +470,17 @@ void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, /* * Otherwise, save them up for a few bit times so we can pick the best. */ - if (candidate[chan][subchan].packet_p != NULL) { + if (candidate[chan][subchan][slice].packet_p != NULL) { /* Oops! Didn't expect it to be there. */ - ax25_delete (candidate[chan][subchan].packet_p); - candidate[chan][subchan].packet_p = NULL; + ax25_delete (candidate[chan][subchan][slice].packet_p); + candidate[chan][subchan][slice].packet_p = NULL; } - candidate[chan][subchan].packet_p = pp; - candidate[chan][subchan].alevel = alevel; - candidate[chan][subchan].retries = retries; - candidate[chan][subchan].age = 0; - candidate[chan][subchan].crc = ax25_m_m_crc(pp); + candidate[chan][subchan][slice].packet_p = pp; + candidate[chan][subchan][slice].alevel = alevel; + candidate[chan][subchan][slice].retries = retries; + candidate[chan][subchan][slice].age = 0; + candidate[chan][subchan][slice].crc = ax25_m_m_crc(pp); } @@ -519,56 +501,82 @@ void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, * *--------------------------------------------------------------------*/ +/* This is a suitable order for interleaved "G" demodulators. */ +/* Opposite order would be suitable for multi-frequency although */ +/* multiple slicers are of questionable value for HF SSB. */ + +#define subchan_from_n(x) ((x) % save_audio_config_p->achan[chan].num_subchan) +#define slice_from_n(x) ((x) / save_audio_config_p->achan[chan].num_subchan) + static void pick_best_candidate (int chan) { - int subchan; - int best_subchan, best_score; - char spectrum[MAX_SUBCHANS+1]; - int k; + int best_n, best_score; + char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; + int n, j, k; + int num_bars = save_audio_config_p->achan[chan].num_slicers * save_audio_config_p->achan[chan].num_subchan; memset (spectrum, 0, sizeof(spectrum)); - for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) { + for (n = 0; n < num_bars; n++) { + j = subchan_from_n(n); + k = slice_from_n(n); /* Build the spectrum display. */ - if (candidate[chan][subchan].packet_p == NULL) { - spectrum[subchan] = '_'; + if (candidate[chan][j][k].packet_p == NULL) { + spectrum[n] = '_'; } - else if (candidate[chan][subchan].retries == RETRY_NONE) { - spectrum[subchan] = '|'; + else if (candidate[chan][j][k].retries == RETRY_NONE) { + spectrum[n] = '|'; } - else if (candidate[chan][subchan].retries == RETRY_SWAP_SINGLE) { - spectrum[subchan] = ':'; + else if (candidate[chan][j][k].retries == RETRY_INVERT_SINGLE) { + spectrum[n] = ':'; } else { - spectrum[subchan] = '.'; + spectrum[n] = '.'; } /* Begining score depends on effort to get a valid frame CRC. */ - candidate[chan][subchan].score = RETRY_MAX * 1000 - ((int)candidate[chan][subchan].retries * 1000); + candidate[chan][j][k].score = RETRY_MAX * 1000 - ((int)candidate[chan][j][k].retries * 1000); + } - /* Bump it up slightly if others nearby have the same CRC. */ - - for (k = 0; k < save_audio_config_p->achan[chan].num_subchan; k++) { - if (k != subchan && candidate[chan][k].packet_p != NULL) { - if (candidate[chan][k].crc == candidate[chan][subchan].crc) { - candidate[chan][subchan].score += (MAX_SUBCHANS+1) - abs(subchan-k); + /* Bump it up slightly if others nearby have the same CRC. */ + + for (n = 0; n < num_bars; n++) { + int m; + + j = subchan_from_n(n); + k = slice_from_n(n); + + if (candidate[chan][j][k].packet_p != NULL) { + + for (m = 0; m < num_bars; m++) { + + int mj = subchan_from_n(m); + int mk = slice_from_n(m); + + if (m != n && candidate[chan][mj][mk].packet_p != NULL) { + if (candidate[chan][j][k].crc == candidate[chan][mj][mk].crc) { + candidate[chan][j][k].score += (num_bars+1) - abs(m-n); + } } } } } - best_subchan = 0; + best_n = 0; best_score = 0; - for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) { - if (candidate[chan][subchan].packet_p != NULL) { - if (candidate[chan][subchan].score > best_score) { - best_score = candidate[chan][subchan].score; - best_subchan = subchan; + for (n = 0; n < num_bars; n++) { + j = subchan_from_n(n); + k = slice_from_n(n); + + if (candidate[chan][j][k].packet_p != NULL) { + if (candidate[chan][j][k].score > best_score) { + best_score = candidate[chan][j][k].score; + best_n = n; } } } @@ -577,20 +585,22 @@ static void pick_best_candidate (int chan) text_color_set(DW_COLOR_DEBUG); dw_printf ("\n%s\n", spectrum); - for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) { + for (n = 0; n < num_bars; n++) { + j = subchan_from_n(n); + k = slice_from_n(n); - if (candidate[chan][subchan].packet_p == NULL) { - dw_printf ("%d.%d: ptr=%p\n", chan, subchan, - candidate[chan][subchan].packet_p); + if (candidate[chan][j][k].packet_p == NULL) { + dw_printf ("%d.%d.%d: ptr=%p\n", chan, j, k, + candidate[chan][j][k].packet_p); } else { - dw_printf ("%d.%d: ptr=%p, retry=%d, age=%3d, crc=%04x, score=%d %s\n", chan, subchan, - candidate[chan][subchan].packet_p, - (int)(candidate[chan][subchan].retries), - candidate[chan][subchan].age, - candidate[chan][subchan].crc, - candidate[chan][subchan].score, - subchan == best_subchan ? "***" : ""); + dw_printf ("%d.%d.%d: ptr=%p, retry=%d, age=%3d, crc=%04x, score=%d %s\n", chan, j, k, + candidate[chan][j][k].packet_p, + (int)(candidate[chan][j][k].retries), + candidate[chan][j][k].age, + candidate[chan][j][k].crc, + candidate[chan][j][k].score, + (n == best_n) ? "***" : ""); } } #endif @@ -599,75 +609,38 @@ static void pick_best_candidate (int chan) * send the best one along. */ -#if 1 // v1.2 dev F, Reverse original order. Delete rejects THEN process the best one. - /* Delete those not chosen. */ - for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) { - if (subchan != best_subchan && candidate[chan][subchan].packet_p != NULL) { - ax25_delete (candidate[chan][subchan].packet_p); - candidate[chan][subchan].packet_p = NULL; + for (n = 0; n < num_bars; n++) { + j = subchan_from_n(n); + k = slice_from_n(n); + if (n != best_n && candidate[chan][j][k].packet_p != NULL) { + ax25_delete (candidate[chan][j][k].packet_p); + candidate[chan][j][k].packet_p = NULL; } } /* Pass along one. */ - dlq_append (DLQ_REC_FRAME, chan, best_subchan, - candidate[chan][best_subchan].packet_p, - candidate[chan][best_subchan].alevel, - (int)(candidate[chan][best_subchan].retries), + + j = subchan_from_n(best_n); + k = slice_from_n(best_n); + + dlq_append (DLQ_REC_FRAME, chan, j, k, + candidate[chan][j][k].packet_p, + candidate[chan][j][k].alevel, + (int)(candidate[chan][j][k].retries), spectrum); - if (crc_queue_append(candidate[chan][best_subchan].crc, chan) > MAX_STORED_CRC) - crc_queue_remove(chan); /* Someone else owns it now and will delete it later. */ - candidate[chan][best_subchan].packet_p = NULL; + candidate[chan][j][k].packet_p = NULL; /* Clear in preparation for next time. */ - for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) { + memset (candidate, 0, sizeof(candidate)); - candidate[chan][subchan].alevel.rec = 0; - candidate[chan][subchan].alevel.mark = 0; - candidate[chan][subchan].alevel.space = 0; - - candidate[chan][subchan].retries = 0; - candidate[chan][subchan].age = 0; - candidate[chan][subchan].crc = 0; - } -#else - - dlq_append (DLQ_REC_FRAME, chan, best_subchan, - candidate[chan][best_subchan].packet_p, - candidate[chan][best_subchan].alevel, - (int)(candidate[chan][best_subchan].retries), - spectrum); - if (crc_queue_append(candidate[chan][best_subchan].crc, chan) > MAX_STORED_CRC) - crc_queue_remove(chan); - /* Someone else will delete so don't do it below. */ - candidate[chan][best_subchan].packet_p = NULL; - - /* Clear out in preparation for next time. */ - - for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) { - if (candidate[chan][subchan].packet_p != NULL) { - ax25_delete (candidate[chan][subchan].packet_p); - candidate[chan][subchan].packet_p = NULL; - } - - candidate[chan][subchan].alevel.rec = 0; - candidate[chan][subchan].alevel.mark = 0; - candidate[chan][subchan].alevel.space = 0; - - candidate[chan][subchan].retries = 0; - candidate[chan][subchan].age = 0; - candidate[chan][subchan].crc = 0; - } - -#endif - -} +} /* end pick_best_candidate */ /* end multi_modem.c */ diff --git a/multi_modem.h b/multi_modem.h index e84a549..77c5b6d 100644 --- a/multi_modem.h +++ b/multi_modem.h @@ -14,7 +14,6 @@ void multi_modem_init (struct audio_s *pmodem); void multi_modem_process_sample (int c, int audio_sample); -void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries); - +void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries); #endif diff --git a/recv.c b/recv.c index 7972a8d..a2dba4d 100644 --- a/recv.c +++ b/recv.c @@ -287,11 +287,11 @@ void recv_process (void) dlq_type_t type; int chan; int subchan; + int slice; packet_t pp; alevel_t alevel; retry_t retries; - char spectrum[MAX_SUBCHANS+1]; - + char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; while (1) { @@ -299,18 +299,17 @@ void recv_process (void) #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("recv_process: woke up\n"); -#endif - +#endif + + ok = dlq_remove (&type, &chan, &subchan, &slice, &pp, &alevel, &retries, spectrum, sizeof(spectrum)); - ok = dlq_remove (&type, &chan, &subchan, &pp, &alevel, &retries, spectrum, sizeof(spectrum)); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("recv_process: dlq_remove() returned ok=%d, type=%d, chan=%d, pp=%p\n", ok, (int)type, chan, pp); #endif if (ok) { - - app_process_rec_packet (chan, subchan, pp, alevel, retries, spectrum); + app_process_rec_packet (chan, subchan, slice, pp, alevel, retries, spectrum); } #if DEBUG else { diff --git a/redecode.c b/redecode.c index c54d435..7c779ad 100644 --- a/redecode.c +++ b/redecode.c @@ -98,6 +98,8 @@ static void * redecode_thread (void *arg); void redecode_init (struct audio_s *p_audio_config) { +#if 0 + #if __WIN32__ HANDLE redecode_th; #else @@ -150,7 +152,7 @@ void redecode_init (struct audio_s *p_audio_config) text_color_set(DW_COLOR_DEBUG); dw_printf ("redecode_init: finished \n"); #endif - +#endif } /* end redecode_init */ @@ -174,6 +176,8 @@ void redecode_init (struct audio_s *p_audio_config) * *--------------------------------------------------------------------*/ +#if 0 + #if __WIN32__ static unsigned redecode_thread (void *arg) #else @@ -241,7 +245,7 @@ static void * redecode_thread (void *arg) } /* end redecode_thread */ - +#endif diff --git a/rrbb.c b/rrbb.c index 2348898..5651de7 100644 --- a/rrbb.c +++ b/rrbb.c @@ -23,17 +23,14 @@ * File: rrbb.c * * Purpose: Raw Received Bit Buffer. - * Implementation of an array of bits used to hold data out of + * An array of bits used to hold data out of * the demodulator before feeding it into the HLDC decoding. * - * Version 1.0: Let's try something new. - * Rather than storing a single bit from the demodulator - * output, let's store a value which we can try later - * comparing to threshold values besides 0. - * * Version 1.2: Save initial state of 9600 baud descrambler so we can * attempt bit fix up on G3RUH/K9NG scrambled data. * + * Version 1.3: Store as bytes rather than packing 8 bits per byte. + * *******************************************************************************/ #define RRBB_C @@ -49,44 +46,9 @@ #include "rrbb.h" - - #define MAGIC1 0x12344321 #define MAGIC2 0x56788765 -static const unsigned int masks[SOI] = { - 0x00000001, - 0x00000002, - 0x00000004, - 0x00000008, - 0x00000010, - 0x00000020, - 0x00000040, - 0x00000080, - 0x00000100, - 0x00000200, - 0x00000400, - 0x00000800, - 0x00001000, - 0x00002000, - 0x00004000, - 0x00008000, - 0x00010000, - 0x00020000, - 0x00040000, - 0x00080000, - 0x00100000, - 0x00200000, - 0x00400000, - 0x00800000, - 0x01000000, - 0x02000000, - 0x04000000, - 0x08000000, - 0x10000000, - 0x20000000, - 0x40000000, - 0x80000000 }; static int new_count = 0; static int delete_count = 0; @@ -102,6 +64,8 @@ static int delete_count = 0; * * subchan - Which demodulator of the channel. * + * slice - multiple thresholds per demodulator. + * * is_scrambled - Is data scrambled? (true, false) * * descram_state - State of data descrambler. @@ -114,21 +78,20 @@ static int delete_count = 0; * ***********************************************************************************/ -rrbb_t rrbb_new (int chan, int subchan, int is_scrambled, int descram_state, int prev_descram) +rrbb_t rrbb_new (int chan, int subchan, int slice, int is_scrambled, int descram_state, int prev_descram) { rrbb_t result; - assert (SOI == 8 * sizeof(unsigned int)); - assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); - + assert (slice >= 0 && slice < MAX_SLICERS); result = malloc(sizeof(struct rrbb_s)); result->magic1 = MAGIC1; result->chan = chan; result->subchan = subchan; + result->slice = slice; result->magic2 = MAGIC2; new_count++; @@ -181,6 +144,7 @@ void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state, int prev_descram b->prev_descram = prev_descram; } + /*********************************************************************************** * * Name: rrbb_append_bit @@ -192,35 +156,7 @@ void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state, int prev_descram * ***********************************************************************************/ - -// TODO: Forget about bit packing and just use bytes. -// We have hundreds of MB. Why waste time to save a couple KB? - - -void rrbb_append_bit (rrbb_t b, int val) -{ - unsigned int di, mi; - - assert (b != NULL); - assert (b->magic1 == MAGIC1); - assert (b->magic2 == MAGIC2); - - if (b->len >= MAX_NUM_BITS) { - return; /* Silently discard if full. */ - } - - di = b->len / SOI; - mi = b->len % SOI; - - if (val) { - b->data[di] |= masks[mi]; - } - else { - b->data[di] &= ~ masks[mi]; - } - - b->len++; -} +/* Definition in header file so it can be inlined. */ /*********************************************************************************** @@ -279,46 +215,9 @@ int rrbb_get_len (rrbb_t b) * ***********************************************************************************/ -int rrbb_get_bit (rrbb_t b, unsigned int ind) -{ - assert (b != NULL); - assert (b->magic1 == MAGIC1); - assert (b->magic2 == MAGIC2); +/* Definition in header file so it can be inlined. */ - assert (ind < b->len); - if (b->data[ind / SOI] & masks[ind % SOI]) { - return 1; - } - else { - return 0; - } -} - -unsigned int rrbb_get_computed_bit (rrbb_t b, unsigned int ind) -{ - return b->computed_data[ind]; -} - -int rrbb_compute_bits (rrbb_t b) -{ - unsigned int i,val; - - assert (b != NULL); - assert (b->magic1 == MAGIC1); - assert (b->magic2 == MAGIC2); - - for (i=0;ilen;i++) { - if (b->data[i / SOI] & masks[i % SOI]) { - val = 1; - } - else { - val = 0; - } - b->computed_data[i] = val; - } - return 0; -} /*********************************************************************************** @@ -457,6 +356,28 @@ int rrbb_get_subchan (rrbb_t b) } +/*********************************************************************************** + * + * Name: rrbb_get_slice + * + * Purpose: Get slice number from which bit buffer was received. + * + * Inputs: b Handle for bit array. + * + ***********************************************************************************/ + +int rrbb_get_slice (rrbb_t b) +{ + assert (b != NULL); + assert (b->magic1 == MAGIC1); + assert (b->magic2 == MAGIC2); + + assert (b->slice >= 0 && b->slice < MAX_SLICERS); + + return (b->slice); +} + + /*********************************************************************************** * * Name: rrbb_set_audio_level diff --git a/rrbb.h b/rrbb.h index c24be07..8e94bd7 100644 --- a/rrbb.h +++ b/rrbb.h @@ -4,10 +4,11 @@ #define RRBB_H -typedef short slice_t; +#define FASTER13 1 // Don't pack 8 samples per byte. -#ifdef RRBB_C +//typedef short slice_t; + /* * Maximum size (in bytes) of an AX.25 frame including the 2 octet FCS. @@ -23,13 +24,14 @@ typedef short slice_t; #define MAX_NUM_BITS (MAX_FRAME_LEN * 8 * 6 / 5) -#define SOI 32 - typedef struct rrbb_s { int magic1; struct rrbb_s* nextp; /* Next pointer to maintain a queue. */ + int chan; /* Radio channel from which it was received. */ int subchan; /* Which modem when more than one per channel. */ + int slice; /* Which slicer. */ + alevel_t alevel; /* Received audio level at time of frame capture. */ unsigned int len; /* Current number of samples in array. */ @@ -37,38 +39,37 @@ typedef struct rrbb_s { int descram_state; /* Descrambler state before first data bit of frame. */ int prev_descram; /* Previous descrambled bit. */ - unsigned int data[(MAX_NUM_BITS+SOI-1)/SOI]; - unsigned int computed_data[MAX_NUM_BITS]; + unsigned char fdata[MAX_NUM_BITS]; int magic2; } *rrbb_t; -#else - -/* Hide the implementation. */ - -typedef void *rrbb_t; - -#endif - -rrbb_t rrbb_new (int chan, int subchan, int is_scrambled, int descram_state, int prev_descram); +rrbb_t rrbb_new (int chan, int subchan, int slice, int is_scrambled, int descram_state, int prev_descram); void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state, int prev_descram); -void rrbb_append_bit (rrbb_t b, int val); +static __attribute__((always_inline)) void rrbb_append_bit (rrbb_t b, const unsigned char val) +{ + if (b->len >= MAX_NUM_BITS) { + return; /* Silently discard if full. */ + } + b->fdata[b->len] = val; + b->len++; +} + +static __attribute__((always_inline)) unsigned char rrbb_get_bit (const rrbb_t b, const int ind) +{ + return (b->fdata[ind]); +} void rrbb_chop8 (rrbb_t b); int rrbb_get_len (rrbb_t b); -int rrbb_get_bit (rrbb_t b, unsigned int ind); -unsigned int rrbb_get_computed_bit (rrbb_t b, unsigned int ind); -int rrbb_compute_bits (rrbb_t b); - //void rrbb_flip_bit (rrbb_t b, unsigned int ind); void rrbb_delete (rrbb_t b); @@ -78,6 +79,7 @@ rrbb_t rrbb_get_nextp (rrbb_t b); int rrbb_get_chan (rrbb_t b); int rrbb_get_subchan (rrbb_t b); +int rrbb_get_slice (rrbb_t b); void rrbb_set_audio_level (rrbb_t b, alevel_t alevel); alevel_t rrbb_get_audio_level (rrbb_t b);