New "-g" option for direwolf and atest to force G3RUH modem and override

default for the speed.   atest -h will display frame as hexadecimal
bytes.
This commit is contained in:
wb2osz 2019-01-21 11:07:20 -05:00
parent c7dcfd141e
commit e3dc8bbf1b
17 changed files with 777 additions and 361 deletions

View File

@ -735,7 +735,7 @@ install-rpi :
# Combine some unit tests into a single regression sanity check. # 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? # Can we encode and decode at popular data rates?
@ -753,14 +753,14 @@ check-modem300 : gen_packets atest
check-modem9600 : gen_packets atest check-modem9600 : gen_packets atest
./gen_packets -B9600 -n 100 -o /tmp/test96.wav ./gen_packets -B9600 -n 100 -o /tmp/test96.wav
./atest -B9600 -F0 -L50 -G54 /tmp/test96.wav ./atest -B9600 -F0 -L61 -G65 /tmp/test96.wav
./atest -B9600 -F1 -L55 -G59 /tmp/test96.wav ./atest -B9600 -F1 -L62 -G66 /tmp/test96.wav
rm /tmp/test96.wav rm /tmp/test96.wav
check-modem19200 : gen_packets atest check-modem19200 : gen_packets atest
./gen_packets -r 96000 -B19200 -n 100 -o /tmp/test19.wav ./gen_packets -r 96000 -B19200 -n 100 -o /tmp/test19.wav
./atest -B19200 -F0 -L55 -G59 /tmp/test19.wav ./atest -B19200 -F0 -L60 -G64 /tmp/test19.wav
./atest -B19200 -F1 -L60 -G64 /tmp/test19.wav ./atest -B19200 -F1 -L64 -G68 /tmp/test19.wav
rm /tmp/test19.wav rm /tmp/test19.wav
check-modem2400 : gen_packets atest check-modem2400 : gen_packets atest
@ -769,6 +769,11 @@ check-modem2400 : gen_packets atest
./atest -B2400 -F1 -L80 -G87 /tmp/test24.wav ./atest -B2400 -F1 -L80 -G87 /tmp/test24.wav
rm /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 check-modem4800 : gen_packets atest
./gen_packets -B2400 -n 100 -o /tmp/test48.wav ./gen_packets -B2400 -n 100 -o /tmp/test48.wav
./atest -B2400 -F0 -L70 -G79 /tmp/test48.wav ./atest -B2400 -F0 -L70 -G79 /tmp/test48.wav

View File

@ -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. # 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 $@ $^ $(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 $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
# ------------------------------------------- Libraries -------------------------------------------- # ------------------------------------------- Libraries --------------------------------------------
@ -277,7 +277,7 @@ strlcat.o : misc/strlcat.c
# Combine some unit tests into a single regression sanity check. # 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? # Can we encode and decode at popular data rates?
# Verify that single bit fixup increases the count. # Verify that single bit fixup increases the count.
@ -305,8 +305,8 @@ check-modem9600 : gen_packets atest
sleep 1 sleep 1
gen_packets -B9600 -n 100 -o test96.wav gen_packets -B9600 -n 100 -o test96.wav
sleep 1 sleep 1
atest -B9600 -F0 -L50 -G54 test96.wav atest -B9600 -F0 -L61 -G65 test96.wav
atest -B9600 -F1 -L55 -G59 test96.wav atest -B9600 -F1 -L62 -G66 test96.wav
sleep 1 sleep 1
rm test96.wav rm test96.wav
@ -319,8 +319,8 @@ check-modem19200 : gen_packets atest
sleep 1 sleep 1
gen_packets -r 96000 -B19200 -n 100 -o test19.wav gen_packets -r 96000 -B19200 -n 100 -o test19.wav
sleep 1 sleep 1
atest -B19200 -F0 -L55 -G59 test19.wav atest -B19200 -F0 -L60 -G64 test19.wav
atest -B19200 -F1 -L60 -G64 test19.wav atest -B19200 -F1 -L64 -G68 test19.wav
sleep 1 sleep 1
rm test19.wav rm test19.wav
@ -332,6 +332,13 @@ check-modem2400 : gen_packets atest
sleep 1 sleep 1
rm test24.wav 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 check-modem4800 : gen_packets atest
gen_packets -B4800 -n 100 -o test48.wav gen_packets -B4800 -n 100 -o test48.wav
sleep 1 sleep 1
@ -467,6 +474,9 @@ dtmftest : dtmf.c textcolor.o
tnctest : tnctest.c textcolor.o dtime_now.o serial_port.o misc.a tnctest : tnctest.c textcolor.o dtime_now.o serial_port.o misc.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 $(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. # 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 dwgpsnmea.o dwgps.o serial_port.o tt_text.o dtime_now.o regex.a misc.a
rm -f atest.exe rm -f atest.exe
$(CC) $(CFLAGS) -o atest $^ $(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 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 rm -f atest96.exe
$(CC) $(CFLAGS) -o atest96 $^ $(CC) $(CFLAGS) -o atest96 $^
./atest96 -B 9600 ../walkabout9600c.wav | grep "packets decoded in" >atest.out ./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 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_8bis_22kHz.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 19990303_0225_9600_16bit_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. # 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 $(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. # Touch Tone to Speech sample application.
ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a 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 \
#-------------------------------------------------------------- #--------------------------------------------------------------

185
atest.c
View File

@ -140,7 +140,10 @@ static int e_o_f;
static int packets_decoded_one = 0; static int packets_decoded_one = 0;
static int packets_decoded_total = 0; static int packets_decoded_total = 0;
static int decimate = 0; /* Reduce that sampling rate if set. */ 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; 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 */ /* Use to print timestamp, relative to beginning */
/* of file, when frame was decoded. */ /* 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[]) int main (int argc, char *argv[])
{ {
@ -276,6 +286,7 @@ int main (int argc, char *argv[])
//my_audio_config.achan[channel].passall = 1; //my_audio_config.achan[channel].passall = 1;
} }
while (1) { while (1) {
//int this_option_optind = optind ? optind : 1; //int this_option_optind = optind ? optind : 1;
int option_index = 0; int option_index = 0;
@ -288,7 +299,7 @@ int main (int argc, char *argv[])
/* ':' following option character means arg is required. */ /* ':' 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); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
@ -296,63 +307,13 @@ int main (int argc, char *argv[])
switch (c) { switch (c) {
case 'B': /* -B for data Bit rate */ case 'B': /* -B for data Bit rate */
/* 300 implies 1600/1800 AFSK. */ /* Also implies modem type based on speed. */
/* 1200 implies 1200/2200 AFSK. */ B_opt = atoi(optarg);
/* 2400 implies V.26 */ break;
/* 9600 implies scrambled. */
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); g_opt = 1;
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");
}
break; break;
case 'P': /* -P for modem profile. */ case 'P': /* -P for modem profile. */
@ -375,6 +336,23 @@ int main (int argc, char *argv[])
my_audio_config.achan[0].decimate = decimate; my_audio_config.achan[0].decimate = decimate;
break; 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. */ case 'F': /* -D set "fix bits" level. */
my_audio_config.achan[0].fix_bits = atoi(optarg); my_audio_config.achan[0].fix_bits = atoi(optarg);
@ -411,6 +389,11 @@ int main (int argc, char *argv[])
decode_only = 2; decode_only = 2;
break; break;
case 'h': /* Hexadecimal display. */
h_opt = 1;
break;
case '?': case '?':
/* Unknown option message was already printed. */ /* 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])); 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); ax25_safe_print ((char *)pinfo, info_len, 0);
dw_printf ("\n"); 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. #if 1 // temp experiment TODO: remove this.
#include "decode_aprs.h" #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 (" 1200 (default) baud uses 1200/2200 Hz AFSK.\n");
dw_printf (" 9600 baud uses K9NG/G2RUH standard.\n"); dw_printf (" 9600 baud uses K9NG/G2RUH standard.\n");
dw_printf ("\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 (" -D n Divide audio sample rate by n.\n");
dw_printf ("\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 (" -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 (" 0 (default) = consider only correct frames. \n");
dw_printf (" 1 = Try to fix only a single bit. \n"); dw_printf (" 1 = Try to fix only a single bit. \n");

View File

@ -138,6 +138,8 @@ struct audio_s {
int decimate; /* Reduce AFSK sample rate by this factor to */ int decimate; /* Reduce AFSK sample rate by this factor to */
/* decrease computational requirements. */ /* decrease computational requirements. */
int upsample; /* Upsample by this factor for G3RUH. */
int interleave; /* If > 1, interleave samples among multiple decoders. */ int interleave; /* If > 1, interleave samples among multiple decoders. */
/* Quick hack for experiment. */ /* Quick hack for experiment. */

View File

@ -2308,16 +2308,30 @@ void ax25_hex_dump (packet_t this_p)
dw_printf ("%s\n", cp_text); 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", 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, 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_SSID_MASK)>>SSID_SSID_SHIFT,
(fptr[6]&SSID_H_MASK)>>SSID_H_SHIFT, (fptr[6]&SSID_H_MASK)>>SSID_H_SHIFT,
(fptr[6]&SSID_RR_MASK)>>SSID_RR_SHIFT, (fptr[6]&SSID_RR_MASK)>>SSID_RR_SHIFT,
fptr[6]&SSID_LAST_MASK); fptr[6]&SSID_LAST_MASK);
dw_printf (" source %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n", 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_SSID_MASK)>>SSID_SSID_SHIFT,
(fptr[13]&SSID_H_MASK)>>SSID_H_SHIFT, (fptr[13]&SSID_H_MASK)>>SSID_H_SHIFT,
(fptr[13]&SSID_RR_MASK)>>SSID_RR_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", dw_printf (" digi %d %c%c%c%c%c%c %2d h=%d res=%d last=%d\n",
n - 1, 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_SSID_MASK)>>SSID_SSID_SHIFT,
(fptr[n*7+6]&SSID_H_MASK)>>SSID_H_SHIFT, (fptr[n*7+6]&SSID_H_MASK)>>SSID_H_SHIFT,
(fptr[n*7+6]&SSID_RR_MASK)>>SSID_RR_SHIFT, (fptr[n*7+6]&SSID_RR_MASK)>>SSID_RR_SHIFT,

View File

@ -1261,7 +1261,10 @@ void config_init (char *fname, struct audio_s *p_audio_config,
* Options: * Options:
* mark:space - AFSK tones. Defaults based on speed. * mark:space - AFSK tones. Defaults based on speed.
* num@offset - Multiple decoders on different frequencies. * 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) { 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; p_audio_config->achan[channel].space_freq = 0;
} }
/* Get mark frequency. */ /* Get any options. */
t = split(NULL,0); t = split(NULL,0);
if (t == NULL) { if (t == NULL) {
@ -1332,6 +1335,9 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/* old style */ /* 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); n = atoi(t);
/* Originally the upper limit was 3000. */ /* Originally the upper limit was 3000. */
/* Version 1.0 increased to 5000 because someone */ /* 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 { else {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unrecognized option for MODEM: %s\n", line, t); dw_printf ("Line %d: Unrecognized option for MODEM: %s\n", line, t);

View File

@ -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) ) { if ( ( ! A->g_quiet ) && ( (int)strlen((char*)pinfo) != info_len) ) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf("'nul' character found in Information part. This should never happen.\n"); dw_printf("'nul' character found in Information part. This should never happen with APRS.\n");
dw_printf("It seems that %s is transmitting with defective software.\n", A->g_src); 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) { if (strcmp((char*)pinfo, "4P") == 0) {
dw_printf("The TM-D710 will do this intermittently. A firmware upgrade is needed to fix it.\n"); dw_printf("The TM-D710 will do this intermittently. A firmware upgrade is needed to fix it.\n");

80
demod.c
View File

@ -63,7 +63,7 @@ static struct audio_s *save_audio_config_p;
// TODO: temp experiment. // TODO: temp experiment.
static int upsample = 2; // temp experiment.
static int zerostuff = 1; // temp experiment. static int zerostuff = 1; // temp experiment.
// Current state of all the decoders. // Current state of all the decoders.
@ -648,20 +648,70 @@ int demod_init (struct audio_s *pa)
//#endif //#endif
} }
#ifdef TUNE_UPSAMPLE
upsample = TUNE_UPSAMPLE;
#endif
#ifdef TUNE_ZEROSTUFF #ifdef TUNE_ZEROSTUFF
zerostuff = TUNE_ZEROSTUFF; zerostuff = TUNE_ZEROSTUFF;
#endif #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); text_color_set(DW_COLOR_DEBUG);
dw_printf ("Channel %d: %d baud, K9NG/G3RUH, %s, %d sample rate x %d", 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->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) if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF)
dw_printf (", DTMF decoder enabled"); dw_printf (", DTMF decoder enabled");
dw_printf (".\n"); dw_printf (".\n");
@ -669,6 +719,7 @@ int demod_init (struct audio_s *pa)
struct demodulator_state_s *D; struct demodulator_state_s *D;
D = &demodulator_state[chan][0]; // first subchannel D = &demodulator_state[chan][0]; // first subchannel
save_audio_config_p->achan[chan].num_subchan = 1; save_audio_config_p->achan[chan].num_subchan = 1;
save_audio_config_p->achan[chan].num_slicers = 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); 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", 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, 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) { else if (ratio < 5) {
dw_printf ("This is on the low side for best performance. Can you use a higher sample rate?\n"); 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) { else if (ratio < 6) {
dw_printf ("Increasing the sample rate should improve decoder performance.\n"); 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"); 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) { 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 */ /* So far, both are same in tests with different */
/* optimal low pass filter parameters. */ /* optimal low pass filter parameters. */
for (k=1; k<upsample; k++) { for (k=1; k<save_audio_config_p->achan[chan].upsample; k++) {
demod_9600_process_sample (chan, 0, D); 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 { else {
/* Linear interpolation. */ /* Linear interpolation. */
static int prev_sam; static int prev_sam;
switch (upsample) { switch (save_audio_config_p->achan[chan].upsample) {
case 1: case 1:
demod_9600_process_sample (chan, sam, D); demod_9600_process_sample (chan, sam, D);
break; break;

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // 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 // 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 // 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 'K': // upsample x3 with filtering.
// case 'L': // upsample x4 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. // 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) (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_filter_size = (int) (( D->lp_filter_len_bits * (float)samples_per_sec / baud) + 0.5f);
D->lp_window = BP_WINDOW_HAMMING; D->lp_window = BP_WINDOW_COSINE;
D->lpf_baud = 0.62;
D->lpf_baud = 1.00;
D->agc_fast_attack = 0.080; D->agc_fast_attack = 0.080;
D->agc_slow_decay = 0.00012; D->agc_slow_decay = 0.00012;
D->pll_locked_inertia = 0.89; D->pll_locked_inertia = 0.89;
D->pll_searching_inertia = 0.67; 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; // 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 = D->pll_step_per_sample =
(int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec); (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); //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. */ /* 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 * 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)) __attribute__((hot))
inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_f, struct demodulator_state_s *D) 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; D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
// Perform the add as unsigned to avoid signed overflow error. // 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. */ /* 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);
}
} }
/* /*

View File

@ -382,6 +382,45 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
D->pll_searching_inertia = 0.64; D->pll_searching_inertia = 0.64;
break; 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: default:
text_color_set(DW_COLOR_ERROR); 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 #ifdef TUNE_MS_WINDOW
D->ms_window = TUNE_MS_WINDOW; D->ms_window = TUNE_MS_WINDOW;
#endif #endif
#ifdef TUNE_MS2_WINDOW
D->ms2_window = TUNE_MS2_WINDOW;
#endif
#ifdef TUNE_LP_WINDOW #ifdef TUNE_LP_WINDOW
D->lp_window = TUNE_LP_WINDOW; D->lp_window = TUNE_LP_WINDOW;
#endif #endif
@ -417,7 +459,9 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
#ifdef TUNE_PRE_BAUD #ifdef TUNE_PRE_BAUD
D->prefilter_baud = TUNE_PRE_BAUD; D->prefilter_baud = TUNE_PRE_BAUD;
#endif #endif
#ifdef TUNE_LP_DELAY_FRACT
D->lp_delay_fract = TUNE_LP_DELAY_FRACT;
#endif
/* /*
* Calculate constants used for timing. * 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->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->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 ); D->lp_filter_size = (int) round( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud );
/* Experiment with other sizes. */ /* 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 #ifdef TUNE_MS_FILTER_SIZE
D->ms_filter_size = TUNE_MS_FILTER_SIZE; D->ms_filter_size = TUNE_MS_FILTER_SIZE;
#endif #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 #ifdef TUNE_LP_FILTER_SIZE
D->lp_filter_size = TUNE_LP_FILTER_SIZE; D->lp_filter_size = TUNE_LP_FILTER_SIZE;
#endif #endif
@ -470,6 +522,25 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
exit (1); 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) if (D->lp_filter_size > MAX_FILTER_SIZE)
{ {
text_color_set (DW_COLOR_ERROR); 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; f1 = f1 / (float)samples_per_sec;
f2 = f2 / (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); 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"); dw_printf (" j shape M sin M cos \n");
#endif #endif
float Gs = 0, Gc = 0;
for (j=0; j<D->ms_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; j<D->ms_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 #if DEBUG1
text_color_set(DW_COLOR_DEBUG); 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 ("Space\n");
dw_printf (" j shape S sin S cos\n"); dw_printf (" j shape S sin S cos\n");
#endif #endif
Gs = 0;
Gc = 0;
for (j=0; j<D->ms_filter_size; j++) { gen_ms (space_freq, samples_per_sec, D->s_sin_table, D->s_cos_table, D->ms_filter_size, D->ms_window);
float as;
float center;
float shape = 1.0f;
center = 0.5 * (D->ms_filter_size - 1); // Note that these are twice as long so we can try matching different phases.
as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2.0f * (float)M_PI);
shape = window (D->ms_window, D->ms_filter_size, j); 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);
D->s_sin_table[j] = sinf(as) * shape; gen_ms (space_freq, samples_per_sec, D->s2_sin_table, D->s2_cos_table, D->s2_filter_size * 2, D->ms2_window);
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; j<D->ms_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;
} }
/* /*
@ -616,7 +620,11 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
if (D->lpf_use_fir) { if (D->lpf_use_fir) {
float fc; float fc;
fc = baud * D->lpf_baud / (float)samples_per_sec; 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. * 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) { if (D->use_prefilter) {
float cleaner; float cleaner;
push_sample (fsam, D->raw_cb, D->pre_filter_size); push_sample (fsam, D->raw_cb, D->pre_filter_size);
cleaner = convolve (D->raw_cb, D->pre_filter, 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 { 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: * Here is an excellent explanation:
* http://www.febo.com/packet/layer-one/transmit.html * 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 * considerably smaller amplitude due to the passband characteristics
* of the transmitter and receiver. To make matters worse, it * of the transmitter and receiver. To make matters worse, it
* varies considerably from one station to another. * 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. */ /* 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 (demod_data != D->slicer[slice].prev_demod_data) {
if (hdlc_rec_gathering (chan, subchan, slice)) { if (hdlc_rec_gathering (chan, subchan, slice)) {

View File

@ -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; 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. * No point in having multiple numbers for signal level.

View File

@ -190,7 +190,7 @@ int main (int argc, char *argv[])
struct digi_config_s digi_config; struct digi_config_s digi_config;
struct cdigi_config_s cdigi_config; struct cdigi_config_s cdigi_config;
struct igate_config_s igate_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 P_opt[16];
char l_opt_logdir[80]; char l_opt_logdir[80];
char L_opt_logfile[80]; char L_opt_logfile[80];
@ -199,6 +199,7 @@ int main (int argc, char *argv[])
int t_opt = 1; /* Text color option. */ int t_opt = 1; /* Text color option. */
int a_opt = 0; /* "-a n" interval, in seconds, for audio statistics report. 0 for none. */ 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_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. */ 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_init(t_opt);
text_color_set(DW_COLOR_INFO); 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 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); //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. */ /* ':' 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); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
@ -404,13 +405,18 @@ int main (int argc, char *argv[])
} }
break; break;
case 'g': /* -g G3RUH modem, overriding default mode for speed. */
g_opt = 1;
break;
case 'P': /* -P for modem profile. */ case 'P': /* -P for modem profile. */
//debug: dw_printf ("Demodulator profile set to \"%s\"\n", optarg); //debug: dw_printf ("Demodulator profile set to \"%s\"\n", optarg);
strlcpy (P_opt, optarg, sizeof(P_opt)); strlcpy (P_opt, optarg, sizeof(P_opt));
break; break;
case 'D': /* -D decrease AFSK demodulator sample rate */ case 'D': /* -D divide AFSK demodulator sample rate */
D_opt = atoi(optarg); D_opt = atoi(optarg);
if (D_opt < 1 || D_opt > 8) { if (D_opt < 1 || D_opt > 8) {
@ -420,6 +426,16 @@ int main (int argc, char *argv[])
} }
break; 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. */ case 'x': /* -x for transmit calibration tones. */
xmit_calibrate_option = 1; xmit_calibrate_option = 1;
@ -518,7 +534,7 @@ int main (int argc, char *argv[])
break; 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", dw_printf ("\n UTF-8 test string: ma%c%cana %c%c F%c%c%c%ce\n\n",
0xc3, 0xb1, 0xc3, 0xb1,
@ -610,15 +626,18 @@ int main (int argc, char *argv[])
if (r_opt != 0) { if (r_opt != 0) {
audio_config.adev[0].samples_per_sec = r_opt; audio_config.adev[0].samples_per_sec = r_opt;
} }
if (n_opt != 0) { if (n_opt != 0) {
audio_config.adev[0].num_channels = n_opt; audio_config.adev[0].num_channels = n_opt;
if (n_opt == 2) { if (n_opt == 2) {
audio_config.achan[1].valid = 1; audio_config.achan[1].valid = 1;
} }
} }
if (b_opt != 0) { if (b_opt != 0) {
audio_config.adev[0].bits_per_sample = b_opt; audio_config.adev[0].bits_per_sample = b_opt;
} }
if (B_opt != 0) { if (B_opt != 0) {
audio_config.achan[0].baud = B_opt; 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; audio_config.statistics_interval = a_opt;
if (strlen(P_opt) > 0) { if (strlen(P_opt) > 0) {
@ -674,6 +703,13 @@ int main (int argc, char *argv[])
audio_config.achan[0].decimate = D_opt; 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)); strlcpy(audio_config.timestamp_format, T_opt_timestamp, sizeof(audio_config.timestamp_format));
// temp - only xmit errors. // 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. * If it came from DTMF decoder, send it to APRStt gateway.
* Otherwise, it is a candidate for IGate and digipeater. * Otherwise, it is a candidate for IGate and digipeater.
* *
* TODO: It might be useful to have some way to simulate touch tone * TODO: It would be useful to have some way to simulate touch tone
* sequences with BEACON sendto=R... for testing. * sequences with BEACON sendto=R0 for testing.
*/ */
if (subchan == -1) { if (subchan == -1) {
if (tt_config.gateway_enabled && info_len >= 2) { if (tt_config.gateway_enabled && info_len >= 2) {
aprs_tt_sequence (chan, (char*)(pinfo+1)); 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 (" 2400 bps uses QPSK based on V.26 standard.\n");
dw_printf (" 4800 bps uses 8PSK based on V.27 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 (" 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 n Divide audio sample rate by n for channel 0.\n");
dw_printf (" -d Debug options:\n"); dw_printf (" -d Debug options:\n");
dw_printf (" a a = AGWPE network protocol client.\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"); dw_printf (" -p Enable pseudo terminal for KISS protocol.\n");
#endif #endif
dw_printf (" -x Send Xmit level calibration tones.\n"); 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 (" -S Print symbol tables and exit.\n");
dw_printf (" -T fmt Time stamp format for sent and received frames.\n"); dw_printf (" -T fmt Time stamp format for sent and received frames.\n");
dw_printf ("\n"); dw_printf ("\n");

134
dsp.c
View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // 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 // 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 // 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. // 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. #define DEBUG1 1 // Don't remove this.
#endif #endif
@ -118,13 +118,16 @@ float window (bp_window_t type, int size, int j)
* Inputs: fc - Cutoff frequency as fraction of sampling frequency. * Inputs: fc - Cutoff frequency as fraction of sampling frequency.
* filter_size - Number of filter taps. * filter_size - Number of filter taps.
* wtype - Window type, BP_WINDOW_HAMMING, etc. * wtype - Window type, BP_WINDOW_HAMMING, etc.
* lp_delay_fract - Fudge factor for the delay value.
* *
* Outputs: lp_filter * 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; int j;
float G; 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<filter_size; j++) { for (j=0; j<filter_size; j++) {
lp_filter[j] = lp_filter[j] / G; lp_filter[j] = lp_filter[j] / G;
} }
}
// Calculate the signal delay.
// If a signal at level 0 steps to level 1, this is the time that it would
// take for the output to reach 0.5.
//
// Examples:
//
// Filter has one tap with value of 1.0.
// Output is immediate so I would call this delay of 0.
//
// Filter coefficients: 0.2, 0.2, 0.2, 0.2, 0.2
// "1" inputs Out
// 1 0.2
// 2 0.4
// 3 0.6
//
// In this case, the output does not change immediately.
// It takes two more samples to reach the half way point
// so it has a delay of 2.
float sum = 0;
int delay = 0;
if (lp_delay_fract == 0) lp_delay_fract = 0.5;
for (j=0; j<filter_size; j++) {
sum += lp_filter[j];
#if DEBUG1
dw_printf ("lp_filter[%d] = %.3f sum = %.3f lp_delay_fract = %.3f\n", j, lp_filter[j], sum, lp_delay_fract);
#endif
if (sum > 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 * 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. * Inputs: f1 - Lower cutoff frequency as fraction of sampling frequency.
* f2 - Upper cutoff frequency... * f2 - Upper cutoff frequency...
@ -201,7 +259,6 @@ void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_win
float G; float G;
float center = 0.5 * (filter_size - 1); float center = 0.5 * (filter_size - 1);
#if DEBUG1 #if DEBUG1
text_color_set(DW_COLOR_DEBUG); 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 #if DEBUG1
dw_printf ("%6d %6.2f %6.3f %6.3f\n", j, shape, sinc, bp_filter[j] ) ; dw_printf ("%6d %6.2f %6.3f %6.3f\n", j, shape, sinc, bp_filter[j] ) ;
#endif #endif
} }
/* /*
* Normalize bandpass for unity gain in middle of passband. * 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; j<filter_size; j++) { for (j=0; j<filter_size; j++) {
bp_filter[j] = bp_filter[j] / G; bp_filter[j] = bp_filter[j] / G;
} }
}
} /* end gen_bandpass */
/*------------------------------------------------------------------
*
* Name: gen_ms
*
* Purpose: Generate mark and space filters.
*
* Inputs: fc - Tone frequency, i.e. mark or space.
* sps - Samples per second.
* filter_size - Number of filter taps.
* wtype - Window type, BP_WINDOW_HAMMING, etc.
*
* Outputs: bp_filter
*
* Reference: http://www.labbookpages.co.uk/audio/firWindowing.html
*
* Does it need to be an odd length?
*
*----------------------------------------------------------------*/
void gen_ms (int fc, int sps, float *sin_table, float *cos_table, int filter_size, int wtype)
{
int j;
float Gs = 0, Gc = 0;;
for (j=0; j<filter_size; j++) {
float center = 0.5f * (filter_size - 1);
float am = ((float)(j - center) / (float)sps) * ((float)fc) * (2.0f * (float)M_PI);
float shape = window (wtype, filter_size, j);
sin_table[j] = sinf(am) * shape;
cos_table[j] = cosf(am) * shape;
Gs += sin_table[j] * sinf(am);
Gc += cos_table[j] * cosf(am);
#if DEBUG1
dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, sin_table[j], 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; j<filter_size; j++) {
sin_table[j] = sin_table[j] / Gs;
cos_table[j] = cos_table[j] / Gc;
}
} /* end gen_ms */
/* end dsp.c */ /* end dsp.c */

5
dsp.h
View File

@ -5,6 +5,9 @@
float window (bp_window_t type, int size, int j); float window (bp_window_t type, int size, int j);
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);
void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype); void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype);
void gen_ms (int fc, int samples_per_sec, float *sin_table, float *cos_table, int filter_size, int wtype);

View File

@ -31,6 +31,8 @@ struct demodulator_state_s
char profile; // 'A', 'B', etc. Upper case. char profile; // 'A', 'B', etc. Upper case.
// Only needed to see if we are using 'F' to take fast path. // Only needed to see if we are using 'F' to take fast path.
int play_it_again_sample; // Enable new synchronous demod in version 1.6.
#define TICKS_PER_PLL_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 ) #define TICKS_PER_PLL_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 )
int pll_step_per_sample; // PLL is advanced by this much each audio sample. int pll_step_per_sample; // PLL is advanced by this much each audio sample.
@ -39,9 +41,17 @@ struct demodulator_state_s
int ms_filter_size; /* Size of mark & space filters, in audio samples. */ int ms_filter_size; /* Size of mark & space filters, in audio samples. */
/* Started off as a guess of one bit length */ /* Started off as a guess of one bit length */
/* but somewhat longer turned out to be better. */ /* but about 2 bit times turned out to be better. */
/* Currently using same size for any prefilter. */ /* Currently using same size for any prefilter. */
int m2_filter_size;
int s2_filter_size; /* Size of mark & space filters, in audio samples */
/* for the synchronous demodulator. I'm expecting */
/* smaller, perhaps just over 1 bit time here. */
int lp2_filter_size; /* FSK resampling - Size of Low Pass filter, in audio samples. */
#define MAX_FILTER_SIZE 320 /* 304 is needed for profile C, 300 baud & 44100. */ #define MAX_FILTER_SIZE 320 /* 304 is needed for profile C, 300 baud & 44100. */
/* /*
@ -49,6 +59,9 @@ struct demodulator_state_s
* e.g. 1 means 1/1200 second for 1200 baud. * e.g. 1 means 1/1200 second for 1200 baud.
*/ */
float ms_filter_len_bits; float ms_filter_len_bits;
float m2_filter_len_bits;
float s2_filter_len_bits;
float lp_delay_fract;
/* /*
* Window type for the various filters. * Window type for the various filters.
@ -57,6 +70,7 @@ struct demodulator_state_s
bp_window_t pre_window; bp_window_t pre_window;
bp_window_t ms_window; bp_window_t ms_window;
bp_window_t lp_window; bp_window_t lp_window;
bp_window_t ms2_window; /* New in 1.6. */
/* /*
@ -80,6 +94,12 @@ struct demodulator_state_s
/* Previously it was always the same as the M/S */ /* Previously it was always the same as the M/S */
/* filters but in version 1.2 it's now independent. */ /* filters but in version 1.2 it's now independent. */
int lp_filter_delay; /* Number of samples that the low pass filter */
/* delays the signal. */
/* New in 1.6. */
/* /*
* Automatic gain control. Fast attack and slow decay factors. * Automatic gain control. Fast attack and slow decay factors.
*/ */
@ -135,6 +155,18 @@ struct demodulator_state_s
float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
/*
* Same for the synchronous re-demodulator.
*/
float m2_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
float m2_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
float s2_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
float s2_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
float lp2_filter[MAX_FILTER_SIZE] __attribute__((aligned(16)));
/* /*
* These are for PSK only. * These are for PSK only.
* They are number of delay line taps into previous symbol. * They are number of delay line taps into previous symbol.

View File

@ -250,7 +250,9 @@ int main(int argc, char **argv)
/* 9600 implies scrambled. */ /* 9600 implies scrambled. */
/* If you want something else, specify -B first */ /* If you want something else, specify -B first */
/* then anything to override these defaults. */ /* then anything to override these defaults with -m, -s, or -g. */
// FIXME: options should not be order dependent.
modem.achan[0].baud = atoi(optarg); modem.achan[0].baud = atoi(optarg);
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
@ -308,6 +310,8 @@ int main(int argc, char **argv)
case 'g': /* -g for g3ruh scrambling */ case 'g': /* -g for g3ruh scrambling */
// FIXME: order dependent. -g must come after -B.
modem.achan[0].modem_type = MODEM_SCRAMBLE; modem.achan[0].modem_type = MODEM_SCRAMBLE;
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); dw_printf ("Using scrambled baseband signal rather than AFSK.\n");

View File

@ -1,10 +1,7 @@
//#define DEBUG 1
//#define DEBUG2 1
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2014, 2015, 2016 John Langner, WB2OSZ // Copyright (C) 2011, 2014, 2015, 2016, 2019 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -106,59 +103,9 @@ static int bit_count[MAX_CHANS]; // Counter incremented for each bit transmitted
static int save_bit[MAX_CHANS]; static int save_bit[MAX_CHANS];
/* static int prev_dat[MAX_CHANS]; // Previous data bit. Used for G3RUH style.
* The K9NG/G3RUH output originally took a very simple and lazy approach.
* We simply generated a square wave with + or - the desired amplitude.
* This has a couple undesirable properties.
*
* - Transmitting a square wave would splatter into adjacent
* channels of the transmitter doesn't limit the bandwidth.
*
* - The usual sample rate of 44100 is not a multiple of the
* baud rate so jitter would be added to the zero crossings.
*
* Starting in version 1.2, we try to overcome these issues by using
* a higher sample rate, low pass filtering, and down sampling.
*
* What sort of low pass filter would be appropriate? Intuitively,
* we would expect a cutoff frequency somewhere between baud/2 and baud.
* The current values were found with a small amount of trial and
* error for best results. Future improvement is certainly possible.
*/
/*
* For low pass filtering of 9600 baud data.
*/
/* Add sample to buffer and shift the rest down. */
// TODO: Can we have one copy of these in dsp.h?
static inline void push_sample (float val, float *buff, int size)
{
memmove(buff+1,buff,(size-1)*sizeof(float));
buff[0] = val;
}
/* FIR filter kernel. */
static inline float convolve (const float *data, const float *filter, int filter_size)
{
float sum = 0;
int j;
for (j=0; j<filter_size; j++) {
sum += filter[j] * data[j];
}
return (sum);
}
static int lp_filter_size[MAX_CHANS];
static float raw[MAX_CHANS][MAX_FILTER_SIZE] __attribute__((aligned(16)));
static float lp_filter[MAX_CHANS][MAX_FILTER_SIZE] __attribute__((aligned(16)));
static int resample[MAX_CHANS];
#define UPSAMPLE 2
/*------------------------------------------------------------------ /*------------------------------------------------------------------
@ -180,6 +127,8 @@ static int resample[MAX_CHANS];
* *
* amp - Signal amplitude on scale of 0 .. 100. * amp - Signal amplitude on scale of 0 .. 100.
* *
* 100% uses the full 16 bit sample range of +-32k.
*
* gen_packets - True if being called from "gen_packets" utility * gen_packets - True if being called from "gen_packets" utility
* rather than the "direwolf" application. * rather than the "direwolf" application.
* *
@ -211,9 +160,7 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets)
save_audio_config_p = audio_config_p; save_audio_config_p = audio_config_p;
amp16bit = (int)((32767 * amp) / 100);
amp16bit = (32767 * amp) / 100;
for (chan = 0; chan < MAX_CHANS; chan++) { for (chan = 0; chan < MAX_CHANS; chan++) {
@ -266,7 +213,15 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets)
f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used. f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used.
break; break;
default: case MODEM_BASEBAND:
case MODEM_SCRAMBLE:
// Tone is half baud.
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].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); 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); 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; 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); return (0);
} /* end gen_tone_init */ } /* 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.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}; 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. */ do { /* until enough audio samples for this symbol. */
int sam; int sam;
float fsam;
switch (save_audio_config_p->achan[chan].modem_type) { 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_BASEBAND:
case MODEM_SCRAMBLE: case MODEM_SCRAMBLE:
#if DEBUG2 if (dat != prev_dat[chan]) {
text_color_set(DW_COLOR_DEBUG); tone_phase[chan] += f1_change_per_sample[chan];
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);
} }
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; break;
default: default:
@ -532,7 +416,10 @@ void tone_gen_put_bit (int chan, int dat)
} while (bit_len_acc[chan] < ticks_per_bit[chan]); } while (bit_len_acc[chan] < ticks_per_bit[chan]);
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) { 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); 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; if (sam < -32767) {
else if (sam > 32767) 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) { if (save_audio_config_p->adev[a].num_channels == 1) {