diff --git a/Makefile.linux b/Makefile.linux index ed8b418..d695f92 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -735,7 +735,7 @@ install-rpi : # Combine some unit tests into a single regression sanity check. -check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800 +check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem2400-g check-modem4800 # Can we encode and decode at popular data rates? @@ -753,14 +753,14 @@ check-modem300 : gen_packets atest check-modem9600 : gen_packets atest ./gen_packets -B9600 -n 100 -o /tmp/test96.wav - ./atest -B9600 -F0 -L50 -G54 /tmp/test96.wav - ./atest -B9600 -F1 -L55 -G59 /tmp/test96.wav + ./atest -B9600 -F0 -L61 -G65 /tmp/test96.wav + ./atest -B9600 -F1 -L62 -G66 /tmp/test96.wav rm /tmp/test96.wav check-modem19200 : gen_packets atest ./gen_packets -r 96000 -B19200 -n 100 -o /tmp/test19.wav - ./atest -B19200 -F0 -L55 -G59 /tmp/test19.wav - ./atest -B19200 -F1 -L60 -G64 /tmp/test19.wav + ./atest -B19200 -F0 -L60 -G64 /tmp/test19.wav + ./atest -B19200 -F1 -L64 -G68 /tmp/test19.wav rm /tmp/test19.wav check-modem2400 : gen_packets atest @@ -769,6 +769,11 @@ check-modem2400 : gen_packets atest ./atest -B2400 -F1 -L80 -G87 /tmp/test24.wav rm /tmp/test24.wav +check-modem2400-g : gen_packets atest + ./gen_packets -B2400 -g -n 100 -o /tmp/test24-g.wav + ./atest -B2400 -g -F0 -L99 -G100 /tmp/test24-g.wav + rm /tmp/test24-g.wav + check-modem4800 : gen_packets atest ./gen_packets -B2400 -n 100 -o /tmp/test48.wav ./atest -B2400 -F0 -L70 -G79 /tmp/test48.wav diff --git a/Makefile.win b/Makefile.win index 671d1a0..51f3519 100644 --- a/Makefile.win +++ b/Makefile.win @@ -16,7 +16,7 @@ # -all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest kissutil +all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest tnctest-issue-132 kissutil # People say we need -mthreads option for threads to work properly. @@ -197,13 +197,13 @@ gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o morse. $(CC) $(CFLAGS) -o $@ $^ -# Connected mode packet application server. -appserver : appserver.o textcolor.o ax25_pad.o fcs_calc.o misc.a +# Connected mode sample applications for talking to network TNC with AGW protocol. + +appserver : appserver.o agwlib.o dwsock.o textcolor.o dtime_now.o misc.a $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 - # ------------------------------------------- Libraries -------------------------------------------- @@ -277,7 +277,7 @@ strlcat.o : misc/strlcat.c # Combine some unit tests into a single regression sanity check. -check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800 +check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem2400-g check-modem4800 # Can we encode and decode at popular data rates? # Verify that single bit fixup increases the count. @@ -305,8 +305,8 @@ check-modem9600 : gen_packets atest sleep 1 gen_packets -B9600 -n 100 -o test96.wav sleep 1 - atest -B9600 -F0 -L50 -G54 test96.wav - atest -B9600 -F1 -L55 -G59 test96.wav + atest -B9600 -F0 -L61 -G65 test96.wav + atest -B9600 -F1 -L62 -G66 test96.wav sleep 1 rm test96.wav @@ -319,8 +319,8 @@ check-modem19200 : gen_packets atest sleep 1 gen_packets -r 96000 -B19200 -n 100 -o test19.wav sleep 1 - atest -B19200 -F0 -L55 -G59 test19.wav - atest -B19200 -F1 -L60 -G64 test19.wav + atest -B19200 -F0 -L60 -G64 test19.wav + atest -B19200 -F1 -L64 -G68 test19.wav sleep 1 rm test19.wav @@ -332,6 +332,13 @@ check-modem2400 : gen_packets atest sleep 1 rm test24.wav +check-modem2400-g : gen_packets atest + gen_packets -B2400 -g -n 100 -o test24-g.wav + sleep 1 + atest -B2400 -g -F0 -L99 -G100 test24-g.wav + sleep 1 + rm test24-g.wav + check-modem4800 : gen_packets atest gen_packets -B4800 -n 100 -o test48.wav sleep 1 @@ -467,6 +474,9 @@ dtmftest : dtmf.c textcolor.o tnctest : tnctest.c textcolor.o dtime_now.o serial_port.o misc.a $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 +tnctest-issue-132 : tnctest-issue-132.c textcolor.o dtime_now.o serial_port.o misc.a + $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 + # For tweaking the demodulator. @@ -481,7 +491,7 @@ testagc : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.o fsk_demod_ dwgpsnmea.o dwgps.o serial_port.o tt_text.o dtime_now.o regex.a misc.a rm -f atest.exe $(CC) $(CFLAGS) -o atest $^ - ./atest -P GGG- -F 0 ../02_Track_2.wav | grep "packets decoded in" >atest.out + ./atest -P H+ -F 0 ../01_Track_1.wav ../02_Track_2.wav | grep "packets decoded in" >atest.out echo " " > tune.h @@ -509,6 +519,8 @@ testagc96 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c de rm -f atest96.exe $(CC) $(CFLAGS) -o atest96 $^ ./atest96 -B 9600 ../walkabout9600c.wav | grep "packets decoded in" >atest.out + #./atest96 -B 9600 ../walkabout9600c.wav noisy96.wav zzz16.wav zzz16.wav zzz16.wav zzz8.wav zzz8.wav zzz8.wav | grep "packets decoded in" >atest.out + #./atest96 -B 9600 zzz16.wav zzz8.wav | grep "packets decoded in" >atest.out #./atest96 -B 9600 noisy96.wav | grep "packets decoded in" >atest.out #./atest96 -B 9600 19990303_0225_9600_8bis_22kHz.wav | grep "packets decoded in" >atest.out #./atest96 -B 9600 19990303_0225_9600_16bit_22kHz.wav | grep "packets decoded in" >atest.out @@ -561,10 +573,15 @@ aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a # Note: kiss_frame.c has conditional compilation on KISSUTIL. -kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o sock.o dtime_now.o misc.a regex.a +kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o dwsock.o dtime_now.o misc.a regex.a $(CC) $(CFLAGS) -DKISSUTIL -o $@ $^ -lwinmm -lws2_32 +mqtest : aprsmsg.c kiss_frame.c encode_aprs.o ax25_pad.o fcs_calc.o textcolor.o serial_port.o dwsock.o dtime_now.o latlong.o misc.a regex.a + $(CC) $(CFLAGS) -DMQTEST -DKISSUTIL -o $@ $^ -lwinmm -lws2_32 + + + # Touch Tone to Speech sample application. ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a @@ -585,6 +602,7 @@ walk96 : walk96.c dwgps.o dwgpsnmea.o kiss_frame.o \ + #-------------------------------------------------------------- diff --git a/atest.c b/atest.c index 9715712..67b8ed9 100644 --- a/atest.c +++ b/atest.c @@ -140,7 +140,10 @@ static int e_o_f; static int packets_decoded_one = 0; static int packets_decoded_total = 0; static int decimate = 0; /* Reduce that sampling rate if set. */ - /* 1 = normal, 2 = half, etc. */ + /* 1 = normal, 2 = half, 3 = 1/3, etc. */ + +static int upsample = 0; /* Upsample for G3RUH decoder. */ + /* Non-zero will override the default. */ static struct audio_s my_audio_config; @@ -175,6 +178,13 @@ static int sample_number = -1; /* Sample number from the file. */ /* Use to print timestamp, relative to beginning */ /* of file, when frame was decoded. */ +// command line options. + +static int B_opt = DEFAULT_BAUD; // Bits per second. Need to change all baud references to bps. +static int g_opt = 0; // G3RUH modem regardless of speed. +static int h_opt = 0; // Hexadecimal display of received packet. + + int main (int argc, char *argv[]) { @@ -276,6 +286,7 @@ int main (int argc, char *argv[]) //my_audio_config.achan[channel].passall = 1; } + while (1) { //int this_option_optind = optind ? optind : 1; int option_index = 0; @@ -288,7 +299,7 @@ int main (int argc, char *argv[]) /* ':' following option character means arg is required. */ - c = getopt_long(argc, argv, "B:P:D:F:L:G:012", + c = getopt_long(argc, argv, "B:P:D:U:gF:L:G:012h", long_options, &option_index); if (c == -1) break; @@ -296,63 +307,13 @@ int main (int argc, char *argv[]) switch (c) { case 'B': /* -B for data Bit rate */ - /* 300 implies 1600/1800 AFSK. */ - /* 1200 implies 1200/2200 AFSK. */ - /* 2400 implies V.26 */ - /* 9600 implies scrambled. */ + /* Also implies modem type based on speed. */ + B_opt = atoi(optarg); + break; - my_audio_config.achan[0].baud = atoi(optarg); + case 'g': /* -G Force G3RUH regardless of speed. */ - dw_printf ("Data rate set to %d bits / second.\n", my_audio_config.achan[0].baud); - - if (my_audio_config.achan[0].baud < MIN_BAUD || my_audio_config.achan[0].baud > MAX_BAUD) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); - exit (EXIT_FAILURE); - } - - /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ - /* that need to be kept in sync. Maybe it could be a common function someday. */ - - if (my_audio_config.achan[0].baud == 100) { - my_audio_config.achan[0].modem_type = MODEM_AFSK; - my_audio_config.achan[0].mark_freq = 1615; - my_audio_config.achan[0].space_freq = 1785; - strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles)); - } - else if (my_audio_config.achan[0].baud < 600) { - my_audio_config.achan[0].modem_type = MODEM_AFSK; - my_audio_config.achan[0].mark_freq = 1600; - my_audio_config.achan[0].space_freq = 1800; - strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles)); - } - else if (my_audio_config.achan[0].baud < 1800) { - my_audio_config.achan[0].modem_type = MODEM_AFSK; - my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ; - my_audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ; - // Should default to E+ or something similar later. - } - else if (my_audio_config.achan[0].baud < 3600) { - my_audio_config.achan[0].modem_type = MODEM_QPSK; - my_audio_config.achan[0].mark_freq = 0; - my_audio_config.achan[0].space_freq = 0; - strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); - dw_printf ("Using V.26 QPSK rather than AFSK.\n"); - } - else if (my_audio_config.achan[0].baud < 7200) { - my_audio_config.achan[0].modem_type = MODEM_8PSK; - my_audio_config.achan[0].mark_freq = 0; - my_audio_config.achan[0].space_freq = 0; - strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); - dw_printf ("Using V.27 8PSK rather than AFSK.\n"); - } - else { - my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE; - my_audio_config.achan[0].mark_freq = 0; - my_audio_config.achan[0].space_freq = 0; - strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later. - dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); - } + g_opt = 1; break; case 'P': /* -P for modem profile. */ @@ -375,6 +336,23 @@ int main (int argc, char *argv[]) my_audio_config.achan[0].decimate = decimate; break; + case 'U': /* -U upsample for G3RUH to improve performance */ + /* when the sample rate to baud ratio is low. */ + /* Actually it is set automatically and this will */ + /* override the normal calculation. */ + + upsample = atoi(optarg); + + dw_printf ("Multiply audio sample rate by %d\n", upsample); + if (upsample < 1 || upsample > 4) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Unreasonable value for -U.\n"); + exit (EXIT_FAILURE); + } + dw_printf ("Multiply audio sample rate by %d\n", upsample); + my_audio_config.achan[0].upsample = upsample; + break; + case 'F': /* -D set "fix bits" level. */ my_audio_config.achan[0].fix_bits = atoi(optarg); @@ -411,6 +389,11 @@ int main (int argc, char *argv[]) decode_only = 2; break; + case 'h': /* Hexadecimal display. */ + + h_opt = 1; + break; + case '?': /* Unknown option message was already printed. */ @@ -426,6 +409,75 @@ int main (int argc, char *argv[]) } } +/* + * Set modem type based on data rate. + * (Could be overridden by -g later.) + */ + /* 300 implies 1600/1800 AFSK. */ + /* 1200 implies 1200/2200 AFSK. */ + /* 2400 implies V.26 QPSK. */ + /* 4800 implies V.27 8PSK. */ + /* 9600 implies G3RUH baseband scrambled. */ + + my_audio_config.achan[0].baud = B_opt; + + if (my_audio_config.achan[0].baud < MIN_BAUD || my_audio_config.achan[0].baud > MAX_BAUD) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); + exit (EXIT_FAILURE); + } + + /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ + /* that need to be kept in sync. Maybe it could be a common function someday. */ + + if (my_audio_config.achan[0].baud == 100) { + my_audio_config.achan[0].modem_type = MODEM_AFSK; + my_audio_config.achan[0].mark_freq = 1615; + my_audio_config.achan[0].space_freq = 1785; + strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles)); + } + else if (my_audio_config.achan[0].baud < 600) { + my_audio_config.achan[0].modem_type = MODEM_AFSK; + my_audio_config.achan[0].mark_freq = 1600; + my_audio_config.achan[0].space_freq = 1800; + strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles)); + } + else if (my_audio_config.achan[0].baud < 1800) { + my_audio_config.achan[0].modem_type = MODEM_AFSK; + my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ; + my_audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ; + // Should default to E+ or something similar later. + } + else if (my_audio_config.achan[0].baud < 3600) { + my_audio_config.achan[0].modem_type = MODEM_QPSK; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); + } + else if (my_audio_config.achan[0].baud < 7200) { + my_audio_config.achan[0].modem_type = MODEM_8PSK; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); + } + else { + my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later. + } + +/* + * -g option means force g3RUH regardless of speed. + */ + + if (g_opt) { + my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later. + } + memcpy (&my_audio_config.achan[1], &my_audio_config.achan[0], sizeof(my_audio_config.achan[0])); @@ -763,6 +815,21 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev ax25_safe_print ((char *)pinfo, info_len, 0); dw_printf ("\n"); +/* + * -h option for hexadecimal display. (new in 1.6) + */ + + if (h_opt) { + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("------\n"); + ax25_hex_dump (pp); + dw_printf ("------\n"); + } + + + + #if 1 // temp experiment TODO: remove this. #include "decode_aprs.h" @@ -814,8 +881,12 @@ static void usage (void) { dw_printf (" 1200 (default) baud uses 1200/2200 Hz AFSK.\n"); dw_printf (" 9600 baud uses K9NG/G2RUH standard.\n"); dw_printf ("\n"); + dw_printf (" -g Force G3RUH modem rather rather than default for data rate.\n"); + dw_printf ("\n"); dw_printf (" -D n Divide audio sample rate by n.\n"); dw_printf ("\n"); + dw_printf (" -h Print frame contents as hexadecimal bytes.\n"); + dw_printf ("\n"); dw_printf (" -F n Amount of effort to try fixing frames with an invalid CRC. \n"); dw_printf (" 0 (default) = consider only correct frames. \n"); dw_printf (" 1 = Try to fix only a single bit. \n"); diff --git a/audio.h b/audio.h index 9ff86c5..7bdf374 100644 --- a/audio.h +++ b/audio.h @@ -138,6 +138,8 @@ struct audio_s { int decimate; /* Reduce AFSK sample rate by this factor to */ /* decrease computational requirements. */ + int upsample; /* Upsample by this factor for G3RUH. */ + int interleave; /* If > 1, interleave samples among multiple decoders. */ /* Quick hack for experiment. */ diff --git a/ax25_pad.c b/ax25_pad.c index 23e061b..e584d0b 100644 --- a/ax25_pad.c +++ b/ax25_pad.c @@ -2308,16 +2308,30 @@ void ax25_hex_dump (packet_t this_p) dw_printf ("%s\n", cp_text); } + // Address fields must be only upper case letters and digits. + // If less than 6 characters, trailing positions are filled with ASCII space. + // Using all zero bits in one of these 6 positions is wrong. + // Any non printable characters will be printed as "." here. - dw_printf (" dest %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n", - fptr[0]>>1, fptr[1]>>1, fptr[2]>>1, fptr[3]>>1, fptr[4]>>1, fptr[5]>>1, + dw_printf (" dest %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n", + isprint(fptr[0]>>1) ? fptr[0]>>1 : '.', + isprint(fptr[1]>>1) ? fptr[1]>>1 : '.', + isprint(fptr[2]>>1) ? fptr[2]>>1 : '.', + isprint(fptr[3]>>1) ? fptr[3]>>1 : '.', + isprint(fptr[4]>>1) ? fptr[4]>>1 : '.', + isprint(fptr[5]>>1) ? fptr[5]>>1 : '.', (fptr[6]&SSID_SSID_MASK)>>SSID_SSID_SHIFT, (fptr[6]&SSID_H_MASK)>>SSID_H_SHIFT, (fptr[6]&SSID_RR_MASK)>>SSID_RR_SHIFT, fptr[6]&SSID_LAST_MASK); dw_printf (" source %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n", - fptr[7]>>1, fptr[8]>>1, fptr[9]>>1, fptr[10]>>1, fptr[11]>>1, fptr[12]>>1, + isprint(fptr[7]>>1) ? fptr[7]>>1 : '.', + isprint(fptr[8]>>1) ? fptr[8]>>1 : '.', + isprint(fptr[9]>>1) ? fptr[9]>>1 : '.', + isprint(fptr[10]>>1) ? fptr[10]>>1 : '.', + isprint(fptr[11]>>1) ? fptr[11]>>1 : '.', + isprint(fptr[12]>>1) ? fptr[12]>>1 : '.', (fptr[13]&SSID_SSID_MASK)>>SSID_SSID_SHIFT, (fptr[13]&SSID_H_MASK)>>SSID_H_SHIFT, (fptr[13]&SSID_RR_MASK)>>SSID_RR_SHIFT, @@ -2327,7 +2341,12 @@ void ax25_hex_dump (packet_t this_p) dw_printf (" digi %d %c%c%c%c%c%c %2d h=%d res=%d last=%d\n", n - 1, - fptr[n*7+0]>>1, fptr[n*7+1]>>1, fptr[n*7+2]>>1, fptr[n*7+3]>>1, fptr[n*7+4]>>1, fptr[n*7+5]>>1, + isprint(fptr[n*7+0]>>1) ? fptr[n*7+0]>>1 : '.', + isprint(fptr[n*7+1]>>1) ? fptr[n*7+1]>>1 : '.', + isprint(fptr[n*7+2]>>1) ? fptr[n*7+2]>>1 : '.', + isprint(fptr[n*7+3]>>1) ? fptr[n*7+3]>>1 : '.', + isprint(fptr[n*7+4]>>1) ? fptr[n*7+4]>>1 : '.', + isprint(fptr[n*7+5]>>1) ? fptr[n*7+5]>>1 : '.', (fptr[n*7+6]&SSID_SSID_MASK)>>SSID_SSID_SHIFT, (fptr[n*7+6]&SSID_H_MASK)>>SSID_H_SHIFT, (fptr[n*7+6]&SSID_RR_MASK)>>SSID_RR_SHIFT, diff --git a/config.c b/config.c index b5dab4e..12013da 100644 --- a/config.c +++ b/config.c @@ -1261,7 +1261,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, * Options: * mark:space - AFSK tones. Defaults based on speed. * num@offset - Multiple decoders on different frequencies. - * + * /9 - Divide sample rate by specified number. + * *9 - Upsample ratio for G3RUH. + * [A-Z+-]+ - Letters, plus, minus for the demodulator "profile." + * g3ruh - This modem type regardless of default for speed. */ else if (strcasecmp(t, "MODEM") == 0) { @@ -1320,7 +1323,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_audio_config->achan[channel].space_freq = 0; } - /* Get mark frequency. */ + /* Get any options. */ t = split(NULL,0); if (t == NULL) { @@ -1332,6 +1335,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* old style */ + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Old style (pre version 1.2) format will no longer be supported in next version.\n", line); + n = atoi(t); /* Originally the upper limit was 3000. */ /* Version 1.0 increased to 5000 because someone */ @@ -1516,6 +1522,25 @@ void config_init (char *fname, struct audio_s *p_audio_config, } } + else if (*t == '*') { /* *upsample */ + int n = atoi(t+1); + + if (n >= 1 && n <= 4) { + p_audio_config->achan[channel].upsample = n; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Ignoring unreasonable upsample ratio of %d.\n", line, n); + } + } + + else if (strcasecmp(t, "G3RUH") == 0) { /* Force G3RUH modem regardless of default for speed. New in 1.6. */ + + p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; + p_audio_config->achan[channel].mark_freq = 0; + p_audio_config->achan[channel].space_freq = 0; + } + else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unrecognized option for MODEM: %s\n", line, t); diff --git a/decode_aprs.c b/decode_aprs.c index 3b7f6c8..7a8831c 100644 --- a/decode_aprs.c +++ b/decode_aprs.c @@ -219,8 +219,8 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) if ( ( ! A->g_quiet ) && ( (int)strlen((char*)pinfo) != info_len) ) { text_color_set(DW_COLOR_ERROR); - dw_printf("'nul' character found in Information part. This should never happen.\n"); - dw_printf("It seems that %s is transmitting with defective software.\n", A->g_src); + dw_printf("'nul' character found in Information part. This should never happen with APRS.\n"); + dw_printf("If this is meant to be APRS, %s is transmitting with defective software.\n", A->g_src); if (strcmp((char*)pinfo, "4P") == 0) { dw_printf("The TM-D710 will do this intermittently. A firmware upgrade is needed to fix it.\n"); diff --git a/demod.c b/demod.c index 3953622..c945b49 100644 --- a/demod.c +++ b/demod.c @@ -63,7 +63,7 @@ static struct audio_s *save_audio_config_p; // TODO: temp experiment. -static int upsample = 2; // temp experiment. + static int zerostuff = 1; // temp experiment. // Current state of all the decoders. @@ -648,20 +648,70 @@ int demod_init (struct audio_s *pa) //#endif } -#ifdef TUNE_UPSAMPLE - upsample = TUNE_UPSAMPLE; -#endif #ifdef TUNE_ZEROSTUFF zerostuff = TUNE_ZEROSTUFF; #endif + +/* + * We need a minimum number of audio samples per bit time for good performance. + * Easier to check here because demod_9600_init might have an adjusted sample rate. + */ + + float ratio = (float)(save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec) + / (float)(save_audio_config_p->achan[chan].baud); + +/* + * Set reasonable upsample ratio if user did not override. + */ + + if (save_audio_config_p->achan[chan].upsample == 0) { + + if (ratio < 5) { + + // example: 44100 / 9600 is 4.59 + // Big improvement with x2. + // x4 seems to work the best. + // The other parameters are not as touchy. + // Might reduce on ARM if it takes too much CPU power. + + save_audio_config_p->achan[chan].upsample = 4; + } + else if (ratio < 10) { + + // 48000 / 9600 is 5.00 + // Need more reasearch. Treat like above for now. + + save_audio_config_p->achan[chan].upsample = 4; + } + else if (ratio < 15) { + + // ... + + save_audio_config_p->achan[chan].upsample = 2; + } + else { // >= 15 + // + // An example of this might be ..... + // Probably no benefit. + + save_audio_config_p->achan[chan].upsample = 1; + } + } + +#ifdef TUNE_UPSAMPLE + save_audio_config_p->achan[chan].upsample = TUNE_UPSAMPLE; +#endif + text_color_set(DW_COLOR_DEBUG); dw_printf ("Channel %d: %d baud, K9NG/G3RUH, %s, %d sample rate x %d", - chan, save_audio_config_p->achan[chan].baud, + chan, + save_audio_config_p->achan[chan].baud, save_audio_config_p->achan[chan].profiles, - save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, upsample); + save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, + save_audio_config_p->achan[chan].upsample); if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) dw_printf (", DTMF decoder enabled"); dw_printf (".\n"); @@ -669,6 +719,7 @@ int demod_init (struct audio_s *pa) struct demodulator_state_s *D; D = &demodulator_state[chan][0]; // first subchannel + save_audio_config_p->achan[chan].num_subchan = 1; save_audio_config_p->achan[chan].num_slicers = 1; @@ -681,12 +732,6 @@ int demod_init (struct audio_s *pa) } - /* We need a minimum number of audio samples per bit time for good performance. */ - /* Easier to check here because demod_9600_init might have an adjusted sample rate. */ - - float ratio = (float)(save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec) - / (float)(save_audio_config_p->achan[chan].baud); - text_color_set(DW_COLOR_INFO); dw_printf ("The ratio of audio samples per sec (%d) to data rate in baud (%d) is %.1f\n", save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, @@ -698,6 +743,9 @@ int demod_init (struct audio_s *pa) } else if (ratio < 5) { dw_printf ("This is on the low side for best performance. Can you use a higher sample rate?\n"); + if (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec == 44100) { + dw_printf ("For example, can you use 48000 rather than 44100?\n"); + } } else if (ratio < 6) { dw_printf ("Increasing the sample rate should improve decoder performance.\n"); @@ -709,7 +757,7 @@ int demod_init (struct audio_s *pa) dw_printf ("This is a suitable ratio for good performance.\n"); } - demod_9600_init (upsample * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, D); + demod_9600_init (save_audio_config_p->achan[chan].upsample * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, D); if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) { @@ -942,17 +990,17 @@ void demod_process_sample (int chan, int subchan, int sam) /* So far, both are same in tests with different */ /* optimal low pass filter parameters. */ - for (k=1; kachan[chan].upsample; k++) { demod_9600_process_sample (chan, 0, D); } - demod_9600_process_sample (chan, sam * upsample, D); + demod_9600_process_sample (chan, sam * save_audio_config_p->achan[chan].upsample, D); } else { /* Linear interpolation. */ static int prev_sam; - switch (upsample) { + switch (save_audio_config_p->achan[chan].upsample) { case 1: demod_9600_process_sample (chan, sam, D); break; diff --git a/demod_9600.c b/demod_9600.c index e94f8aa..e6e97cd 100644 --- a/demod_9600.c +++ b/demod_9600.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2015 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2015, 2019 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 @@ -141,23 +141,43 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s // case 'K': // upsample x3 with filtering. // case 'L': // upsample x4 with filtering. - D->lp_filter_len_bits = 76 * 9600.0 / (44100.0 * 2.0); + + D->lp_filter_len_bits = 1.0; // Works best with odd number in some tests. Even is better in others. //D->lp_filter_size = ((int) (0.5f * ( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud ))) * 2 + 1; + D->lp_filter_size = (int) (( D->lp_filter_len_bits * (float)samples_per_sec / baud) + 0.5f); - D->lp_window = BP_WINDOW_HAMMING; - D->lpf_baud = 0.62; + D->lp_window = BP_WINDOW_COSINE; + + D->lpf_baud = 1.00; D->agc_fast_attack = 0.080; D->agc_slow_decay = 0.00012; D->pll_locked_inertia = 0.89; D->pll_searching_inertia = 0.67; + + D->play_it_again_sample = 0; // TODO: 1.6 experiment. + // assuming lp_filter_size > lp2_filter_size + + D->lp2_filter_size = samples_per_sec / baud; // samples for 1 bit + + // break; // } +#if 0 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("---------- %s (%d, %d) -----------\n", __func__, samples_per_sec, baud); + dw_printf ("filter_len_bits = %.2f\n", D->lp_filter_len_bits); + dw_printf ("lp_filter_size = %d\n", D->lp_filter_size); + dw_printf ("lp_window = %d\n", D->lp_window); + dw_printf ("lpf_baud = %.2f\n", D->lpf_baud); + dw_printf ("samples per bit = %.1f\n", (double)samples_per_sec / baud); +#endif + D->pll_step_per_sample = (int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec); @@ -194,7 +214,13 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s //dw_printf ("demod_9600_init: call gen_lowpass(fc=%.2f, , size=%d, )\n", fc, D->lp_filter_size); - gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window); + (void)gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window, 0); + +// Go back and resample where bit is expected. + + fc = (float)baud * 1 / (float)samples_per_sec; + + (void)gen_lowpass (fc, D->lp2_filter, D->lp2_filter_size, D->lp_window, 0); /* Version 1.2: Experiment with different slicing levels. */ @@ -481,15 +507,14 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D * * Results??? TBD * + * Version 1.6: New experiment where filter size to extract clock is not the same + * as filter to extract the data bit value. + * *--------------------------------------------------------------------*/ __attribute__((hot)) inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_f, struct demodulator_state_s *D) { - -/* - */ - D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; // Perform the add as unsigned to avoid signed overflow error. @@ -499,7 +524,31 @@ inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_ /* Overflow. Was large positive, wrapped around, now large negative. */ - hdlc_rec_bit (chan, subchan, slice, demod_out_f > 0, 1, D->slicer[slice].lfsr); + + if (D->play_it_again_sample) { // New experiment in 1.6. + +// FIXME: double check position and draw picture. + + int offset = ( D->lp_filter_size - D->lp2_filter_size ) / 2; + + float amp = convolve (D->raw_cb + offset, D->lp2_filter, D->lp2_filter_size); + + int resampled; + + if (D->num_slicers > 1) { + resampled = amp - slice_point[slice] > 0;; + } + else { + resampled = amp > 0; + } + + hdlc_rec_bit (chan, subchan, slice, resampled, 1, D->slicer[slice].lfsr); + } + else { + +// traditional + hdlc_rec_bit (chan, subchan, slice, demod_out_f > 0, 1, D->slicer[slice].lfsr); + } } /* diff --git a/demod_afsk.c b/demod_afsk.c index 80bd40e..59afbfa 100644 --- a/demod_afsk.c +++ b/demod_afsk.c @@ -382,6 +382,45 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, D->pll_searching_inertia = 0.64; break; + case 'H': + + /* Experiment in Version 1.6 */ + /* 1200 baud - Started out as a copy of E but */ + /* will probably have little tweaks after the */ + /* major experiment. */ + /* Enhancements: */ + /* + Look back and sample the bit position. */ + /* + Avoid smearing by long filter and low pass. */ + + D->use_prefilter = 1; /* first, a bandpass filter. */ + D->prefilter_baud = 0.21; + D->pre_filter_len_bits = 184 * 1200. / 44100.; + D->pre_filter_len_bits = 235 * 1200. / 44100.; + D->pre_window = BP_WINDOW_TRUNCATED; + + D->ms_filter_len_bits = 65 * 1200. / 44100.; // Just over 2 bit times. + D->ms_window = BP_WINDOW_COSINE; + + /* New for synchronous re-demod in 1.6. */ + D->play_it_again_sample = 1; + D->m2_filter_len_bits = 44 * 1200. / 44100.; // a little more than 1 bit time. + D->s2_filter_len_bits = 48 * 1200. / 44100.; // a little more than 1 bit time. + D->ms2_window = BP_WINDOW_TRUNCATED; + D->lp_delay_fract = 0.53; // FIXME: This is backwards. + + D->lpf_use_fir = 1; + D->lpf_baud = 1.10; + D->lp_filter_len_bits = 64 * 1200. / 44100.; + D->lp_window = BP_WINDOW_TRUNCATED; + + D->agc_fast_attack = 0.820; + D->agc_slow_decay = 0.000214; + D->hysteresis = 0.001; + + D->pll_locked_inertia = 0.765; + D->pll_searching_inertia = 0.44; + break; + default: text_color_set(DW_COLOR_ERROR); @@ -395,6 +434,9 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, #ifdef TUNE_MS_WINDOW D->ms_window = TUNE_MS_WINDOW; #endif +#ifdef TUNE_MS2_WINDOW + D->ms2_window = TUNE_MS2_WINDOW; +#endif #ifdef TUNE_LP_WINDOW D->lp_window = TUNE_LP_WINDOW; #endif @@ -417,7 +459,9 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, #ifdef TUNE_PRE_BAUD D->prefilter_baud = TUNE_PRE_BAUD; #endif - +#ifdef TUNE_LP_DELAY_FRACT + D->lp_delay_fract = TUNE_LP_DELAY_FRACT; +#endif /* * Calculate constants used for timing. @@ -432,6 +476,8 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, D->pre_filter_size = (int) round( D->pre_filter_len_bits * (float)samples_per_sec / (float)baud ); D->ms_filter_size = (int) round( D->ms_filter_len_bits * (float)samples_per_sec / (float)baud ); + D->m2_filter_size = (int) round( D->m2_filter_len_bits * (float)samples_per_sec / (float)baud ); + D->s2_filter_size = (int) round( D->s2_filter_len_bits * (float)samples_per_sec / (float)baud ); D->lp_filter_size = (int) round( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud ); /* Experiment with other sizes. */ @@ -442,6 +488,12 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, #ifdef TUNE_MS_FILTER_SIZE D->ms_filter_size = TUNE_MS_FILTER_SIZE; #endif +#ifdef TUNE_M2_FILTER_SIZE + D->m2_filter_size = TUNE_M2_FILTER_SIZE; +#endif +#ifdef TUNE_S2_FILTER_SIZE + D->s2_filter_size = TUNE_S2_FILTER_SIZE; +#endif #ifdef TUNE_LP_FILTER_SIZE D->lp_filter_size = TUNE_LP_FILTER_SIZE; #endif @@ -450,7 +502,7 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, assert (D->ms_filter_size >= 4); //assert (D->lp_filter_size >= 4); - if (D->pre_filter_size > MAX_FILTER_SIZE) + if (D->pre_filter_size > MAX_FILTER_SIZE) { text_color_set (DW_COLOR_ERROR); dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size); @@ -460,7 +512,7 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, exit (1); } - if (D->ms_filter_size > MAX_FILTER_SIZE) + 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); @@ -470,6 +522,25 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, exit (1); } + if (D->m2_filter_size * 2 > MAX_FILTER_SIZE) + { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Calculated filter size of %d is too large.\n", D->m2_filter_size); + dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); + dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", + MAX_FILTER_SIZE); + exit (1); + } + if (D->s2_filter_size * 2 > MAX_FILTER_SIZE) + { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Calculated filter size of %d is too large.\n", D->s2_filter_size); + dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); + dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", + MAX_FILTER_SIZE); + exit (1); + } + if (D->lp_filter_size > MAX_FILTER_SIZE) { text_color_set (DW_COLOR_ERROR); @@ -502,10 +573,6 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, f1 = f1 / (float)samples_per_sec; f2 = f2 / (float)samples_per_sec; - //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_HAMMING); - //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_BLACKMAN); - //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_COSINE); - //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->bp_window); gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->pre_window); } @@ -525,46 +592,8 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, dw_printf (" j shape M sin M cos \n"); #endif - float Gs = 0, Gc = 0; - - for (j=0; jms_filter_size; j++) { - float am; - float center; - float shape = 1.0f; /* 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.5f * (D->ms_filter_size - 1); - am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2.0f * (float)M_PI); - - shape = window (D->ms_window, D->ms_filter_size, j); - - D->m_sin_table[j] = sinf(am) * shape; - D->m_cos_table[j] = cosf(am) * shape; - - Gs += D->m_sin_table[j] * sinf(am); - Gc += D->m_cos_table[j] * cosf(am); - -#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 - } - - -/* Normalize for unity gain */ - -#if DEBUG1 - dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ; -#endif - for (j=0; jms_filter_size; j++) { - D->m_sin_table[j] = D->m_sin_table[j] / Gs; - D->m_cos_table[j] = D->m_cos_table[j] / Gc; - } + gen_ms (mark_freq, samples_per_sec, D->m_sin_table, D->m_cos_table, D->ms_filter_size, D->ms_window); #if DEBUG1 text_color_set(DW_COLOR_DEBUG); @@ -572,39 +601,14 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, dw_printf ("Space\n"); dw_printf (" j shape S sin S cos\n"); #endif - Gs = 0; - Gc = 0; - for (j=0; jms_filter_size; j++) { - float as; - float center; - float shape = 1.0f; + gen_ms (space_freq, samples_per_sec, D->s_sin_table, D->s_cos_table, D->ms_filter_size, D->ms_window); - center = 0.5 * (D->ms_filter_size - 1); - as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2.0f * (float)M_PI); + // Note that these are twice as long so we can try matching different phases. - shape = window (D->ms_window, D->ms_filter_size, j); - - D->s_sin_table[j] = sinf(as) * shape; - D->s_cos_table[j] = cosf(as) * shape; - - Gs += D->s_sin_table[j] * sinf(as); - Gc += D->s_cos_table[j] * cosf(as); - -#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 - } - - -/* Normalize for unity gain */ - -#if DEBUG1 - dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ; -#endif - for (j=0; jms_filter_size; j++) { - D->s_sin_table[j] = D->s_sin_table[j] / Gs; - D->s_cos_table[j] = D->s_cos_table[j] / Gc; + if (D->play_it_again_sample) { + gen_ms (mark_freq, samples_per_sec, D->m2_sin_table, D->m2_cos_table, D->m2_filter_size * 2, D->ms2_window); + gen_ms (space_freq, samples_per_sec, D->s2_sin_table, D->s2_cos_table, D->s2_filter_size * 2, D->ms2_window); } /* @@ -616,7 +620,11 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, 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, D->lp_window); + D->lp_filter_delay = gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window, D->lp_delay_fract); + } + else { + // D->lp_filter_delay = + // Only needed for looking back and I don't expect to use IIR in that case. } /* @@ -853,15 +861,22 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat * Optional bandpass filter before the mark/space discriminator. */ +// FIXME: calculate how much we really need. + + int extra = 0; + if (D->play_it_again_sample) { + extra = D->lp_filter_size; + } + if (D->use_prefilter) { float cleaner; push_sample (fsam, D->raw_cb, D->pre_filter_size); cleaner = convolve (D->raw_cb, D->pre_filter, D->pre_filter_size); - push_sample (cleaner, D->ms_in_cb, D->ms_filter_size); + push_sample (cleaner, D->ms_in_cb, D->ms_filter_size + extra); } else { - push_sample (fsam, D->ms_in_cb, D->ms_filter_size); + push_sample (fsam, D->ms_in_cb, D->ms_filter_size + extra); } /* @@ -978,7 +993,7 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat * Here is an excellent explanation: * http://www.febo.com/packet/layer-one/transmit.html * - * Under real conditions, we find that the higher tone has a + * Under real conditions, we find that the higher tone usually has a * considerably smaller amplitude due to the passband characteristics * of the transmitter and receiver. To make matters worse, it * varies considerably from one station to another. @@ -1133,9 +1148,80 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, /* Overflow. */ - hdlc_rec_bit (chan, subchan, slice, demod_data, 0, -1); +// In version 1.6 we will try a new experiment. +// The tone filters are about 2 bit times wide so this smears the signal. +// I originally expected this to be about 1 bit time but 2 turned out +// to give the best results after extensive experimentation. +// We are looking at half of each adjacent bit, not just the one we want. +// The low pass filter also smears the signal. +// +// We will try to look back in time and re-demodulate only the time period of the bit. +// + + if (D->play_it_again_sample) { // New in 1.6. Currently 'H' demod profile. + +// FIXME: double check position and draw picture. + +#if 0 + // This provided a slight benefit. + + int offset = ( D->ms_filter_size - D->m2_filter_size ) / 2 + D->lp_filter_delay; + + float m_sum1 = convolve (D->ms_in_cb + offset, D->m2_sin_table, D->m2_filter_size); + float m_sum2 = convolve (D->ms_in_cb + offset, D->m2_cos_table, D->m2_filter_size); + float m_amp = sqrtf(m_sum1 * m_sum1 + m_sum2 * m_sum2); + + offset = ( D->ms_filter_size - D->s2_filter_size ) / 2 + D->lp_filter_delay; + + float s_sum1 = convolve (D->ms_in_cb + offset, D->s2_sin_table, D->s2_filter_size); + float s_sum2 = convolve (D->ms_in_cb + offset, D->s2_cos_table, D->s2_filter_size); + float s_amp = sqrtf(s_sum1 * s_sum1 + s_sum2 * s_sum2); +#else + // Here we try something completely new. + // Rather than taking the vector magnitude of the I+Q components, + // correlate it with only a sine wave. We don't know the phase so + // we will try matching with a bunch of different phases and take the best match. + + // Trying match with cosine as well could be beneficial for lower sample rates. + + int j; + float m_amp = 0; + float s_amp = 0; + + int offset = ( D->ms_filter_size - D->m2_filter_size ) / 2 + D->lp_filter_delay; + + for (j = 0; j <= D->m2_filter_size; j++) { + float match = fabsf(convolve (D->ms_in_cb + offset, D->m2_sin_table + j, D->m2_filter_size)); + if (match > m_amp) m_amp = match; + } + + offset = ( D->ms_filter_size - D->s2_filter_size ) / 2 + D->lp_filter_delay; + + for (j = 0; j <= D->s2_filter_size; j++) { + float match = fabsf(convolve (D->ms_in_cb + offset, D->s2_sin_table + j, D->s2_filter_size)); + if (match > s_amp) s_amp = match; + } +#endif + + int resampled; + if (D->num_slicers > 1) { + resampled = m_amp > s_amp * space_gain[slice]; + } + else { + resampled = m_amp > s_amp; + } + + hdlc_rec_bit (chan, subchan, slice, resampled, 0, -1); + } + else { + // Traditional way, after the low pass filter. + hdlc_rec_bit (chan, subchan, slice, demod_data, 0, -1); + } } + // Even if we used alternative method to extract the data bit, + // we still use the low pass output for the PLL. + if (demod_data != D->slicer[slice].prev_demod_data) { if (hdlc_rec_gathering (chan, subchan, slice)) { diff --git a/demod_psk.c b/demod_psk.c index ee660ef..3b2a487 100644 --- a/demod_psk.c +++ b/demod_psk.c @@ -510,7 +510,7 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char */ float fc = correct_baud * D->lpf_baud / (float)samples_per_sec; - gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window); + (void)gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window, 0); /* * No point in having multiple numbers for signal level. diff --git a/direwolf.c b/direwolf.c index d920957..f02f624 100644 --- a/direwolf.c +++ b/direwolf.c @@ -190,7 +190,7 @@ int main (int argc, char *argv[]) struct digi_config_s digi_config; struct cdigi_config_s cdigi_config; struct igate_config_s igate_config; - int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0; /* Command line options. */ + int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0, U_opt = 0; /* Command line options. */ char P_opt[16]; char l_opt_logdir[80]; char L_opt_logfile[80]; @@ -199,6 +199,7 @@ int main (int argc, char *argv[]) int t_opt = 1; /* Text color option. */ int a_opt = 0; /* "-a n" interval, in seconds, for audio statistics report. 0 for none. */ + int g_opt = 0; /* G3RUH mode, ignoring default for speed. */ int d_k_opt = 0; /* "-d k" option for serial port KISS. Can be repeated for more detail. */ int d_n_opt = 0; /* "-d n" option for Network KISS. Can be repeated for more detail. */ @@ -263,7 +264,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 4\n", MAJOR_VERSION, MINOR_VERSION, __DATE__); - dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "A", __DATE__); + dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "B", __DATE__); //dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); @@ -352,7 +353,7 @@ int main (int argc, char *argv[]) /* ':' following option character means arg is required. */ - c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:L:Sa:E:T:", + c = getopt_long(argc, argv, "P:B:gD:U:c:pxr:b:n:d:q:t:ul:L:Sa:E:T:", long_options, &option_index); if (c == -1) break; @@ -404,13 +405,18 @@ int main (int argc, char *argv[]) } break; + case 'g': /* -g G3RUH modem, overriding default mode for speed. */ + + g_opt = 1; + break; + case 'P': /* -P for modem profile. */ //debug: dw_printf ("Demodulator profile set to \"%s\"\n", optarg); strlcpy (P_opt, optarg, sizeof(P_opt)); break; - case 'D': /* -D decrease AFSK demodulator sample rate */ + case 'D': /* -D divide AFSK demodulator sample rate */ D_opt = atoi(optarg); if (D_opt < 1 || D_opt > 8) { @@ -420,6 +426,16 @@ int main (int argc, char *argv[]) } break; + case 'U': /* -U multiply G3RUH demodulator sample rate (upsample) */ + + U_opt = atoi(optarg); + if (U_opt < 1 || U_opt > 4) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Crazy value for -U. \n"); + exit (EXIT_FAILURE); + } + break; + case 'x': /* -x for transmit calibration tones. */ xmit_calibrate_option = 1; @@ -518,7 +534,7 @@ int main (int argc, char *argv[]) break; - case 'U': /* Print UTF-8 test and exit. */ + case 'u': /* Print UTF-8 test and exit. */ dw_printf ("\n UTF-8 test string: ma%c%cana %c%c F%c%c%c%ce\n\n", 0xc3, 0xb1, @@ -610,15 +626,18 @@ int main (int argc, char *argv[]) if (r_opt != 0) { audio_config.adev[0].samples_per_sec = r_opt; } + if (n_opt != 0) { audio_config.adev[0].num_channels = n_opt; if (n_opt == 2) { audio_config.achan[1].valid = 1; } } + if (b_opt != 0) { audio_config.adev[0].bits_per_sample = b_opt; } + if (B_opt != 0) { audio_config.achan[0].baud = B_opt; @@ -661,6 +680,16 @@ int main (int argc, char *argv[]) } } + if (g_opt) { + + // Force G3RUH mode, overriding default for speed. + // Example: -B 2400 -g + + audio_config.achan[0].modem_type = MODEM_SCRAMBLE; + audio_config.achan[0].mark_freq = 0; + audio_config.achan[0].space_freq = 0; + } + audio_config.statistics_interval = a_opt; if (strlen(P_opt) > 0) { @@ -674,6 +703,13 @@ int main (int argc, char *argv[]) audio_config.achan[0].decimate = D_opt; } + if (U_opt != 0) { + // Increase G3RUH audio sampling rate to improve performance. + // The value is normally determined automatically based on audio + // sample rate and baud. This allows override for experimentation. + audio_config.achan[0].upsample = U_opt; + } + strlcpy(audio_config.timestamp_format, T_opt_timestamp, sizeof(audio_config.timestamp_format)); // temp - only xmit errors. @@ -1145,9 +1181,10 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev * If it came from DTMF decoder, send it to APRStt gateway. * Otherwise, it is a candidate for IGate and digipeater. * - * TODO: It might be useful to have some way to simulate touch tone - * sequences with BEACON sendto=R... for testing. + * TODO: It would be useful to have some way to simulate touch tone + * sequences with BEACON sendto=R0 for testing. */ + if (subchan == -1) { if (tt_config.gateway_enabled && info_len >= 2) { aprs_tt_sequence (chan, (char*)(pinfo+1)); @@ -1254,6 +1291,7 @@ static void usage (char **argv) dw_printf (" 2400 bps uses QPSK based on V.26 standard.\n"); dw_printf (" 4800 bps uses 8PSK based on V.27 standard.\n"); dw_printf (" 9600 bps and up uses K9NG/G3RUH standard.\n"); + dw_printf (" -g Force G3RUH modem regardless of speed.\n"); dw_printf (" -D n Divide audio sample rate by n for channel 0.\n"); dw_printf (" -d Debug options:\n"); dw_printf (" a a = AGWPE network protocol client.\n"); @@ -1281,7 +1319,7 @@ static void usage (char **argv) dw_printf (" -p Enable pseudo terminal for KISS protocol.\n"); #endif dw_printf (" -x Send Xmit level calibration tones.\n"); - dw_printf (" -U Print UTF-8 test string and exit.\n"); + dw_printf (" -u Print UTF-8 test string and exit.\n"); dw_printf (" -S Print symbol tables and exit.\n"); dw_printf (" -T fmt Time stamp format for sent and received frames.\n"); dw_printf ("\n"); diff --git a/dsp.c b/dsp.c index fa16305..6ba7094 100644 --- a/dsp.c +++ b/dsp.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2015 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2015, 2019 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 @@ -51,7 +51,7 @@ // Don't remove this. It serves as a reminder that an experiment is underway. -#if defined(TUNE_MS_FILTER_SIZE) || defined(TUNE_AGC_FAST) || defined(TUNE_LPF_BAUD) || defined(TUNE_PLL_LOCKED) || defined(TUNE_PROFILE) +#if defined(TUNE_MS_FILTER_SIZE) || defined(TUNE_MS2_FILTER_SIZE) || defined(TUNE_AGC_FAST) || defined(TUNE_LPF_BAUD) || defined(TUNE_PLL_LOCKED) || defined(TUNE_PROFILE) #define DEBUG1 1 // Don't remove this. #endif @@ -118,13 +118,16 @@ float window (bp_window_t type, int size, int j) * Inputs: fc - Cutoff frequency as fraction of sampling frequency. * filter_size - Number of filter taps. * wtype - Window type, BP_WINDOW_HAMMING, etc. + * lp_delay_fract - Fudge factor for the delay value. * * Outputs: lp_filter * + * Returns: Signal delay thru the filter in number of audio samples. + * *----------------------------------------------------------------*/ -void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype) +int gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype, float lp_delay_fract) { int j; float G; @@ -171,14 +174,69 @@ void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype for (j=0; j lp_delay_fract) { + delay = j; + break; + } + } + +#if DEBUG1 + dw_printf ("Low Pass Delay = %d samples\n", delay) ; +#endif + +// Hmmm. This might have been wasted effort. The result is always half the number of taps. + + if (delay < 2 || delay > filter_size - 2) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error, %s %d, delay %d for size %d\n", __func__, __LINE__, delay, filter_size); + } + + return (delay); + +} /* end gen_lowpass */ + + +#undef DEBUG1 + /*------------------------------------------------------------------ * * Name: gen_bandpass * - * Purpose: Generate band pass filter kernel. + * Purpose: Generate band pass filter kernel for the prefilter. + * This is NOT for the mark/space filters. * * Inputs: f1 - Lower cutoff frequency as fraction of sampling frequency. * f2 - Upper cutoff frequency... @@ -187,10 +245,10 @@ void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype * * Outputs: bp_filter * - * Reference: http://www.labbookpages.co.uk/audio/firWindowing.html + * Reference: http://www.labbookpages.co.uk/audio/firWindowing.html * * Does it need to be an odd length? - * + * *----------------------------------------------------------------*/ @@ -201,7 +259,6 @@ void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_win float G; float center = 0.5 * (filter_size - 1); - #if DEBUG1 text_color_set(DW_COLOR_DEBUG); @@ -229,7 +286,8 @@ void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_win #if DEBUG1 dw_printf ("%6d %6.2f %6.3f %6.3f\n", j, shape, sinc, bp_filter[j] ) ; #endif - } + } + /* * Normalize bandpass for unity gain in middle of passband. @@ -249,6 +307,66 @@ void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_win for (j=0; jachan[chan].baud ) + 0.5); + f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].baud * 0.5 * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); + break; + + default: // AFSK ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); @@ -298,75 +253,6 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets) sine_table[j] = s; } - -/* - * Low pass filter for 9600 baud. - */ - - for (chan = 0; chan < MAX_CHANS; chan++) { - - if (audio_config_p->achan[chan].valid && - (audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE - || audio_config_p->achan[chan].modem_type == MODEM_BASEBAND)) { - - int a = ACHAN2ADEV(chan); - int samples_per_sec; /* Might be scaled up! */ - int baud; - - /* These numbers were by trial and error. Need more investigation here. */ - - float filter_len_bits = 88 * 9600.0 / (44100.0 * 2.0); - /* Filter length in number of data bits. */ - /* Currently 9.58 */ - - float lpf_baud = 0.8; /* Lowpass cutoff freq as fraction of baud rate */ - - float fc; /* Cutoff frequency as fraction of sampling frequency. */ - -/* - * Normally, we want to generate the same thing whether sending over the air - * or putting it into a file for other testing. - * (There is an important exception. gen_packets can introduce random noise.) - * In this case, we want more aggressive low pass filtering so it looks more like - * what we see coming out of a receiver. - * Specifically, single bits of the same state have considerably reduced amplitude - * below several same values in a row. - */ - - if (gen_packets) { - filter_len_bits = 4; - lpf_baud = 0.55; /* Lowpass cutoff freq as fraction of baud rate */ - } - - samples_per_sec = audio_config_p->adev[a].samples_per_sec * UPSAMPLE; - baud = audio_config_p->achan[chan].baud; - - ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)samples_per_sec ) + 0.5); - ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)baud ) + 0.5); - - lp_filter_size[chan] = (int) (( filter_len_bits * (float)samples_per_sec / baud) + 0.5); - - if (lp_filter_size[chan] < 10) { - text_color_set(DW_COLOR_DEBUG); - dw_printf ("gen_tone_init: unexpected, chan %d, lp_filter_size %d < 10\n", chan, lp_filter_size[chan]); - lp_filter_size[chan] = 10; - } - else if (lp_filter_size[chan] > MAX_FILTER_SIZE) { - text_color_set(DW_COLOR_DEBUG); - dw_printf ("gen_tone_init: unexpected, chan %d, lp_filter_size %d > %d\n", chan, lp_filter_size[chan], MAX_FILTER_SIZE); - lp_filter_size[chan] = MAX_FILTER_SIZE; - } - - fc = (float)baud * lpf_baud / (float)samples_per_sec; - - //text_color_set(DW_COLOR_DEBUG); - //dw_printf ("gen_tone_init: chan %d, call gen_lowpass(fc=%.2f, , size=%d, )\n", chan, fc, lp_filter_size[chan]); - - gen_lowpass (fc, lp_filter[chan], lp_filter_size[chan], BP_WINDOW_HAMMING); - - } - } - return (0); } /* end gen_tone_init */ @@ -389,6 +275,11 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets) * * Version 1.4: Attempt to implement 2400 and 4800 bps PSK modes. * + * Version 1.6: For G3RUH, rather than generating square wave and low + * pass filtering, generate the waveform directly. + * This avoids overshoot, ringing, and adding more jitter. + * Alternating bits come out has sine wave of baud/2 Hz. + * *--------------------------------------------------------------------*/ static const int gray2phase_v26[4] = {0, 1, 3, 2}; @@ -469,7 +360,6 @@ void tone_gen_put_bit (int chan, int dat) do { /* until enough audio samples for this symbol. */ int sam; - float fsam; switch (save_audio_config_p->achan[chan].modem_type) { @@ -499,23 +389,17 @@ void tone_gen_put_bit (int chan, int dat) case MODEM_BASEBAND: case MODEM_SCRAMBLE: -#if DEBUG2 - text_color_set(DW_COLOR_DEBUG); - dw_printf ("tone_gen_put_bit %d SCR\n", __LINE__); -#endif - fsam = dat ? amp16bit : (-amp16bit); - - /* version 1.2 - added a low pass filter instead of square wave out. */ - - push_sample (fsam, raw[chan], lp_filter_size[chan]); - - resample[chan]++; - if (resample[chan] >= UPSAMPLE) { - - sam = (int) convolve (raw[chan], lp_filter[chan], lp_filter_size[chan]); - resample[chan] = 0; - gen_tone_put_sample (chan, a, sam); + if (dat != prev_dat[chan]) { + tone_phase[chan] += f1_change_per_sample[chan]; } + else { + if (tone_phase[chan] & 0x80000000) + tone_phase[chan] = 0xc0000000; // 270 degrees. + else + tone_phase[chan] = 0x40000000; // 90 degrees. + } + sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; + gen_tone_put_sample (chan, a, sam); break; default: @@ -532,7 +416,10 @@ void tone_gen_put_bit (int chan, int dat) } while (bit_len_acc[chan] < ticks_per_bit[chan]); bit_len_acc[chan] -= ticks_per_bit[chan]; -} + + prev_dat[chan] = dat; // Only needed for G3RUH baseband/scrambled. + +} /* end tone_gen_put_bit */ void gen_tone_put_sample (int chan, int a, int sam) { @@ -547,10 +434,21 @@ void gen_tone_put_sample (int chan, int a, int sam) { assert (save_audio_config_p->adev[a].bits_per_sample == 16 || save_audio_config_p->adev[a].bits_per_sample == 8); - // TODO: Should print message telling user to reduce output level. + // Bad news if we are clipping and distorting the signal. + // We are using the full range. + // Too late to change now because everyone would need to recalibrate their + // transmit audio level. - if (sam < -32767) sam = -32767; - else if (sam > 32767) sam = 32767; + if (sam < -32767) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Warning: Audio sample %d clipped to -32767.\n", sam); + sam = -32767; + } + else if (sam > 32767) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Warning: Audio sample %d clipped to +32767.\n", sam); + sam = 32767; + } if (save_audio_config_p->adev[a].num_channels == 1) {