From 2033a940a24b43f802d02a910bc89d113d50556f Mon Sep 17 00:00:00 2001
From: John Wiseman <john.wiseman@cantab.net>
Date: Sat, 2 Sep 2023 11:56:09 +0100
Subject: [PATCH] 0.0.0.67

---
 ALSASound.c                               |   36 +-
 Config.cpp                                |   41 +-
 Config.cpp.bak                            |  426 --
 HEAD                                      |    1 -
 LinuxBits.c                               |  311 ++
 ModemDialog.ui                            |  355 +-
 Modulate.c                                |   31 +-
 QtSoundModem-DESKTOP-MHE5LO8.vcxproj      |  292 ++
 QtSoundModem.cpp                          |  499 ++-
 QtSoundModem.cpp.bak                      | 2866 +++++++++++++
 QtSoundModem.pro                          |    3 +-
 QtSoundModem.ui                           |    4 +-
 QtSoundModem.vcxproj                      |  196 +-
 QtSoundModem.vcxproj-DESKTOP-MHE5LO8.user |   34 +
 QtSoundModem.vcxproj.filters              |    6 +-
 QtSoundModem.vcxproj.user                 |   10 +-
 SMMain-DESKTOP-MHE5LO8.c                  | 1433 +++++++
 SMMain.c                                  |  412 +-
 SoundInput.c                              |   10 +-
 UZ7HOStuff-DESKTOP-MHE5LO8.h              | 1110 +++++
 UZ7HOStuff.h                              |  107 +-
 UZ7HOStuff.h.bak                          | 1049 +++++
 Waveout.c                                 |   31 +-
 agwlib.h                                  |   45 +
 ais.h                                     |    8 +
 aprs_tt.h                                 |  191 +
 audio_stats.h                             |    7 +
 ax25.c                                    |   73 +-
 ax25_agw.c                                |    2 +-
 ax25_demod.c                              |  300 +-
 ax25_l2.c                                 |    6 +-
 ax25_link.h                               |   88 +
 ax25_mod-DESKTOP-MHE5LO8.c                | 1810 +++++++++
 ax25_mod.c                                |  148 +-
 ax25_pad2.h                               |   55 +
 beacon.h                                  |    6 +
 cdigipeater.h                             |   62 +
 cm108.h                                   |    5 +
 config.h                                  |  262 ++
 debug/QtSoundModem.ini                    |   43 +-
 debug/moc_predefs-DESKTOP-MHE5LO8.h       |   12 +
 decode_aprs.h                             |  150 +
 dedupe.h                                  |   10 +
 demod.h                                   |   17 +
 demod_afsk.h                              |    8 +
 demod_psk.h                               |    7 +
 devicesDialog.ui                          |  101 +-
 digipeater.h                              |   78 +
 dlq.h                                     |  148 +
 dns_sd_common.h                           |    7 +
 dns_sd_dw.h                               |   10 +
 dtime_now.h                               |   18 +
 dtmf.h                                    |   14 +
 dw9600.c                                  | 4184 +++++++++++++++++++
 dw9600.h                                  | 2042 ++++++++++
 dwgps.h                                   |   61 +
 dwgpsd.h                                  |   22 +
 dwgpsnmea.h                               |   32 +
 dwsock.h                                  |   21 +
 encode_aprs.h                             |   17 +
 fcs_calc.h                                |   11 +
 fsk_filters.h                             |    7 +
 fsk_gen_filter.h                          |   15 +
 gen_tone.h                                |   17 +
 grm_sym.h                                 |  501 +++
 hdlc_send.h                               |   17 +
 igate.h                                   |  128 +
 il2p.c                                    |  105 +-
 il2p.c.bak                                | 4502 +++++++++++++++++++++
 kiss.h                                    |   24 +
 kiss_frame.h                              |  126 +
 kiss_mode.c                               |    2 +-
 kissnet.h                                 |   29 +
 kissserial.h                              |   23 +
 latlong.h                                 |   24 +
 libfftw3f-64-3.lib                        |  Bin 0 -> 247600 bytes
 log.h                                     |   19 +
 main.cpp                                  |    2 +-
 mgn_icon.h                                |  276 ++
 mheard.h                                  |   20 +
 morse.h                                   |    8 +
 multi_modem.h                             |   24 +
 pfilter.h                                 |   13 +
 pktARDOP.c                                |    2 +-
 ptt.h                                     |   26 +
 recv.h                                    |    6 +
 redecode.h                                |   15 +
 release/moc_predefs-DESKTOP-MHE5LO8.h     |   11 +
 rpack.h                                   |   94 +
 rrbb.h                                    |   92 +
 serial_port.h                             |   32 +
 server.h                                  |   32 +
 sm_main.c                                 | 1952 ++-------
 symbols.h                                 |   19 +
 telemetry.h                               |   15 +
 tq.h                                      |   41 +
 tt_text.h                                 |   38 +
 tt_user.h                                 |   15 +
 tune.h                                    |    1 +
 version.h                                 |   21 +
 waypoint.h                                |   24 +
 xid.h                                     |   32 +
 xmit.h                                    |   27 +
 103 files changed, 24938 insertions(+), 2783 deletions(-)
 delete mode 100644 Config.cpp.bak
 delete mode 100644 HEAD
 create mode 100644 LinuxBits.c
 create mode 100644 QtSoundModem-DESKTOP-MHE5LO8.vcxproj
 create mode 100644 QtSoundModem.cpp.bak
 create mode 100644 QtSoundModem.vcxproj-DESKTOP-MHE5LO8.user
 create mode 100644 SMMain-DESKTOP-MHE5LO8.c
 create mode 100644 UZ7HOStuff-DESKTOP-MHE5LO8.h
 create mode 100644 UZ7HOStuff.h.bak
 create mode 100644 agwlib.h
 create mode 100644 ais.h
 create mode 100644 aprs_tt.h
 create mode 100644 audio_stats.h
 create mode 100644 ax25_link.h
 create mode 100644 ax25_mod-DESKTOP-MHE5LO8.c
 create mode 100644 ax25_pad2.h
 create mode 100644 beacon.h
 create mode 100644 cdigipeater.h
 create mode 100644 cm108.h
 create mode 100644 config.h
 create mode 100644 debug/moc_predefs-DESKTOP-MHE5LO8.h
 create mode 100644 decode_aprs.h
 create mode 100644 dedupe.h
 create mode 100644 demod.h
 create mode 100644 demod_afsk.h
 create mode 100644 demod_psk.h
 create mode 100644 digipeater.h
 create mode 100644 dlq.h
 create mode 100644 dns_sd_common.h
 create mode 100644 dns_sd_dw.h
 create mode 100644 dtime_now.h
 create mode 100644 dtmf.h
 create mode 100644 dw9600.c
 create mode 100644 dw9600.h
 create mode 100644 dwgps.h
 create mode 100644 dwgpsd.h
 create mode 100644 dwgpsnmea.h
 create mode 100644 dwsock.h
 create mode 100644 encode_aprs.h
 create mode 100644 fcs_calc.h
 create mode 100644 fsk_filters.h
 create mode 100644 fsk_gen_filter.h
 create mode 100644 gen_tone.h
 create mode 100644 grm_sym.h
 create mode 100644 hdlc_send.h
 create mode 100644 igate.h
 create mode 100644 il2p.c.bak
 create mode 100644 kiss.h
 create mode 100644 kiss_frame.h
 create mode 100644 kissnet.h
 create mode 100644 kissserial.h
 create mode 100644 latlong.h
 create mode 100644 libfftw3f-64-3.lib
 create mode 100644 log.h
 create mode 100644 mgn_icon.h
 create mode 100644 mheard.h
 create mode 100644 morse.h
 create mode 100644 multi_modem.h
 create mode 100644 pfilter.h
 create mode 100644 ptt.h
 create mode 100644 recv.h
 create mode 100644 redecode.h
 create mode 100644 release/moc_predefs-DESKTOP-MHE5LO8.h
 create mode 100644 rpack.h
 create mode 100644 rrbb.h
 create mode 100644 serial_port.h
 create mode 100644 server.h
 create mode 100644 symbols.h
 create mode 100644 telemetry.h
 create mode 100644 tq.h
 create mode 100644 tt_text.h
 create mode 100644 tt_user.h
 create mode 100644 tune.h
 create mode 100644 version.h
 create mode 100644 waypoint.h
 create mode 100644 xid.h
 create mode 100644 xmit.h

diff --git a/ALSASound.c b/ALSASound.c
index d6739a4..3507e9f 100644
--- a/ALSASound.c
+++ b/ALSASound.c
@@ -93,6 +93,22 @@ extern BOOL UseKISS;			// Enable Packet (KISS) interface
 
 extern short * DMABuffer;
 
+#define MaxReceiveSize 2048		// Enough for 9600
+#define MaxSendSize 4096
+
+short buffer[2][MaxSendSize * 2];		// Two Transfer/DMA buffers of 0.1 Sec  (x2 for Stereo)
+short inbuffer[MaxReceiveSize * 2];	// Input Transfer/ buffers of 0.1 Sec (x2 for Stereo)
+
+
+extern short * DMABuffer;
+extern int Number;
+
+int ReceiveSize = 512;
+int SendSize = 1024;
+int using48000 = 0;
+
+int SampleRate = 12000;
+
 
 BOOL UseLeft = TRUE;
 BOOL UseRight = TRUE;
@@ -123,8 +139,6 @@ void Sleep(int mS)
 // Windows and ALSA work with signed samples +- 32767
 // STM32 and Teensy DAC uses unsigned 0 - 4095
 
-short buffer[2][1200 * 2];			// Two Transfer/DMA buffers of 0.1 Sec
-short inbuffer[1200 * 2];		// Two Transfer/DMA buffers of 0.1 Sec
 
 BOOL Loopback = FALSE;
 //BOOL Loopback = TRUE;
@@ -967,7 +981,7 @@ int SoundCardWrite(short * input, int nSamples)
 
 //	Debugprintf("Tosend %d Avail %d", nSamples, (int)avail);
 
-	while (avail < nSamples || (MaxAvail - avail) > 12000)				// Limit to 1 sec of audio
+	while (avail < nSamples || (MaxAvail - avail) > SampleRate)				// Limit to 1 sec of audio
 	{
 		txSleep(10);
 		avail = snd_pcm_avail_update(playhandle);
@@ -1203,15 +1217,29 @@ void GetSoundDevices()
 
 int InitSound(BOOL Quiet)
 {
+	if (using48000)
+	{
+		ReceiveSize = 2048;
+		SendSize = 4096;		// 100 mS for now
+		SampleRate = 48000;
+	}
+	else
+	{
+		ReceiveSize = 512;
+		SendSize = 1024;
+		SampleRate = 12000;
+	}
+
 	GetSoundDevices();
 
 	switch (SoundMode)
 	{
 	case 0:				// ALSA
 
-		if (!OpenSoundCard(CaptureDevice, PlaybackDevice, 12000, 12000, Quiet))
+		if (!OpenSoundCard(CaptureDevice, PlaybackDevice, SampleRate, SampleRate, Quiet))
 			return FALSE;
 
+
 		break;
 
 	case 1:				// OSS
diff --git a/Config.cpp b/Config.cpp
index 0ca299e..bb5ebd8 100644
--- a/Config.cpp
+++ b/Config.cpp
@@ -1,4 +1,4 @@
-/*
+/*extern "C" 
 Copyright (C) 2019-2020 Andrei Kopanchuk UZ7HO
 
 This file is part of QtSoundModem
@@ -21,6 +21,7 @@ along with QtSoundModem.  If not, see http://www.gnu.org/licenses
 // UZ7HO Soundmodem Port by John Wiseman G8BPQ
 
 #include <QSettings>
+#include <QDialog>
 
 #include "UZ7HOStuff.h"
 
@@ -31,6 +32,9 @@ extern "C" int SoundMode;
 extern "C" int RX_SR;
 extern "C" int TX_SR;
 extern "C" int multiCore;
+extern "C" char * Wisdom;
+extern int WaterfallMin;
+extern int WaterfallMax;
 
 extern "C" word MEMRecovery[5];
 
@@ -40,8 +44,11 @@ extern "C" int UDPServerPort;
 extern "C" int TXPort;
 
 extern char UDPHost[64];
+extern QDialog * constellationDialog;
+extern QRect PSKRect;
 
 extern char CWIDCall[128];
+extern "C" char CWIDMark[32];
 extern int CWIDInterval;
 extern int CWIDLeft;
 extern int CWIDRight;
@@ -63,9 +70,13 @@ void GetPortSettings(int Chan);
 QVariant getAX25Param(const char * key, QVariant Default)
 {
 	char fullKey[64];
-
+	QVariant Q;
+	QByteArray x;
 	sprintf(fullKey, "%s/%s", Prefix, key);
-	return settings->value(fullKey, Default);
+	Q = settings->value(fullKey, Default);
+	x = Q.toString().toUtf8();
+
+	return Q;
 }
 
 void getAX25Params(int chan)
@@ -89,8 +100,6 @@ void GetPortSettings(int Chan)
 	resptime[Chan] = getAX25Param("RespTime", 1500).toInt();
 	TXFrmMode[Chan] = getAX25Param("TXFrmMode", 1).toInt();
 	max_frame_collector[Chan] = getAX25Param("FrameCollector", 6).toInt();
-	//exclude_callsigns[Chan]= getAX25Param("ExcludeCallsigns/");
-	//exclude_APRS_frm[Chan]= getAX25Param("ExcludeAPRSFrmType/");
 	KISS_opt[Chan] = getAX25Param("KISSOptimization", false).toInt();;
 	dyn_frack[Chan] = getAX25Param("DynamicFrack", false).toInt();;
 	recovery[Chan] = getAX25Param("BitRecovery", 0).toInt();
@@ -99,6 +108,8 @@ void GetPortSettings(int Chan)
 	IPOLL[Chan] = getAX25Param("IPOLL", 80).toInt();
 
 	strcpy(MyDigiCall[Chan], getAX25Param("MyDigiCall", "").toString().toUtf8());
+	strcpy(exclude_callsigns[Chan], getAX25Param("ExcludeCallsigns", "").toString().toUtf8());
+
 	fx25_mode[Chan] = getAX25Param("FX25", FX25_MODE_RX).toInt();
 	il2p_mode[Chan] = getAX25Param("IL2P", IL2P_MODE_NONE).toInt();
 	RSID_UI[Chan] = getAX25Param("RSID_UI", 0).toInt();
@@ -113,6 +124,8 @@ void getSettings()
 	QSettings* settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
 	settings->sync();
 
+	PSKRect = settings->value("PSKWindow").toRect();
+
 	SoundMode = settings->value("Init/SoundMode", 0).toInt();
 	UDPClientPort = settings->value("Init/UDPClientPort", 8888).toInt();
 	UDPServerPort = settings->value("Init/UDPServerPort", 8884).toInt();
@@ -150,9 +163,12 @@ void getSettings()
 
 	DualPTT = settings->value("Init/DualPTT", 1).toInt();
 	TX_rotate = settings->value("Init/TXRotate", 0).toInt();
-
 	multiCore = settings->value("Init/multiCore", 0).toInt();
 	MintoTray = settings->value("Init/MinimizetoTray", 1).toInt();
+	Wisdom = strdup(settings->value("Init/Wisdom", "").toString().toUtf8());
+	WaterfallMin = settings->value("Init/WaterfallMin", 0).toInt();
+	WaterfallMax = settings->value("Init/WaterfallMax", 3300).toInt();
+
 
 	rx_freq[0] = settings->value("Modem/RXFreq1", 1700).toInt();
 	rx_freq[1] = settings->value("Modem/RXFreq2", 1700).toInt();
@@ -209,7 +225,13 @@ void getSettings()
 	txdelay[2] = settings->value("Modem/TxDelay3", 250).toInt();
 	txdelay[3] = settings->value("Modem/TxDelay4", 250).toInt();
 
+	txtail[0] = settings->value("Modem/TxTail1", 50).toInt();
+	txtail[1] = settings->value("Modem/TxTail2", 50).toInt();
+	txtail[2] = settings->value("Modem/TxTail3", 50).toInt();
+	txtail[3] = settings->value("Modem/TxTail4", 50).toInt();
+
 	strcpy(CWIDCall, settings->value("Modem/CWIDCall", "").toString().toUtf8().toUpper());
+	strcpy(CWIDMark, settings->value("Modem/CWIDMark", "").toString().toUtf8().toUpper());
 	CWIDInterval = settings->value("Modem/CWIDInterval", 0).toInt();
 	CWIDLeft = settings->value("Modem/CWIDLeft", 0).toInt();
 	CWIDRight = settings->value("Modem/CWIDRight", 0).toInt();
@@ -331,6 +353,7 @@ void saveSettings()
 {
 	QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
 
+	settings->setValue("PSKWindow", constellationDialog->geometry());
 	settings->setValue("Init/SoundMode", SoundMode);
 	settings->setValue("Init/UDPClientPort", UDPClientPort);
 	settings->setValue("Init/UDPServerPort", UDPServerPort);
@@ -366,8 +389,11 @@ void saveSettings()
 	settings->setValue("Init/HamLibPort", HamLibPort);
 	settings->setValue("Init/HamLibHost", HamLibHost);
 	settings->setValue("Init/MinimizetoTray", MintoTray);
-
 	settings->setValue("Init/multiCore", multiCore);
+	settings->setValue("Init/Wisdom", Wisdom);
+
+	settings->setValue("Init/WaterfallMin", WaterfallMin);
+	settings->setValue("Init/WaterfallMax", WaterfallMax);
 
 	// Don't save freq on close as it could be offset by multiple decoders
 
@@ -423,6 +449,7 @@ void saveSettings()
 	settings->setValue("Modem/TxTail4", txtail[3]);
 
 	settings->setValue("Modem/CWIDCall", CWIDCall);
+	settings->setValue("Modem/CWIDMark", CWIDMark);
 	settings->setValue("Modem/CWIDInterval", CWIDInterval);
 	settings->setValue("Modem/CWIDLeft", CWIDLeft);
 	settings->setValue("Modem/CWIDRight", CWIDRight);
diff --git a/Config.cpp.bak b/Config.cpp.bak
deleted file mode 100644
index 51702ff..0000000
--- a/Config.cpp.bak
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
-Copyright (C) 2019-2020 Andrei Kopanchuk UZ7HO
-
-This file is part of QtSoundModem
-
-QtSoundModem is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-QtSoundModem is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with QtSoundModem.  If not, see http://www.gnu.org/licenses
-
-*/
-
-// UZ7HO Soundmodem Port by John Wiseman G8BPQ
-
-#include <QSettings>
-
-#include "UZ7HOStuff.h"
-
-extern "C" void get_exclude_list(char * line, TStringList * list);
-extern "C" void get_exclude_frm(char * line, TStringList * list);
-
-extern "C" int SoundMode; 
-extern "C" int RX_SR;
-extern "C" int TX_SR;
-extern "C" int multiCore;
-
-extern "C" word MEMRecovery[5];
-
-extern int MintoTray;
-extern "C" int UDPClientPort;
-extern "C" int UDPServerPort;
-extern "C" int TXPort;
-
-extern char UDPHost[64];
-
-extern char CWIDCall[128];
-extern int CWIDInterval;
-extern int CWIDLeft;
-extern int CWIDRight;
-extern int CWIDType;	
-
-// This makes geting settings for more channels easier
-
-char Prefix[16] = "AX25_A";
-
-void GetPortSettings(int Chan);
-
-QVariant getAX25Param(const char * key, QVariant Default)
-{
-	char fullKey[64];
-
-	sprintf(fullKey, "%s/%s", Prefix, key);
-	return settings->value(fullKey, Default);
-}
-
-void getAX25Params(int chan)
-{
-	Prefix[5] = chan + 'A';
-	GetPortSettings(chan);
-}
-
-
-void GetPortSettings(int Chan)
-{
-	tx_hitoneraisedb[Chan] = getAX25Param("HiToneRaise", 0).toInt();
-
-	maxframe[Chan] = getAX25Param("Maxframe", 3).toInt();
-	fracks[Chan] = getAX25Param("Retries", 15).toInt();
-	frack_time[Chan] = getAX25Param("FrackTime", 5).toInt();
-
-	idletime[Chan] = getAX25Param("IdleTime", 180).toInt();
-	slottime[Chan] = getAX25Param("SlotTime", 100).toInt();
-	persist[Chan] = getAX25Param("Persist", 128).toInt();
-	resptime[Chan] = getAX25Param("RespTime", 1500).toInt();
-	TXFrmMode[Chan] = getAX25Param("TXFrmMode", 1).toInt();
-	max_frame_collector[Chan] = getAX25Param("FrameCollector", 6).toInt();
-	//exclude_callsigns[Chan]= getAX25Param("ExcludeCallsigns/");
-	//exclude_APRS_frm[Chan]= getAX25Param("ExcludeAPRSFrmType/");
-	KISS_opt[Chan] = getAX25Param("KISSOptimization", false).toInt();;
-	dyn_frack[Chan] = getAX25Param("DynamicFrack", false).toInt();;
-	recovery[Chan] = getAX25Param("BitRecovery", 0).toInt();
-	NonAX25[Chan] = getAX25Param("NonAX25Frm", false).toInt();;
-	MEMRecovery[Chan]= getAX25Param("MEMRecovery", 200).toInt();
-	IPOLL[Chan] = getAX25Param("IPOLL", 80).toInt();
-
-	strcpy(MyDigiCall[Chan], getAX25Param("MyDigiCall", "").toString().toUtf8());
-	fx25_mode[Chan] = getAX25Param("FX25", FX25_MODE_RX).toInt();
-}
-
-void getSettings()
-{
-	int snd_ch;
-
-	QSettings* settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
-	settings->sync();
-
-	SoundMode = settings->value("Init/SoundMode", 0).toInt();
-	UDPClientPort = settings->value("Init/UDPClientPort", 8888).toInt();
-	UDPServerPort = settings->value("Init/UDPServerPort", 8884).toInt();
-	TXPort = settings->value("Init/TXPort", UDPServerPort).toInt();
-
-	strcpy(UDPHost, settings->value("Init/UDPHost", "192.168.1.255").toString().toUtf8());
-	UDPServ = settings->value("Init/UDPServer", FALSE).toBool();
-
-	RX_SR = settings->value("Init/RXSampleRate", 12000).toInt();
-	TX_SR = settings->value("Init/TXSampleRate", 12000).toInt();
-
-	strcpy(CaptureDevice, settings->value("Init/SndRXDeviceName", "hw:1,0").toString().toUtf8());
-	strcpy(PlaybackDevice, settings->value("Init/SndTXDeviceName", "hw:1,0").toString().toUtf8());
-
-	raduga = settings->value("Init/DispMode", DISP_RGB).toInt();
-
-	strcpy(PTTPort, settings->value("Init/PTT", "").toString().toUtf8());
-	PTTMode = settings->value("Init/PTTMode", 19200).toInt();
-	PTTBAUD = settings->value("Init/PTTBAUD", 19200).toInt();
-
-	strcpy(PTTOnString, settings->value("Init/PTTOnString", "").toString().toUtf8());
-	strcpy(PTTOffString, settings->value("Init/PTTOffString", "").toString().toUtf8());
-
-	pttGPIOPin = settings->value("Init/pttGPIOPin", 17).toInt();
-	pttGPIOPinR = settings->value("Init/pttGPIOPinR", 17).toInt();
-
-#ifdef WIN32
-	strcpy(CM108Addr, settings->value("Init/CM108Addr", "0xD8C:0x08").toString().toUtf8());
-#else
-	strcpy(CM108Addr, settings->value("Init/CM108Addr", "/dev/hidraw0").toString().toUtf8());
-#endif
-
-	HamLibPort = settings->value("Init/HamLibPort", 4532).toInt();
-	strcpy(HamLibHost, settings->value("Init/HamLibHost", "127.0.0.1").toString().toUtf8());
-
-	DualPTT = settings->value("Init/DualPTT", 1).toInt();
-	TX_rotate = settings->value("Init/TXRotate", 0).toInt();
-
-	multiCore = settings->value("Init/multiCore", 0).toInt();
-	MintoTray = settings->value("Init/MinimizetoTray", 1).toInt();
-
-	rx_freq[0] = settings->value("Modem/RXFreq1", 1700).toInt();
-	rx_freq[1] = settings->value("Modem/RXFreq2", 1700).toInt();
-	rx_freq[2] = settings->value("Modem/RXFreq3", 1700).toInt();
-	rx_freq[3] = settings->value("Modem/RXFreq4", 1700).toInt();
-
-	rcvr_offset[0] = settings->value("Modem/RcvrShift1", 30).toInt();
-	rcvr_offset[1] = settings->value("Modem/RcvrShift2", 30).toInt();
-	rcvr_offset[2] = settings->value("Modem/RcvrShift3", 30).toInt();
-	rcvr_offset[3] = settings->value("Modem/RcvrShift4", 30).toInt();
-	speed[0] = settings->value("Modem/ModemType1", SPEED_1200).toInt();
-	speed[1] = settings->value("Modem/ModemType2", SPEED_1200).toInt();
-	speed[2] = settings->value("Modem/ModemType3", SPEED_1200).toInt();
-	speed[3] = settings->value("Modem/ModemType4", SPEED_1200).toInt();
-
-	RCVR[0] = settings->value("Modem/NRRcvrPairs1", 0).toInt();;
-	RCVR[1] = settings->value("Modem/NRRcvrPairs2", 0).toInt();;
-	RCVR[2] = settings->value("Modem/NRRcvrPairs3", 0).toInt();;
-	RCVR[3] = settings->value("Modem/NRRcvrPairs4", 0).toInt();;
-
-	soundChannel[0] = settings->value("Modem/soundChannel1", 1).toInt();
-	soundChannel[1] = settings->value("Modem/soundChannel2", 0).toInt();
-	soundChannel[2] = settings->value("Modem/soundChannel3", 0).toInt();
-	soundChannel[3] = settings->value("Modem/soundChannel4", 0).toInt();
-
-	SCO = settings->value("Init/SCO", 0).toInt();
-
-	dcd_threshold = settings->value("Modem/DCDThreshold", 40).toInt();
-	rxOffset = settings->value("Modem/rxOffset", 0).toInt();
-
-	AGWServ = settings->value("AGWHost/Server", TRUE).toBool();
-	AGWPort = settings->value("AGWHost/Port", 8000).toInt();
-	KISSServ = settings->value("KISS/Server", FALSE).toBool();
-	KISSPort = settings->value("KISS/Port", 8105).toInt();
-
-	RX_Samplerate = RX_SR + RX_SR * 0.000001*RX_PPM;
-	TX_Samplerate = TX_SR + TX_SR * 0.000001*TX_PPM;
-
-	emph_all[0] = settings->value("Modem/PreEmphasisAll1", FALSE).toBool();
-	emph_all[1] = settings->value("Modem/PreEmphasisAll2", FALSE).toBool();
-	emph_all[2] = settings->value("Modem/PreEmphasisAll3", FALSE).toBool();
-	emph_all[3] = settings->value("Modem/PreEmphasisAll4", FALSE).toBool();
-
-	emph_db[0] = settings->value("Modem/PreEmphasisDB1", 0).toInt();
-	emph_db[1] = settings->value("Modem/PreEmphasisDB2", 0).toInt();
-	emph_db[2] = settings->value("Modem/PreEmphasisDB3", 0).toInt();
-	emph_db[3] = settings->value("Modem/PreEmphasisDB4", 0).toInt();
-
-	Firstwaterfall = settings->value("Window/Waterfall1", TRUE).toInt();
-	Secondwaterfall = settings->value("Window/Waterfall2", TRUE).toInt();
-
-	txdelay[0] = settings->value("Modem/TxDelay1", 250).toInt();
-	txdelay[1] = settings->value("Modem/TxDelay2", 250).toInt();
-	txdelay[2] = settings->value("Modem/TxDelay3", 250).toInt();
-	txdelay[3] = settings->value("Modem/TxDelay4", 250).toInt();
-
-	strcpy(CWIDCall, settings->value("Modem/CWIDCall", "").toString().toUtf8().toUpper());
-	CWIDInterval = settings->value("Modem/CWIDInterval", 0).toInt();
-	CWIDLeft = settings->value("Modem/CWIDLeft", 0).toInt();
-	CWIDRight = settings->value("Modem/CWIDRight", 0).toInt();
-	CWIDType = settings->value("Modem/CWIDType", 1).toInt();			// on/off
-
-
-	getAX25Params(0);
-	getAX25Params(1);
-	getAX25Params(2);
-	getAX25Params(3);
-
-	// Validate and process settings
-
-	UsingLeft = 0;
-	UsingRight = 0;
-	UsingBothChannels = 0;
-
-	for (int i = 0; i < 4; i++)
-	{
-		if (soundChannel[i] == LEFT)
-		{
-			UsingLeft = 1;
-			modemtoSoundLR[i] = 0;
-		}
-		else if (soundChannel[i] == RIGHT)
-		{
-			UsingRight = 1;
-			modemtoSoundLR[i] = 1;
-		}
-	}
-
-	if (UsingLeft && UsingRight)
-		UsingBothChannels = 1;
-
-	for (snd_ch = 0; snd_ch < 4; snd_ch++)
-	{
-		tx_hitoneraise[snd_ch] = powf(10.0f, -abs(tx_hitoneraisedb[snd_ch]) / 20.0f);
-
-		if (IPOLL[snd_ch] < 0)
-			IPOLL[snd_ch] = 0;
-		else if (IPOLL[snd_ch] > 65535)
-			IPOLL[snd_ch] = 65535;
-
-		if (MEMRecovery[snd_ch] < 1)
-			MEMRecovery[snd_ch] = 1;
-
-		//			if (MEMRecovery[snd_ch]> 65535)
-		//				MEMRecovery[snd_ch]= 65535;
-
-				/*
-				if resptime[snd_ch] < 0 then resptime[snd_ch]= 0;
-					if resptime[snd_ch] > 65535 then resptime[snd_ch]= 65535;
-					if persist[snd_ch] > 255 then persist[snd_ch]= 255;
-					if persist[snd_ch] < 32 then persist[snd_ch]= 32;
-					if fracks[snd_ch] < 1 then fracks[snd_ch]= 1;
-					if frack_time[snd_ch] < 1 then frack_time[snd_ch]= 1;
-					if idletime[snd_ch] < frack_time[snd_ch] then idletime[snd_ch]= 180;
-				*/
-
-		if (emph_db[snd_ch] < 0 || emph_db[snd_ch] > nr_emph)
-			emph_db[snd_ch] = 0;
-
-		if (max_frame_collector[snd_ch] > 6) max_frame_collector[snd_ch] = 6;
-		if (maxframe[snd_ch] == 0 || maxframe[snd_ch] > 7) maxframe[snd_ch] = 3;
-		if (qpsk_set[snd_ch].mode > 1)  qpsk_set[snd_ch].mode = 0;
-
-	}
-
-	delete(settings);
-}
-
-void SavePortSettings(int Chan);
-
-void saveAX25Param(const char * key, QVariant Value)
-{
-	char fullKey[64];
-
-	sprintf(fullKey, "%s/%s", Prefix, key);
-
-	settings->setValue(fullKey, Value);
-}
-
-void saveAX25Params(int chan)
-{
-	Prefix[5] = chan + 'A';
-	SavePortSettings(chan);
-}
-
-void SavePortSettings(int Chan)
-{
-	saveAX25Param("Retries", fracks[Chan]);
-	saveAX25Param("HiToneRaise", tx_hitoneraisedb[Chan]);
-	saveAX25Param("Maxframe",maxframe[Chan]);
-	saveAX25Param("Retries", fracks[Chan]);
-	saveAX25Param("FrackTime", frack_time[Chan]);
-	saveAX25Param("IdleTime", idletime[Chan]);
-	saveAX25Param("SlotTime", slottime[Chan]);
-	saveAX25Param("Persist", persist[Chan]);
-	saveAX25Param("RespTime", resptime[Chan]);
-	saveAX25Param("TXFrmMode", TXFrmMode[Chan]);
-	saveAX25Param("FrameCollector", max_frame_collector[Chan]);
-	saveAX25Param("ExcludeCallsigns", exclude_callsigns[Chan]);
-	saveAX25Param("ExcludeAPRSFrmType", exclude_APRS_frm[Chan]);
-	saveAX25Param("KISSOptimization", KISS_opt[Chan]);
-	saveAX25Param("DynamicFrack", dyn_frack[Chan]);
-	saveAX25Param("BitRecovery", recovery[Chan]);
-	saveAX25Param("NonAX25Frm", NonAX25[Chan]);
-	getAX25Param("MEMRecovery", MEMRecovery[Chan]);
-	saveAX25Param("IPOLL", IPOLL[Chan]);
-	saveAX25Param("MyDigiCall", MyDigiCall[Chan]);
-	saveAX25Param("FX25", fx25_mode[Chan]);
-}
-
-
-
-void saveSettings()
-{
-	QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
-
-	settings->setValue("Init/SoundMode", SoundMode);
-	settings->setValue("Init/UDPClientPort", UDPClientPort);
-	settings->setValue("Init/UDPServerPort", UDPServerPort);
-	settings->setValue("Init/TXPort", TXPort);
-
-	settings->setValue("Init/UDPServer", UDPServ);
-	settings->setValue("Init/UDPHost", UDPHost);
-
-
-	settings->setValue("Init/TXSampleRate", TX_SR);
-	settings->setValue("Init/RXSampleRate", RX_SR);
-
-	settings->setValue("Init/SndRXDeviceName", CaptureDevice);
-	settings->setValue("Init/SndTXDeviceName", PlaybackDevice);
-
-	settings->setValue("Init/SCO", SCO);
-	settings->setValue("Init/DualPTT", DualPTT);
-	settings->setValue("Init/TXRotate", TX_rotate);
-
-	settings->setValue("Init/DispMode", raduga);
-
-	settings->setValue("Init/PTT", PTTPort);
-	settings->setValue("Init/PTTBAUD", PTTBAUD);
-	settings->setValue("Init/PTTMode", PTTMode);
-
-	settings->setValue("Init/PTTOffString", PTTOffString);
-	settings->setValue("Init/PTTOnString", PTTOnString);
-
-	settings->setValue("Init/pttGPIOPin", pttGPIOPin);
-	settings->setValue("Init/pttGPIOPinR", pttGPIOPinR);
-
-	settings->setValue("Init/CM108Addr", CM108Addr);
-	settings->setValue("Init/HamLibPort", HamLibPort);
-	settings->setValue("Init/HamLibHost", HamLibHost);
-	settings->setValue("Init/MinimizetoTray", MintoTray);
-
-	settings->setValue("Init/multiCore", multiCore);
-
-	// Don't save freq on close as it could be offset by multiple decoders
-
-	settings->setValue("Modem/NRRcvrPairs1", RCVR[0]);
-	settings->setValue("Modem/NRRcvrPairs2", RCVR[1]);
-	settings->setValue("Modem/NRRcvrPairs3", RCVR[2]);
-	settings->setValue("Modem/NRRcvrPairs4", RCVR[3]);
-
-	settings->setValue("Modem/RcvrShift1", rcvr_offset[0]);
-	settings->setValue("Modem/RcvrShift2", rcvr_offset[1]);
-	settings->setValue("Modem/RcvrShift3", rcvr_offset[2]);
-	settings->setValue("Modem/RcvrShift4", rcvr_offset[3]);
-
-	settings->setValue("Modem/ModemType1", speed[0]);
-	settings->setValue("Modem/ModemType2", speed[1]);
-	settings->setValue("Modem/ModemType3", speed[2]);
-	settings->setValue("Modem/ModemType4", speed[3]);
-
-	settings->setValue("Modem/soundChannel1", soundChannel[0]);
-	settings->setValue("Modem/soundChannel2", soundChannel[1]);
-	settings->setValue("Modem/soundChannel3", soundChannel[2]);
-	settings->setValue("Modem/soundChannel4", soundChannel[3]);
-
-	settings->setValue("Modem/DCDThreshold", dcd_threshold);
-	settings->setValue("Modem/rxOffset", rxOffset);
-
-	settings->setValue("AGWHost/Server", AGWServ);
-	settings->setValue("AGWHost/Port", AGWPort);
-	settings->setValue("KISS/Server", KISSServ);
-	settings->setValue("KISS/Port", KISSPort);
-
-	settings->setValue("Modem/PreEmphasisAll1", emph_all[0]);
-	settings->setValue("Modem/PreEmphasisAll2", emph_all[1]);
-	settings->setValue("Modem/PreEmphasisAll3", emph_all[2]);
-	settings->setValue("Modem/PreEmphasisAll4", emph_all[3]);
-
-	settings->setValue("Modem/PreEmphasisDB1", emph_db[0]);
-	settings->setValue("Modem/PreEmphasisDB2", emph_db[1]);
-	settings->setValue("Modem/PreEmphasisDB3", emph_db[2]);
-	settings->setValue("Modem/PreEmphasisDB4", emph_db[3]);
-
-	settings->setValue("Window/Waterfall1", Firstwaterfall);
-	settings->setValue("Window/Waterfall2", Secondwaterfall);
-
-	settings->setValue("Modem/TxDelay1", txdelay[0]);
-	settings->setValue("Modem/TxDelay2", txdelay[1]);
-	settings->setValue("Modem/TxDelay3", txdelay[2]);
-	settings->setValue("Modem/TxDelay4", txdelay[3]);
-
-	settings->setValue("Modem/TxTail1", txtail[0]);
-	settings->setValue("Modem/TxTail2", txtail[1]);
-	settings->setValue("Modem/TxTail3", txtail[2]);
-	settings->setValue("Modem/TxTail4", txtail[3]);
-
-	settings->setValue("Modem/CWIDCall", CWIDCall);
-	settings->setValue("Modem/CWIDInterval", CWIDInterval);
-	settings->setValue("Modem/CWIDLeft", CWIDLeft);
-	settings->setValue("Modem/CWIDRight", CWIDRight);
-	settings->setValue("Modem/CWIDType", CWIDType);			
-
-
-	saveAX25Params(1);
-	saveAX25Params(2);
-	saveAX25Params(3);
-
-	settings->sync();
-
-	delete(settings);
-}
diff --git a/HEAD b/HEAD
deleted file mode 100644
index cb089cd..0000000
--- a/HEAD
+++ /dev/null
@@ -1 +0,0 @@
-ref: refs/heads/master
diff --git a/LinuxBits.c b/LinuxBits.c
new file mode 100644
index 0000000..75cb84b
--- /dev/null
+++ b/LinuxBits.c
@@ -0,0 +1,311 @@
+/*
+Copyright (C) 2019-2020 Andrei Kopanchuk UZ7HO
+
+This file is part of QtSoundModem
+
+QtSoundModem is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+QtSoundModem is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with QtSoundModem.  If not, see http://www.gnu.org/licenses
+
+*/
+
+//#define TXSILENCE
+
+// UZ7HO Soundmodem Port by John Wiseman G8BPQ
+//
+//	Audio interface Routine
+
+//	Passes audio samples to/from the sound interface
+
+//	As this is platform specific it also has the main() routine, which does
+//	platform specific initialisation before calling ardopmain()
+
+//	This is ALSASound.c for Linux
+//	Windows Version is Waveout.c
+
+
+
+
+void gpioSetMode(unsigned gpio, unsigned mode);
+void gpioWrite(unsigned gpio, unsigned level);
+int _memicmp(unsigned char *a, unsigned char *b, int n);
+int stricmp(const unsigned char * pStr1, const unsigned char *pStr2);
+int gpioInitialise(void);
+
+void Sleep(int mS)
+{
+	usleep(mS * 1000);
+	return;
+}
+
+
+
+
+// GPIO access stuff for PTT on PI
+
+#ifdef __ARM_ARCH
+
+/*
+   tiny_gpio.c
+   2016-04-30
+   Public Domain
+*/
+#include <stdio.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#define GPSET0 7
+#define GPSET1 8
+
+#define GPCLR0 10
+#define GPCLR1 11
+
+#define GPLEV0 13
+#define GPLEV1 14
+
+#define GPPUD     37
+#define GPPUDCLK0 38
+#define GPPUDCLK1 39
+
+unsigned piModel;
+unsigned piRev;
+
+static volatile uint32_t  *gpioReg = MAP_FAILED;
+
+#define PI_BANK (gpio>>5)
+#define PI_BIT  (1<<(gpio&0x1F))
+
+/* gpio modes. */
+
+// PTT via GPIO code
+
+#ifdef __ARM_ARCH
+
+#define PI_INPUT  0
+#define PI_OUTPUT 1
+#define PI_ALT0   4
+#define PI_ALT1   5
+#define PI_ALT2   6
+#define PI_ALT3   7
+#define PI_ALT4   3
+#define PI_ALT5   2
+
+// Set GPIO pin as output and set low
+
+void SetupGPIOPTT()
+{
+
+}
+#endif
+
+
+
+void gpioSetMode(unsigned gpio, unsigned mode)
+{
+   int reg, shift;
+
+   reg   =  gpio/10;
+   shift = (gpio%10) * 3;
+
+   gpioReg[reg] = (gpioReg[reg] & ~(7<<shift)) | (mode<<shift);
+}
+
+int gpioGetMode(unsigned gpio)
+{
+   int reg, shift;
+
+   reg   =  gpio/10;
+   shift = (gpio%10) * 3;
+
+   return (*(gpioReg + reg) >> shift) & 7;
+}
+
+/* Values for pull-ups/downs off, pull-down and pull-up. */
+
+#define PI_PUD_OFF  0
+#define PI_PUD_DOWN 1
+#define PI_PUD_UP   2
+
+void gpioSetPullUpDown(unsigned gpio, unsigned pud)
+{
+   *(gpioReg + GPPUD) = pud;
+
+   usleep(20);
+
+   *(gpioReg + GPPUDCLK0 + PI_BANK) = PI_BIT;
+
+   usleep(20);
+  
+   *(gpioReg + GPPUD) = 0;
+
+   *(gpioReg + GPPUDCLK0 + PI_BANK) = 0;
+}
+
+int gpioRead(unsigned gpio)
+{
+   if ((*(gpioReg + GPLEV0 + PI_BANK) & PI_BIT) != 0) return 1;
+   else                                         return 0;
+}
+void gpioWrite(unsigned gpio, unsigned level)
+{
+   if (level == 0)
+	   *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT;
+   else
+	   *(gpioReg + GPSET0 + PI_BANK) = PI_BIT;
+}
+
+void gpioTrigger(unsigned gpio, unsigned pulseLen, unsigned level)
+{
+   if (level == 0) *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT;
+   else            *(gpioReg + GPSET0 + PI_BANK) = PI_BIT;
+
+   usleep(pulseLen);
+
+   if (level != 0) *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT;
+   else            *(gpioReg + GPSET0 + PI_BANK) = PI_BIT;
+}
+
+/* Bit (1<<x) will be set if gpio x is high. */
+
+uint32_t gpioReadBank1(void) { return (*(gpioReg + GPLEV0)); }
+uint32_t gpioReadBank2(void) { return (*(gpioReg + GPLEV1)); }
+
+/* To clear gpio x bit or in (1<<x). */
+
+void gpioClearBank1(uint32_t bits) { *(gpioReg + GPCLR0) = bits; }
+void gpioClearBank2(uint32_t bits) { *(gpioReg + GPCLR1) = bits; }
+
+/* To set gpio x bit or in (1<<x). */
+
+void gpioSetBank1(uint32_t bits) { *(gpioReg + GPSET0) = bits; }
+void gpioSetBank2(uint32_t bits) { *(gpioReg + GPSET1) = bits; }
+
+unsigned gpioHardwareRevision(void)
+{
+   static unsigned rev = 0;
+
+   FILE * filp;
+   char buf[512];
+   char term;
+   int chars=4; /* number of chars in revision string */
+
+   if (rev) return rev;
+
+   piModel = 0;
+
+   filp = fopen ("/proc/cpuinfo", "r");
+
+   if (filp != NULL)
+   {
+      while (fgets(buf, sizeof(buf), filp) != NULL)
+      {
+         if (piModel == 0)
+         {
+            if (!strncasecmp("model name", buf, 10))
+            {
+               if (strstr (buf, "ARMv6") != NULL)
+               {
+                  piModel = 1;
+                  chars = 4;
+               }
+               else if (strstr (buf, "ARMv7") != NULL)
+               {
+                  piModel = 2;
+                  chars = 6;
+               }
+               else if (strstr (buf, "ARMv8") != NULL)
+               {
+                  piModel = 2;
+                  chars = 6;
+               }
+            }
+         }
+
+         if (!strncasecmp("revision", buf, 8))
+         {
+            if (sscanf(buf+strlen(buf)-(chars+1),
+               "%x%c", &rev, &term) == 2)
+            {
+               if (term != '\n') rev = 0;
+            }
+         }
+      }
+
+      fclose(filp);
+   }
+   return rev;
+}
+
+int gpioInitialise(void)
+{
+   int fd;
+
+   piRev = gpioHardwareRevision(); /* sets piModel and piRev */
+
+   fd = open("/dev/gpiomem", O_RDWR | O_SYNC) ;
+
+   if (fd < 0)
+   {
+      fprintf(stderr, "failed to open /dev/gpiomem\n");
+      return -1;
+   }
+
+   gpioReg = (uint32_t *)mmap(NULL, 0xB4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+
+   close(fd);
+
+   if (gpioReg == MAP_FAILED)
+   {
+      fprintf(stderr, "Bad, mmap failed\n");
+      return -1;
+   }
+   return 0;
+}
+
+
+	
+#endif
+
+
+
+int stricmp(const unsigned char * pStr1, const unsigned char *pStr2)
+{
+    unsigned char c1, c2;
+    int  v;
+
+	if (pStr1 == NULL)
+	{
+		if (pStr2)
+			Debugprintf("stricmp called with NULL 1st param - 2nd %s ", pStr2);
+		else
+			Debugprintf("stricmp called with two NULL params");
+
+		return 1;
+	}
+
+
+    do {
+        c1 = *pStr1++;
+        c2 = *pStr2++;
+        /* The casts are necessary when pStr1 is shorter & char is signed */
+        v = tolower(c1) - tolower(c2);
+    } while ((v == 0) && (c1 != '\0') && (c2 != '\0') );
+
+    return v;
+}
+
+
diff --git a/ModemDialog.ui b/ModemDialog.ui
index 5bc043c..fa91460 100644
--- a/ModemDialog.ui
+++ b/ModemDialog.ui
@@ -320,7 +320,7 @@
         </rect>
        </property>
        <property name="title">
-        <string>Modem type</string>
+        <string>Modem params</string>
        </property>
        <widget class="QLineEdit" name="RXShiftA">
         <property name="geometry">
@@ -337,7 +337,7 @@
          <rect>
           <x>100</x>
           <y>21</y>
-          <width>61</width>
+          <width>41</width>
           <height>23</height>
          </rect>
         </property>
@@ -346,8 +346,8 @@
         <property name="geometry">
          <rect>
           <x>100</x>
-          <y>51</y>
-          <width>61</width>
+          <y>50</y>
+          <width>41</width>
           <height>23</height>
          </rect>
         </property>
@@ -399,7 +399,7 @@
         <property name="geometry">
          <rect>
           <x>10</x>
-          <y>50</y>
+          <y>49</y>
           <width>71</width>
           <height>23</height>
          </rect>
@@ -464,7 +464,7 @@
         <property name="geometry">
          <rect>
           <x>176</x>
-          <y>51</y>
+          <y>50</y>
           <width>71</width>
           <height>23</height>
          </rect>
@@ -541,7 +541,7 @@
         <property name="geometry">
          <rect>
           <x>10</x>
-          <y>80</y>
+          <y>79</y>
           <width>71</width>
           <height>23</height>
          </rect>
@@ -554,7 +554,7 @@
         <property name="geometry">
          <rect>
           <x>176</x>
-          <y>81</y>
+          <y>80</y>
           <width>71</width>
           <height>23</height>
          </rect>
@@ -567,31 +567,8 @@
         <property name="geometry">
          <rect>
           <x>100</x>
-          <y>81</y>
-          <width>61</width>
-          <height>23</height>
-         </rect>
-        </property>
-       </widget>
-       <widget class="QLabel" name="label_71">
-        <property name="geometry">
-         <rect>
-          <x>10</x>
-          <y>109</y>
-          <width>71</width>
-          <height>23</height>
-         </rect>
-        </property>
-        <property name="text">
-         <string>Retries</string>
-        </property>
-       </widget>
-       <widget class="QLineEdit" name="RetriesA">
-        <property name="geometry">
-         <rect>
-          <x>100</x>
-          <y>110</y>
-          <width>61</width>
+          <y>80</y>
+          <width>41</width>
           <height>23</height>
          </rect>
         </property>
@@ -714,6 +691,52 @@
          </property>
         </item>
        </widget>
+       <widget class="QLabel" name="label_87">
+        <property name="geometry">
+         <rect>
+          <x>130</x>
+          <y>111</y>
+          <width>71</width>
+          <height>23</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>maxFrame</string>
+        </property>
+       </widget>
+       <widget class="QLineEdit" name="MaxFrameA">
+        <property name="geometry">
+         <rect>
+          <x>200</x>
+          <y>112</y>
+          <width>41</width>
+          <height>23</height>
+         </rect>
+        </property>
+       </widget>
+       <widget class="QLineEdit" name="RetriesA">
+        <property name="geometry">
+         <rect>
+          <x>70</x>
+          <y>111</y>
+          <width>41</width>
+          <height>23</height>
+         </rect>
+        </property>
+       </widget>
+       <widget class="QLabel" name="label_71">
+        <property name="geometry">
+         <rect>
+          <x>10</x>
+          <y>110</y>
+          <width>71</width>
+          <height>23</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Retries</string>
+        </property>
+       </widget>
       </widget>
      </widget>
     </widget>
@@ -1051,7 +1074,7 @@
         </rect>
        </property>
        <property name="title">
-        <string>Modem type</string>
+        <string>Modem params</string>
        </property>
        <widget class="QLineEdit" name="RXShiftB">
         <property name="geometry">
@@ -1304,29 +1327,6 @@
          </rect>
         </property>
        </widget>
-       <widget class="QLabel" name="label_74">
-        <property name="geometry">
-         <rect>
-          <x>10</x>
-          <y>109</y>
-          <width>71</width>
-          <height>23</height>
-         </rect>
-        </property>
-        <property name="text">
-         <string>Retries</string>
-        </property>
-       </widget>
-       <widget class="QLineEdit" name="RetriesB">
-        <property name="geometry">
-         <rect>
-          <x>100</x>
-          <y>110</y>
-          <width>61</width>
-          <height>23</height>
-         </rect>
-        </property>
-       </widget>
        <widget class="QPushButton" name="SendRSID_2">
         <property name="geometry">
          <rect>
@@ -1446,6 +1446,52 @@
         </item>
        </widget>
       </widget>
+      <widget class="QLabel" name="label_88">
+       <property name="geometry">
+        <rect>
+         <x>130</x>
+         <y>121</y>
+         <width>71</width>
+         <height>23</height>
+        </rect>
+       </property>
+       <property name="text">
+        <string>maxFrame</string>
+       </property>
+      </widget>
+      <widget class="QLineEdit" name="RetriesB">
+       <property name="geometry">
+        <rect>
+         <x>70</x>
+         <y>121</y>
+         <width>41</width>
+         <height>23</height>
+        </rect>
+       </property>
+      </widget>
+      <widget class="QLabel" name="label_74">
+       <property name="geometry">
+        <rect>
+         <x>10</x>
+         <y>121</y>
+         <width>71</width>
+         <height>23</height>
+        </rect>
+       </property>
+       <property name="text">
+        <string>Retries</string>
+       </property>
+      </widget>
+      <widget class="QLineEdit" name="MaxFrameB">
+       <property name="geometry">
+        <rect>
+         <x>200</x>
+         <y>121</y>
+         <width>41</width>
+         <height>23</height>
+        </rect>
+       </property>
+      </widget>
      </widget>
     </widget>
    </widget>
@@ -1782,7 +1828,7 @@
         </rect>
        </property>
        <property name="title">
-        <string>Modem type</string>
+        <string>Modem params</string>
        </property>
        <widget class="QLineEdit" name="RXShiftC">
         <property name="geometry">
@@ -2035,29 +2081,6 @@
          </rect>
         </property>
        </widget>
-       <widget class="QLabel" name="label_77">
-        <property name="geometry">
-         <rect>
-          <x>10</x>
-          <y>109</y>
-          <width>71</width>
-          <height>23</height>
-         </rect>
-        </property>
-        <property name="text">
-         <string>Retries</string>
-        </property>
-       </widget>
-       <widget class="QLineEdit" name="RetriesC">
-        <property name="geometry">
-         <rect>
-          <x>100</x>
-          <y>110</y>
-          <width>61</width>
-          <height>23</height>
-         </rect>
-        </property>
-       </widget>
        <widget class="QCheckBox" name="RSIDSABM_C">
         <property name="geometry">
          <rect>
@@ -2133,6 +2156,52 @@
          </rect>
         </property>
        </widget>
+       <widget class="QLabel" name="label_89">
+        <property name="geometry">
+         <rect>
+          <x>130</x>
+          <y>111</y>
+          <width>71</width>
+          <height>23</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>maxFrame</string>
+        </property>
+       </widget>
+       <widget class="QLineEdit" name="RetriesC">
+        <property name="geometry">
+         <rect>
+          <x>70</x>
+          <y>111</y>
+          <width>41</width>
+          <height>23</height>
+         </rect>
+        </property>
+       </widget>
+       <widget class="QLabel" name="label_77">
+        <property name="geometry">
+         <rect>
+          <x>10</x>
+          <y>111</y>
+          <width>71</width>
+          <height>23</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Retries</string>
+        </property>
+       </widget>
+       <widget class="QLineEdit" name="MaxFrameC">
+        <property name="geometry">
+         <rect>
+          <x>200</x>
+          <y>111</y>
+          <width>41</width>
+          <height>23</height>
+         </rect>
+        </property>
+       </widget>
       </widget>
       <widget class="QComboBox" name="IL2PModeC">
        <property name="geometry">
@@ -2513,7 +2582,7 @@
         </rect>
        </property>
        <property name="title">
-        <string>Modem type</string>
+        <string>Modem params</string>
        </property>
        <widget class="QLineEdit" name="RXShiftD">
         <property name="geometry">
@@ -2766,29 +2835,6 @@
          </rect>
         </property>
        </widget>
-       <widget class="QLabel" name="label_80">
-        <property name="geometry">
-         <rect>
-          <x>10</x>
-          <y>109</y>
-          <width>71</width>
-          <height>23</height>
-         </rect>
-        </property>
-        <property name="text">
-         <string>Retries</string>
-        </property>
-       </widget>
-       <widget class="QLineEdit" name="RetriesD">
-        <property name="geometry">
-         <rect>
-          <x>100</x>
-          <y>110</y>
-          <width>61</width>
-          <height>23</height>
-         </rect>
-        </property>
-       </widget>
        <widget class="QCheckBox" name="RSIDSABM_D">
         <property name="geometry">
          <rect>
@@ -2864,6 +2910,52 @@
          </rect>
         </property>
        </widget>
+       <widget class="QLabel" name="label_90">
+        <property name="geometry">
+         <rect>
+          <x>130</x>
+          <y>111</y>
+          <width>71</width>
+          <height>23</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>maxFrame</string>
+        </property>
+       </widget>
+       <widget class="QLineEdit" name="RetriesD">
+        <property name="geometry">
+         <rect>
+          <x>70</x>
+          <y>111</y>
+          <width>41</width>
+          <height>23</height>
+         </rect>
+        </property>
+       </widget>
+       <widget class="QLabel" name="label_80">
+        <property name="geometry">
+         <rect>
+          <x>10</x>
+          <y>111</y>
+          <width>71</width>
+          <height>23</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Retries</string>
+        </property>
+       </widget>
+       <widget class="QLineEdit" name="MaxFrameD">
+        <property name="geometry">
+         <rect>
+          <x>200</x>
+          <y>111</y>
+          <width>41</width>
+          <height>23</height>
+         </rect>
+        </property>
+       </widget>
       </widget>
       <widget class="QComboBox" name="IL2PModeD">
        <property name="geometry">
@@ -2963,8 +3055,8 @@
   <widget class="QLabel" name="label_7">
    <property name="geometry">
     <rect>
-     <x>80</x>
-     <y>520</y>
+     <x>44</x>
+     <y>516</y>
      <width>71</width>
      <height>20</height>
     </rect>
@@ -2976,8 +3068,8 @@
   <widget class="QLineEdit" name="CWIDCall">
    <property name="geometry">
     <rect>
-     <x>150</x>
-     <y>520</y>
+     <x>114</x>
+     <y>516</y>
      <width>51</width>
      <height>20</height>
     </rect>
@@ -2989,8 +3081,8 @@
   <widget class="QRadioButton" name="CWIDType">
    <property name="geometry">
     <rect>
-     <x>340</x>
-     <y>520</y>
+     <x>410</x>
+     <y>516</y>
      <width>61</width>
      <height>20</height>
     </rect>
@@ -3002,8 +3094,8 @@
   <widget class="QRadioButton" name="radioButton_2">
    <property name="geometry">
     <rect>
-     <x>390</x>
-     <y>520</y>
+     <x>460</x>
+     <y>516</y>
      <width>101</width>
      <height>20</height>
     </rect>
@@ -3015,8 +3107,8 @@
   <widget class="QLabel" name="label_66">
    <property name="geometry">
     <rect>
-     <x>220</x>
-     <y>520</y>
+     <x>190</x>
+     <y>516</y>
      <width>61</width>
      <height>20</height>
     </rect>
@@ -3028,13 +3120,42 @@
   <widget class="QLineEdit" name="CWIDInterval">
    <property name="geometry">
     <rect>
-     <x>270</x>
-     <y>520</y>
+     <x>240</x>
+     <y>516</y>
      <width>31</width>
      <height>20</height>
     </rect>
    </property>
   </widget>
+  <widget class="QLabel" name="label_91">
+   <property name="geometry">
+    <rect>
+     <x>294</x>
+     <y>516</y>
+     <width>61</width>
+     <height>20</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Mark Freq</string>
+   </property>
+  </widget>
+  <widget class="QLineEdit" name="CWIDMark">
+   <property name="geometry">
+    <rect>
+     <x>348</x>
+     <y>516</y>
+     <width>41</width>
+     <height>20</height>
+    </rect>
+   </property>
+   <property name="toolTip">
+    <string>Leave blank for default</string>
+   </property>
+   <property name="text">
+    <string/>
+   </property>
+  </widget>
  </widget>
  <resources/>
  <connections/>
diff --git a/Modulate.c b/Modulate.c
index 5eb8522..1f996db 100644
--- a/Modulate.c
+++ b/Modulate.c
@@ -5,7 +5,9 @@
 
 #define ARDOPBufferSize 12000 * 100
 
-extern short ARDOPTXBuffer[4][12000 * 100];	// Enough to hold whole frame of samples
+extern char CWIDMark[32];
+
+extern short ARDOPTXBuffer[4][ARDOPBufferSize];	// Enough to hold whole frame of samples
 
 extern int ARDOPTXLen[4];				// Length of frame
 extern int ARDOPTXPtr[4];				// Tx Pointer
@@ -966,7 +968,7 @@ void sendCWID(char * strID, BOOL CWOnOff, int Chan)
            0x557, 0x155, 0x755, 0x1DD5, 0x7775, 0x1DDDD, 0x1D57, 0x1D57};
 
 
-	float dblHiPhaseInc = 2 * M_PI * 1609.375f / 12000; // 1609.375 Hz High tone
+	float dblHiPhaseInc = 2 * M_PI * 1509.375f / 12000; // 1609.375 Hz High tone
 	float dblLoPhaseInc = 2 * M_PI * 1390.625f / 12000; // 1390.625  low tone
 	float dblHiPhase = 0;
  	float dblLoPhase = 0;
@@ -978,6 +980,23 @@ void sendCWID(char * strID, BOOL CWOnOff, int Chan)
 	char * index;
 	int intMask;
 	int idoffset;
+	int Filter = 1500;
+
+	if (CWIDMark[0])
+	{
+		// Want  nonstandard tones
+
+		float Mark = atof(CWIDMark);
+		float Space = Mark - 200;
+
+		dblHiPhaseInc = 2 * M_PI * Mark / 12000; // 1609.375 Hz High tone
+		dblLoPhaseInc = 2 * M_PI * Space / 12000; // 1390.625  low tone
+
+		if (CWOnOff)
+			Filter = Mark;
+		else
+			Filter = (Mark + Space) / 2;
+	}
 
     strlop(strID, '-');		// Remove any SSID    
 
@@ -1000,8 +1019,12 @@ void sendCWID(char * strID, BOOL CWOnOff, int Chan)
 			dblLoPhase -= 2 * M_PI;
 	}
 	
-	initFilter(500,1500, Chan);
-   
+	if (CWOnOff)
+		initFilter(500, Filter, Chan);
+	else
+		initFilter(200, Filter, Chan);
+
+
 	//Generate leader for VOX 6 dots long
 
 	for (k = 6; k >0; k--)
diff --git a/QtSoundModem-DESKTOP-MHE5LO8.vcxproj b/QtSoundModem-DESKTOP-MHE5LO8.vcxproj
new file mode 100644
index 0000000..adcd4b6
--- /dev/null
+++ b/QtSoundModem-DESKTOP-MHE5LO8.vcxproj
@@ -0,0 +1,292 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{4EDE958E-D0AC-37B4-81F7-78313A262DCD}</ProjectGuid>
+    <RootNamespace>QtSoundModem</RootNamespace>
+    <Keyword>QtVS_v304</Keyword>
+    <WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
+    <QtMsBuild Condition="'$(QtMsBuild)'=='' or !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <PlatformToolset>v141</PlatformToolset>
+    <OutputDirectory>release\</OutputDirectory>
+    <ATLMinimizesCRunTimeLibraryUsage>false</ATLMinimizesCRunTimeLibraryUsage>
+    <CharacterSet>NotSet</CharacterSet>
+    <ConfigurationType>Application</ConfigurationType>
+    <IntermediateDirectory>release\</IntermediateDirectory>
+    <PrimaryOutput>QtSoundModem</PrimaryOutput>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <PlatformToolset>v141</PlatformToolset>
+    <OutputDirectory>debug\</OutputDirectory>
+    <ATLMinimizesCRunTimeLibraryUsage>false</ATLMinimizesCRunTimeLibraryUsage>
+    <CharacterSet>NotSet</CharacterSet>
+    <ConfigurationType>Application</ConfigurationType>
+    <IntermediateDirectory>debug\</IntermediateDirectory>
+    <PrimaryOutput>QtSoundModem</PrimaryOutput>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
+    <Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
+  </Target>
+  <ImportGroup Label="ExtensionSettings" />
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
+    <Import Project="$(QtMsBuild)\qt_defaults.props" />
+  </ImportGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)Intermed\$(Platform)\$(Configuration)\</IntDir>
+    <TargetName>QtSoundModem</TargetName>
+    <IgnoreImportLibrary>true</IgnoreImportLibrary>
+    <LinkIncremental>true</LinkIncremental>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)Intermed\$(Platform)\$(Configuration)\\</IntDir>
+    <TargetName>QtSoundModem</TargetName>
+    <IgnoreImportLibrary>true</IgnoreImportLibrary>
+    <LinkIncremental>false</LinkIncremental>
+  </PropertyGroup>
+  <PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <QtInstall>5.14</QtInstall>
+    <QtModules>core;network;gui;widgets;serialport</QtModules>
+  </PropertyGroup>
+  <PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <QtInstall>5.14.2</QtInstall>
+    <QtModules>core;network;gui;widgets;serialport</QtModules>
+  </PropertyGroup>
+  <ImportGroup Condition="Exists('$(QtMsBuild)\qt.props')">
+    <Import Project="$(QtMsBuild)\qt.props" />
+  </ImportGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <AdditionalIncludeDirectories>rsid;.\GeneratedFiles\$(ConfigurationName);.\GeneratedFiles;.;release;/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalOptions>-Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <BrowseInformation>false</BrowseInformation>
+      <DebugInformationFormat>None</DebugInformationFormat>
+      <DisableSpecificWarnings>4577;4467;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+      <ExceptionHandling>Sync</ExceptionHandling>
+      <ObjectFileName>$(IntDir)</ObjectFileName>
+      <Optimization>MaxSpeed</Optimization>
+      <PreprocessorDefinitions>_WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;NDEBUG;QT_NO_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessToFile>false</PreprocessToFile>
+      <ProgramDataBaseFileName>$(OutDir)</ProgramDataBaseFileName>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+      <WarningLevel>Level3</WarningLevel>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies>libfftw3f-3.lib;shell32.lib;setupapi.lib;WS2_32.Lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>C:\opensslx86\lib;C:\Utils\my_sql\mysql-5.7.25-win32\lib;C:\Utils\postgresqlx86\pgsql\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
+      <DataExecutionPrevention>true</DataExecutionPrevention>
+      <GenerateDebugInformation>false</GenerateDebugInformation>
+      <IgnoreImportLibrary>true</IgnoreImportLibrary>
+      <LinkIncremental>false</LinkIncremental>
+      <OptimizeReferences>true</OptimizeReferences>
+      <OutputFile>$(OutDir)QtSoundModem.exe</OutputFile>
+      <RandomizedBaseAddress>true</RandomizedBaseAddress>
+      <SubSystem>Windows</SubSystem>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+    </Link>
+    <Midl>
+      <DefaultCharType>Unsigned</DefaultCharType>
+      <EnableErrorChecks>None</EnableErrorChecks>
+      <WarningLevel>0</WarningLevel>
+    </Midl>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;NDEBUG;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_NETWORK_LIB;QT_SERIALPORT_LIB;QT_CORE_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ResourceCompile>
+    <QtMoc>
+      <CompilerFlavor>msvc</CompilerFlavor>
+      <Include>./$(Configuration)/moc_predefs.h</Include>
+      <ExecutionDescription>Moc'ing %(Identity)...</ExecutionDescription>
+      <DynamicSource>output</DynamicSource>
+      <QtMocDir>$(IntDir)</QtMocDir>
+      <QtMocFileName>moc_%(Filename).cpp</QtMocFileName>
+    </QtMoc>
+    <QtRcc>
+      <InitFuncName>QtSoundModem</InitFuncName>
+      <Compression>default</Compression>
+      <ExecutionDescription>Rcc'ing %(Identity)...</ExecutionDescription>
+      <QtRccDir>$(IntDir)</QtRccDir>
+      <QtRccFileName>qrc_%(Filename).cpp</QtRccFileName>
+    </QtRcc>
+    <QtUic>
+      <ExecutionDescription>Uic'ing %(Identity)...</ExecutionDescription>
+      <QtUicDir>$(IntDir)</QtUicDir>
+      <QtUicFileName>ui_%(Filename).h</QtUicFileName>
+    </QtUic>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <AdditionalIncludeDirectories>.\GeneratedFiles\$(ConfigurationName);.\GeneratedFiles;.;debug;/include;rsid;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalOptions>-Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <BrowseInformation>false</BrowseInformation>
+      <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+      <DisableSpecificWarnings>4577;4467;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+      <ExceptionHandling>Sync</ExceptionHandling>
+      <ObjectFileName>$(IntDir)</ObjectFileName>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>_WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessToFile>false</PreprocessToFile>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+      <WarningLevel>Level3</WarningLevel>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <ProgramDataBaseFileName>$(OutDir)</ProgramDataBaseFileName>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies>libfftw3f-3.lib;shell32.lib;setupapi.lib;WS2_32.Lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>C:\opensslx86\lib;C:\Utils\my_sql\mysql-5.7.25-win32\lib;C:\Utils\postgresqlx86\pgsql\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
+      <DataExecutionPrevention>true</DataExecutionPrevention>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <IgnoreImportLibrary>true</IgnoreImportLibrary>
+      <OutputFile>$(OutDir)\QtSoundModem.exe</OutputFile>
+      <RandomizedBaseAddress>true</RandomizedBaseAddress>
+      <SubSystem>Windows</SubSystem>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+    </Link>
+    <Midl>
+      <DefaultCharType>Unsigned</DefaultCharType>
+      <EnableErrorChecks>None</EnableErrorChecks>
+      <WarningLevel>0</WarningLevel>
+    </Midl>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;QT_WIDGETS_LIB;QT_GUI_LIB;QT_NETWORK_LIB;QT_SERIALPORT_LIB;QT_CORE_LIB;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ResourceCompile>
+    <QtMoc>
+      <CompilerFlavor>msvc</CompilerFlavor>
+      <Include>./$(Configuration)/moc_predefs.h</Include>
+      <ExecutionDescription>Moc'ing %(Identity)...</ExecutionDescription>
+      <DynamicSource>output</DynamicSource>
+      <QtMocDir>$(IntDir)</QtMocDir>
+      <QtMocFileName>moc_%(Filename).cpp</QtMocFileName>
+    </QtMoc>
+    <QtRcc>
+      <InitFuncName>QtSoundModem</InitFuncName>
+      <Compression>default</Compression>
+      <ExecutionDescription>Rcc'ing %(Identity)...</ExecutionDescription>
+      <QtRccDir>$(IntDir)</QtRccDir>
+      <QtRccFileName>qrc_%(Filename).cpp</QtRccFileName>
+    </QtRcc>
+    <QtUic>
+      <ExecutionDescription>Uic'ing %(Identity)...</ExecutionDescription>
+      <QtUicDir>$(IntDir)</QtUicDir>
+      <QtUicFileName>ui_%(Filename).h</QtUicFileName>
+    </QtUic>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="ARDOPC.c" />
+    <ClCompile Include="BusyDetect.c" />
+    <ClCompile Include="Config.cpp" />
+    <ClCompile Include="dw9600.c" />
+    <ClCompile Include="hid.c" />
+    <ClCompile Include="il2p.c">
+      <CompileAs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">CompileAsC</CompileAs>
+    </ClCompile>
+    <ClCompile Include="Modulate.c" />
+    <ClCompile Include="QtSoundModem.cpp" />
+    <ClCompile Include="rsid.c" />
+    <ClCompile Include="RSUnit.c" />
+    <ClCompile Include="SMMain.c" />
+    <ClCompile Include="ShowFilter.cpp" />
+    <ClCompile Include="SoundInput.c" />
+    <ClCompile Include="UZ7HOUtils.c" />
+    <ClCompile Include="ardopSampleArrays.c" />
+    <ClCompile Include="ax25.c" />
+    <ClCompile Include="ax25_agw.c" />
+    <ClCompile Include="ax25_demod.c" />
+    <ClCompile Include="ax25_fec.c" />
+    <ClCompile Include="ax25_l2.c" />
+    <ClCompile Include="ax25_mod.c" />
+    <ClCompile Include="berlekamp.c" />
+    <ClCompile Include="galois.c" />
+    <ClCompile Include="kiss_mode.c" />
+    <ClCompile Include="main.cpp" />
+    <ClCompile Include="ofdm.c" />
+    <ClCompile Include="pktARDOP.c" />
+    <ClCompile Include="rs.c" />
+    <ClCompile Include="sm_main.c" />
+    <ClCompile Include="tcpCode.cpp" />
+    <ClCompile Include="Waveout.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <QtMoc Include="QtSoundModem.h">
+    </QtMoc>
+    <ClInclude Include="UZ7HOStuff.h" />
+    <QtMoc Include="tcpCode.h">
+    </QtMoc>
+  </ItemGroup>
+  <ItemGroup>
+    <CustomBuild Include="debug\moc_predefs.h.cbt">
+      <FileType>Document</FileType>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs)</AdditionalInputs>
+      <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -Zi -MDd -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2&gt;NUL &gt;debug\moc_predefs.h</Command>
+      <Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Generate moc_predefs.h</Message>
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">debug\moc_predefs.h;%(Outputs)</Outputs>
+    </CustomBuild>
+    <CustomBuild Include="release\moc_predefs.h.cbt">
+      <FileType>Document</FileType>
+      <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs)</AdditionalInputs>
+      <Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -O2 -MD -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2&gt;NUL &gt;release\moc_predefs.h</Command>
+      <Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Generate moc_predefs.h</Message>
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">release\moc_predefs.h;%(Outputs)</Outputs>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+    </CustomBuild>
+  </ItemGroup>
+  <ItemGroup>
+    <QtUic Include="ModemDialog.ui">
+    </QtUic>
+    <QtUic Include="QtSoundModem.ui">
+    </QtUic>
+    <QtUic Include="calibrateDialog.ui">
+    </QtUic>
+    <QtUic Include="devicesDialog.ui">
+    </QtUic>
+    <QtUic Include="filterWindow.ui">
+    </QtUic>
+  </ItemGroup>
+  <ItemGroup>
+    <QtRcc Include="QtSoundModem.qrc">
+    </QtRcc>
+  </ItemGroup>
+  <ItemGroup>
+    <ResourceCompile Include=".\QtSoundModem_resource.rc" />
+  </ItemGroup>
+  <ItemGroup>
+    <Image Include="QtSoundModem.ico" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
+    <Import Project="$(QtMsBuild)\qt.targets" />
+  </ImportGroup>
+  <ImportGroup Label="ExtensionTargets" />
+</Project>
\ No newline at end of file
diff --git a/QtSoundModem.cpp b/QtSoundModem.cpp
index c89bbf6..fdf137f 100644
--- a/QtSoundModem.cpp
+++ b/QtSoundModem.cpp
@@ -24,6 +24,14 @@ along with QtSoundModem.  If not, see http://www.gnu.org/licenses
 
 // Not Working 4psk100 FEC 
 
+// Thoughts on Waterfall Display.
+
+// Original used a 2048 sample FFT giving 5.859375 Hz bins. We plotted 1024 points, giving a 0 to 6000 specrum
+
+// If we want say 300 to 3300 we need about half the bin size so twice the fft size. But should we also fit required range to window size?
+
+// Unless we resize the most displayed bit of the screen in around 900 pixels. So each bin should be 3300 / 900 = 3.66667 Hz or a FFT size of around 3273
+
 #include "QtSoundModem.h"
 #include <qheaderview.h>
 //#include <QDebug>
@@ -42,7 +50,7 @@ along with QtSoundModem.  If not, see http://www.gnu.org/licenses
 #include "UZ7HOStuff.h"
 
 
-QImage *Constellation;
+QImage *Constellation[4];
 QImage *Waterfall[4] = { 0,0,0,0 };
 QImage *Header[4];
 QLabel *DCDLabel[4];
@@ -65,7 +73,7 @@ void saveSettings();
 void getSettings();
 extern "C" void CloseSound();
 extern "C" void GetSoundDevices();
-extern "C" char modes_name[modes_count][20];
+extern "C" char modes_name[modes_count][21];
 extern "C" int speed[5];
 extern "C" int KISSPort;
 extern "C" short rx_freq[5];
@@ -83,6 +91,7 @@ extern "C" int SoundMode;
 extern "C" int multiCore;
 
 extern "C" int refreshModems;
+int NeedPSKRefresh;
 
 extern "C" int pnt_change[5];
 extern "C" int needRSID[4];
@@ -93,6 +102,11 @@ extern "C" float MagOut[4096];
 extern "C" float MaxMagOut;
 extern "C" int MaxMagIndex;
 
+
+extern "C" int using48000;			// Set if using 48K sample rate (ie RUH Modem active)
+extern "C" int ReceiveSize;
+extern "C" int SendSize;		// 100 mS for now
+
 extern "C"
 { 
 	int InitSound(BOOL Report);
@@ -129,14 +143,23 @@ int FreqD = 1500;
 int DCD = 50;
 
 char CWIDCall[128] = "";
+extern "C" char CWIDMark[32] = "";
 int CWIDInterval = 0;
 int CWIDLeft = 0;
 int CWIDRight = 0;
 int CWIDType = 1;			// on/off
 
+int WaterfallMin = 00;
+int WaterfallMax = 6000;
+
+int Configuring = 0;
+
+extern "C" float BinSize;
+
 extern "C" { int RSID_SABM[4]; }
 extern "C" { int RSID_UI[4]; }
 extern "C" { int RSID_SetModem[4]; }
+extern "C" unsigned int pskStates[4];
 
 int Closing = FALSE;				// Set to stop background thread
 
@@ -446,17 +469,80 @@ void QtSoundModem::initWaterfall(int chan, int state)
 	QApplication::sendEvent(this, event);
 }
 
+QRect PSKRect = { 100,100,100,100 };
+
+QDialog * constellationDialog;
+QLabel * constellationLabel[4];
+QLabel * QualLabel[4];
+
 // Local copies
 
 QLabel *RXOffsetLabel;
 QSlider *RXOffset;
 
+extern "C" void CheckPSKWindows()
+{
+	NeedPSKRefresh = 1;
+}
+void DoPSKWindows()
+{
+	// Display Constellation for PSK Window;
+
+	int NextX = 0;
+	int i;
+
+	for (i = 0; i < 4; i++)
+	{
+		if (pskStates[i])
+		{
+			constellationLabel[i]->setGeometry(QRect(NextX, 19, 121, 121));
+			QualLabel[i]->setGeometry(QRect(1 + NextX, 1, 120, 15));
+			constellationLabel[i]->setVisible(1);
+			QualLabel[i]->setVisible(1);
+
+			NextX += 122;
+		}
+		else
+		{
+			constellationLabel[i]->setVisible(0);
+			QualLabel[i]->setVisible(0);
+		}
+	}
+	constellationDialog->resize(NextX, 140);
+}
+
+
+
 QtSoundModem::QtSoundModem(QWidget *parent) : QMainWindow(parent)
 {
 	ui.setupUi(this);
-
+ 
 	QSettings mysettings("QtSoundModem.ini", QSettings::IniFormat);
 
+	constellationDialog = new QDialog(nullptr, Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
+	constellationDialog->resize(488, 140);
+	constellationDialog->setGeometry(PSKRect);
+
+	QFont f("Arial", 8, QFont::Normal);
+
+	for (int i = 0; i < 4; i++)
+	{
+		char Text[16];
+		sprintf(Text, "Chan %c", i + 'A');
+
+		constellationLabel[i] = new QLabel(constellationDialog);
+		constellationDialog->setWindowTitle("PSK Constellations");
+
+		QualLabel[i] = new QLabel(constellationDialog);
+		QualLabel[i]->setText(Text);
+		QualLabel[i]->setFont(f);
+
+		Constellation[i] = new QImage(121, 121, QImage::Format_RGB32);
+		Constellation[i]->fill(black);
+		constellationLabel[i]->setPixmap(QPixmap::fromImage(*Constellation[i]));
+	}
+	constellationDialog->show();
+
 	if (MintoTray)
 	{
 		char popUp[256];
@@ -464,10 +550,35 @@ QtSoundModem::QtSoundModem(QWidget *parent) : QMainWindow(parent)
 		trayIcon = new QSystemTrayIcon(QIcon(":/QtSoundModem/soundmodem.ico"), this);
 		trayIcon->setToolTip(popUp);
 		trayIcon->show();
-
+		
 		connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(TrayActivated(QSystemTrayIcon::ActivationReason)));
 	}
 
+	using48000 = 0;			// Set if using 48K sample rate (ie RUH Modem active)
+	ReceiveSize = 512;
+	SendSize = 1024;		// 100 mS for now
+
+	for (int i = 0; i < 4; i++)
+	{
+		if (soundChannel[i] && (speed[i] == SPEED_RUH48 || speed[i] == SPEED_RUH96))
+		{
+			using48000 = 1;			// Set if using 48K sample rate (ie RUH Modem active)
+			ReceiveSize = 2048;
+			SendSize = 4096;		// 100 mS for now
+		}
+	}
+
+	float FFTCalc = 12000.0f / ((WaterfallMax - WaterfallMin) / 900.0f);
+
+	FFTSize = FFTCalc + 0.4999;
+
+	if (FFTSize > 8191)
+		FFTSize = 8190;
+
+	if (FFTSize & 1)		// odd
+		FFTSize--;
+
+	BinSize = 12000.0 / FFTSize;
 
 	restoreGeometry(mysettings.value("geometry").toByteArray());
 	restoreState(mysettings.value("windowState").toByteArray());
@@ -622,6 +733,11 @@ QtSoundModem::QtSoundModem(QWidget *parent) : QMainWindow(parent)
 	connect(ui.modeC, SIGNAL(currentIndexChanged(int)), this, SLOT(clickedSlotI(int)));
 	connect(ui.modeD, SIGNAL(currentIndexChanged(int)), this, SLOT(clickedSlotI(int)));
 
+	ModemA = speed[0];
+	ModemB = speed[1];
+	ModemC = speed[2];
+	ModemD = speed[3];
+
 	ui.modeA->setCurrentIndex(speed[0]);
 	ui.modeB->setCurrentIndex(speed[1]);
 	ui.modeC->setCurrentIndex(speed[2]);
@@ -666,7 +782,11 @@ QtSoundModem::QtSoundModem(QWidget *parent) : QMainWindow(parent)
 	connect(timer, SIGNAL(timeout()), this, SLOT(MyTimerSlot()));
 	timer->start(100);
 
+	QTimer *wftimer = new QTimer(this);
+	connect(wftimer, SIGNAL(timeout()), this, SLOT(doRestartWF()));
+	wftimer->start(1000 * 300);
 
+	
 	cwidtimer = new QTimer(this);
 	connect(cwidtimer, SIGNAL(timeout()), this, SLOT(CWIDTimer()));
 
@@ -750,6 +870,11 @@ void QtSoundModem::MyTimerSlot()
 		ui.centerC->setValue(rx_freq[2]);
 		ui.centerD->setValue(rx_freq[3]);
 	}
+	if (NeedPSKRefresh)
+	{
+		NeedPSKRefresh = 0;
+		DoPSKWindows();
+	}
 
 	show_grid();
 }
@@ -772,6 +897,44 @@ void QtSoundModem::returnPressed()
 
 }
 
+void CheckforChanges(int Mode, int OldMode)
+{
+	int old48000 = using48000;
+
+	if (OldMode != Mode && Mode == 15)
+	{
+		QMessageBox msgBox;
+
+		msgBox.setText("Warning!!\nARDOP Packet is NOT the same as ARDOP\n"
+			"It is an experimental mode for sending ax.25 frames using ARDOP packet formats\n");
+
+		msgBox.setStandardButtons(QMessageBox::Ok);
+
+		msgBox.exec();
+	}
+
+	// See if need to switch beween 12000 and 48000
+
+	using48000 = 0;			// Set if using 48K sample rate (ie RUH Modem active)
+	ReceiveSize = 512;
+	SendSize = 1024;		// 100 mS for now
+
+	for (int i = 0; i < 4; i++)
+	{
+		if (soundChannel[i] && (speed[i] == SPEED_RUH48 || speed[i] == SPEED_RUH96))
+		{
+			using48000 = 1;			// Set if using 48K sample rate (ie RUH Modem active)
+			ReceiveSize = 2048;
+			SendSize = 4096;		// 100 mS for now
+		}
+	}
+
+	if (using48000 != old48000)
+	{
+		InitSound(1);
+	}
+}
+
 
 void QtSoundModem::clickedSlotI(int i)
 {
@@ -781,8 +944,10 @@ void QtSoundModem::clickedSlotI(int i)
 
 	if (strcmp(Name, "modeA") == 0)
 	{
+		int OldModem = ModemA;
 		ModemA = ui.modeA->currentIndex();
 		set_speed(0, ModemA);
+		CheckforChanges(ModemA, OldModem);
 		saveSettings();
 		AGW_Report_Modem_Change(0);
 		return;
@@ -790,8 +955,10 @@ void QtSoundModem::clickedSlotI(int i)
 
 	if (strcmp(Name, "modeB") == 0)
 	{
+		int OldModem = ModemB;
 		ModemB = ui.modeB->currentIndex();
 		set_speed(1, ModemB);
+		CheckforChanges(ModemB, OldModem);
 		saveSettings();
 		AGW_Report_Modem_Change(1);
 		return;
@@ -799,8 +966,10 @@ void QtSoundModem::clickedSlotI(int i)
 
 	if (strcmp(Name, "modeC") == 0)
 	{
+		int OldModem = ModemC;
 		ModemC = ui.modeC->currentIndex();
 		set_speed(2, ModemC);
+		CheckforChanges(ModemC, OldModem);
 		saveSettings();
 		AGW_Report_Modem_Change(2);
 		return;
@@ -808,8 +977,10 @@ void QtSoundModem::clickedSlotI(int i)
 
 	if (strcmp(Name, "modeD") == 0)
 	{
+		int OldModem = ModemD;
 		ModemD = ui.modeD->currentIndex();
 		set_speed(3, ModemD);
+		CheckforChanges(ModemD, OldModem);
 		saveSettings();
 		AGW_Report_Modem_Change(3);
 		return;
@@ -817,7 +988,7 @@ void QtSoundModem::clickedSlotI(int i)
 
 	if (strcmp(Name, "centerA") == 0)
 	{
-		if (i > 300)
+		if (i > 299)
 		{
 			QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
 			ui.centerA->setValue(Freq_Change(0, i));
@@ -830,7 +1001,7 @@ void QtSoundModem::clickedSlotI(int i)
 
 	if (strcmp(Name, "centerB") == 0)
 	{
-		if (i > 300)
+		if (i > 299)
 		{
 			QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
 			ui.centerB->setValue(Freq_Change(1, i));
@@ -842,7 +1013,7 @@ void QtSoundModem::clickedSlotI(int i)
 
 	if (strcmp(Name, "centerC") == 0)
 	{
-		if (i > 300)
+		if (i > 299)
 		{
 			QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
 			ui.centerC->setValue(Freq_Change(2, i));
@@ -854,7 +1025,7 @@ void QtSoundModem::clickedSlotI(int i)
 
 	if (strcmp(Name, "centerD") == 0)
 	{
-		if (i > 300)
+		if (i > 299)
 		{
 			QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
 			ui.centerD->setValue(Freq_Change(3, i));
@@ -1161,6 +1332,15 @@ void QtSoundModem::doModems()
 	Dlg->KISSOptC->setChecked(KISS_opt[2]);
 	Dlg->KISSOptD->setChecked(KISS_opt[3]);
 
+	sprintf(valChar, "%d", maxframe[0]);
+	Dlg->MaxFrameA->setText(valChar);
+	sprintf(valChar, "%d", maxframe[1]);
+	Dlg->MaxFrameB->setText(valChar);
+	sprintf(valChar, "%d", maxframe[2]);
+	Dlg->MaxFrameC->setText(valChar);
+	sprintf(valChar, "%d", maxframe[3]);
+	Dlg->MaxFrameD->setText(valChar);
+
 	sprintf(valChar, "%d", txdelay[0]);
 	Dlg->TXDelayA->setText(valChar);
 	sprintf(valChar, "%d", txdelay[1]);
@@ -1170,6 +1350,7 @@ void QtSoundModem::doModems()
 	sprintf(valChar, "%d", txdelay[3]);
 	Dlg->TXDelayD->setText(valChar);
 
+
 	sprintf(valChar, "%d", txtail[0]);
 	Dlg->TXTailA->setText(valChar);
 	sprintf(valChar, "%d", txtail[1]);
@@ -1229,6 +1410,7 @@ void QtSoundModem::doModems()
 
 	Dlg->CWIDCall->setText(CWIDCall);
 	Dlg->CWIDInterval->setText(QString::number(CWIDInterval));
+	Dlg->CWIDMark->setText(CWIDMark);
 
 	if (CWIDType)
 		Dlg->radioButton_2->setChecked(1);
@@ -1370,6 +1552,23 @@ void QtSoundModem::modemSave()
 	Q = Dlg->TXDelayD->text();
 	txdelay[3] = Q.toInt();
 
+	Q = Dlg->MaxFrameA->text();
+	maxframe[0] = Q.toInt();
+
+	Q = Dlg->MaxFrameB->text();
+	maxframe[1] = Q.toInt();
+
+	Q = Dlg->MaxFrameC->text();
+	maxframe[2] = Q.toInt();
+
+	Q = Dlg->MaxFrameD->text();
+	maxframe[3] = Q.toInt();
+
+	if (maxframe[0] == 0 || maxframe[0] > 7) maxframe[0] = 3;
+	if (maxframe[1] == 0 || maxframe[1] > 7) maxframe[1] = 3;
+	if (maxframe[2] == 0 || maxframe[2] > 7) maxframe[2] = 3;
+	if (maxframe[3] == 0 || maxframe[3] > 7) maxframe[3] = 3;
+
 	Q = Dlg->TXTailA->text();
 	txtail[0] = Q.toInt();
 
@@ -1432,6 +1631,7 @@ void QtSoundModem::modemSave()
 
 
 	strcpy(CWIDCall, Dlg->CWIDCall->text().toUtf8().toUpper());
+	strcpy(CWIDMark, Dlg->CWIDMark->text().toUtf8().toUpper());
 	CWIDInterval = Dlg->CWIDInterval->text().toInt();
 	CWIDType = Dlg->radioButton_2->isChecked();
 
@@ -1802,8 +2002,6 @@ void QtSoundModem::doDevices()
 	connect(Dev->DualPTT, SIGNAL(toggled(bool)), this, SLOT(DualPTTChanged(bool)));
 	connect(Dev->PTTPort, SIGNAL(currentIndexChanged(int)), this, SLOT(PTTPortChanged(int)));
 
-
-
 	if (PTTMode == PTTCAT)
 		Dev->CAT->setChecked(true);
 	else
@@ -1841,6 +2039,9 @@ void QtSoundModem::doDevices()
 
 	Dev->multiCore->setChecked(multiCore);
 
+	Dev->WaterfallMin->setCurrentIndex(Dev->WaterfallMin->findText(QString::number(WaterfallMin), Qt::MatchFixedString));
+	Dev->WaterfallMax->setCurrentIndex(Dev->WaterfallMax->findText(QString::number(WaterfallMax), Qt::MatchFixedString));
+
 	QObject::connect(Dev->okButton, SIGNAL(clicked()), this, SLOT(deviceaccept()));
 	QObject::connect(Dev->cancelButton, SIGNAL(clicked()), this, SLOT(devicereject()));
 
@@ -1853,6 +2054,8 @@ void QtSoundModem::deviceaccept()
 	QVariant Q = Dev->inputDevice->currentText();
 	int cardChanged = 0;
 	char portString[32];
+	int newMax;
+	int newMin;
 
 	if (Dev->UDP->isChecked())
 	{
@@ -1879,9 +2082,8 @@ void QtSoundModem::deviceaccept()
 		int i = msgBox.exec();
 
 		if (i == QMessageBox::Ok)
-		{
+		{			
 			SoundMode = newSoundMode;
-
 			saveSettings();
 
 			Closing = 1;
@@ -2018,6 +2220,36 @@ void QtSoundModem::deviceaccept()
 		strcpy(HamLibHost, Q.toString().toUtf8());
 	}
 
+	Q = Dev->WaterfallMax->currentText();
+	newMax = Q.toInt();
+
+	Q = Dev->WaterfallMin->currentText();
+	newMin = Q.toInt();
+
+	if (newMax != WaterfallMax || newMin != WaterfallMin)
+	{
+		QMessageBox msgBox;
+
+		msgBox.setText("QtSoundModem must restart to change Waterfall range. Program will close if you hit Ok\n"
+		"It may take up to 30 seconds for the program to start for the first time after changing settings");
+
+		msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
+
+		int i = msgBox.exec();
+
+		if (i == QMessageBox::Ok)
+		{
+			Configuring = 1;			// Stop Waterfall
+
+			WaterfallMax = newMax;
+			WaterfallMin = newMin;
+			saveSettings();
+			Closing = 1;
+			return;
+		}
+	}
+
+
 	ClosePTTPort();
 	OpenPTTPort();
 
@@ -2033,10 +2265,10 @@ void QtSoundModem::deviceaccept()
 		InitSound(1);
 	}
 
-	// Reset title and tooltip in case ports changed
+	// Reset title and tooltip in case ports changed 
 
 	char Title[128];
-	sprintf(Title, "QtSoundModem Version %s Ports %d/%d", VersionString, AGWPort, KISSPort);
+	sprintf(Title, "QtSoundModem Version %s Ports %d%s/%d%s", VersionString, AGWPort, AGWServ ? "*": "", KISSPort, KISSServ ? "*" : "");
 	w->setWindowTitle(Title);
 
 	sprintf(Title, "QtSoundModem %d %d", AGWPort, KISSPort);
@@ -2275,8 +2507,7 @@ extern "C" void wf_Scale(int Chan)
 	if (nonGUIMode)
 		return;
 
-	float k;
-	int maxfreq, x, i;
+	int x, i;
 	char Textxx[20];
 	QImage * bm = Header[Chan];
 
@@ -2284,35 +2515,47 @@ extern "C" void wf_Scale(int Chan)
 	qPainter.setBrush(Qt::black);
 	qPainter.setPen(Qt::white);
 
-	maxfreq = roundf(RX_Samplerate*0.005);
-	k = 100 * fft_size / RX_Samplerate;
-
 	if (Chan == 0)
 		sprintf(Textxx, "Left");
 	else
 		sprintf(Textxx, "Right");
 
-	qPainter.drawText(2, 1,
-		100, 20, 0, Textxx);
+#ifdef WIN32
+	int Top = 3;
+#else
+	int Top = 4;
+#endif
 
-	for (i = 0; i < maxfreq; i++)
+	qPainter.drawText(2, Top, 100, 20, 0, Textxx);
+
+	// We drew markers every 100 Hz or 100 / binsize pixels
+
+
+	int Markers = ((WaterfallMax - WaterfallMin) / 100) + 5;			// Number of Markers to draw
+	int Freq = WaterfallMin;
+	float PixelsPerMarker = 100.0 / BinSize;
+
+
+
+	for (i = 0; i < Markers; i++)
 	{
-		x = round(k*i);
+		x = round(PixelsPerMarker * i);
 		if (x < 1025)
 		{
-			if ((i % 5) == 0)
-				qPainter.drawLine(x, 20, x, 13);
+			if ((Freq % 500) == 0)
+				qPainter.drawLine(x, 22, x, 15);
 			else
-				qPainter.drawLine(x, 20, x, 16);
+				qPainter.drawLine(x, 22, x, 18);
 
-			if ((i % 10) == 0)
+			if ((Freq % 500) == 0)
 			{
-				sprintf(Textxx, "%d", i * 100);
+				sprintf(Textxx, "%d", Freq);
 
-				qPainter.drawText(x - 12, 1,
-					100, 20, 0, Textxx);
+				if (x < 924)
+					qPainter.drawText(x - 12, Top, 100, 20, 0, Textxx);
 			}
 		}
+		Freq += 100;
 	}
 	HeaderCopy[Chan]->setPixmap(QPixmap::fromImage(*bm));
 
@@ -2326,7 +2569,6 @@ void do_pointer(int waterfall)
 	if (nonGUIMode)
 		return;
 
-	float x;
 
 	int x1, x2, k, pos1, pos2, pos3;
 	QImage * bm = Header[waterfall];
@@ -2337,10 +2579,12 @@ void do_pointer(int waterfall)
 
 	//	bm->fill(black);
 
-	qPainter.fillRect(0, 26, 1024, 9, Qt::black);
+	qPainter.fillRect(0, 23, 1024, 10, Qt::black);
 
-	k = 29;
-	x = fft_size / RX_Samplerate;
+	// We drew markers every 100 Hz or 100 / binsize pixels
+
+	float PixelsPerHz = 1.0 / BinSize;
+	k = 28;
 
 	// draw all enabled ports on the ports on this soundcard
 
@@ -2366,9 +2610,9 @@ void do_pointer(int waterfall)
 			if ((waterfall == 0 && soundChannel[i] == RIGHT) || (waterfall == 1 && soundChannel[i] == LEFT))
 				continue;
 
-		pos1 = roundf(((rxOffset + chanOffset[i] + rx_freq[i]) - 0.5*rx_shift[i])*x) - 5;
-		pos2 = roundf(((rxOffset + chanOffset[i] + rx_freq[i]) + 0.5*rx_shift[i])*x) - 5;
-		pos3 = roundf((rxOffset + chanOffset[i] + rx_freq[i]) * x);
+		pos1 = roundf((((rxOffset + chanOffset[i] + rx_freq[i]) - 0.5*rx_shift[i]) - WaterfallMin) * PixelsPerHz) - 5;
+		pos2 = roundf((((rxOffset + chanOffset[i] + rx_freq[i]) + 0.5*rx_shift[i]) - WaterfallMin) * PixelsPerHz) - 5;
+		pos3 = roundf(((rxOffset + chanOffset[i] + rx_freq[i])) - WaterfallMin  * PixelsPerHz);
 		x1 = pos1 + 5;
 		x2 = pos2 + 5;
 
@@ -2382,12 +2626,11 @@ void do_pointer(int waterfall)
 		{
 			// Draw TX posn if rxOffset used
 
-			pos3 = roundf(rx_freq[i] * x);
+			pos3 = roundf((rx_freq[i] - WaterfallMin) * PixelsPerHz);
 			qPainter.setPen(Qt::magenta);
 			qPainter.drawLine(pos3, k - 3, pos3, k + 3);
 			qPainter.drawLine(pos3, k - 3, pos3, k + 3);
 			qPainter.drawLine(pos3 - 2, k - 3, pos3 + 2, k - 3);
-
 		}
 	}
 	HeaderCopy[waterfall]->setPixmap(QPixmap::fromImage(*bm));
@@ -2448,6 +2691,7 @@ extern "C" void doWaterfall(int snd_ch)
 
 
 extern "C" float aFFTAmpl[1024];
+extern "C" void SMUpdateBusyDetector(int LR, float * Real, float *Imag);
 
 void doWaterfallThread(void * param)
 {
@@ -2455,20 +2699,22 @@ void doWaterfallThread(void * param)
 
 	QImage * bm = Waterfall[snd_ch];
 	
-	word  i, wid;
+	int  i;
 	single  mag;
 	UCHAR * p;
-	UCHAR Line[4096];
+	UCHAR Line[4096] = "";			// 4 bytes per pixel
 
-	int lineLen;
-	word  hfft_size;
+	int lineLen, Start, End;
+	word  hFFTSize;
 	Byte  n;
-	float RealOut[4096] = { 0 };
-	float ImagOut[4096];
+	float RealOut[8192] = { 0 };
+	float ImagOut[8192];
 
-	QRegion exposed;
+	if (Configuring)
+		return;
+
+	hFFTSize = FFTSize / 2;
 
-	hfft_size = fft_size / 2;
 
 	// I think an FFT should produce n/2 bins, each of Samp/n Hz
 	// Looks like my code only works with n a power of 2
@@ -2476,13 +2722,17 @@ void doWaterfallThread(void * param)
 	// So can use 1024 or 4096. 1024 gives 512 bins of 11.71875 and a 512 pixel 
 	// display (is this enough?)
 
-	// This does 2048
+
+
+	Start = (WaterfallMin / BinSize);		// First and last bins to process
+	End = (WaterfallMax / BinSize);
+
 
 	if (0)	//RSID_WF
 	{
 		// Use the Magnitudes in float aFFTAmpl[RSID_FFT_SIZE];
 
-		for (i = 0; i < hfft_size; i++)
+		for (i = 0; i < hFFTSize; i++)
 		{
 			mag = aFFTAmpl[i];
 
@@ -2503,12 +2753,12 @@ void doWaterfallThread(void * param)
 		}
 	}
 	else
-	{
+	{		
 		dofft(&fft_buf[snd_ch][0], RealOut, ImagOut);
 
 		//	FourierTransform(1024, &fft_buf[snd_ch][0], RealOut, ImagOut, 0);
 
-		for (i = 0; i < hfft_size; i++)
+		for (i = Start; i < End; i++)
 		{
 			//mag: = ComplexMag(fft_d[i])*0.00000042;
 
@@ -2537,75 +2787,22 @@ void doWaterfallThread(void * param)
 			if (mag < 0)
 				mag = 0;
 
-			MagOut[i] = mag;
+			MagOut[i] = mag;					// for Freq Guess
 			fft_disp[snd_ch][i] = round(mag);
 		}
 	}
 
+	SMUpdateBusyDetector(snd_ch, RealOut, ImagOut);
 
-
-	/*
-		for (i = 0; i < hfft_size; i++)
-			fft[i] = (powf(RealOut[i], 2) + powf(ImagOut[i], 2));
-
-		for (i = 0; i < hfft_size; i++)
-		{
-			if (fft[i] > max)
-			{
-				max = fft[i];
-				imax = i;
-			}
-		}
-
-		if (max > 0)
-		{
-			for (i = 0; i < hfft_size; i++)
-				fft[i] = fft[i] / max;
-		}
-
-
-		for (i = 0; i < hfft_size; i++)
-		{
-			mag = fft[i];
-
-			if (mag < 0.00001f)
-				mag = 0.00001f;
-
-			if (mag > 1.0f)
-				mag = 1.0f;
-
-			mag = 22 * log2f(mag) + 255;
-
-			if (mag < 0)
-				mag = 0;
-
-			fft_disp[snd_ch][i] = round(mag);
-		}
-
-		*/
-
-	//	bm[snd_ch].Canvas.CopyRect(d, bm[snd_ch].canvas, s)
-
-	//pm->scroll(0, 1, 0, 0, 1024, 80, &exposed);
-
-	// Each bin is 12000 /2048 = 5.859375
-	// I think we plot at 6 Hz per pixel.
-
-	wid = bm->width();
-	if (wid > hfft_size)
-		wid = hfft_size;
-
-	wid = wid - 1;
+	if (bm == 0)
+		return;
 
 	p = Line;
 	lineLen = bm->bytesPerLine();
 
-	if (wid > lineLen / 4)
-		wid = lineLen / 4;
-
 	if (raduga == DISP_MONO)
 	{
-		for (i = 0; i < wid; i++)
+		for (i = Start; i < End; i++)
 		{
 			n = fft_disp[snd_ch][i];
 			*(p++) = n;					// all colours the same
@@ -2616,11 +2813,10 @@ void doWaterfallThread(void * param)
 	}
 	else
 	{
-		for (i = 0; i < wid; i++)
+		for (i = Start; i < End; i++)
 		{
 			n = fft_disp[snd_ch][i];
-
-		memcpy(p, &RGBWF[n], 4);
+			memcpy(p, &RGBWF[n], 4);
 			p += 4;
 		}
 	}
@@ -2856,3 +3052,82 @@ void QtSoundModem::onTEselectionChanged()
 	x->copy();
 }
 
+#define ConstellationHeight 121
+#define ConstellationWidth 121
+#define PLOTRADIUS 60
+
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+
+extern "C" int SMUpdatePhaseConstellation(int chan, float * Phases, float * Mags, int intPSKPhase, int Count)
+{
+	// Subroutine to update bmpConstellation plot for PSK modes...
+	// Skip plotting and calculations of intPSKPhase(0) as this is a reference phase (9/30/2014)
+
+	float dblPhaseError;
+	float dblPhaseErrorSum = 0;
+	float intP = 0;
+	float dblRad = 0;
+	float dblAvgRad = 0;
+	float dbPhaseStep;
+	float MagMax = 0;
+	float dblPlotRotation = 0;
+
+	int i, intQuality;
+
+	int intX, intY;
+	int yCenter = (ConstellationHeight - 2) / 2;
+	int xCenter = (ConstellationWidth - 2) / 2;
+
+	Constellation[chan]->fill(black);
+
+	for (i = 0; i < 120; i++)
+	{
+		Constellation[chan]->setPixel(xCenter, i, cyan);
+		Constellation[chan]->setPixel(i, xCenter, cyan);
+	}
+
+	if (Count == 0)
+		return 0;
+
+	dbPhaseStep = 2 * M_PI / intPSKPhase;
+
+	for (i = 1; i < Count; i++)  // Don't plot the first phase (reference)
+	{
+		MagMax = MAX(MagMax, Mags[i]); // find the max magnitude to auto scale
+		dblAvgRad += Mags[i];
+	}
+
+	dblAvgRad = dblAvgRad / Count; // the average radius
+
+	for (i = 0; i < Count; i++) 
+	{
+		dblRad = PLOTRADIUS * Mags[i] / MagMax; //  scale the radius dblRad based on intMag
+		intP = round((Phases[i]) / dbPhaseStep);
+
+		// compute the Phase error
+
+		dblPhaseError = fabsf(Phases[i] - intP * dbPhaseStep); // always positive and < .5 *  dblPhaseStep
+		dblPhaseErrorSum += dblPhaseError;
+
+		intX = xCenter + dblRad * cosf(dblPlotRotation + Phases[i]);
+		intY = yCenter + dblRad * sinf(dblPlotRotation + Phases[i]);
+
+		if (intX > 0 && intY > 0)
+			if (intX != xCenter && intY != yCenter)
+				Constellation[chan]->setPixel(intX, intY, yellow);
+	}
+
+	dblAvgRad = dblAvgRad / Count; // the average radius
+
+	intQuality = MAX(0, ((100 - 200 * (dblPhaseErrorSum / (Count)) / dbPhaseStep))); // ignore radius error for (PSK) but include for QAM
+
+	char QualText[64];
+	sprintf(QualText, "Chan %c Qual = %d", chan + 'A', intQuality);
+	QualLabel[chan]->setText(QualText);
+	constellationLabel[chan]->setPixmap(QPixmap::fromImage(*Constellation[chan]));
+//	constellationDialog[chan]->setWindowTitle(QualText);
+	return intQuality;
+}
+
+
+
diff --git a/QtSoundModem.cpp.bak b/QtSoundModem.cpp.bak
new file mode 100644
index 0000000..3efef28
--- /dev/null
+++ b/QtSoundModem.cpp.bak
@@ -0,0 +1,2866 @@
+/*
+Copyright (C) 2019-2020 Andrei Kopanchuk UZ7HO
+
+This file is part of QtSoundModem
+
+QtSoundModem is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+QtSoundModem is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with QtSoundModem.  If not, see http://www.gnu.org/licenses
+
+*/
+
+// UZ7HO Soundmodem Port by John Wiseman G8BPQ
+
+// UZ7HO Soundmodem Port
+
+// Not Working 4psk100 FEC 
+
+// Thoughts on Waterfall Display.
+
+// Original used a 2048 sample FFT giving 5.859375 Hz bins. We plotted 1024 points, giving a 0 to 6000 specrum
+
+// If we want say 300 to 3300 we need about half the bin size so twice the fft size. But should we also fit required range to window size?
+
+// Unless we resize the most displayed bit of the screen in around 900 pixels. So each bin should be 3300 / 900 = 3.66667 Hz or a FFT size of around 3273
+
+#include "QtSoundModem.h"
+#include <qheaderview.h>
+//#include <QDebug>
+#include <QHostAddress>
+#include <QAbstractSocket>
+#include <QSettings>
+#include <QPainter>
+#include <QtSerialPort/QSerialPort>
+#include <QtSerialPort/QSerialPortInfo>
+#include <QMessageBox>
+#include <QTimer>
+#include <qevent.h>
+#include <QStandardItemModel>
+#include <QScrollBar>
+
+#include "UZ7HOStuff.h"
+
+
+QImage *Constellation;
+QImage *Waterfall[4] = { 0,0,0,0 };
+QImage *Header[4];
+QLabel *DCDLabel[4];
+QLineEdit *chanOffsetLabel[4];
+QImage *DCDLed[4];
+
+QImage *RXLevel;
+
+QLabel *WaterfallCopy[2];
+QLabel *HeaderCopy[2];
+
+QTextEdit * monWindowCopy;
+
+extern workerThread *t;
+extern QtSoundModem * w;
+
+QList<QSerialPortInfo> Ports = QSerialPortInfo::availablePorts();
+
+void saveSettings();
+void getSettings();
+extern "C" void CloseSound();
+extern "C" void GetSoundDevices();
+extern "C" char modes_name[modes_count][20];
+extern "C" int speed[5];
+extern "C" int KISSPort;
+extern "C" short rx_freq[5];
+
+extern "C" int CaptureCount;
+extern "C" int PlaybackCount;
+
+extern "C" int CaptureIndex;		// Card number
+extern "C" int PlayBackIndex;
+
+extern "C" char CaptureNames[16][256];
+extern "C" char PlaybackNames[16][256];
+
+extern "C" int SoundMode;
+extern "C" int multiCore;
+
+extern "C" int refreshModems;
+
+extern "C" int pnt_change[5];
+extern "C" int needRSID[4];
+
+extern "C" int needSetOffset[4];
+
+extern "C" float MagOut[4096];
+extern "C" float MaxMagOut;
+extern "C" int MaxMagIndex;
+
+extern "C"
+{ 
+	int InitSound(BOOL Report);
+	void soundMain();
+	void MainLoop();
+	void modulator(UCHAR snd_ch, int buf_size);
+	void SampleSink(int LR, short Sample);
+	void doCalib(int Port, int Act);
+	int Freq_Change(int Chan, int Freq);
+	void set_speed(int snd_ch, int Modem);
+	void init_speed(int snd_ch);
+	void wf_pointer(int snd_ch);
+	void FourierTransform(int NumSamples, short * RealIn, float * RealOut, float * ImagOut, int InverseTransform);
+	void dofft(short * in, float * outr, float * outi);
+	void init_raduga();
+	void wf_Scale(int Chan);
+	void AGW_Report_Modem_Change(int port);
+	char * strlop(char * buf, char delim);
+	void sendRSID(int Chan, int dropTX);
+	void RSIDinitfft();
+	void il2p_init(int il2p_debug);
+}
+
+void make_graph_buf(float * buf, short tap, QPainter * bitmap);
+
+int ModemA = 2;
+int ModemB = 2;
+int ModemC = 2;
+int ModemD = 2;
+int FreqA = 1500;
+int FreqB = 1500;
+int FreqC = 1500;
+int FreqD = 1500;
+int DCD = 50;
+
+char CWIDCall[128] = "";
+int CWIDInterval = 0;
+int CWIDLeft = 0;
+int CWIDRight = 0;
+int CWIDType = 1;			// on/off
+
+extern "C" { int RSID_SABM[4]; }
+extern "C" { int RSID_UI[4]; }
+extern "C" { int RSID_SetModem[4]; }
+
+int Closing = FALSE;				// Set to stop background thread
+
+QRgb white = qRgb(255, 255, 255);
+QRgb black = qRgb(0, 0, 0);
+
+QRgb green = qRgb(0, 255, 0);
+QRgb red = qRgb(255, 0, 0);
+QRgb yellow = qRgb(255, 255, 0);
+QRgb cyan = qRgb(0, 255, 255);
+
+// Indexed colour list from ARDOPC
+
+#define WHITE 0
+#define Tomato 1
+#define Gold 2
+#define Lime 3
+#define Yellow 4
+#define Orange 5
+#define Khaki 6
+#define Cyan 7
+#define DeepSkyBlue 8
+#define RoyalBlue 9
+#define Navy 10
+#define Black 11
+#define Goldenrod 12
+#define Fuchsia 13
+
+QRgb vbColours[16] = { qRgb(255, 255, 255), qRgb(255, 99, 71), qRgb(255, 215, 0), qRgb(0, 255, 0),
+						qRgb(255, 255, 0), qRgb(255, 165, 0), qRgb(240, 240, 140), qRgb(0, 255, 255),
+						qRgb(0, 191, 255), qRgb(65, 105, 225), qRgb(0, 0, 128), qRgb(0, 0, 0),
+						qRgb(218, 165, 32), qRgb(255, 0, 255) };
+
+unsigned char  WaterfallLines[2][80][4096] = { 0 };
+int NextWaterfallLine[2] = { 0 };
+
+unsigned int LastLevel = 255;
+unsigned int LastBusy = 255;
+
+extern "C" int UDPClientPort;
+extern "C" int UDPServerPort;
+extern "C" int TXPort;
+extern char UDPHost[64];
+
+QTimer *cwidtimer;
+
+QSystemTrayIcon * trayIcon = nullptr;
+
+int MintoTray = 1;
+
+int RSID_WF = 0;				// Set to use RSID FFT for Waterfall. 
+
+extern "C" void WriteDebugLog(char * Mess)
+{
+	qDebug() << Mess;
+}
+
+void QtSoundModem::doupdateDCD(int Chan, int State)
+{
+	DCDLabel[Chan]->setVisible(State);
+}
+
+extern "C" char * frame_monitor(string * frame, char * code, bool tx_stat);
+extern "C" char * ShortDateTime();
+
+extern "C" void mon_rsid(int snd_ch, char * RSID)
+{
+	int Len;
+	char * Msg = (char *)malloc(1024);		// Cant pass local variable via signal/slot
+
+	sprintf(Msg, "%d:%s [%s%c]", snd_ch + 1, RSID, ShortDateTime(), 'R');
+
+	Len = strlen(Msg);
+
+	if (Msg[Len - 1] != '\r')
+	{
+		Msg[Len++] = '\r';
+		Msg[Len] = 0;
+	}
+
+	emit t->sendtoTrace(Msg, 0);
+}
+
+extern "C" void put_frame(int snd_ch, string * frame, char * code, int  tx, int excluded)
+{
+	UNUSED(excluded);
+
+	int Len;
+	char * Msg = (char *)malloc(1024);		// Cant pass local variable via signal/slot
+
+	if (strcmp(code, "NON-AX25") == 0)
+		sprintf(Msg, "%d: <NON-AX25 frame Len = %d [%s%c]\r", snd_ch, frame->Length, ShortDateTime(), 'R');
+	else
+		sprintf(Msg, "%d:%s", snd_ch + 1, frame_monitor(frame, code, tx));
+
+	Len = strlen(Msg);
+
+	if (Msg[Len - 1] != '\r')
+	{
+		Msg[Len++] = '\r';
+		Msg[Len] = 0;
+	}
+
+	emit t->sendtoTrace(Msg, tx);
+}
+
+extern "C" void updateDCD(int Chan, bool State)
+{
+	emit t->updateDCD(Chan, State);
+}
+
+bool QtSoundModem::eventFilter(QObject* obj, QEvent *evt)
+{
+	UNUSED(obj);
+
+	if (evt->type() == QEvent::Resize)
+	{
+		return QWidget::event(evt);
+	}
+
+	if (evt->type() == QEvent::WindowStateChange)
+	{
+		if (windowState().testFlag(Qt::WindowMinimized) == true)
+			w_state = WIN_MINIMIZED;
+		else
+			w_state = WIN_MAXIMIZED;
+	}
+//	if (evt->type() == QGuiApplication::applicationStateChanged) - this is a sigma;
+//	{
+//		qDebug() << "App State changed =" << evt->type() << endl;
+//	}
+
+	return QWidget::event(evt);
+}
+
+void QtSoundModem::resizeEvent(QResizeEvent* event)
+{
+	QMainWindow::resizeEvent(event);
+
+	QRect r = geometry();
+
+	int A, B, C, W;
+	int modemBoxHeight = 30;
+
+	ui.modeB->setVisible(soundChannel[1]);
+	ui.centerB->setVisible(soundChannel[1]);
+	ui.labelB->setVisible(soundChannel[1]);
+	DCDLabel[1]->setVisible(soundChannel[1]);
+	ui.RXOffsetB->setVisible(soundChannel[1]);
+
+	ui.modeC->setVisible(soundChannel[2]);
+	ui.centerC->setVisible(soundChannel[2]);
+	ui.labelC->setVisible(soundChannel[2]);
+	DCDLabel[2]->setVisible(soundChannel[2]);
+	ui.RXOffsetC->setVisible(soundChannel[2]);
+
+	ui.modeD->setVisible(soundChannel[3]);
+	ui.centerD->setVisible(soundChannel[3]);
+	ui.labelD->setVisible(soundChannel[3]);
+	DCDLabel[3]->setVisible(soundChannel[3]);
+	ui.RXOffsetD->setVisible(soundChannel[3]);
+
+	if (soundChannel[2] || soundChannel[3])
+		modemBoxHeight = 60;
+
+
+	A = r.height() - 25;   // No waterfalls
+
+	if (UsingBothChannels && Secondwaterfall)
+	{
+		// Two waterfalls
+
+		ui.WaterfallA->setVisible(1);
+		ui.HeaderA->setVisible(1);
+		ui.WaterfallB->setVisible(1);
+		ui.HeaderB->setVisible(1);
+
+		A = r.height() - 258;   // Top of Waterfall A
+		B = A + 115;			// Top of Waterfall B
+	}
+	else
+	{
+		// One waterfall
+
+		// Could be Left or Right
+
+		if (Firstwaterfall)
+		{
+			if (soundChannel[0] == RIGHT)
+			{
+				ui.WaterfallA->setVisible(0);
+				ui.HeaderA->setVisible(0);
+				ui.WaterfallB->setVisible(1);
+				ui.HeaderB->setVisible(1);
+			}
+			else
+			{
+				ui.WaterfallA->setVisible(1);
+				ui.HeaderA->setVisible(1);
+				ui.WaterfallB->setVisible(0);
+				ui.HeaderB->setVisible(0);
+			}
+
+			A = r.height() - 145;   // Top of Waterfall A
+		}
+		else
+			A = r.height() - 25;   // Top of Waterfall A
+	}
+
+	C = A - 150;			// Bottom of Monitor, Top of connection list
+	W = r.width();
+
+	// Calc Positions of Waterfalls
+
+	ui.monWindow->setGeometry(QRect(0, modemBoxHeight, W, C - (modemBoxHeight + 26)));
+	sessionTable->setGeometry(QRect(0, C, W, 175));
+
+	if (UsingBothChannels)
+	{
+		ui.HeaderA->setGeometry(QRect(0, A, W, 35));
+		ui.WaterfallA->setGeometry(QRect(0, A + 35, W, 80));
+		ui.HeaderB->setGeometry(QRect(0, B, W, 35));
+		ui.WaterfallB->setGeometry(QRect(0, B + 35, W, 80));
+	}
+	else
+	{
+		if (soundChannel[0] == RIGHT)
+		{
+			ui.HeaderB->setGeometry(QRect(0, A, W, 35));
+			ui.WaterfallB->setGeometry(QRect(0, A + 35, W, 80));
+		}
+		else
+		{
+			ui.HeaderA->setGeometry(QRect(0, A, W, 35));
+			ui.WaterfallA->setGeometry(QRect(0, A + 35, W, 80));
+		}
+	}
+}
+
+QAction * setupMenuLine(QMenu * Menu, char * Label, QObject * parent, int State)
+{
+	QAction * Act = new QAction(Label, parent);
+	Menu->addAction(Act);
+
+	Act->setCheckable(true);
+	if (State)
+		Act->setChecked(true);
+
+	parent->connect(Act, SIGNAL(triggered()), parent, SLOT(menuChecked()));
+
+	return Act;
+}
+
+void QtSoundModem::menuChecked()
+{
+	QAction * Act = static_cast<QAction*>(QObject::sender());
+
+	int state = Act->isChecked();
+
+	if (Act == actWaterfall1)
+	{
+		int oldstate = Firstwaterfall;
+		Firstwaterfall = state;
+
+		if (state != oldstate)
+			initWaterfall(0, state);
+
+	}
+	else if (Act == actWaterfall2)
+	{
+		int oldstate = Secondwaterfall;
+		Secondwaterfall = state;
+
+		if (state != oldstate)
+			initWaterfall(1, state);
+
+	}
+	saveSettings();
+}
+
+void QtSoundModem::initWaterfall(int chan, int state)
+{
+	if (state == 1)
+	{
+		if (chan == 0)
+		{
+			ui.WaterfallA = new QLabel(ui.centralWidget);
+			WaterfallCopy[0] = ui.WaterfallA;
+		}
+		else
+		{
+			ui.WaterfallB = new QLabel(ui.centralWidget);
+			WaterfallCopy[1] = ui.WaterfallB;
+		}
+		Waterfall[chan] = new QImage(1024, 80, QImage::Format_RGB32);
+		Waterfall[chan]->fill(black);
+
+	}
+	else
+	{
+		delete(Waterfall[chan]);
+		Waterfall[chan] = 0;
+	}
+
+	QSize Size(800, 602);						// Not actually used, but Event constructor needs it
+	QResizeEvent *event = new QResizeEvent(Size, Size);
+	QApplication::sendEvent(this, event);
+}
+
+// Local copies
+
+QLabel *RXOffsetLabel;
+QSlider *RXOffset;
+
+QtSoundModem::QtSoundModem(QWidget *parent) : QMainWindow(parent)
+{
+	ui.setupUi(this);
+
+	QSettings mysettings("QtSoundModem.ini", QSettings::IniFormat);
+
+	if (MintoTray)
+	{
+		char popUp[256];
+		sprintf(popUp, "QtSoundModem %d %d", AGWPort, KISSPort);
+		trayIcon = new QSystemTrayIcon(QIcon(":/QtSoundModem/soundmodem.ico"), this);
+		trayIcon->setToolTip(popUp);
+		trayIcon->show();
+
+		connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(TrayActivated(QSystemTrayIcon::ActivationReason)));
+	}
+
+
+	restoreGeometry(mysettings.value("geometry").toByteArray());
+	restoreState(mysettings.value("windowState").toByteArray());
+
+	sessionTable = new QTableWidget(this);
+
+	sessionTable->verticalHeader()->setVisible(FALSE);
+	sessionTable->verticalHeader()->setDefaultSectionSize(20);
+	sessionTable->horizontalHeader()->setDefaultSectionSize(68);
+	sessionTable->setRowCount(1);
+	sessionTable->setColumnCount(12);
+	m_TableHeader << "MyCall" << "DestCall" << "Status" << "Sent pkts" << "Sent Bytes" << "Rcvd pkts" << "Rcvd bytes" << "Rcvd FC" << "FEC corr" << "CPS TX" << "CPS RX" << "Direction";
+
+	sessionTable->setStyleSheet("QHeaderView::section { background-color:rgb(224, 224, 224) }");
+
+	sessionTable->setHorizontalHeaderLabels(m_TableHeader);
+	sessionTable->setColumnWidth(0, 80);
+	sessionTable->setColumnWidth(1, 80);
+	sessionTable->setColumnWidth(4, 76);
+	sessionTable->setColumnWidth(5, 76);
+	sessionTable->setColumnWidth(6, 80);
+	sessionTable->setColumnWidth(11, 72);
+
+	for (int i = 0; i < modes_count; i++)
+	{
+		ui.modeA->addItem(modes_name[i]);
+		ui.modeB->addItem(modes_name[i]);
+		ui.modeC->addItem(modes_name[i]);
+		ui.modeD->addItem(modes_name[i]);
+	}
+
+	// Set up Menus
+
+	setupMenu = ui.menuBar->addMenu(tr("Settings"));
+
+	actDevices = new QAction("Setup Devices", this);
+	setupMenu->addAction(actDevices);
+
+	connect(actDevices, SIGNAL(triggered()), this, SLOT(clickedSlot()));
+	actDevices->setObjectName("actDevices");
+	actModems = new QAction("Setup Modems", this);
+	actModems->setObjectName("actModems");
+	setupMenu->addAction(actModems);
+
+	connect(actModems, SIGNAL(triggered()), this, SLOT(clickedSlot()));
+
+	actMintoTray = setupMenu->addAction("Minimize to Tray", this, SLOT(MinimizetoTray()));
+	actMintoTray->setCheckable(1);
+	actMintoTray->setChecked(MintoTray);
+
+	viewMenu = ui.menuBar->addMenu(tr("&View"));
+
+	actWaterfall1 = setupMenuLine(viewMenu, (char *)"First waterfall", this, Firstwaterfall);
+	actWaterfall2 = setupMenuLine(viewMenu, (char *)"Second Waterfall", this, Secondwaterfall);
+
+	actCalib = ui.menuBar->addAction("&Calibration");
+	connect(actCalib, SIGNAL(triggered()), this, SLOT(doCalibrate()));
+
+	actRestartWF = ui.menuBar->addAction("Restart Waterfall");
+	connect(actRestartWF, SIGNAL(triggered()), this, SLOT(doRestartWF()));
+
+	actAbout = ui.menuBar->addAction("&About");
+	connect(actAbout, SIGNAL(triggered()), this, SLOT(doAbout()));
+
+	//	Constellation = new QImage(91, 91, QImage::Format_RGB32);
+
+	Header[0] = new QImage(1024, 35, QImage::Format_RGB32);
+	Header[1] = new QImage(1024, 35, QImage::Format_RGB32);
+	RXLevel = new QImage(150, 10, QImage::Format_RGB32);
+
+	DCDLabel[0] = new QLabel(this);
+	DCDLabel[0]->setObjectName(QString::fromUtf8("DCDLedA"));
+	DCDLabel[0]->setGeometry(QRect(280, 31, 12, 12));
+	DCDLabel[0]->setVisible(TRUE);
+
+	DCDLabel[1] = new QLabel(this);
+	DCDLabel[1]->setObjectName(QString::fromUtf8("DCDLedB"));
+	DCDLabel[1]->setGeometry(QRect(575, 31, 12, 12));
+	DCDLabel[1]->setVisible(TRUE);
+
+	DCDLabel[2] = new QLabel(this);
+	DCDLabel[2]->setObjectName(QString::fromUtf8("DCDLedC"));
+	DCDLabel[2]->setGeometry(QRect(280, 61, 12, 12));
+	DCDLabel[2]->setVisible(FALSE);
+
+	DCDLabel[3] = new QLabel(this);
+	DCDLabel[3]->setObjectName(QString::fromUtf8("DCDLedD"));
+	DCDLabel[3]->setGeometry(QRect(575, 61, 12, 12));
+	DCDLabel[3]->setVisible(FALSE);
+	
+	DCDLed[0] = new QImage(12, 12, QImage::Format_RGB32);
+	DCDLed[1] = new QImage(12, 12, QImage::Format_RGB32);
+	DCDLed[2] = new QImage(12, 12, QImage::Format_RGB32);
+	DCDLed[3] = new QImage(12, 12, QImage::Format_RGB32);
+
+	DCDLed[0]->fill(red);
+	DCDLed[1]->fill(red);
+	DCDLed[2]->fill(red);
+	DCDLed[3]->fill(red);
+
+	DCDLabel[0]->setPixmap(QPixmap::fromImage(*DCDLed[0]));
+	DCDLabel[1]->setPixmap(QPixmap::fromImage(*DCDLed[1]));
+	DCDLabel[2]->setPixmap(QPixmap::fromImage(*DCDLed[2]));
+	DCDLabel[3]->setPixmap(QPixmap::fromImage(*DCDLed[3]));
+
+	chanOffsetLabel[0] = ui.RXOffsetA;
+	chanOffsetLabel[1] = ui.RXOffsetB;
+	chanOffsetLabel[2] = ui.RXOffsetC;
+	chanOffsetLabel[3] = ui.RXOffsetD;
+
+
+	//	Waterfall[0]->setColorCount(16);
+	//	Waterfall[1]->setColorCount(16);
+
+
+	//	for (i = 0; i < 16; i++)
+	//	{
+	//	Waterfall[0]->setColor(i, vbColours[i]);
+	//		Waterfall[1]->setColor(i, vbColours[i]);
+	//	}
+
+	WaterfallCopy[0] = ui.WaterfallA;
+	WaterfallCopy[1] = ui.WaterfallB;
+
+	initWaterfall(0, 1);
+	initWaterfall(1, 1);
+
+	Header[0]->fill(black);
+	Header[1]->fill(black);
+
+	HeaderCopy[0] = ui.HeaderA;
+	HeaderCopy[1] = ui.HeaderB;
+	monWindowCopy = ui.monWindow;
+
+	ui.monWindow->document()->setMaximumBlockCount(10000);
+
+//	connect(ui.monWindow, SIGNAL(selectionChanged()), this, SLOT(onTEselectionChanged()));
+
+	ui.HeaderA->setPixmap(QPixmap::fromImage(*Header[0]));
+	ui.HeaderB->setPixmap(QPixmap::fromImage(*Header[1]));
+
+	wf_pointer(soundChannel[0]);
+	wf_pointer(soundChannel[1]);
+	wf_Scale(0);
+	wf_Scale(1);
+
+	//	RefreshLevel(0);
+	//	RXLevel->setPixmap(QPixmap::fromImage(*RXLevel));
+
+	connect(ui.modeA, SIGNAL(currentIndexChanged(int)), this, SLOT(clickedSlotI(int)));
+	connect(ui.modeB, SIGNAL(currentIndexChanged(int)), this, SLOT(clickedSlotI(int)));
+	connect(ui.modeC, SIGNAL(currentIndexChanged(int)), this, SLOT(clickedSlotI(int)));
+	connect(ui.modeD, SIGNAL(currentIndexChanged(int)), this, SLOT(clickedSlotI(int)));
+
+	ui.modeA->setCurrentIndex(speed[0]);
+	ui.modeB->setCurrentIndex(speed[1]);
+	ui.modeC->setCurrentIndex(speed[2]);
+	ui.modeD->setCurrentIndex(speed[3]);
+
+	ModemA = ui.modeA->currentIndex();
+
+	ui.centerA->setValue(rx_freq[0]);
+	ui.centerB->setValue(rx_freq[1]);
+	ui.centerC->setValue(rx_freq[2]);
+	ui.centerD->setValue(rx_freq[3]);
+
+	connect(ui.centerA, SIGNAL(valueChanged(int)), this, SLOT(clickedSlotI(int)));
+	connect(ui.centerB, SIGNAL(valueChanged(int)), this, SLOT(clickedSlotI(int)));
+	connect(ui.centerC, SIGNAL(valueChanged(int)), this, SLOT(clickedSlotI(int)));
+	connect(ui.centerD, SIGNAL(valueChanged(int)), this, SLOT(clickedSlotI(int)));
+
+	ui.DCDSlider->setValue(dcd_threshold);
+
+
+	char valChar[32];
+	sprintf(valChar, "RX Offset %d", rxOffset);
+	ui.RXOffsetLabel->setText(valChar);
+	ui.RXOffset->setValue(rxOffset);
+
+	RXOffsetLabel = ui.RXOffsetLabel;
+	RXOffset = ui.RXOffset;
+
+	connect(ui.DCDSlider, SIGNAL(sliderMoved(int)), this, SLOT(clickedSlotI(int)));
+	connect(ui.RXOffset, SIGNAL(valueChanged(int)), this, SLOT(clickedSlotI(int)));
+
+
+	QObject::connect(t, SIGNAL(sendtoTrace(char *, int)), this, SLOT(sendtoTrace(char *, int)), Qt::QueuedConnection);
+	QObject::connect(t, SIGNAL(updateDCD(int, int)), this, SLOT(doupdateDCD(int, int)), Qt::QueuedConnection);
+
+	connect(ui.RXOffsetA, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
+	connect(ui.RXOffsetB, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
+	connect(ui.RXOffsetC, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
+	connect(ui.RXOffsetD, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
+
+	QTimer *timer = new QTimer(this);
+	connect(timer, SIGNAL(timeout()), this, SLOT(MyTimerSlot()));
+	timer->start(100);
+
+
+	cwidtimer = new QTimer(this);
+	connect(cwidtimer, SIGNAL(timeout()), this, SLOT(CWIDTimer()));
+
+	if (CWIDInterval)
+		cwidtimer->start(CWIDInterval * 60000);
+
+	if (RSID_SetModem[0])
+	{
+		RSID_WF = 1;
+		RSIDinitfft();
+	}
+	il2p_init(1);
+}
+
+void QtSoundModem::MinimizetoTray()
+{
+	MintoTray = actMintoTray->isChecked();
+	saveSettings();
+	QMessageBox::about(this, tr("QtSoundModem"),
+	tr("Program must be restarted to change Minimize mode"));
+}
+
+
+void QtSoundModem::TrayActivated(QSystemTrayIcon::ActivationReason reason)
+{
+	if (reason == 3)
+	{
+		showNormal();
+		w->setWindowState((w->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
+	} 
+}
+
+extern "C" void sendCWID(char * strID, BOOL blnPlay, int Chan);
+
+void QtSoundModem::CWIDTimer()
+{
+	sendCWID(CWIDCall, CWIDType, 0);
+	calib_mode[0] = 4;
+}
+
+void extSetOffset(int chan)
+{
+	char valChar[32];
+	sprintf(valChar, "%d", chanOffset[chan]);
+	chanOffsetLabel[chan]->setText(valChar);
+
+	wf_pointer(soundChannel[chan]);
+
+	pnt_change[0] = 1;
+	pnt_change[1] = 1;
+	pnt_change[2] = 1;
+	pnt_change[3] = 1;
+
+	return;
+}
+	
+void QtSoundModem::MyTimerSlot()
+{
+	// 100 mS Timer Event
+
+	for (int i = 0; i < 4; i++)
+	{
+
+		if (needSetOffset[i])
+		{
+			needSetOffset[i] = 0;
+			extSetOffset(i);						// Update GUI
+		}
+	}
+
+	if (refreshModems)
+	{
+		refreshModems = 0;
+
+		ui.modeA->setCurrentIndex(speed[0]);
+		ui.modeB->setCurrentIndex(speed[1]);
+		ui.modeC->setCurrentIndex(speed[2]);
+		ui.modeD->setCurrentIndex(speed[3]);
+		ui.centerA->setValue(rx_freq[0]);
+		ui.centerB->setValue(rx_freq[1]);
+		ui.centerC->setValue(rx_freq[2]);
+		ui.centerD->setValue(rx_freq[3]);
+	}
+
+	show_grid();
+}
+
+void QtSoundModem::returnPressed()
+{
+	char Name[32];
+	int Chan;
+	QString val;
+	
+	strcpy(Name, sender()->objectName().toUtf8());
+
+	Chan = Name[8] - 'A';
+
+	val = chanOffsetLabel[Chan]->text();
+
+	chanOffset[Chan] = val.toInt();
+	needSetOffset[Chan] = 1;				// Update GUI
+
+
+}
+
+
+void QtSoundModem::clickedSlotI(int i)
+{
+	char Name[32];
+
+	strcpy(Name, sender()->objectName().toUtf8());
+
+	if (strcmp(Name, "modeA") == 0)
+	{
+		ModemA = ui.modeA->currentIndex();
+		set_speed(0, ModemA);
+		saveSettings();
+		AGW_Report_Modem_Change(0);
+		return;
+	}
+
+	if (strcmp(Name, "modeB") == 0)
+	{
+		ModemB = ui.modeB->currentIndex();
+		set_speed(1, ModemB);
+		saveSettings();
+		AGW_Report_Modem_Change(1);
+		return;
+	}
+
+	if (strcmp(Name, "modeC") == 0)
+	{
+		ModemC = ui.modeC->currentIndex();
+		set_speed(2, ModemC);
+		saveSettings();
+		AGW_Report_Modem_Change(2);
+		return;
+	}
+
+	if (strcmp(Name, "modeD") == 0)
+	{
+		ModemD = ui.modeD->currentIndex();
+		set_speed(3, ModemD);
+		saveSettings();
+		AGW_Report_Modem_Change(3);
+		return;
+	}
+
+	if (strcmp(Name, "centerA") == 0)
+	{
+		if (i > 300)
+		{
+			QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
+			ui.centerA->setValue(Freq_Change(0, i));
+			settings->setValue("Modem/RXFreq1", ui.centerA->value());
+			AGW_Report_Modem_Change(0);
+
+		}
+		return;
+	}
+
+	if (strcmp(Name, "centerB") == 0)
+	{
+		if (i > 300)
+		{
+			QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
+			ui.centerB->setValue(Freq_Change(1, i));
+			settings->setValue("Modem/RXFreq2", ui.centerB->value());
+			AGW_Report_Modem_Change(1);
+		}
+		return;
+	}
+
+	if (strcmp(Name, "centerC") == 0)
+	{
+		if (i > 300)
+		{
+			QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
+			ui.centerC->setValue(Freq_Change(2, i));
+			settings->setValue("Modem/RXFreq3", ui.centerC->value());
+			AGW_Report_Modem_Change(2);
+		}
+		return;
+	}
+
+	if (strcmp(Name, "centerD") == 0)
+	{
+		if (i > 300)
+		{
+			QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
+			ui.centerD->setValue(Freq_Change(3, i));
+			settings->setValue("Modem/RXFreq4", ui.centerD->value());
+			AGW_Report_Modem_Change(3);
+		}
+		return;
+	}
+
+	if (strcmp(Name, "DCDSlider") == 0)
+	{
+		dcd_threshold = i;
+		saveSettings();
+		return;
+	}
+	
+	if (strcmp(Name, "RXOffset") == 0)
+	{
+		char valChar[32];
+		rxOffset = i;
+		sprintf(valChar, "RX Offset %d",rxOffset);
+		ui.RXOffsetLabel->setText(valChar);
+
+		wf_pointer(soundChannel[0]);
+		wf_pointer(soundChannel[1]);
+
+		pnt_change[0] = 1;
+		pnt_change[1] = 1;
+		pnt_change[2] = 1;
+		pnt_change[3] = 1;
+
+		saveSettings();
+		return;
+	}
+
+
+	QMessageBox msgBox;
+	msgBox.setWindowTitle("MessageBox Title");
+	msgBox.setText("You Clicked " + ((QPushButton*)sender())->objectName());
+	msgBox.exec();
+}
+
+
+void QtSoundModem::clickedSlot()
+{
+	char Name[32];
+
+	strcpy(Name, sender()->objectName().toUtf8());
+
+	if (strcmp(Name, "actDevices") == 0)
+	{
+		doDevices();
+		return;
+	}
+
+	if (strcmp(Name, "actModems") == 0)
+	{
+		doModems();
+		return;
+	}
+
+	if (strcmp(Name, "showBPF_A") == 0)
+	{
+		doFilter(0, 0);
+		return;
+	}
+
+	if (strcmp(Name, "showTXBPF_A") == 0)
+	{
+		doFilter(0, 1);
+		return;
+	}
+
+	if (strcmp(Name, "showLPF_A") == 0)
+	{
+		doFilter(0, 2);
+		return;
+	}
+	
+
+	if (strcmp(Name, "showBPF_B") == 0)
+	{
+		doFilter(1, 0);
+		return;
+	}
+
+	if (strcmp(Name, "showTXBPF_B") == 0)
+	{
+		doFilter(1, 1);
+		return;
+	}
+
+	if (strcmp(Name, "showLPF_B") == 0)
+	{
+		doFilter(1, 2);
+		return;
+	}
+
+	if (strcmp(Name, "Low_A") == 0)
+	{
+		handleButton(0, 1);
+		return;
+	}
+
+	if (strcmp(Name, "High_A") == 0)
+	{
+		handleButton(0, 2);
+		return;
+	}
+
+	if (strcmp(Name, "Both_A") == 0)
+	{
+		handleButton(0, 3);
+		return;
+	}
+
+	if (strcmp(Name, "Stop_A") == 0)
+	{
+		handleButton(0, 0);
+		return;
+	}
+
+
+	if (strcmp(Name, "Low_B") == 0)
+	{
+		handleButton(1, 1);
+		return;
+	}
+
+	if (strcmp(Name, "High_B") == 0)
+	{
+		handleButton(1, 2);
+		return;
+	}
+
+	if (strcmp(Name, "Both_B") == 0)
+	{
+		handleButton(1, 3);
+		return;
+	}
+
+	if (strcmp(Name, "Stop_B") == 0)
+	{
+		handleButton(1, 0);
+		return;
+	}
+
+	if (strcmp(Name, "Low_C") == 0)
+	{
+		handleButton(2, 1);
+		return;
+	}
+
+	if (strcmp(Name, "High_C") == 0)
+	{
+		handleButton(2, 2);
+		return;
+	}
+
+	if (strcmp(Name, "Both_C") == 0)
+	{
+		handleButton(2, 3);
+		return;
+	}
+
+	if (strcmp(Name, "Stop_C") == 0)
+	{
+		handleButton(2, 0);
+		return;
+	}
+
+	if (strcmp(Name, "Low_D") == 0)
+	{
+		handleButton(3, 1);
+		return;
+	}
+
+	if (strcmp(Name, "High_D") == 0)
+	{
+		handleButton(3, 2);
+		return;
+	}
+
+	if (strcmp(Name, "Both_D") == 0)
+	{
+		handleButton(3, 3);
+		return;
+	}
+
+	if (strcmp(Name, "Stop_D") == 0)
+	{
+		handleButton(3, 0);
+		return;
+	}
+
+	QMessageBox msgBox;
+	msgBox.setWindowTitle("MessageBox Title");
+	msgBox.setText("You Clicked " + ((QPushButton*)sender())->objectName());
+	msgBox.exec();
+}
+
+Ui_ModemDialog * Dlg;
+
+QDialog * modemUI;
+QDialog * deviceUI;
+
+void QtSoundModem::doModems()
+{
+	Dlg = new(Ui_ModemDialog);
+
+	QDialog UI;
+	char valChar[10];
+
+	Dlg->setupUi(&UI);
+
+	modemUI = &UI;
+	deviceUI = 0;
+
+	myResize *resize = new myResize();
+
+	UI.installEventFilter(resize);
+
+	sprintf(valChar, "%d", bpf[0]);
+	Dlg->BPFWidthA->setText(valChar);
+	sprintf(valChar, "%d", bpf[1]);
+	Dlg->BPFWidthB->setText(valChar);
+	sprintf(valChar, "%d", bpf[2]);
+	Dlg->BPFWidthC->setText(valChar);
+	sprintf(valChar, "%d", bpf[3]);
+	Dlg->BPFWidthD->setText(valChar);
+
+	sprintf(valChar, "%d", txbpf[0]);
+	Dlg->TXBPFWidthA->setText(valChar);
+	sprintf(valChar, "%d", txbpf[1]);
+	Dlg->TXBPFWidthB->setText(valChar);
+	sprintf(valChar, "%d", txbpf[2]);
+	Dlg->TXBPFWidthC->setText(valChar);
+	sprintf(valChar, "%d", txbpf[3]);
+	Dlg->TXBPFWidthD->setText(valChar);
+
+	sprintf(valChar, "%d", lpf[0]);
+	Dlg->LPFWidthA->setText(valChar);
+	sprintf(valChar, "%d", lpf[1]);
+	Dlg->LPFWidthB->setText(valChar);
+	sprintf(valChar, "%d", lpf[2]);
+	Dlg->LPFWidthC->setText(valChar);
+	sprintf(valChar, "%d", lpf[4]);
+	Dlg->LPFWidthD->setText(valChar);
+
+	sprintf(valChar, "%d", BPF_tap[0]);
+	Dlg->BPFTapsA->setText(valChar);
+	sprintf(valChar, "%d", BPF_tap[1]);
+	Dlg->BPFTapsB->setText(valChar);
+	sprintf(valChar, "%d", BPF_tap[2]);
+	Dlg->BPFTapsC->setText(valChar);
+	sprintf(valChar, "%d", BPF_tap[3]);
+	Dlg->BPFTapsD->setText(valChar);
+
+	sprintf(valChar, "%d", LPF_tap[0]);
+	Dlg->LPFTapsA->setText(valChar);
+	sprintf(valChar, "%d", LPF_tap[1]);
+	Dlg->LPFTapsB->setText(valChar);
+	sprintf(valChar, "%d", LPF_tap[2]);
+	Dlg->LPFTapsC->setText(valChar);
+	sprintf(valChar, "%d", LPF_tap[3]);
+	Dlg->LPFTapsD->setText(valChar);
+
+	Dlg->preEmphAllA->setChecked(emph_all[0]);
+
+	if (emph_all[0])
+		Dlg->preEmphA->setDisabled(TRUE);
+	else
+		Dlg->preEmphA->setCurrentIndex(emph_db[0]);
+
+	Dlg->preEmphAllB->setChecked(emph_all[1]);
+
+	if (emph_all[1])
+		Dlg->preEmphB->setDisabled(TRUE);
+	else
+		Dlg->preEmphB->setCurrentIndex(emph_db[1]);
+
+	Dlg->preEmphAllC->setChecked(emph_all[2]);
+
+	if (emph_all[2])
+		Dlg->preEmphC->setDisabled(TRUE);
+	else
+		Dlg->preEmphC->setCurrentIndex(emph_db[2]);
+
+	Dlg->preEmphAllD->setChecked(emph_all[3]);
+
+	if (emph_all[3])
+		Dlg->preEmphD->setDisabled(TRUE);
+	else
+		Dlg->preEmphD->setCurrentIndex(emph_db[3]);
+
+
+	Dlg->nonAX25A->setChecked(NonAX25[0]);
+	Dlg->nonAX25B->setChecked(NonAX25[1]);
+	Dlg->nonAX25C->setChecked(NonAX25[2]);
+	Dlg->nonAX25D->setChecked(NonAX25[3]);
+
+	Dlg->KISSOptA->setChecked(KISS_opt[0]);
+	Dlg->KISSOptB->setChecked(KISS_opt[1]);
+	Dlg->KISSOptC->setChecked(KISS_opt[2]);
+	Dlg->KISSOptD->setChecked(KISS_opt[3]);
+
+	sprintf(valChar, "%d", txdelay[0]);
+	Dlg->TXDelayA->setText(valChar);
+	sprintf(valChar, "%d", txdelay[1]);
+	Dlg->TXDelayB->setText(valChar);
+	sprintf(valChar, "%d", txdelay[2]);
+	Dlg->TXDelayC->setText(valChar);
+	sprintf(valChar, "%d", txdelay[3]);
+	Dlg->TXDelayD->setText(valChar);
+
+	sprintf(valChar, "%d", txtail[0]);
+	Dlg->TXTailA->setText(valChar);
+	sprintf(valChar, "%d", txtail[1]);
+	Dlg->TXTailB->setText(valChar);
+	sprintf(valChar, "%d", txtail[2]);
+	Dlg->TXTailC->setText(valChar);
+	sprintf(valChar, "%d", txtail[3]);
+	Dlg->TXTailD->setText(valChar);
+
+	Dlg->FrackA->setText(QString::number(frack_time[0]));
+	Dlg->FrackB->setText(QString::number(frack_time[1]));
+	Dlg->FrackC->setText(QString::number(frack_time[2]));
+	Dlg->FrackD->setText(QString::number(frack_time[3]));
+
+	Dlg->RetriesA->setText(QString::number(fracks[0]));
+	Dlg->RetriesB->setText(QString::number(fracks[1]));
+	Dlg->RetriesC->setText(QString::number(fracks[2]));
+	Dlg->RetriesD->setText(QString::number(fracks[3]));
+
+	sprintf(valChar, "%d", RCVR[0]);
+	Dlg->AddRXA->setText(valChar);
+	sprintf(valChar, "%d", RCVR[1]);
+	Dlg->AddRXB->setText(valChar);
+	sprintf(valChar, "%d", RCVR[2]);
+	Dlg->AddRXC->setText(valChar);
+	sprintf(valChar, "%d", RCVR[3]);
+	Dlg->AddRXD->setText(valChar);
+
+	sprintf(valChar, "%d", rcvr_offset[0]);
+	Dlg->RXShiftA->setText(valChar);
+
+	sprintf(valChar, "%d", rcvr_offset[1]);
+	Dlg->RXShiftB->setText(valChar);
+
+	sprintf(valChar, "%d", rcvr_offset[2]);
+	Dlg->RXShiftC->setText(valChar);
+	sprintf(valChar, "%d", rcvr_offset[3]);
+	Dlg->RXShiftD->setText(valChar);
+
+	//	speed[1]
+	//	speed[2];
+
+	Dlg->recoverBitA->setCurrentIndex(recovery[0]);
+	Dlg->recoverBitB->setCurrentIndex(recovery[1]);
+	Dlg->recoverBitC->setCurrentIndex(recovery[2]);
+	Dlg->recoverBitD->setCurrentIndex(recovery[3]);
+
+	Dlg->fx25ModeA->setCurrentIndex(fx25_mode[0]);
+	Dlg->fx25ModeB->setCurrentIndex(fx25_mode[1]);
+	Dlg->fx25ModeC->setCurrentIndex(fx25_mode[2]);
+	Dlg->fx25ModeD->setCurrentIndex(fx25_mode[3]);
+
+	Dlg->IL2PModeA->setCurrentIndex(il2p_mode[0]);
+	Dlg->IL2PModeB->setCurrentIndex(il2p_mode[1]);
+	Dlg->IL2PModeC->setCurrentIndex(il2p_mode[2]);
+	Dlg->IL2PModeD->setCurrentIndex(il2p_mode[3]);
+
+	Dlg->CWIDCall->setText(CWIDCall);
+	Dlg->CWIDInterval->setText(QString::number(CWIDInterval));
+
+	if (CWIDType)
+		Dlg->radioButton_2->setChecked(1);
+	else
+		Dlg->CWIDType->setChecked(1);
+
+	Dlg->RSIDSABM_A->setChecked(RSID_SABM[0]);
+	Dlg->RSIDSABM_B->setChecked(RSID_SABM[1]);
+	Dlg->RSIDSABM_C->setChecked(RSID_SABM[2]);
+	Dlg->RSIDSABM_D->setChecked(RSID_SABM[3]);
+
+	Dlg->RSIDUI_A->setChecked(RSID_UI[0]);
+	Dlg->RSIDUI_B->setChecked(RSID_UI[1]);
+	Dlg->RSIDUI_C->setChecked(RSID_UI[2]);
+	Dlg->RSIDUI_D->setChecked(RSID_UI[3]);
+
+	Dlg->DigiCallsA->setText(MyDigiCall[0]);
+	Dlg->DigiCallsB->setText(MyDigiCall[1]);
+	Dlg->DigiCallsC->setText(MyDigiCall[2]);
+	Dlg->DigiCallsD->setText(MyDigiCall[3]);
+
+	Dlg->RSID_1_SETMODEM->setChecked(RSID_SetModem[0]);
+	Dlg->RSID_2_SETMODEM->setChecked(RSID_SetModem[1]);
+	Dlg->RSID_3_SETMODEM->setChecked(RSID_SetModem[2]);
+	Dlg->RSID_4_SETMODEM->setChecked(RSID_SetModem[3]);
+	
+	connect(Dlg->showBPF_A, SIGNAL(released()), this, SLOT(clickedSlot()));
+	connect(Dlg->showTXBPF_A, SIGNAL(released()), this, SLOT(clickedSlot()));
+	connect(Dlg->showLPF_A, SIGNAL(released()), this, SLOT(clickedSlot()));
+
+	connect(Dlg->showBPF_B, SIGNAL(released()), this, SLOT(clickedSlot()));
+	connect(Dlg->showTXBPF_B, SIGNAL(released()), this, SLOT(clickedSlot()));
+	connect(Dlg->showLPF_B, SIGNAL(released()), this, SLOT(clickedSlot()));
+
+	connect(Dlg->showBPF_C, SIGNAL(released()), this, SLOT(clickedSlot()));
+	connect(Dlg->showTXBPF_C, SIGNAL(released()), this, SLOT(clickedSlot()));
+	connect(Dlg->showLPF_C, SIGNAL(released()), this, SLOT(clickedSlot()));
+
+	connect(Dlg->showBPF_D, SIGNAL(released()), this, SLOT(clickedSlot()));
+	connect(Dlg->showTXBPF_D, SIGNAL(released()), this, SLOT(clickedSlot()));
+	connect(Dlg->showLPF_D, SIGNAL(released()), this, SLOT(clickedSlot()));
+
+	connect(Dlg->okButton, SIGNAL(clicked()), this, SLOT(modemaccept()));
+	connect(Dlg->modemSave, SIGNAL(clicked()), this, SLOT(modemSave()));
+	connect(Dlg->cancelButton, SIGNAL(clicked()), this, SLOT(modemreject()));
+
+	connect(Dlg->SendRSID_1, SIGNAL(clicked()), this, SLOT(doRSIDA()));
+	connect(Dlg->SendRSID_2, SIGNAL(clicked()), this, SLOT(doRSIDB()));
+	connect(Dlg->SendRSID_3, SIGNAL(clicked()), this, SLOT(doRSIDC()));
+	connect(Dlg->SendRSID_4, SIGNAL(clicked()), this, SLOT(doRSIDD()));
+
+	connect(Dlg->preEmphAllA, SIGNAL(stateChanged(int)), this, SLOT(preEmphAllAChanged(int)));
+	connect(Dlg->preEmphAllB, SIGNAL(stateChanged(int)), this, SLOT(preEmphAllBChanged(int)));
+	connect(Dlg->preEmphAllC, SIGNAL(stateChanged(int)), this, SLOT(preEmphAllCChanged(int)));
+	connect(Dlg->preEmphAllD, SIGNAL(stateChanged(int)), this, SLOT(preEmphAllDChanged(int)));
+
+	UI.exec();
+}
+
+void QtSoundModem::preEmphAllAChanged(int state)
+{
+	Dlg->preEmphA->setDisabled(state);
+}
+
+void QtSoundModem::preEmphAllBChanged(int state)
+{
+	Dlg->preEmphB->setDisabled(state);
+}
+
+void QtSoundModem::preEmphAllCChanged(int state)
+{
+	Dlg->preEmphC->setDisabled(state);
+}
+
+void QtSoundModem::preEmphAllDChanged(int state)
+{
+	Dlg->preEmphD->setDisabled(state);
+}
+
+extern "C" void get_exclude_list(char * line, TStringList * list);
+
+void QtSoundModem::modemaccept()
+{
+	modemSave();
+	delete(Dlg);
+	saveSettings();
+
+	modemUI->accept();
+
+}
+
+void QtSoundModem::modemSave()
+{
+	QVariant Q;
+	
+	emph_all[0] = Dlg->preEmphAllA->isChecked();
+	emph_db[0] = Dlg->preEmphA->currentIndex();
+
+	emph_all[1] = Dlg->preEmphAllB->isChecked();
+	emph_db[1] = Dlg->preEmphB->currentIndex();
+
+	emph_all[2] = Dlg->preEmphAllC->isChecked();
+	emph_db[2] = Dlg->preEmphC->currentIndex();
+
+	emph_all[3] = Dlg->preEmphAllD->isChecked();
+	emph_db[3] = Dlg->preEmphD->currentIndex();
+
+	NonAX25[0] = Dlg->nonAX25A->isChecked();
+	NonAX25[1] = Dlg->nonAX25B->isChecked();
+	NonAX25[2] = Dlg->nonAX25C->isChecked();
+	NonAX25[3] = Dlg->nonAX25D->isChecked();
+
+	KISS_opt[0] = Dlg->KISSOptA->isChecked();
+	KISS_opt[1] = Dlg->KISSOptB->isChecked();
+	KISS_opt[2] = Dlg->KISSOptC->isChecked();
+	KISS_opt[3] = Dlg->KISSOptD->isChecked();
+
+	if (emph_db[0] < 0 || emph_db[0] > nr_emph)
+		emph_db[0] = 0;
+
+	if (emph_db[1] < 0 || emph_db[1] > nr_emph)
+		emph_db[1] = 0;
+
+	if (emph_db[2] < 0 || emph_db[2] > nr_emph)
+		emph_db[2] = 0;
+
+	if (emph_db[3] < 0 || emph_db[3] > nr_emph)
+		emph_db[3] = 0;
+
+	Q = Dlg->TXDelayA->text();
+	txdelay[0] = Q.toInt();
+
+	Q = Dlg->TXDelayB->text();
+	txdelay[1] = Q.toInt();
+	
+	Q = Dlg->TXDelayC->text();
+	txdelay[2] = Q.toInt();
+
+	Q = Dlg->TXDelayD->text();
+	txdelay[3] = Q.toInt();
+
+	Q = Dlg->TXTailA->text();
+	txtail[0] = Q.toInt();
+
+	Q = Dlg->TXTailB->text();
+	txtail[1] = Q.toInt();
+
+	Q = Dlg->TXTailC->text();
+	txtail[2] = Q.toInt();
+
+	txtail[3] = Dlg->TXTailD->text().toInt();
+
+	frack_time[0] = Dlg->FrackA->text().toInt();
+	frack_time[1] = Dlg->FrackB->text().toInt();
+	frack_time[2] = Dlg->FrackC->text().toInt();
+	frack_time[3] = Dlg->FrackD->text().toInt();
+
+	fracks[0] = Dlg->RetriesA->text().toInt();
+	fracks[1] = Dlg->RetriesB->text().toInt();
+	fracks[2] = Dlg->RetriesC->text().toInt();
+	fracks[3] = Dlg->RetriesD->text().toInt();
+
+	Q = Dlg->AddRXA->text();
+	RCVR[0] = Q.toInt();
+
+	Q = Dlg->AddRXB->text();
+	RCVR[1] = Q.toInt();
+
+	Q = Dlg->AddRXC->text();
+	RCVR[2] = Q.toInt();
+
+	Q = Dlg->AddRXD->text();
+	RCVR[3] = Q.toInt();
+
+	Q = Dlg->RXShiftA->text();
+	rcvr_offset[0] = Q.toInt();
+
+	Q = Dlg->RXShiftB->text();
+	rcvr_offset[1] = Q.toInt();
+
+	Q = Dlg->RXShiftC->text();
+	rcvr_offset[2] = Q.toInt();
+
+	Q = Dlg->RXShiftD->text();
+	rcvr_offset[3] = Q.toInt();
+
+	fx25_mode[0] = Dlg->fx25ModeA->currentIndex();
+	fx25_mode[1] = Dlg->fx25ModeB->currentIndex();
+	fx25_mode[2] = Dlg->fx25ModeC->currentIndex();
+	fx25_mode[3] = Dlg->fx25ModeD->currentIndex();
+
+	il2p_mode[0] = Dlg->IL2PModeA->currentIndex();
+	il2p_mode[1] = Dlg->IL2PModeB->currentIndex();
+	il2p_mode[2] = Dlg->IL2PModeC->currentIndex();
+	il2p_mode[3] = Dlg->IL2PModeD->currentIndex();
+
+	recovery[0] = Dlg->recoverBitA->currentIndex();
+	recovery[1] = Dlg->recoverBitB->currentIndex();
+	recovery[2] = Dlg->recoverBitC->currentIndex();
+	recovery[3] = Dlg->recoverBitD->currentIndex();
+
+
+	strcpy(CWIDCall, Dlg->CWIDCall->text().toUtf8().toUpper());
+	CWIDInterval = Dlg->CWIDInterval->text().toInt();
+	CWIDType = Dlg->radioButton_2->isChecked();
+
+	if (CWIDInterval)
+		cwidtimer->start(CWIDInterval * 60000);
+	else
+		cwidtimer->stop();
+
+
+	RSID_SABM[0] = Dlg->RSIDSABM_A->isChecked();
+	RSID_SABM[1] = Dlg->RSIDSABM_B->isChecked();
+	RSID_SABM[2] = Dlg->RSIDSABM_C->isChecked();
+	RSID_SABM[3] = Dlg->RSIDSABM_D->isChecked();
+
+	RSID_UI[0] = Dlg->RSIDUI_A->isChecked();
+	RSID_UI[1] = Dlg->RSIDUI_B->isChecked();
+	RSID_UI[2] = Dlg->RSIDUI_C->isChecked();
+	RSID_UI[3] = Dlg->RSIDUI_D->isChecked();
+
+	RSID_SetModem[0] = Dlg->RSID_1_SETMODEM->isChecked();
+	RSID_SetModem[1] = Dlg->RSID_2_SETMODEM->isChecked();
+	RSID_SetModem[2] = Dlg->RSID_3_SETMODEM->isChecked();
+	RSID_SetModem[3] = Dlg->RSID_4_SETMODEM->isChecked();
+
+	Q = Dlg->DigiCallsA->text();
+	strcpy(MyDigiCall[0], Q.toString().toUtf8().toUpper());
+
+	Q = Dlg->DigiCallsB->text();
+	strcpy(MyDigiCall[1], Q.toString().toUtf8().toUpper());
+
+	Q = Dlg->DigiCallsC->text();
+	strcpy(MyDigiCall[2], Q.toString().toUtf8().toUpper());
+
+	Q = Dlg->DigiCallsD->text();
+	strcpy(MyDigiCall[3], Q.toString().toUtf8().toUpper());
+
+	int i;
+
+	for (i = 0; i < 4; i++)
+	{
+		initTStringList(&list_digi_callsigns[i]);
+
+		get_exclude_list(MyDigiCall[i], &list_digi_callsigns[i]);
+	}
+
+}
+
+void QtSoundModem::modemreject()
+{
+	delete(Dlg);
+	modemUI->reject();
+}
+
+void QtSoundModem::doRSIDA()
+{
+	needRSID[0] = 1;
+}
+
+void QtSoundModem::doRSIDB()
+{
+	needRSID[1] = 1;
+}
+
+void QtSoundModem::doRSIDC()
+{
+	needRSID[2] = 1;
+}
+
+void QtSoundModem::doRSIDD()
+{
+	needRSID[3] = 1;
+}
+
+
+
+
+void QtSoundModem::doFilter(int Chan, int Filter)
+{
+	Ui_Dialog Dev;
+	QImage * bitmap;
+
+	QDialog UI;
+
+	Dev.setupUi(&UI);
+
+	bitmap = new QImage(642, 312, QImage::Format_RGB32);
+
+	bitmap->fill(qRgb(255, 255, 255));
+
+	QPainter qPainter(bitmap);
+	qPainter.setBrush(Qt::NoBrush);
+	qPainter.setPen(Qt::black);
+
+	if (Filter == 0)
+		make_graph_buf(DET[0][0].BPF_core[Chan], BPF_tap[Chan], &qPainter);
+	else if (Filter == 1)
+		make_graph_buf(tx_BPF_core[Chan], tx_BPF_tap[Chan], &qPainter);
+	else
+		make_graph_buf(LPF_core[Chan], LPF_tap[Chan], &qPainter);
+
+	qPainter.end();
+	Dev.label->setPixmap(QPixmap::fromImage(*bitmap));
+
+	UI.exec();
+
+}
+
+Ui_devicesDialog * Dev;
+
+char NewPTTPort[80];
+
+int newSoundMode = 0;
+int oldSoundMode = 0;
+
+void QtSoundModem::SoundModeChanged(bool State)
+{
+	UNUSED(State);
+
+	// Mustn't change SoundMode until dialog is accepted
+
+	if (Dev->UDP->isChecked())
+		newSoundMode = 3;
+	else if (Dev->PULSE->isChecked())
+		newSoundMode = 2;
+	else
+		newSoundMode = Dev->OSS->isChecked();
+
+}
+
+void QtSoundModem::DualPTTChanged(bool State)
+{
+	UNUSED(State);
+
+	// Forse Evaluation of Cat Port setting
+
+	PTTPortChanged(0);
+}
+
+void QtSoundModem::CATChanged(bool State)
+{
+	UNUSED(State);
+	PTTPortChanged(0);
+}
+
+void QtSoundModem::PTTPortChanged(int Selected)
+{
+	UNUSED(Selected);
+
+	QVariant Q = Dev->PTTPort->currentText();
+	strcpy(NewPTTPort, Q.toString().toUtf8());
+
+	Dev->RTSDTR->setVisible(false);
+	Dev->CAT->setVisible(false);
+
+	Dev->PTTOnLab->setVisible(false);
+	Dev->PTTOn->setVisible(false);
+	Dev->PTTOff->setVisible(false);
+	Dev->PTTOffLab->setVisible(false);
+	Dev->CATLabel->setVisible(false);
+	Dev->CATSpeed->setVisible(false);
+
+	Dev->GPIOLab->setVisible(false);
+	Dev->GPIOLeft->setVisible(false);
+	Dev->GPIORight->setVisible(false);
+	Dev->GPIOLab2->setVisible(false);
+
+	Dev->CM108Label->setVisible(false);
+	Dev->VIDPID->setVisible(false);
+
+	if (strcmp(NewPTTPort, "None") == 0)
+	{
+	}
+	else if (strcmp(NewPTTPort, "GPIO") == 0)
+	{
+		Dev->GPIOLab->setVisible(true);
+		Dev->GPIOLeft->setVisible(true);
+		if (Dev->DualPTT->isChecked())
+		{
+			Dev->GPIORight->setVisible(true);
+			Dev->GPIOLab2->setVisible(true);
+		}
+	}
+
+	else if (strcmp(NewPTTPort, "CM108") == 0)
+	{
+		Dev->CM108Label->setVisible(true);
+//#ifdef __ARM_ARCHX
+		Dev->CM108Label->setText("CM108 Device");
+//#else
+//		Dev->CM108Label->setText("CM108 VID/PID");
+//#endif
+		Dev->VIDPID->setText(CM108Addr);
+		Dev->VIDPID->setVisible(true);
+	}
+	else if (strcmp(NewPTTPort, "HAMLIB") == 0)
+	{
+		Dev->CM108Label->setVisible(true);
+		Dev->CM108Label->setText("rigctrld Port");
+		Dev->VIDPID->setText(QString::number(HamLibPort));
+		Dev->VIDPID->setVisible(true);
+		Dev->PTTOnLab->setText("rigctrld Host");
+		Dev->PTTOnLab->setVisible(true);
+		Dev->PTTOn->setText(HamLibHost);
+		Dev->PTTOn->setVisible(true);
+	}
+	else
+	{
+		Dev->RTSDTR->setVisible(true);
+		Dev->CAT->setVisible(true);
+
+		if (Dev->CAT->isChecked())
+		{
+			Dev->PTTOnLab->setVisible(true);
+			Dev->PTTOnLab->setText("PTT On String");
+			Dev->PTTOn->setText(PTTOnString);
+			Dev->PTTOn->setVisible(true);
+			Dev->PTTOff->setVisible(true);
+			Dev->PTTOff->setText(PTTOffString);
+			Dev->PTTOffLab->setVisible(true);
+			Dev->CATLabel->setVisible(true);
+			Dev->CATSpeed->setVisible(true);
+		}
+	}
+}
+
+bool myResize::eventFilter(QObject *obj, QEvent *event)
+{
+	if (event->type() == QEvent::Resize)
+	{
+		QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
+		QSize size = resizeEvent->size();
+		int h = size.height();
+		int w = size.width();
+
+		if (obj == deviceUI)
+			Dev->scrollArea->setGeometry(QRect(5, 5, w - 10, h - 10));
+		else
+			Dlg->scrollArea->setGeometry(QRect(5, 5, w - 10, h - 10));
+
+		return true;
+	}
+	return QObject::eventFilter(obj, event);
+}
+
+void QtSoundModem::doDevices()
+{
+	char valChar[10];
+
+	Dev = new(Ui_devicesDialog);
+
+	QDialog UI;
+
+	int i;
+
+	Dev->setupUi(&UI);
+
+	deviceUI = &UI;
+	modemUI = 0;
+
+	myResize *resize = new myResize();
+
+	UI.installEventFilter(resize);
+
+	newSoundMode = SoundMode;
+	oldSoundMode = SoundMode;
+
+#ifdef WIN32
+	Dev->ALSA->setText("WaveOut");
+	Dev->OSS->setVisible(0);
+	Dev->PULSE->setVisible(0);
+#endif
+
+	if (SoundMode == 0)
+		Dev->ALSA->setChecked(1);
+	else if (SoundMode == 1)
+		Dev->OSS->setChecked(1);
+	else if (SoundMode == 2)
+		Dev->PULSE->setChecked(1);
+	else if (SoundMode == 2)
+		Dev->UDP->setChecked(1);
+
+	connect(Dev->ALSA, SIGNAL(toggled(bool)), this, SLOT(SoundModeChanged(bool)));
+	connect(Dev->OSS, SIGNAL(toggled(bool)), this, SLOT(SoundModeChanged(bool)));
+	connect(Dev->PULSE, SIGNAL(toggled(bool)), this, SLOT(SoundModeChanged(bool)));
+	connect(Dev->UDP, SIGNAL(toggled(bool)), this, SLOT(SoundModeChanged(bool)));
+
+	for (i = 0; i < PlaybackCount; i++)
+		Dev->outputDevice->addItem(&PlaybackNames[i][0]);
+
+	i = Dev->outputDevice->findText(PlaybackDevice, Qt::MatchContains);
+
+
+	if (i == -1)
+	{
+		// Add device to list
+
+		Dev->outputDevice->addItem(PlaybackDevice);
+		i = Dev->outputDevice->findText(PlaybackDevice, Qt::MatchContains);
+	}
+
+	Dev->outputDevice->setCurrentIndex(i);
+
+	for (i = 0; i < CaptureCount; i++)
+		Dev->inputDevice->addItem(&CaptureNames[i][0]);
+
+	i = Dev->inputDevice->findText(CaptureDevice, Qt::MatchContains);
+
+	if (i == -1)
+	{
+		// Add device to list
+
+		Dev->inputDevice->addItem(CaptureDevice);
+		i = Dev->inputDevice->findText(CaptureDevice, Qt::MatchContains);
+	}
+	Dev->inputDevice->setCurrentIndex(i);
+
+	Dev->Modem_1_Chan->setCurrentIndex(soundChannel[0]);
+	Dev->Modem_2_Chan->setCurrentIndex(soundChannel[1]);
+	Dev->Modem_3_Chan->setCurrentIndex(soundChannel[2]);
+	Dev->Modem_4_Chan->setCurrentIndex(soundChannel[3]);
+
+	// Disable "None" option in first modem
+
+	QStandardItemModel *model = dynamic_cast<QStandardItemModel *>(Dev->Modem_1_Chan->model());
+	QStandardItem * item = model->item(0, 0);
+	item->setEnabled(false);
+
+	Dev->singleChannelOutput->setChecked(SCO);
+	Dev->colourWaterfall->setChecked(raduga);
+
+	sprintf(valChar, "%d", KISSPort);
+	Dev->KISSPort->setText(valChar);
+	Dev->KISSEnabled->setChecked(KISSServ);
+
+	sprintf(valChar, "%d", AGWPort);
+	Dev->AGWPort->setText(valChar);
+	Dev->AGWEnabled->setChecked(AGWServ);
+
+	Dev->PTTOn->setText(PTTOnString);
+	Dev->PTTOff->setText(PTTOffString);
+
+	sprintf(valChar, "%d", PTTBAUD);
+	Dev->CATSpeed->setText(valChar);
+
+	sprintf(valChar, "%d", UDPClientPort);
+	Dev->UDPPort->setText(valChar);
+	Dev->UDPTXHost->setText(UDPHost);
+
+	if (UDPServerPort != TXPort)
+		sprintf(valChar, "%d/%d", UDPServerPort, TXPort);
+	else
+		sprintf(valChar, "%d", UDPServerPort);
+
+	Dev->UDPTXPort->setText(valChar);
+
+	Dev->UDPEnabled->setChecked(UDPServ);
+
+	sprintf(valChar, "%d", pttGPIOPin);
+	Dev->GPIOLeft->setText(valChar);
+	sprintf(valChar, "%d", pttGPIOPinR);
+	Dev->GPIORight->setText(valChar);
+
+	Dev->VIDPID->setText(CM108Addr);
+
+	QStringList items;
+
+	connect(Dev->CAT, SIGNAL(toggled(bool)), this, SLOT(CATChanged(bool)));
+	connect(Dev->DualPTT, SIGNAL(toggled(bool)), this, SLOT(DualPTTChanged(bool)));
+	connect(Dev->PTTPort, SIGNAL(currentIndexChanged(int)), this, SLOT(PTTPortChanged(int)));
+
+
+
+	if (PTTMode == PTTCAT)
+		Dev->CAT->setChecked(true);
+	else
+		Dev->RTSDTR->setChecked(true);
+
+	for (const QSerialPortInfo &info : Ports)
+	{
+		items.append(info.portName());
+	}
+
+	items.sort();
+
+	Dev->PTTPort->addItem("None");
+	Dev->PTTPort->addItem("CM108");
+
+	//#ifdef __ARM_ARCH
+
+	Dev->PTTPort->addItem("GPIO");
+
+	//#endif
+
+	Dev->PTTPort->addItem("HAMLIB");
+
+	for (const QString &info : items)
+	{
+		Dev->PTTPort->addItem(info);
+	}
+
+	Dev->PTTPort->setCurrentIndex(Dev->PTTPort->findText(PTTPort, Qt::MatchFixedString));
+
+	PTTPortChanged(0);				// Force reevaluation
+
+	Dev->txRotation->setChecked(TX_rotate);
+	Dev->DualPTT->setChecked(DualPTT);
+
+	Dev->multiCore->setChecked(multiCore);
+
+	QObject::connect(Dev->okButton, SIGNAL(clicked()), this, SLOT(deviceaccept()));
+	QObject::connect(Dev->cancelButton, SIGNAL(clicked()), this, SLOT(devicereject()));
+
+	UI.exec();
+
+}
+
+void QtSoundModem::deviceaccept()
+{
+	QVariant Q = Dev->inputDevice->currentText();
+	int cardChanged = 0;
+	char portString[32];
+
+	if (Dev->UDP->isChecked())
+	{
+		// cant have server and slave
+
+		if (Dev->UDPEnabled->isChecked())
+		{
+			QMessageBox::about(this, tr("QtSoundModem"),
+				tr("Can't have UDP sound source and UDP server at same time"));
+			return;
+		}
+	}
+
+	if (oldSoundMode != newSoundMode)
+	{
+		QMessageBox msgBox;
+
+		msgBox.setText("QtSoundModem must restart to change Sound Mode.\n"
+			"Program will close if you hit Ok\n"
+			"You will need to reselect audio devices after restarting");
+
+		msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
+
+		int i = msgBox.exec();
+
+		if (i == QMessageBox::Ok)
+		{
+			SoundMode = newSoundMode;
+
+			saveSettings();
+
+			Closing = 1;
+			return;
+		}
+
+		if (oldSoundMode == 0)
+			Dev->ALSA->setChecked(1);
+		else if (oldSoundMode == 1)
+			Dev->OSS->setChecked(1);
+		else if (oldSoundMode == 2)
+			Dev->PULSE->setChecked(1);
+		else if (oldSoundMode == 3)
+			Dev->UDP->setChecked(1);
+
+		QMessageBox::about(this, tr("Info"),
+			tr("<p align = 'center'>Changes not saved</p>"));
+
+		return;
+
+	}
+
+	if (strcmp(CaptureDevice, Q.toString().toUtf8()) != 0)
+	{
+		strcpy(CaptureDevice, Q.toString().toUtf8());
+		cardChanged = 1;
+	}
+
+	CaptureIndex = Dev->inputDevice->currentIndex();
+
+	Q = Dev->outputDevice->currentText();
+
+	if (strcmp(PlaybackDevice, Q.toString().toUtf8()) != 0)
+	{
+		strcpy(PlaybackDevice, Q.toString().toUtf8());
+		cardChanged = 1;
+	}
+
+	PlayBackIndex = Dev->outputDevice->currentIndex();
+
+	soundChannel[0] = Dev->Modem_1_Chan->currentIndex();
+	soundChannel[1] = Dev->Modem_2_Chan->currentIndex();
+	soundChannel[2] = Dev->Modem_3_Chan->currentIndex();
+	soundChannel[3] = Dev->Modem_4_Chan->currentIndex();
+
+	UsingLeft = 0;
+	UsingRight = 0;
+	UsingBothChannels = 0;
+
+	for (int i = 0; i < 4; i++)
+	{
+		if (soundChannel[i] == LEFT)
+		{
+			UsingLeft = 1;
+			modemtoSoundLR[i] = 0;
+		}
+		else if (soundChannel[i] == RIGHT)
+		{
+			UsingRight = 1;
+			modemtoSoundLR[i] = 1;
+		}
+	}
+
+	if (UsingLeft && UsingRight)
+		UsingBothChannels = 1;
+
+
+	SCO = Dev->singleChannelOutput->isChecked();
+	raduga = Dev->colourWaterfall->isChecked();
+	AGWServ = Dev->AGWEnabled->isChecked();
+	KISSServ = Dev->KISSEnabled->isChecked();
+
+	Q = Dev->KISSPort->text();
+	KISSPort = Q.toInt();
+
+	Q = Dev->AGWPort->text();
+	AGWPort = Q.toInt();
+
+	Q = Dev->PTTPort->currentText();
+	strcpy(PTTPort, Q.toString().toUtf8());
+
+	DualPTT = Dev->DualPTT->isChecked();
+	TX_rotate = Dev->txRotation->isChecked();
+	multiCore = Dev->multiCore->isChecked();
+
+	if (Dev->CAT->isChecked())
+		PTTMode = PTTCAT;
+	else
+		PTTMode = PTTRTS;
+
+	Q = Dev->PTTOn->text();
+	strcpy(PTTOnString, Q.toString().toUtf8());
+	Q = Dev->PTTOff->text();
+	strcpy(PTTOffString, Q.toString().toUtf8());
+
+	Q = Dev->CATSpeed->text();
+	PTTBAUD = Q.toInt();
+
+	Q = Dev->UDPPort->text();
+	UDPClientPort = Q.toInt();
+
+
+	Q = Dev->UDPTXPort->text();
+	strcpy(portString, Q.toString().toUtf8());
+	UDPServerPort = atoi(portString);
+
+	if (strchr(portString, '/'))
+	{
+		char * ptr = strlop(portString, '/');
+		TXPort = atoi(ptr);
+	}
+	else
+		TXPort = UDPServerPort;
+
+	Q = Dev->UDPTXHost->text();
+	strcpy(UDPHost, Q.toString().toUtf8());
+
+	UDPServ = Dev->UDPEnabled->isChecked();
+
+	Q = Dev->GPIOLeft->text();
+	pttGPIOPin = Q.toInt();
+
+	Q = Dev->GPIORight->text();
+	pttGPIOPinR = Q.toInt();
+
+	Q = Dev->VIDPID->text();
+
+	if (strcmp(PTTPort, "CM108") == 0)
+		strcpy(CM108Addr, Q.toString().toUtf8());
+	else if (strcmp(PTTPort, "HAMLIB") == 0)
+	{
+		HamLibPort = Q.toInt();
+		Q = Dev->PTTOn->text();
+		strcpy(HamLibHost, Q.toString().toUtf8());
+	}
+
+	ClosePTTPort();
+	OpenPTTPort();
+
+	wf_pointer(soundChannel[0]);
+	wf_pointer(soundChannel[1]);
+
+	delete(Dev);
+	saveSettings();
+	deviceUI->accept();
+
+	if (cardChanged)
+	{
+		InitSound(1);
+	}
+
+	// Reset title and tooltip in case ports changed
+
+	char Title[128];
+	sprintf(Title, "QtSoundModem Version %s Ports %d/%d", VersionString, AGWPort, KISSPort);
+	w->setWindowTitle(Title);
+
+	sprintf(Title, "QtSoundModem %d %d", AGWPort, KISSPort);
+	if (trayIcon)
+		trayIcon->setToolTip(Title);
+
+	QSize newSize(this->size());
+	QSize oldSize(this->size());
+
+	QResizeEvent *myResizeEvent = new QResizeEvent(newSize, oldSize);
+
+	QCoreApplication::postEvent(this, myResizeEvent);  
+}
+
+void QtSoundModem::devicereject()
+{	
+	delete(Dev);
+	deviceUI->reject();
+}
+
+void QtSoundModem::handleButton(int Port, int Type)
+{
+	// interlock calib with CWID
+
+	if (calib_mode[0] == 4)		// CWID
+		return;
+	
+	doCalib(Port, Type);
+}
+
+void QtSoundModem::doRestartWF()
+{
+	if (Firstwaterfall)
+	{
+		initWaterfall(0, 0);
+		initWaterfall(0, 1);
+	}
+
+	if (Secondwaterfall)
+	{
+		initWaterfall(1, 0);
+		initWaterfall(1, 1);
+	}
+}
+
+
+void QtSoundModem::doAbout()
+{
+	QMessageBox::about(this, tr("About"),
+		tr("G8BPQ's port of UZ7HO's Soundmodem\n\nCopyright (C) 2019-2020 Andrei Kopanchuk UZ7HO"));
+}
+
+void QtSoundModem::doCalibrate()
+{
+	Ui_calDialog Calibrate;
+	{
+		QDialog UI;
+		Calibrate.setupUi(&UI);
+
+		connect(Calibrate.Low_A, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.High_A, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.Both_A, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.Stop_A, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.Low_B, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.High_B, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.Both_B, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.Stop_B, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.Low_C, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.High_C, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.Both_C, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.Stop_C, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.Low_D, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.High_D, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.Both_D, SIGNAL(released()), this, SLOT(clickedSlot()));
+		connect(Calibrate.Stop_D, SIGNAL(released()), this, SLOT(clickedSlot()));
+
+		/*
+		
+		connect(Calibrate.Low_A, &QPushButton::released, this, [=] { handleButton(0, 1); });
+		connect(Calibrate.High_A, &QPushButton::released, this, [=] { handleButton(0, 2); });
+		connect(Calibrate.Both_A, &QPushButton::released, this, [=] { handleButton(0, 3); });
+		connect(Calibrate.Stop_A, &QPushButton::released, this, [=] { handleButton(0, 0); });
+		connect(Calibrate.Low_B, &QPushButton::released, this, [=] { handleButton(1, 1); });
+		connect(Calibrate.High_B, &QPushButton::released, this, [=] { handleButton(1, 2); });
+		connect(Calibrate.Both_B, &QPushButton::released, this, [=] { handleButton(1, 3); });
+		connect(Calibrate.Stop_B, &QPushButton::released, this, [=] { handleButton(1, 0); });
+
+//		connect(Calibrate.High_A, SIGNAL(released()), this, SLOT(handleButton(1, 2)));
+*/
+		UI.exec();
+	}
+}
+
+void QtSoundModem::RefreshSpectrum(unsigned char * Data)
+{
+	int i;
+
+	// Last 4 bytes are level busy and Tuning lines
+
+	Waterfall[0]->fill(Black);
+
+	if (Data[206] != LastLevel)
+	{
+		LastLevel = Data[206];
+//		RefreshLevel(LastLevel);
+	}
+
+	if (Data[207] != LastBusy)
+	{
+		LastBusy = Data[207];
+//		Busy->setVisible(LastBusy);
+	}
+
+	for (i = 0; i < 205; i++)
+	{
+		int val = Data[0];
+
+		if (val > 63)
+			val = 63;
+
+		Waterfall[0]->setPixel(i, val, Yellow);
+		if (val < 62)
+			Waterfall[0]->setPixel(i, val + 1, Gold);
+		Data++;
+	}
+
+	ui.WaterfallA->setPixmap(QPixmap::fromImage(*Waterfall[0]));
+
+}
+
+void QtSoundModem::RefreshWaterfall(int snd_ch, unsigned char * Data)
+{
+	int j;
+	unsigned char * Line;
+	int len = Waterfall[0]->bytesPerLine();
+	int TopLine = NextWaterfallLine[snd_ch];
+
+	// Write line to cyclic buffer then draw starting with the line just written
+
+	// Length is 208 bytes, including Level and Busy flags
+
+	memcpy(&WaterfallLines[snd_ch][NextWaterfallLine[snd_ch]++][0], Data, 206);
+	if (NextWaterfallLine[snd_ch] > 63)
+		NextWaterfallLine[snd_ch] = 0;
+
+	for (j = 63; j > 0; j--)
+	{
+		Line = Waterfall[0]->scanLine(j);
+		memcpy(Line, &WaterfallLines[snd_ch][TopLine++][0], len);
+		if (TopLine > 63)
+			TopLine = 0;
+	}
+
+	ui.WaterfallA->setPixmap(QPixmap::fromImage(*Waterfall[0]));
+}
+
+
+void QtSoundModem::sendtoTrace(char * Msg, int tx)
+{
+	const QTextCursor old_cursor = monWindowCopy->textCursor();
+	const int old_scrollbar_value = monWindowCopy->verticalScrollBar()->value();
+	const bool is_scrolled_down = old_scrollbar_value == monWindowCopy->verticalScrollBar()->maximum();
+
+	// Move the cursor to the end of the document.
+	monWindowCopy->moveCursor(QTextCursor::End);
+
+	// Insert the text at the position of the cursor (which is the end of the document).
+
+	if (tx)
+		monWindowCopy->setTextColor(qRgb(192, 0, 0));
+	else
+		monWindowCopy->setTextColor(qRgb(0, 0, 192));
+
+	monWindowCopy->textCursor().insertText(Msg);
+
+	if (old_cursor.hasSelection() || !is_scrolled_down)
+	{
+		// The user has selected text or scrolled away from the bottom: maintain position.
+		monWindowCopy->setTextCursor(old_cursor);
+		monWindowCopy->verticalScrollBar()->setValue(old_scrollbar_value);
+	}
+	else
+	{
+		// The user hasn't selected any text and the scrollbar is at the bottom: scroll to the bottom.
+		monWindowCopy->moveCursor(QTextCursor::End);
+		monWindowCopy->verticalScrollBar()->setValue(monWindowCopy->verticalScrollBar()->maximum());
+	}
+
+	free(Msg);
+}
+
+
+// I think this does the waterfall
+
+typedef struct TRGBQ_t
+{
+	Byte b, g, r, re;
+
+} TRGBWQ;
+
+typedef struct tagRECT
+{
+	int    left;
+	int    top;
+	int    right;
+	int    bottom;
+} RECT;
+
+unsigned int RGBWF[256] ;
+
+
+extern "C" void init_raduga()
+{
+	Byte offset[6] = {0, 51, 102, 153, 204};
+	Byte i, n;
+
+	for (n = 0; n < 52; n++)
+	{
+		i = n * 5;
+
+		RGBWF[n + offset[0]] = qRgb(0, 0, i);
+		RGBWF[n + offset[1]] = qRgb(0, i, 255);
+		RGBWF[n + offset[2]] = qRgb(0, 255, 255 - i);
+		RGBWF[n + offset[3]] = qRgb(1, 255, 0);
+		RGBWF[n + offset[4]] = qRgb(255, 255 - 1, 0);
+	}
+}
+
+extern "C" int nonGUIMode;
+
+
+// This draws the Frequency Scale on Waterfall
+
+extern "C" void wf_Scale(int Chan)
+{
+	if (nonGUIMode)
+		return;
+
+	float k;
+	int maxfreq, x, i;
+	char Textxx[20];
+	QImage * bm = Header[Chan];
+
+	QPainter qPainter(bm);
+	qPainter.setBrush(Qt::black);
+	qPainter.setPen(Qt::white);
+
+	maxfreq = roundf(RX_Samplerate*0.005);
+	k = 100 * fft_size / RX_Samplerate;
+
+	if (Chan == 0)
+		sprintf(Textxx, "Left");
+	else
+		sprintf(Textxx, "Right");
+
+	qPainter.drawText(2, 1,
+		100, 20, 0, Textxx);
+
+	for (i = 0; i < maxfreq; i++)
+	{
+		x = round(k*i);
+		if (x < 1025)
+		{
+			if ((i % 5) == 0)
+				qPainter.drawLine(x, 20, x, 13);
+			else
+				qPainter.drawLine(x, 20, x, 16);
+
+			if ((i % 10) == 0)
+			{
+				sprintf(Textxx, "%d", i * 100);
+
+				qPainter.drawText(x - 12, 1,
+					100, 20, 0, Textxx);
+			}
+		}
+	}
+	HeaderCopy[Chan]->setPixmap(QPixmap::fromImage(*bm));
+
+}
+
+// This draws the frequency Markers on the Waterfall
+
+
+void do_pointer(int waterfall)
+{
+	if (nonGUIMode)
+		return;
+
+	float x;
+
+	int x1, x2, k, pos1, pos2, pos3;
+	QImage * bm = Header[waterfall];
+
+	QPainter qPainter(bm);
+	qPainter.setBrush(Qt::NoBrush);
+	qPainter.setPen(Qt::white);
+
+	//	bm->fill(black);
+
+	qPainter.fillRect(0, 26, 1024, 9, Qt::black);
+
+	k = 29;
+	x = fft_size / RX_Samplerate;
+
+	// draw all enabled ports on the ports on this soundcard
+
+	// First Modem is always on the first waterfall
+	// If second is enabled it is on the first unless different
+	//		channel from first
+
+	for (int i = 0; i < 4; i++)
+	{
+		if (UsingBothChannels == 0)
+		{
+			// Only One Waterfall. If first chan is 
+
+			if ((waterfall == 0 && soundChannel[i] == RIGHT) || (waterfall == 1 && soundChannel[i] == LEFT))
+				return;
+		}
+
+		if (soundChannel[i] == 0)
+			continue;
+
+
+		if (UsingBothChannels == 1)
+			if ((waterfall == 0 && soundChannel[i] == RIGHT) || (waterfall == 1 && soundChannel[i] == LEFT))
+				continue;
+
+		pos1 = roundf(((rxOffset + chanOffset[i] + rx_freq[i]) - 0.5*rx_shift[i])*x) - 5;
+		pos2 = roundf(((rxOffset + chanOffset[i] + rx_freq[i]) + 0.5*rx_shift[i])*x) - 5;
+		pos3 = roundf((rxOffset + chanOffset[i] + rx_freq[i]) * x);
+		x1 = pos1 + 5;
+		x2 = pos2 + 5;
+
+		qPainter.setPen(Qt::white);
+		qPainter.drawLine(x1, k, x2, k);
+		qPainter.drawLine(x1, k - 3, x1, k + 3);
+		qPainter.drawLine(x2, k - 3, x2, k + 3);
+		qPainter.drawLine(pos3, k - 3, pos3, k + 3);
+
+		if (rxOffset || chanOffset[i])
+		{
+			// Draw TX posn if rxOffset used
+
+			pos3 = roundf(rx_freq[i] * x);
+			qPainter.setPen(Qt::magenta);
+			qPainter.drawLine(pos3, k - 3, pos3, k + 3);
+			qPainter.drawLine(pos3, k - 3, pos3, k + 3);
+			qPainter.drawLine(pos3 - 2, k - 3, pos3 + 2, k - 3);
+
+		}
+	}
+	HeaderCopy[waterfall]->setPixmap(QPixmap::fromImage(*bm));
+}
+
+void wf_pointer(int snd_ch)
+{
+	UNUSED(snd_ch);
+
+	do_pointer(0);
+	do_pointer(1);
+//	do_pointer(2);
+//	do_pointer(3);
+}
+
+
+void doWaterfallThread(void * param);
+
+/*
+#ifdef WIN32
+
+#define pthread_t uintptr_t
+
+extern "C" uintptr_t _beginthread(void(__cdecl *start_address)(void *), unsigned stack_size, void *arglist);
+
+#else
+
+#include <pthread.h>
+
+extern "C" pthread_t _beginthread(void(*start_address)(void *), unsigned stack_size, void * arglist)
+{
+	pthread_t thread;
+
+	if (pthread_create(&thread, NULL, (void * (*)(void *))start_address, (void*)arglist) != 0)
+		perror("New Thread");
+	else
+		pthread_detach(thread);
+
+	return thread;
+}
+
+#endif
+*/
+extern "C" void doWaterfall(int snd_ch)
+{
+	if (nonGUIMode)
+		return;
+
+	if (Closing)
+		return;
+
+//	if (multiCore)			// Run modems in separate threads
+//		_beginthread(doWaterfallThread, 0, xx);
+//	else
+		doWaterfallThread((void *)(size_t)snd_ch);
+
+}
+
+
+extern "C" float aFFTAmpl[1024];
+
+void doWaterfallThread(void * param)
+{
+	int snd_ch = (int)(size_t)param;
+
+	QImage * bm = Waterfall[snd_ch];
+	
+	word  i, wid;
+	single  mag;
+	UCHAR * p;
+	UCHAR Line[4096];
+
+	int lineLen;
+	word  hfft_size;
+	Byte  n;
+	float RealOut[4096] = { 0 };
+	float ImagOut[4096];
+
+	QRegion exposed;
+
+	hfft_size = fft_size / 2;
+
+	// I think an FFT should produce n/2 bins, each of Samp/n Hz
+	// Looks like my code only works with n a power of 2
+
+	// So can use 1024 or 4096. 1024 gives 512 bins of 11.71875 and a 512 pixel 
+	// display (is this enough?)
+
+	// This does 2048
+
+	if (0)	//RSID_WF
+	{
+		// Use the Magnitudes in float aFFTAmpl[RSID_FFT_SIZE];
+
+		for (i = 0; i < hfft_size; i++)
+		{
+			mag = aFFTAmpl[i];
+
+			mag *= 0.00000042f;
+
+			if (mag < 0.00001f)
+				mag = 0.00001f;
+
+			if (mag > 1.0f)
+				mag = 1.0f;
+
+			mag = 22 * log2f(mag) + 255;
+
+			if (mag < 0)
+				mag = 0;
+
+			fft_disp[snd_ch][i] = round(mag);
+		}
+	}
+	else
+	{
+		dofft(&fft_buf[snd_ch][0], RealOut, ImagOut);
+
+		//	FourierTransform(1024, &fft_buf[snd_ch][0], RealOut, ImagOut, 0);
+
+		for (i = 0; i < hfft_size; i++)
+		{
+			//mag: = ComplexMag(fft_d[i])*0.00000042;
+
+	//		mag = sqrtf(powf(RealOut[i], 2) + powf(ImagOut[i], 2)) * 0.00000042f;
+
+			mag = powf(RealOut[i], 2);
+			mag += powf(ImagOut[i], 2);
+			mag = sqrtf(mag);
+			mag *= 0.00000042f;
+
+
+			if (mag > MaxMagOut)
+			{
+				MaxMagOut = mag;
+				MaxMagIndex = i;
+			}
+
+			if (mag < 0.00001f)
+				mag = 0.00001f;
+
+			if (mag > 1.0f)
+				mag = 1.0f;
+
+			mag = 22 * log2f(mag) + 255;
+
+			if (mag < 0)
+				mag = 0;
+
+			MagOut[i] = mag;
+			fft_disp[snd_ch][i] = round(mag);
+		}
+	}
+
+
+
+	/*
+		for (i = 0; i < hfft_size; i++)
+			fft[i] = (powf(RealOut[i], 2) + powf(ImagOut[i], 2));
+
+		for (i = 0; i < hfft_size; i++)
+		{
+			if (fft[i] > max)
+			{
+				max = fft[i];
+				imax = i;
+			}
+		}
+
+		if (max > 0)
+		{
+			for (i = 0; i < hfft_size; i++)
+				fft[i] = fft[i] / max;
+		}
+
+
+		for (i = 0; i < hfft_size; i++)
+		{
+			mag = fft[i];
+
+			if (mag < 0.00001f)
+				mag = 0.00001f;
+
+			if (mag > 1.0f)
+				mag = 1.0f;
+
+			mag = 22 * log2f(mag) + 255;
+
+			if (mag < 0)
+				mag = 0;
+
+			fft_disp[snd_ch][i] = round(mag);
+		}
+
+		*/
+
+	//	bm[snd_ch].Canvas.CopyRect(d, bm[snd_ch].canvas, s)
+
+	//pm->scroll(0, 1, 0, 0, 1024, 80, &exposed);
+
+	// Each bin is 12000 /2048 = 5.859375
+	// I think we plot at 6 Hz per pixel.
+
+	wid = bm->width();
+	if (wid > hfft_size)
+		wid = hfft_size;
+
+	wid = wid - 1;
+
+	p = Line;
+	lineLen = bm->bytesPerLine();
+
+	if (wid > lineLen / 4)
+		wid = lineLen / 4;
+
+	if (raduga == DISP_MONO)
+	{
+		for (i = 0; i < wid; i++)
+		{
+			n = fft_disp[snd_ch][i];
+			*(p++) = n;					// all colours the same
+			*(p++) = n;
+			*(p++) = n;
+			p++;
+		}
+	}
+	else
+	{
+		for (i = 0; i < wid; i++)
+		{
+			n = fft_disp[snd_ch][i];
+
+		memcpy(p, &RGBWF[n], 4);
+			p += 4;
+		}
+	}
+
+	// Scroll
+
+	int TopLine = NextWaterfallLine[snd_ch];
+
+	// Write line to cyclic buffer then draw starting with the line just written
+
+	memcpy(&WaterfallLines[snd_ch][NextWaterfallLine[snd_ch]++][0], Line, 4096);
+	if (NextWaterfallLine[snd_ch] > 79)
+		NextWaterfallLine[snd_ch] = 0;
+
+	for (int j = 79; j > 0; j--)
+	{
+		p = bm->scanLine(j);
+		memcpy(p, &WaterfallLines[snd_ch][TopLine][0], lineLen);
+		TopLine++;
+		if (TopLine > 79)
+			TopLine = 0;
+	}
+
+	WaterfallCopy[snd_ch]->setPixmap(QPixmap::fromImage(*bm));
+	//	WaterfallCopy[snd_ch - 1]->setPixmap(*pm);
+		//	WaterfallCopy[1]->setPixmap(QPixmap::fromImage(*bm));
+
+}
+
+
+
+void QtSoundModem::changeEvent(QEvent* e)
+{
+	if (e->type() == QEvent::WindowStateChange)
+	{
+		QWindowStateChangeEvent* ev = static_cast<QWindowStateChangeEvent*>(e);
+
+		qDebug() << windowState();
+
+		if (!(ev->oldState() & Qt::WindowMinimized) && windowState() & Qt::WindowMinimized)
+		{
+			if (trayIcon)
+				setVisible(false);
+		}
+//		if (!(ev->oldState() != Qt::WindowNoState) && windowState() == Qt::WindowNoState)
+//		{
+//			QMessageBox::information(this, "", "Window has been restored");
+//		}
+
+	}
+	QWidget::changeEvent(e);
+}
+
+#include <QCloseEvent>
+
+void QtSoundModem::closeEvent(QCloseEvent *event)
+{
+	UNUSED(event);
+
+	QSettings mysettings("QtSoundModem.ini", QSettings::IniFormat);
+	mysettings.setValue("geometry", QWidget::saveGeometry());
+	mysettings.setValue("windowState", saveState());
+
+	Closing = TRUE;
+	qDebug() << "Closing";
+
+	QThread::msleep(100);
+}
+
+	
+QtSoundModem::~QtSoundModem()
+{
+	qDebug() << "Saving Settings";
+		
+	QSettings mysettings("QtSoundModem.ini", QSettings::IniFormat);
+	mysettings.setValue("geometry", saveGeometry());
+	mysettings.setValue("windowState", saveState());
+	
+	saveSettings();	
+	Closing = TRUE;
+	qDebug() << "Closing";
+
+	QThread::msleep(100);
+}
+
+extern "C" void QSleep(int ms)
+{
+	QThread::msleep(ms);
+}
+
+int upd_time = 30;
+
+void QtSoundModem::show_grid()
+{
+	// This refeshes the session list
+
+	int  snd_ch, i, num_rows, row_idx;
+	QTableWidgetItem *item;
+	const char * msg;
+
+	int  speed_tx, speed_rx;
+
+	if (grid_time < 10)
+	{
+		grid_time++;
+		return;
+	}
+
+	grid_time = 0;
+
+	//label7.Caption = inttostr(stat_r_mem); mem_arq
+
+	num_rows = 0;
+	row_idx = 0;
+
+	for (snd_ch = 0; snd_ch < 4; snd_ch++)
+	{
+		for (i = 0; i < port_num; i++)
+		{
+			if (AX25Port[snd_ch][i].status != STAT_NO_LINK)
+				num_rows++;
+		}
+	}
+
+	if (num_rows == 0)
+	{
+		sessionTable->clearContents();
+		sessionTable->setRowCount(0);
+		sessionTable->setRowCount(1);
+	}
+	else
+		sessionTable->setRowCount(num_rows);
+
+
+	for (snd_ch = 0; snd_ch < 4; snd_ch++)
+	{
+		for (i = 0; i < port_num; i++)
+		{
+			if (AX25Port[snd_ch][i].status != STAT_NO_LINK)
+			{
+				switch (AX25Port[snd_ch][i].status)
+				{
+				case STAT_NO_LINK:
+
+					msg = "No link";
+					break;
+
+				case STAT_LINK:
+
+					msg = "Link";
+					break;
+
+				case STAT_CHK_LINK:
+
+					msg = "Chk link";
+					break;
+
+				case STAT_WAIT_ANS:
+
+					msg = "Wait ack";
+					break;
+
+				case STAT_TRY_LINK:
+
+					msg = "Try link";
+					break;
+
+				case STAT_TRY_UNLINK:
+
+					msg = "Try unlink";
+				}
+
+
+				item = new QTableWidgetItem((char *)AX25Port[snd_ch][i].mycall);
+				sessionTable->setItem(row_idx, 0, item);
+
+				item = new QTableWidgetItem(AX25Port[snd_ch][i].kind);
+				sessionTable->setItem(row_idx, 11, item);
+
+				item = new QTableWidgetItem((char *)AX25Port[snd_ch][i].corrcall);
+				sessionTable->setItem(row_idx, 1, item);
+
+				item = new QTableWidgetItem(msg);
+				sessionTable->setItem(row_idx, 2, item);
+
+				item = new QTableWidgetItem(QString::number(AX25Port[snd_ch][i].info.stat_s_pkt));
+				sessionTable->setItem(row_idx, 3, item);
+
+				item = new QTableWidgetItem(QString::number(AX25Port[snd_ch][i].info.stat_s_byte));
+				sessionTable->setItem(row_idx, 4, item);
+
+				item = new QTableWidgetItem(QString::number(AX25Port[snd_ch][i].info.stat_r_pkt));
+				sessionTable->setItem(row_idx, 5, item);
+
+				item = new QTableWidgetItem(QString::number(AX25Port[snd_ch][i].info.stat_r_byte));
+				sessionTable->setItem(row_idx, 6, item);
+
+				item = new QTableWidgetItem(QString::number(AX25Port[snd_ch][i].info.stat_r_fc));
+				sessionTable->setItem(row_idx, 7, item);
+
+				item = new QTableWidgetItem(QString::number(AX25Port[snd_ch][i].info.stat_fec_count));
+				sessionTable->setItem(row_idx, 8, item);
+
+				if (grid_timer != upd_time)
+					grid_timer++;
+				else
+				{
+					grid_timer = 0;
+					speed_tx = round(abs(AX25Port[snd_ch][i].info.stat_s_byte - AX25Port[snd_ch][i].info.stat_l_s_byte) / upd_time);
+					speed_rx = round(abs(AX25Port[snd_ch][i].info.stat_r_byte - AX25Port[snd_ch][i].info.stat_l_r_byte) / upd_time);
+
+					item = new QTableWidgetItem(QString::number(speed_tx));
+					sessionTable->setItem(row_idx, 9, item);
+
+					item = new QTableWidgetItem(QString::number(speed_rx));
+					sessionTable->setItem(row_idx, 10, item);
+
+					AX25Port[snd_ch][i].info.stat_l_r_byte = AX25Port[snd_ch][i].info.stat_r_byte;
+					AX25Port[snd_ch][i].info.stat_l_s_byte = AX25Port[snd_ch][i].info.stat_s_byte;	
+				}
+
+				row_idx++;
+			}
+		}
+	}
+}
+
+// "Copy on Select" Code
+
+void QtSoundModem::onTEselectionChanged()
+{
+	QTextEdit * x = static_cast<QTextEdit*>(QObject::sender());
+	x->copy();
+}
+
diff --git a/QtSoundModem.pro b/QtSoundModem.pro
index db1a6af..a02654c 100644
--- a/QtSoundModem.pro
+++ b/QtSoundModem.pro
@@ -43,7 +43,8 @@ SOURCES += ./audio.c \
     ./Modulate.c \
     ./ofdm.c \
     ./pktARDOP.c \
-     ./BusyDetect.c
+     ./BusyDetect.c \
+	 ./DW9600.c
 
 
 
diff --git a/QtSoundModem.ui b/QtSoundModem.ui
index 65fd2ea..ef72635 100644
--- a/QtSoundModem.ui
+++ b/QtSoundModem.ui
@@ -124,9 +124,9 @@
    <widget class="QLabel" name="WaterfallA">
     <property name="geometry">
      <rect>
-      <x>0</x>
+      <x>80</x>
       <y>488</y>
-      <width>953</width>
+      <width>881</width>
       <height>80</height>
      </rect>
     </property>
diff --git a/QtSoundModem.vcxproj b/QtSoundModem.vcxproj
index 93bbb10..7f39cd1 100644
--- a/QtSoundModem.vcxproj
+++ b/QtSoundModem.vcxproj
@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Release|Win32">
       <Configuration>Release</Configuration>
       <Platform>Win32</Platform>
@@ -9,12 +13,16 @@
       <Configuration>Debug</Configuration>
       <Platform>Win32</Platform>
     </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
   </ItemGroup>
   <PropertyGroup Label="Globals">
     <ProjectGuid>{4EDE958E-D0AC-37B4-81F7-78313A262DCD}</ProjectGuid>
     <RootNamespace>QtSoundModem</RootNamespace>
     <Keyword>QtVS_v304</Keyword>
-    <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
     <WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
     <QtMsBuild Condition="'$(QtMsBuild)'=='' or !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
   </PropertyGroup>
@@ -28,6 +36,15 @@
     <IntermediateDirectory>release\</IntermediateDirectory>
     <PrimaryOutput>QtSoundModem</PrimaryOutput>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <PlatformToolset>v141</PlatformToolset>
+    <OutputDirectory>release\</OutputDirectory>
+    <ATLMinimizesCRunTimeLibraryUsage>false</ATLMinimizesCRunTimeLibraryUsage>
+    <CharacterSet>NotSet</CharacterSet>
+    <ConfigurationType>Application</ConfigurationType>
+    <IntermediateDirectory>release\</IntermediateDirectory>
+    <PrimaryOutput>QtSoundModem</PrimaryOutput>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <PlatformToolset>v141</PlatformToolset>
     <OutputDirectory>debug\</OutputDirectory>
@@ -37,6 +54,15 @@
     <IntermediateDirectory>debug\</IntermediateDirectory>
     <PrimaryOutput>QtSoundModem</PrimaryOutput>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <PlatformToolset>v141</PlatformToolset>
+    <OutputDirectory>debug\</OutputDirectory>
+    <ATLMinimizesCRunTimeLibraryUsage>false</ATLMinimizesCRunTimeLibraryUsage>
+    <CharacterSet>NotSet</CharacterSet>
+    <ConfigurationType>Application</ConfigurationType>
+    <IntermediateDirectory>debug\</IntermediateDirectory>
+    <PrimaryOutput>QtSoundModem</PrimaryOutput>
+  </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
     <Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
@@ -45,9 +71,15 @@
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+  </ImportGroup>
   <PropertyGroup Label="UserMacros" />
   <ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
     <Import Project="$(QtMsBuild)\qt_defaults.props" />
@@ -59,6 +91,13 @@
     <IgnoreImportLibrary>true</IgnoreImportLibrary>
     <LinkIncremental>true</LinkIncremental>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)Intermed\$(Platform)\$(Configuration)\</IntDir>
+    <TargetName>QtSoundModem</TargetName>
+    <IgnoreImportLibrary>true</IgnoreImportLibrary>
+    <LinkIncremental>true</LinkIncremental>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
     <IntDir>$(SolutionDir)Intermed\$(Platform)\$(Configuration)\\</IntDir>
@@ -66,14 +105,29 @@
     <IgnoreImportLibrary>true</IgnoreImportLibrary>
     <LinkIncremental>false</LinkIncremental>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)Intermed\$(Platform)\$(Configuration)\\</IntDir>
+    <TargetName>QtSoundModem</TargetName>
+    <IgnoreImportLibrary>true</IgnoreImportLibrary>
+    <LinkIncremental>false</LinkIncremental>
+  </PropertyGroup>
   <PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <QtInstall>5.14.2</QtInstall>
     <QtModules>core;network;gui;widgets;serialport</QtModules>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="QtSettings">
+    <QtInstall>msvc 2017 5.1464</QtInstall>
+    <QtModules>core;network;gui;widgets;serialport</QtModules>
+  </PropertyGroup>
   <PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <QtInstall>5.14.2</QtInstall>
     <QtModules>core;network;gui;widgets;serialport</QtModules>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="QtSettings">
+    <QtInstall>5.14.2</QtInstall>
+    <QtModules>core;network;gui;widgets;serialport</QtModules>
+  </PropertyGroup>
   <ImportGroup Condition="Exists('$(QtMsBuild)\qt.props')">
     <Import Project="$(QtMsBuild)\qt.props" />
   </ImportGroup>
@@ -140,6 +194,69 @@
       <QtUicFileName>ui_%(Filename).h</QtUicFileName>
     </QtUic>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <AdditionalIncludeDirectories>rsid;.\GeneratedFiles\$(ConfigurationName);.\GeneratedFiles;.;release;/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalOptions>-Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <BrowseInformation>false</BrowseInformation>
+      <DebugInformationFormat>None</DebugInformationFormat>
+      <DisableSpecificWarnings>4577;4467;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+      <ExceptionHandling>Sync</ExceptionHandling>
+      <ObjectFileName>$(IntDir)</ObjectFileName>
+      <Optimization>MaxSpeed</Optimization>
+      <PreprocessorDefinitions>_WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;NDEBUG;QT_NO_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessToFile>false</PreprocessToFile>
+      <ProgramDataBaseFileName>$(OutDir)</ProgramDataBaseFileName>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+      <WarningLevel>Level3</WarningLevel>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies>libfftw3f-3.lib;shell32.lib;setupapi.lib;WS2_32.Lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>C:\opensslx86\lib;C:\Utils\my_sql\mysql-5.7.25-win32\lib;C:\Utils\postgresqlx86\pgsql\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
+      <DataExecutionPrevention>true</DataExecutionPrevention>
+      <GenerateDebugInformation>false</GenerateDebugInformation>
+      <IgnoreImportLibrary>true</IgnoreImportLibrary>
+      <LinkIncremental>false</LinkIncremental>
+      <OptimizeReferences>true</OptimizeReferences>
+      <OutputFile>$(OutDir)QtSoundModem.exe</OutputFile>
+      <RandomizedBaseAddress>true</RandomizedBaseAddress>
+      <SubSystem>Windows</SubSystem>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+    </Link>
+    <Midl>
+      <DefaultCharType>Unsigned</DefaultCharType>
+      <EnableErrorChecks>None</EnableErrorChecks>
+      <WarningLevel>0</WarningLevel>
+    </Midl>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;NDEBUG;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_NETWORK_LIB;QT_SERIALPORT_LIB;QT_CORE_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ResourceCompile>
+    <QtMoc>
+      <CompilerFlavor>msvc</CompilerFlavor>
+      <Include>./$(Configuration)/moc_predefs.h</Include>
+      <ExecutionDescription>Moc'ing %(Identity)...</ExecutionDescription>
+      <DynamicSource>output</DynamicSource>
+      <QtMocDir>$(IntDir)</QtMocDir>
+      <QtMocFileName>moc_%(Filename).cpp</QtMocFileName>
+    </QtMoc>
+    <QtRcc>
+      <InitFuncName>QtSoundModem</InitFuncName>
+      <Compression>default</Compression>
+      <ExecutionDescription>Rcc'ing %(Identity)...</ExecutionDescription>
+      <QtRccDir>$(IntDir)</QtRccDir>
+      <QtRccFileName>qrc_%(Filename).cpp</QtRccFileName>
+    </QtRcc>
+    <QtUic>
+      <ExecutionDescription>Uic'ing %(Identity)...</ExecutionDescription>
+      <QtUicDir>$(IntDir)</QtUicDir>
+      <QtUicFileName>ui_%(Filename).h</QtUicFileName>
+    </QtUic>
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
       <AdditionalIncludeDirectories>.\GeneratedFiles\$(ConfigurationName);.\GeneratedFiles;.;debug;/include;rsid;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
@@ -202,13 +319,77 @@
       <QtUicFileName>ui_%(Filename).h</QtUicFileName>
     </QtUic>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <AdditionalIncludeDirectories>.\GeneratedFiles\$(ConfigurationName);.\GeneratedFiles;.;debug;/include;rsid;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalOptions>-Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <BrowseInformation>false</BrowseInformation>
+      <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+      <DisableSpecificWarnings>4577;4467;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+      <ExceptionHandling>Sync</ExceptionHandling>
+      <ObjectFileName>$(IntDir)</ObjectFileName>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>_WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessToFile>false</PreprocessToFile>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+      <WarningLevel>Level3</WarningLevel>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <ProgramDataBaseFileName>$(OutDir)</ProgramDataBaseFileName>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies>libfftw3f-64-3.lib;shell32.lib;setupapi.lib;WS2_32.Lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>C:\opensslx86\lib;C:\Utils\my_sql\mysql-5.7.25-win32\lib;C:\Utils\postgresqlx86\pgsql\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
+      <DataExecutionPrevention>true</DataExecutionPrevention>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <IgnoreImportLibrary>true</IgnoreImportLibrary>
+      <OutputFile>$(OutDir)\QtSoundModem.exe</OutputFile>
+      <RandomizedBaseAddress>true</RandomizedBaseAddress>
+      <SubSystem>Windows</SubSystem>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+    </Link>
+    <Midl>
+      <DefaultCharType>Unsigned</DefaultCharType>
+      <EnableErrorChecks>None</EnableErrorChecks>
+      <WarningLevel>0</WarningLevel>
+    </Midl>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;QT_WIDGETS_LIB;QT_GUI_LIB;QT_NETWORK_LIB;QT_SERIALPORT_LIB;QT_CORE_LIB;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ResourceCompile>
+    <QtMoc>
+      <CompilerFlavor>msvc</CompilerFlavor>
+      <Include>./$(Configuration)/moc_predefs.h</Include>
+      <ExecutionDescription>Moc'ing %(Identity)...</ExecutionDescription>
+      <DynamicSource>output</DynamicSource>
+      <QtMocDir>$(IntDir)</QtMocDir>
+      <QtMocFileName>moc_%(Filename).cpp</QtMocFileName>
+    </QtMoc>
+    <QtRcc>
+      <InitFuncName>QtSoundModem</InitFuncName>
+      <Compression>default</Compression>
+      <ExecutionDescription>Rcc'ing %(Identity)...</ExecutionDescription>
+      <QtRccDir>$(IntDir)</QtRccDir>
+      <QtRccFileName>qrc_%(Filename).cpp</QtRccFileName>
+    </QtRcc>
+    <QtUic>
+      <ExecutionDescription>Uic'ing %(Identity)...</ExecutionDescription>
+      <QtUicDir>$(IntDir)</QtUicDir>
+      <QtUicFileName>ui_%(Filename).h</QtUicFileName>
+    </QtUic>
+  </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="ARDOPC.c" />
     <ClCompile Include="BusyDetect.c" />
     <ClCompile Include="Config.cpp" />
+    <ClCompile Include="dw9600.c" />
     <ClCompile Include="hid.c" />
     <ClCompile Include="il2p.c">
       <CompileAs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">CompileAsC</CompileAs>
+      <CompileAs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">CompileAsC</CompileAs>
     </ClCompile>
     <ClCompile Include="Modulate.c" />
     <ClCompile Include="QtSoundModem.cpp" />
@@ -247,18 +428,15 @@
     <CustomBuild Include="debug\moc_predefs.h.cbt">
       <FileType>Document</FileType>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
       <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs)</AdditionalInputs>
+      <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs)</AdditionalInputs>
       <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -Zi -MDd -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2&gt;NUL &gt;debug\moc_predefs.h</Command>
+      <Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -Zi -MDd -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2&gt;NUL &gt;debug\moc_predefs.h</Command>
       <Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Generate moc_predefs.h</Message>
+      <Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Generate moc_predefs.h</Message>
       <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">debug\moc_predefs.h;%(Outputs)</Outputs>
-    </CustomBuild>
-    <CustomBuild Include="release\moc_predefs.h.cbt">
-      <FileType>Document</FileType>
-      <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs)</AdditionalInputs>
-      <Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -O2 -MD -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2&gt;NUL &gt;release\moc_predefs.h</Command>
-      <Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Generate moc_predefs.h</Message>
-      <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">release\moc_predefs.h;%(Outputs)</Outputs>
-      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">debug\moc_predefs.h;%(Outputs)</Outputs>
     </CustomBuild>
   </ItemGroup>
   <ItemGroup>
diff --git a/QtSoundModem.vcxproj-DESKTOP-MHE5LO8.user b/QtSoundModem.vcxproj-DESKTOP-MHE5LO8.user
new file mode 100644
index 0000000..a71d175
--- /dev/null
+++ b/QtSoundModem.vcxproj-DESKTOP-MHE5LO8.user
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <LocalDebuggerWorkingDirectory>C:\DevProgs\BPQ32\SMTest</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+    <LocalDebuggerCommandArguments>
+    </LocalDebuggerCommandArguments>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <LocalDebuggerWorkingDirectory>c:\devprogs\bpq32\SMSAT2</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+    <LocalDebuggerCommandArguments>&lt; d:\samples.wav</LocalDebuggerCommandArguments>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <LocalDebuggerWorkingDirectory>.\debug</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <LocalDebuggerWorkingDirectory>C:\DevProgs\BPQ32\SMSat</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+  </PropertyGroup>
+  <PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <QtLastBackgroundBuild>2023-08-21T20:12:53.1523329Z</QtLastBackgroundBuild>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="QtSettings">
+    <QtLastBackgroundBuild>2022-03-11T19:38:31.5906689Z</QtLastBackgroundBuild>
+  </PropertyGroup>
+  <PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <QtLastBackgroundBuild>2023-08-18T07:29:42.4175478Z</QtLastBackgroundBuild>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="QtSettings">
+    <QtLastBackgroundBuild>2022-03-11T19:38:33.3845083Z</QtLastBackgroundBuild>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/QtSoundModem.vcxproj.filters b/QtSoundModem.vcxproj.filters
index 3e97db7..c50daf6 100644
--- a/QtSoundModem.vcxproj.filters
+++ b/QtSoundModem.vcxproj.filters
@@ -137,6 +137,9 @@
     <ClCompile Include="il2p.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="dw9600.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <QtMoc Include="QtSoundModem.h">
@@ -153,9 +156,6 @@
     <CustomBuild Include="debug\moc_predefs.h.cbt">
       <Filter>Generated Files</Filter>
     </CustomBuild>
-    <CustomBuild Include="release\moc_predefs.h.cbt">
-      <Filter>Generated Files</Filter>
-    </CustomBuild>
   </ItemGroup>
   <ItemGroup>
     <QtUic Include="ModemDialog.ui">
diff --git a/QtSoundModem.vcxproj.user b/QtSoundModem.vcxproj.user
index 260e64c..b2b9590 100644
--- a/QtSoundModem.vcxproj.user
+++ b/QtSoundModem.vcxproj.user
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
-    <LocalDebuggerWorkingDirectory>C:\DevProgs\BPQ32\SM2</LocalDebuggerWorkingDirectory>
+    <LocalDebuggerWorkingDirectory>C:\DevProgs\BPQ32\SMTest</LocalDebuggerWorkingDirectory>
     <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
     <LocalDebuggerCommandArguments>
     </LocalDebuggerCommandArguments>
@@ -20,15 +20,15 @@
     <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
   </PropertyGroup>
   <PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
-    <QtLastBackgroundBuild>2023-04-25T14:18:34.8597729Z</QtLastBackgroundBuild>
+    <QtLastBackgroundBuild>2023-08-31T18:31:29.1703485Z</QtLastBackgroundBuild>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="QtSettings">
-    <QtLastBackgroundBuild>2022-03-11T19:38:31.5906689Z</QtLastBackgroundBuild>
+    <QtLastBackgroundBuild>2023-08-31T18:31:29.3763536Z</QtLastBackgroundBuild>
   </PropertyGroup>
   <PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
-    <QtLastBackgroundBuild>2023-04-25T14:18:38.8848952Z</QtLastBackgroundBuild>
+    <QtLastBackgroundBuild>2023-08-31T18:31:30.2753833Z</QtLastBackgroundBuild>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="QtSettings">
-    <QtLastBackgroundBuild>2022-03-11T19:38:33.3845083Z</QtLastBackgroundBuild>
+    <QtLastBackgroundBuild>2023-08-31T18:31:32.1264353Z</QtLastBackgroundBuild>
   </PropertyGroup>
 </Project>
\ No newline at end of file
diff --git a/SMMain-DESKTOP-MHE5LO8.c b/SMMain-DESKTOP-MHE5LO8.c
new file mode 100644
index 0000000..8fc249e
--- /dev/null
+++ b/SMMain-DESKTOP-MHE5LO8.c
@@ -0,0 +1,1433 @@
+/*
+Copyright (C) 2019-2020 Andrei Kopanchuk UZ7HO
+
+This file is part of QtSoundModem
+
+QtSoundModem is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+QtSoundModem is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with QtSoundModem.  If not, see http://www.gnu.org/licenses
+
+*/
+
+// UZ7HO Soundmodem Port by John Wiseman G8BPQ
+
+#include "UZ7HOStuff.h"
+#include "fftw3.h"
+#include <time.h>
+#include "ecc.h"				// RS Constants
+#include "hidapi.h"
+#include <fcntl.h>
+#include <errno.h>
+#include <stdint.h>   
+
+BOOL KISSServ;
+int KISSPort;
+
+BOOL AGWServ;
+int AGWPort;
+
+int Number = 0;				// Number waiting to be sent
+
+int SoundIsPlaying = 0;
+int UDPSoundIsPlaying = 0;
+int Capturing = 0;
+
+extern unsigned short buffer[2][1200];
+extern int SoundMode;
+extern int needRSID[4];
+
+extern short * DMABuffer;
+
+unsigned short * SendtoCard(unsigned short * buf, int n);
+short * SoundInit();
+void DoTX(int Chan);
+void UDPPollReceivedSamples();
+
+
+extern int SampleNo;
+
+extern int pnt_change[5];				// Freq Changed Flag
+
+// fftw library interface
+
+
+fftwf_complex *in, *out;
+fftwf_plan p;
+
+int FFTSize = 4096;
+
+char * Wisdom;
+
+void initfft()
+{
+	fftwf_import_wisdom_from_string(Wisdom); 
+	fftwf_set_timelimit(30);
+
+#ifndef WIN32
+	printf("It may take up to 30 seconds for the program to start for the first time\n");
+#endif
+
+	in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * 10000);
+	out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * 10000);
+	p = fftwf_plan_dft_1d(FFTSize, in, out, FFTW_FORWARD, FFTW_PATIENT);
+
+	Wisdom = fftwf_export_wisdom_to_string();
+}
+
+void dofft(short * inp, float * outr, float * outi)
+{
+	int i;
+	
+	fftwf_complex * fft = in;
+
+	for (i = 0; i < FFTSize; i++)
+	{
+		fft[0][0] = inp[0] * 1.0f;
+		fft[0][1] = 0;
+		fft++;
+		inp++;
+	}
+
+	fftwf_execute(p); 
+
+	fft = out;
+
+	for (i = 0; i < FFTSize; i++)
+	{
+		outr[0] = fft[0][0];
+		outi[0] = fft[0][1];
+		fft++;
+		outi++;
+		outr++;
+	}
+}
+
+void freefft()
+{
+	fftwf_destroy_plan(p);
+	fftwf_free(in);
+	fftwf_free(out);
+}
+
+int nonGUIMode = 0;
+
+void soundMain()
+{
+	// non platform specific initialisation
+
+	platformInit();
+
+	// initialise fft library
+
+	RsCreate();				// RS code for MPSK
+
+	detector_init();
+	KISS_init();
+	ax25_init();
+	init_raduga();			// Set up waterfall colour table
+
+	initfft();
+
+	if (nonGUIMode)
+	{
+		Firstwaterfall = 0;
+		Secondwaterfall = 0;
+	}
+
+	OpenPTTPort();
+}
+
+
+void SampleSink(int LR, short Sample)
+{
+	// This version is passed samples one at a time, as we don't have
+	//	enough RAM in embedded systems to hold a full audio frame
+
+	// LR - 1 == Right Chan
+
+#ifdef TEENSY	
+		int work = Sample;
+		DMABuffer[Number++] = (work + 32768) >> 4; // 12 bit left justify
+#else
+	if (SCO)			// Single Channel Output - same to both L and R
+	{
+		DMABuffer[2 * Number] = Sample;
+		DMABuffer[1 + 2 * Number] = Sample;
+
+	}
+	else
+	{
+		if (LR)				// Right
+		{
+			DMABuffer[1 + 2 * Number] = Sample;
+			DMABuffer[2 * Number] = 0;
+		}
+		else
+		{
+			DMABuffer[2 * Number] = Sample;
+			DMABuffer[1 + 2 * Number] = 0;
+		}
+	}
+	if (using48000)
+	{
+		// Need to upsample to 48K. Try just duplicating sample
+
+		uint32_t * ptr = &DMABuffer[2 * Number];
+
+		*(&ptr[1]) = *(ptr);
+		*(&ptr[2]) = *(ptr);
+		*(&ptr[3]) = *(ptr);
+
+		Number += 3;
+	}
+	Number++;
+#endif
+		if (Number >= SendSize)
+		{
+			// send this buffer to sound interface
+
+			DMABuffer = SendtoCard(DMABuffer, SendSize);
+			Number = 0;
+		}
+	
+
+//	Last120[Last120Put++] = Sample;
+
+//	if (Last120Put == (intN + 1))
+//		Last120Put = 0;
+
+	SampleNo++;
+}
+
+
+void Flush()
+{
+	SoundFlush(Number);
+}
+
+int ipow(int base, int exp)
+{
+	int result = 1;
+	while (exp)
+	{
+		if (exp & 1)
+			result *= base;
+		exp >>= 1;
+		base *= base;
+	}
+
+	return result;
+}
+
+int NumberOfBitsNeeded(int PowerOfTwo)
+{
+	int i;
+
+	for (i = 0; i <= 16; i++)
+	{
+		if ((PowerOfTwo & ipow(2, i)) != 0)
+			return i;
+
+	}
+	return 0;
+}
+
+
+int ReverseBits(int Index, int NumBits)
+{
+	int i, Rev = 0;
+
+	for (i = 0; i < NumBits; i++)
+	{
+		Rev = (Rev * 2) | (Index & 1);
+		Index = Index / 2;
+	}
+
+	return Rev;
+}
+
+
+void FourierTransform(int NumSamples, short * RealIn, float * RealOut, float * ImagOut, int InverseTransform)
+{
+	float AngleNumerator;
+	unsigned char NumBits;
+
+	int i, j, K, n, BlockSize, BlockEnd;
+	float DeltaAngle, DeltaAr;
+	float Alpha, Beta;
+	float TR, TI, AR, AI;
+
+	if (InverseTransform)
+		AngleNumerator = -2.0f * M_PI;
+	else
+		AngleNumerator = 2.0f * M_PI;
+
+	NumBits = NumberOfBitsNeeded(NumSamples);
+
+	for (i = 0; i < NumSamples; i++)
+	{
+		j = ReverseBits(i, NumBits);
+		RealOut[j] = RealIn[i];
+		ImagOut[j] = 0.0f; // Not using i in ImageIn[i];
+	}
+
+	BlockEnd = 1;
+	BlockSize = 2;
+
+	while (BlockSize <= NumSamples)
+	{
+		DeltaAngle = AngleNumerator / BlockSize;
+		Alpha = sinf(0.5f * DeltaAngle);
+		Alpha = 2.0f * Alpha * Alpha;
+		Beta = sinf(DeltaAngle);
+
+		i = 0;
+
+		while (i < NumSamples)
+		{
+			AR = 1.0f;
+			AI = 0.0f;
+
+			j = i;
+
+			for (n = 0; n < BlockEnd; n++)
+			{
+				K = j + BlockEnd;
+				TR = AR * RealOut[K] - AI * ImagOut[K];
+				TI = AI * RealOut[K] + AR * ImagOut[K];
+				RealOut[K] = RealOut[j] - TR;
+				ImagOut[K] = ImagOut[j] - TI;
+				RealOut[j] = RealOut[j] + TR;
+				ImagOut[j] = ImagOut[j] + TI;
+				DeltaAr = Alpha * AR + Beta * AI;
+				AI = AI - (Alpha * AI - Beta * AR);
+				AR = AR - DeltaAr;
+				j = j + 1;
+			}
+			i = i + BlockSize;
+		}
+		BlockEnd = BlockSize;
+		BlockSize = BlockSize * 2;
+	}
+
+	if (InverseTransform)
+	{
+		//	Normalize the resulting time samples...
+
+		for (i = 0; i < NumSamples; i++)
+		{
+			RealOut[i] = RealOut[i] / NumSamples;
+			ImagOut[i] = ImagOut[i] / NumSamples;
+		}
+	}
+}
+
+
+
+int LastBusyCheck = 0;
+
+extern UCHAR CurrentLevel;
+
+#ifdef PLOTSPECTRUM		
+float dblMagSpectrum[206];
+float dblMaxScale = 0.0f;
+extern UCHAR Pixels[4096];
+extern UCHAR * pixelPointer;
+#endif
+
+extern int blnBusyStatus;
+BusyDet = 5;
+
+#define PLOTWATERFALL
+
+int WaterfallActive = 1;
+int SpectrumActive;
+
+float BinSize;
+
+extern int intLastStart;
+extern int intLastStop;
+
+void SMSortSignals2(float * dblMag, int intStartBin, int intStopBin, int intNumBins, float *  dblAVGSignalPerBin, float *  dblAVGBaselinePerBin);
+
+
+BOOL SMBusyDetect3(float * dblMag, int intStart, int intStop)        // this only called while searching for leader ...once leader detected, no longer called.
+{
+	// First sort signals and look at highes signals:baseline ratio..
+
+	float dblAVGSignalPerBinNarrow, dblAVGSignalPerBinWide, dblAVGBaselineNarrow, dblAVGBaselineWide;
+	float dblSlowAlpha = 0.2f;
+	float dblAvgStoNNarrow = 0, dblAvgStoNWide = 0;
+	int intNarrow = 8;  // 8 x 11.72 Hz about 94 z
+	int intWide = ((intStop - intStart) * 2) / 3; //* 0.66);
+	int blnBusy = FALSE;
+	int  BusyDet4th = BusyDet * BusyDet * BusyDet * BusyDet;
+
+	// First sort signals and look at highest signals:baseline ratio..
+	// First narrow band (~94Hz)
+
+	SMSortSignals2(dblMag, intStart, intStop, intNarrow, &dblAVGSignalPerBinNarrow, &dblAVGBaselineNarrow);
+
+	if (intLastStart == intStart && intLastStop == intStop)
+		dblAvgStoNNarrow = (1 - dblSlowAlpha) * dblAvgStoNNarrow + dblSlowAlpha * dblAVGSignalPerBinNarrow / dblAVGBaselineNarrow;
+	else
+	{
+		// This initializes the Narrow average after a bandwidth change
+
+		dblAvgStoNNarrow = dblAVGSignalPerBinNarrow / dblAVGBaselineNarrow;
+		intLastStart = intStart;
+		intLastStop = intStop;
+	}
+
+	// Wide band (66% of current bandwidth)
+
+	SMSortSignals2(dblMag, intStart, intStop, intWide, &dblAVGSignalPerBinWide, &dblAVGBaselineWide);
+
+	if (intLastStart == intStart && intLastStop == intStop)
+		dblAvgStoNWide = (1 - dblSlowAlpha) * dblAvgStoNWide + dblSlowAlpha * dblAVGSignalPerBinWide / dblAVGBaselineWide;
+	else
+	{
+		// This initializes the Wide average after a bandwidth change
+
+		dblAvgStoNWide = dblAVGSignalPerBinWide / dblAVGBaselineWide;
+		intLastStart = intStart;
+		intLastStop = intStop;
+	}
+
+	// Preliminary calibration...future a function of bandwidth and BusyDet.
+
+
+	blnBusy = (dblAvgStoNNarrow > (3 + 0.008 * BusyDet4th)) || (dblAvgStoNWide > (5 + 0.02 * BusyDet4th));
+
+//	if (BusyDet == 0)
+//		blnBusy = FALSE;		// 0 Disables check ?? Is this the best place to do this?
+
+//	WriteDebugLog(LOGDEBUG, "Busy %d Wide %f Narrow %f", blnBusy, dblAvgStoNWide, dblAvgStoNNarrow); 
+
+	return blnBusy;
+}
+
+extern int compare(const void *p1, const void *p2);
+
+void SMSortSignals2(float * dblMag, int intStartBin, int intStopBin, int intNumBins, float *  dblAVGSignalPerBin, float *  dblAVGBaselinePerBin)
+{
+	// puts the top intNumber of bins between intStartBin and intStopBin into dblAVGSignalPerBin, the rest into dblAvgBaselinePerBin
+	// for decent accuracy intNumBins should be < 75% of intStopBin-intStartBin)
+
+	// This version uses a native sort function which is much faster and reduces CPU loading significantly on wide bandwidths. 
+
+	float dblSort[8192];
+	float dblSum1 = 0, dblSum2 = 0;
+	int numtoSort = (intStopBin - intStartBin) + 1, i;
+
+	memcpy(dblSort, &dblMag[intStartBin], numtoSort * sizeof(float));
+
+	qsort((void *)dblSort, numtoSort, sizeof(float), compare);
+
+	for (i = numtoSort - 1; i >= 0; i--)
+	{
+		if (i >= (numtoSort - intNumBins))
+			dblSum1 += dblSort[i];
+		else
+			dblSum2 += dblSort[i];
+	}
+
+	*dblAVGSignalPerBin = dblSum1 / intNumBins;
+	*dblAVGBaselinePerBin = dblSum2 / (intStopBin - intStartBin - intNumBins - 1);
+}
+
+
+
+void SMUpdateBusyDetector(int LR, float * Real, float *Imag)
+{
+	// Energy based detector for each channel.
+	// Fed from FFT generated for waterfall display
+	// FFT size is 4096
+
+	float dblMag[4096];
+
+	static BOOL blnLastBusyStatus[4];
+
+	float dblMagAvg = 0;
+	int intTuneLineLow, intTuneLineHi, intDelta;
+	int i, chan;
+
+	return;
+
+	if (Now - LastBusyCheck < 100)	// ??
+		return;
+
+	LastBusyCheck = Now;
+
+	// We need to run busy test on the frequncies used by each modem.
+
+	for (chan = 0; chan < 4; chan++)
+	{
+		int Low, High, Start, End;
+
+		if (soundChannel[chan] != (LR + 1))	// on this side of soundcard 
+			continue;
+
+		Low = tx_freq[chan] - txbpf[chan] / 2;
+		High = tx_freq[chan] + txbpf[chan] / 2;
+
+		// BinSize is width of each fft bin in Hz
+
+		Start = (Low / BinSize);		// First and last bins to process
+		End = (High / BinSize);
+
+
+		for (i = Start; i < End; i++)
+		{
+			dblMag[i] = powf(Real[i], 2) + powf(Imag[i], 2);	 // first pass
+			dblMagAvg += dblMag[i];
+		}
+
+		blnBusyStatus = SMBusyDetect3(dblMag, Start, End);
+
+		if (blnBusyStatus && !blnLastBusyStatus[chan])
+		{
+			Debugprintf("Ch %d Busy True", chan);
+		}
+		else if (blnLastBusyStatus[chan] && !blnBusyStatus)
+		{
+			Debugprintf("Ch %d Busy False", chan);
+		}
+		//    stcStatus.Text = "FALSE"
+		//    queTNCStatus.Enqueue(stcStatus)
+		//    'Debug.WriteLine("BUSY FALSE @ " & Format(DateTime.UtcNow, "HH:mm:ss"))
+
+		blnLastBusyStatus[chan] = blnBusyStatus;
+	}
+}
+
+
+extern short rawSamples[2400];	// Get Frame Type need 2400 and we may add 1200
+int rawSamplesLength = 0;
+extern int maxrawSamplesLength;
+
+void ProcessNewSamples(short * Samples, int nSamples)
+{
+	if (SoundIsPlaying == FALSE && UDPSoundIsPlaying == FALSE)
+		BufferFull(Samples, nSamples);
+};
+
+void doCalib(int Chan, int Act)
+{
+	if (Chan == 0 && calib_mode[1])
+		return;						
+	
+	if (Chan == 1 && calib_mode[0])
+		return;
+
+	calib_mode[Chan] = Act;
+
+	if (Act == 0)
+	{
+		tx_status[Chan] = TX_SILENCE;		// Stop TX
+		Flush();
+		RadioPTT(Chan, 0);
+		Debugprintf("Stop Calib");
+	}
+}
+
+int Freq_Change(int Chan, int Freq)
+{
+	int low, high;
+
+	low = round(rx_shift[1] / 2 + RCVR[Chan] * rcvr_offset[Chan] + 1);
+	high = round(RX_Samplerate / 2 - (rx_shift[Chan] / 2 + RCVR[Chan] * rcvr_offset[Chan]));
+
+	if (Freq < low)
+		return rx_freq[Chan];				// Dont allow change
+
+	if (Freq > high)
+		return rx_freq[Chan];				// Dont allow change
+
+	rx_freq[Chan] = Freq;
+	tx_freq[Chan] = Freq;
+
+	pnt_change[Chan] = TRUE;
+	wf_pointer(soundChannel[Chan]);
+
+	return Freq;
+}
+
+void MainLoop()
+{
+	// Called by background thread every 10 ms (maybe)
+
+	// Actually we may have two cards
+	
+	// Original only allowed one channel per card.
+	// I think we should be able to run more, ie two or more
+	// modems on same soundcard channel
+	
+	// So All the soundcard stuff will need to be generalised
+
+	if (UDPServ)
+		UDPPollReceivedSamples();
+
+	if (SoundMode == 3)
+		UDPPollReceivedSamples();
+	else
+		PollReceivedSamples();
+
+
+	for (int i = 0; i < 4; i++)
+	{
+		if (modem_mode[i] == MODE_ARDOP)
+		{
+			chk_dcd1(i, 512);
+		}
+	}
+	DoTX(0);
+	DoTX(1);
+	DoTX(2);
+	DoTX(3);
+
+}
+
+int ARDOPSendToCard(int Chan, int Len)
+{
+	// Send Next Block of samples to the soundcard
+
+
+	short * in = &ARDOPTXBuffer[Chan][ARDOPTXPtr[Chan]];		// Enough to hold whole frame of samples
+	short * out = DMABuffer;
+
+	int LR = modemtoSoundLR[Chan];
+
+	int i;
+
+	for (i = 0; i < Len; i++)
+	{
+		if (SCO)			// Single Channel Output - same to both L and R
+		{
+			*out++ = *in;
+			*out++ = *in++;
+		}
+		else
+		{
+			if (LR)				// Right
+			{
+				*out++ = 0;
+				*out++ = *in++;
+			}
+			else
+			{
+				*out++ = *in++;
+				*out++ = 0;
+			}
+		}
+	}
+
+	DMABuffer = SendtoCard(DMABuffer, Len);
+
+	ARDOPTXPtr[Chan] += Len;
+
+	// See if end of buffer
+
+	if (ARDOPTXPtr[Chan] > ARDOPTXLen[Chan])
+		return 1;
+
+	return 0;
+}
+void DoTX(int Chan)
+{
+	// This kicks off a send sequence or calibrate
+
+//	printtick("dotx");
+
+	if (calib_mode[Chan])
+	{
+		// Maybe new calib or continuation
+
+		if (pnt_change[Chan])
+		{
+			make_core_BPF(Chan, rx_freq[Chan], bpf[Chan]);
+			make_core_TXBPF(Chan, tx_freq[Chan], txbpf[Chan]);
+			pnt_change[Chan] = FALSE;
+		}
+		
+		// Note this may block in SendtoCard
+
+		modulator(Chan, tx_bufsize);
+		return;
+	}
+
+	// I think we have to detect NO_DATA here and drop PTT and return to SILENCE
+
+	if (tx_status[Chan] == TX_NO_DATA)
+	{
+		Flush();
+		Debugprintf("TX Complete");
+		RadioPTT(0, 0);
+		Continuation[Chan] = 0;
+
+		tx_status[Chan] = TX_SILENCE;
+
+		// We should now send any ackmode acks as the channel is now free for dest to reply
+
+		sendAckModeAcks(Chan);
+	}
+
+	if (tx_status[Chan] != TX_SILENCE)
+	{
+		// Continue the send
+
+		if (modem_mode[Chan] == MODE_ARDOP || modem_mode[Chan] == MODE_RUH)
+		{
+//			if (SeeIfCardBusy())
+//				return 0;
+
+			if (ARDOPSendToCard(Chan, SendSize) == 1)
+			{
+				// End of TX
+
+				Number = 0;
+				Flush();
+
+				// See if more to send. If so, don't drop PTT
+
+				if (all_frame_buf[Chan].Count)
+				{
+					SoundIsPlaying = TRUE;
+					Number = 0;
+
+					Continuation[Chan] = 1;
+
+					Debugprintf("TX Continuing");
+
+					string * myTemp = Strings(&all_frame_buf[Chan], 0);			// get message
+					string * tx_data;
+
+					if ((myTemp->Data[0] & 0x0f) == 12)			// ACKMODE
+					{
+						// Save copy then copy data up 3 bytes
+
+						Add(&KISS_acked[Chan], duplicateString(myTemp));
+
+						mydelete(myTemp, 0, 3);
+						myTemp->Length -= sizeof(void *);
+					}
+					else
+					{
+						// Just remove control 
+
+						mydelete(myTemp, 0, 1);
+					}
+
+					tx_data = duplicateString(myTemp);		// so can free original below
+
+					Delete(&all_frame_buf[Chan], 0);			// This will invalidate temp
+
+					AGW_AX25_frame_analiz(Chan, FALSE, tx_data);
+
+					put_frame(Chan, tx_data, "", TRUE, FALSE);
+
+					if (modem_mode[Chan] == MODE_ARDOP)
+						PktARDOPEncode(tx_data->Data, tx_data->Length - 2, Chan);
+					else
+						RUHEncode(tx_data->Data, tx_data->Length - 2, Chan);
+
+					freeString(tx_data);
+
+					// Samples are now in DMABuffer = Send first block
+
+					DMABuffer = SoundInit();
+
+					ARDOPSendToCard(Chan, SendSize);
+					tx_status[Chan] = TX_FRAME;
+					return;
+				}
+
+				Debugprintf("TX Complete");
+				RadioPTT(0, 0);
+				Continuation[Chan] = 0;
+
+				tx_status[Chan] = TX_SILENCE;
+
+				// We should now send any ackmode acks as the channel is now free for dest to reply
+			}
+
+			return;
+		}
+
+		modulator(Chan, tx_bufsize); 
+		return;
+	}
+
+	if (SoundIsPlaying || UDPSoundIsPlaying)
+		return;
+
+	// Not doing anything so see if we have anything new to send
+
+	// See if frequency has changed
+
+	if (pnt_change[Chan])
+	{
+		make_core_BPF(Chan, rx_freq[Chan], bpf[Chan]);
+		make_core_TXBPF(Chan, tx_freq[Chan], txbpf[Chan]);
+		pnt_change[Chan] = FALSE;
+	}
+
+	// See if we need an RSID
+
+	if (needRSID[Chan])
+	{
+		needRSID[Chan] = 0;
+
+		// Note this may block in SampleSink
+
+		Debugprintf("Sending RSID");
+		sendRSID(Chan, all_frame_buf[Chan].Count == 0);
+		return;
+	}
+
+	if (all_frame_buf[Chan].Count == 0)
+		return;
+
+	// Start a new send. modulator should handle TXD etc
+
+	Debugprintf("TX Start");
+	SampleNo = 0;
+
+	SoundIsPlaying = TRUE;
+	RadioPTT(Chan, 1);
+	Number = 0;
+
+	if (modem_mode[Chan] == MODE_ARDOP)
+	{
+		// I think ARDOP will have to generate a whole frame of samples
+		// then send them out a bit at a time to avoid stopping here for
+		// possibly 10's of seconds
+
+		// Can do this here as unlike normal ardop we don't need to run on Teensy
+		// to 12000 sample rate we need either 24K or 48K per second, depending on
+		// where we do the stereo mux. 
+
+		// Slowest rate is 50 baud, so a 255 byte packet would take about a minute
+		// allowing for RS overhead. Not really realistic put perhaps should be possible.
+		// RAM isn't an issue so maybe allocate 2 MB. 
+
+		// ?? Should we allow two ARDOP modems - could make sense if we can run sound
+		// card channels independently
+
+		string * myTemp = Strings(&all_frame_buf[Chan], 0);			// get message
+		string * tx_data;
+
+		if ((myTemp->Data[0] & 0x0f) == 12)			// ACKMODE
+		{
+			// Save copy then copy data up 3 bytes
+
+			Add(&KISS_acked[Chan], duplicateString(myTemp));
+
+			mydelete(myTemp, 0, 3);
+			myTemp->Length -= sizeof(void *);
+		}
+		else
+		{
+			// Just remove control 
+
+			mydelete(myTemp, 0, 1);
+		}
+
+		tx_data = duplicateString(myTemp);		// so can free original below
+
+		Delete(&all_frame_buf[Chan], 0);			// This will invalidate temp
+
+		AGW_AX25_frame_analiz(Chan, FALSE, tx_data);
+
+		put_frame(Chan, tx_data, "", TRUE, FALSE);
+
+		PktARDOPEncode(tx_data->Data, tx_data->Length - 2, Chan);
+
+		freeString(tx_data);
+
+		// Samples are now in DMABuffer = Send first block
+
+		ARDOPSendToCard(Chan, SendSize);
+		tx_status[Chan] = TX_FRAME;
+
+	}
+	else if (modem_mode[Chan] == MODE_RUH)
+	{
+		// Same as for ARDOP. Generate a whole frame of samples 
+		// then send them out a bit at a time to avoid stopping here
+
+		// We allow two RUH modems
+
+		string * myTemp = Strings(&all_frame_buf[Chan], 0);			// get message
+		string * tx_data;
+
+		if ((myTemp->Data[0] & 0x0f) == 12)			// ACKMODE
+		{
+			// Save copy then copy data up 3 bytes
+
+			Add(&KISS_acked[Chan], duplicateString(myTemp));
+
+			mydelete(myTemp, 0, 3);
+			myTemp->Length -= sizeof(void *);
+		}
+		else
+		{
+			// Just remove control 
+
+			mydelete(myTemp, 0, 1);
+		}
+
+		tx_data = duplicateString(myTemp);		// so can free original below
+
+		Delete(&all_frame_buf[Chan], 0);			// This will invalidate temp
+
+		AGW_AX25_frame_analiz(Chan, FALSE, tx_data);
+
+		put_frame(Chan, tx_data, "", TRUE, FALSE);
+
+		RUHEncode(tx_data->Data, tx_data->Length - 2, Chan);
+
+		freeString(tx_data);
+
+		// Samples are now in DMABuffer = Send first block
+
+		ARDOPSendToCard(Chan, SendSize);
+		tx_status[Chan] = TX_FRAME;
+
+	}
+	else
+		modulator(Chan, tx_bufsize);
+
+	return;
+}
+
+void RX2TX(int snd_ch)
+{
+	if (snd_status[snd_ch] == SND_IDLE)
+	{
+		DoTX(snd_ch);
+	}
+}
+
+// PTT Stuff
+
+int hPTTDevice = 0;
+char PTTPort[80] = "";			// Port for Hardware PTT - may be same as control port.
+int PTTBAUD = 19200;
+int PTTMode = PTTRTS;			// PTT Control Flags.
+
+char PTTOnString[128] = "";
+char PTTOffString[128] = "";
+
+UCHAR PTTOnCmd[64];
+UCHAR PTTOnCmdLen = 0;
+
+UCHAR PTTOffCmd[64];
+UCHAR PTTOffCmdLen = 0;
+
+int pttGPIOPin = 17;			// Default
+int pttGPIOPinR = 17;
+BOOL pttGPIOInvert = FALSE;
+BOOL useGPIO = FALSE;
+BOOL gotGPIO = FALSE;
+
+int HamLibPort = 4532;
+char HamLibHost[32] = "192.168.1.14";
+
+char CM108Addr[80] = "";
+
+int VID = 0;
+int PID = 0;
+
+// CM108 Code
+
+char * CM108Device = NULL;
+
+void DecodeCM108(char * ptr)
+{
+	// Called if Device Name or PTT = Param is CM108
+
+#ifdef WIN32
+
+	// Next Param is VID and PID - 0xd8c:0x8 or Full device name
+	// On Windows device name is very long and difficult to find, so 
+	//	easier to use VID/PID, but allow device in case more than one needed
+
+	char * next;
+	long VID = 0, PID = 0;
+	char product[256] = "Unknown";
+
+	struct hid_device_info *devs, *cur_dev;
+	const char *path_to_open = NULL;
+	hid_device *handle = NULL;
+
+	if (strlen(ptr) > 16)
+		CM108Device = _strdup(ptr);
+	else
+	{
+		VID = strtol(ptr, &next, 0);
+		if (next)
+			PID = strtol(++next, &next, 0);
+
+		// Look for Device
+
+		devs = hid_enumerate((unsigned short)VID, (unsigned short)PID);
+		cur_dev = devs;
+
+		while (cur_dev)
+		{
+			if (cur_dev->product_string)
+				wcstombs(product, cur_dev->product_string, 255);
+			
+			Debugprintf("HID Device %s VID %X PID %X", product, cur_dev->vendor_id, cur_dev->product_id);
+			if (cur_dev->vendor_id == VID && cur_dev->product_id == PID)
+			{
+				path_to_open = cur_dev->path;
+				break;
+			}
+			cur_dev = cur_dev->next;
+		}
+
+		if (path_to_open)
+		{
+			handle = hid_open_path(path_to_open);
+
+			if (handle)
+			{
+				hid_close(handle);
+				CM108Device = _strdup(path_to_open);
+			}
+			else
+			{
+				Debugprintf("Unable to open CM108 device %x %x", VID, PID);
+			}
+		}
+		else
+			Debugprintf("Couldn't find CM108 device %x %x", VID, PID);
+
+		hid_free_enumeration(devs);
+	}
+#else
+
+	// Linux - Next Param HID Device, eg /dev/hidraw0
+
+	CM108Device = _strdup(ptr);
+#endif
+}
+
+char * strlop(char * buf, char delim)
+{
+	// Terminate buf at delim, and return rest of string
+
+	char * ptr = strchr(buf, delim);
+
+	if (ptr == NULL) return NULL;
+
+	*(ptr)++ = 0;
+	return ptr;
+}
+
+void OpenPTTPort()
+{
+	PTTMode &= ~PTTCM108;
+	PTTMode &= ~PTTHAMLIB;
+
+	if (PTTPort[0] && strcmp(PTTPort, "None") != 0)
+	{
+		if (PTTMode == PTTCAT)
+		{
+			// convert config strings from Hex
+
+			char * ptr1 = PTTOffString;
+			UCHAR * ptr2 = PTTOffCmd;
+			char c;
+			int val;
+
+			while (c = *(ptr1++))
+			{
+				val = c - 0x30;
+				if (val > 15) val -= 7;
+				val <<= 4;
+				c = *(ptr1++) - 0x30;
+				if (c > 15) c -= 7;
+				val |= c;
+				*(ptr2++) = val;
+			}
+
+			PTTOffCmdLen = ptr2 - PTTOffCmd;
+
+			ptr1 = PTTOnString;
+			ptr2 = PTTOnCmd;
+
+			while (c = *(ptr1++))
+			{
+				val = c - 0x30;
+				if (val > 15) val -= 7;
+				val <<= 4;
+				c = *(ptr1++) - 0x30;
+				if (c > 15) c -= 7;
+				val |= c;
+				*(ptr2++) = val;
+			}
+
+			PTTOnCmdLen = ptr2 - PTTOnCmd;
+		}
+
+		if (stricmp(PTTPort, "GPIO") == 0)
+		{
+			// Initialise GPIO for PTT if available
+
+#ifdef __ARM_ARCH
+
+			if (gpioInitialise() == 0)
+			{
+				printf("GPIO interface for PTT available\n");
+				gotGPIO = TRUE;
+
+				SetupGPIOPTT();
+			}
+			else
+				printf("Couldn't initialise GPIO interface for PTT\n");
+
+#else
+			printf("GPIO interface for PTT not available on this platform\n");
+#endif
+
+		}
+		else if (stricmp(PTTPort, "CM108") == 0)
+		{
+			DecodeCM108(CM108Addr);
+			PTTMode |= PTTCM108;
+		}
+
+		else if (stricmp(PTTPort, "HAMLIB") == 0)
+		{
+			PTTMode |= PTTHAMLIB;
+			HAMLIBSetPTT(0);			// to open port
+			return;
+		}
+
+		else		//  Not GPIO
+		{
+			hPTTDevice = OpenCOMPort(PTTPort, PTTBAUD, FALSE, FALSE, FALSE, 0);
+		}
+	}
+}
+
+void ClosePTTPort()
+{
+	CloseCOMPort(hPTTDevice);
+	hPTTDevice = 0;
+}
+void CM108_set_ptt(int PTTState)
+{
+	char io[5];
+	hid_device *handle;
+	int n;
+
+	io[0] = 0;
+	io[1] = 0;
+	io[2] = 1 << (3 - 1);
+	io[3] = PTTState << (3 - 1);
+	io[4] = 0;
+
+	if (CM108Device == NULL)
+		return;
+
+#ifdef WIN32
+	handle = hid_open_path(CM108Device);
+
+	if (!handle) {
+		printf("unable to open device\n");
+		return;
+	}
+
+	n = hid_write(handle, io, 5);
+	if (n < 0)
+	{
+		printf("Unable to write()\n");
+		printf("Error: %ls\n", hid_error(handle));
+	}
+
+	hid_close(handle);
+
+#else
+
+	int fd;
+
+	fd = open(CM108Device, O_WRONLY);
+
+	if (fd == -1)
+	{
+		printf("Could not open %s for write, errno=%d\n", CM108Device, errno);
+		return;
+	}
+
+	io[0] = 0;
+	io[1] = 0;
+	io[2] = 1 << (3 - 1);
+	io[3] = PTTState << (3 - 1);
+	io[4] = 0;
+
+	n = write(fd, io, 5);
+	if (n != 5)
+	{
+		printf("Write to %s failed, n=%d, errno=%d\n", CM108Device, n, errno);
+	}
+
+	close(fd);
+#endif
+	return;
+
+}
+
+
+
+void RadioPTT(int snd_ch, BOOL PTTState)
+{
+	snd_status[snd_ch] = PTTState; // SND_IDLE = 0 SND_TX = 1 
+
+#ifdef __ARM_ARCH
+	if (useGPIO)
+	{
+		if (DualPTT && modemtoSoundLR[snd_ch] == 1)
+			gpioWrite(pttGPIOPinR, (pttGPIOInvert ? (1 - PTTState) : (PTTState)));
+		else
+			gpioWrite(pttGPIOPin, (pttGPIOInvert ? (1 - PTTState) : (PTTState)));
+
+		return;
+	}
+
+#endif
+
+	if ((PTTMode & PTTCM108))
+	{
+		CM108_set_ptt(PTTState);
+		return;
+	}
+	
+	if ((PTTMode & PTTHAMLIB))
+	{
+		HAMLIBSetPTT(PTTState);
+		return;
+	}
+	if (hPTTDevice == 0)
+		return;
+
+	if ((PTTMode & PTTCAT))
+	{
+		if (PTTState)
+			WriteCOMBlock(hPTTDevice, PTTOnCmd, PTTOnCmdLen);
+		else
+			WriteCOMBlock(hPTTDevice, PTTOffCmd, PTTOffCmdLen);
+
+		return;
+	}
+
+	if (DualPTT && modemtoSoundLR[snd_ch] == 1)		// use DTR
+	{
+		if (PTTState)
+			COMSetDTR(hPTTDevice);
+		else
+			COMClearDTR(hPTTDevice);
+	}
+	else
+	{
+		if ((PTTMode & PTTRTS))
+		{
+			if (PTTState)
+				COMSetRTS(hPTTDevice);
+			else
+				COMClearRTS(hPTTDevice);
+		}
+	}
+
+}
+
+char ShortDT[] = "HH:MM:SS";
+
+char * ShortDateTime()
+{
+	struct tm * tm;
+	time_t NOW = time(NULL);
+
+	tm = gmtime(&NOW);
+
+	sprintf(ShortDT, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
+	return ShortDT;
+}
+
+
+// Reed Solomon Stuff
+
+
+int NPAR = -1;	// Number of Parity Bytes - used in RS Code
+
+int xMaxErrors = 0;
+
+int RSEncode(UCHAR * bytToRS, UCHAR * RSBytes, int DataLen, int RSLen)
+{
+	// This just returns the Parity Bytes. I don't see the point
+	// in copying the message about
+
+	unsigned char Padded[256];		// The padded Data
+
+	int Length = DataLen + RSLen;	// Final Length of packet
+	int PadLength = 255 - Length;	// Padding bytes needed for shortened RS codes
+
+	//	subroutine to do the RS encode. For full length and shortend RS codes up to 8 bit symbols (mm = 8)
+
+	if (NPAR != RSLen)		// Changed RS Len, so recalc constants;
+	{
+		NPAR = RSLen;
+		xMaxErrors = NPAR / 2;
+		initialize_ecc();
+	}
+
+	// Copy the supplied data to end of data array.
+
+	memset(Padded, 0, PadLength);
+	memcpy(&Padded[PadLength], bytToRS, DataLen);
+
+	encode_data(Padded, 255 - RSLen, RSBytes);
+
+	return RSLen;
+}
+
+//	Main RS decode function
+
+extern int index_of[];
+extern int recd[];
+extern int Corrected[256];
+extern int tt;		//  number of errors that can be corrected 
+extern int kk;		// Info Symbols
+
+extern BOOL blnErrorsCorrected;
+
+
+BOOL RSDecode(UCHAR * bytRcv, int Length, int CheckLen, BOOL * blnRSOK)
+{
+
+
+	// Using a modified version of Henry Minsky's code
+
+	//Copyright Henry Minsky (hqm@alum.mit.edu) 1991-2009
+
+	// Rick's Implementation processes the byte array in reverse. and also 
+	//	has the check bytes in the opposite order. I've modified the encoder
+	//	to allow for this, but so far haven't found a way to mske the decoder
+	//	work, so I have to reverse the data and checksum to decode G8BPQ Nov 2015
+
+	//	returns TRUE if was ok or correction succeeded, FALSE if correction impossible
+
+	UCHAR intTemp[256];				// WOrk Area to pass to Decoder		
+	int i;
+	UCHAR * ptr2 = intTemp;
+	UCHAR * ptr1 = &bytRcv[Length - CheckLen - 1]; // Last Byte of Data
+
+	int DataLen = Length - CheckLen;
+	int PadLength = 255 - Length;		// Padding bytes needed for shortened RS codes
+
+	*blnRSOK = FALSE;
+
+	if (Length > 255 || Length < (1 + CheckLen))		//Too long or too short 
+		return FALSE;
+
+	if (NPAR != CheckLen)		// Changed RS Len, so recalc constants;
+	{
+		NPAR = CheckLen;
+		xMaxErrors = NPAR / 2;
+
+		initialize_ecc();
+	}
+
+
+	//	We reverse the data while zero padding it to speed things up
+
+	//	We Need (Data Reversed) (Zero Padding) (Checkbytes Reversed)
+
+	// Reverse Data
+
+	for (i = 0; i < DataLen; i++)
+	{
+		*(ptr2++) = *(ptr1--);
+	}
+
+	//	Clear padding
+
+	memset(ptr2, 0, PadLength);
+
+	ptr2 += PadLength;
+
+	// Error Bits
+
+	ptr1 = &bytRcv[Length - 1];			// End of check bytes
+
+	for (i = 0; i < CheckLen; i++)
+	{
+		*(ptr2++) = *(ptr1--);
+	}
+
+	decode_data(intTemp, 255);
+
+	// check if syndrome is all zeros 
+
+	if (check_syndrome() == 0)
+	{
+		// RS ok, so no need to correct
+
+		*blnRSOK = TRUE;
+		return TRUE;		// No Need to Correct
+	}
+
+	if (correct_errors_erasures(intTemp, 255, 0, 0) == 0) // Dont support erasures at the momnet
+
+		// Uncorrectable
+
+		return FALSE;
+
+	// Data has been corrected, so need to reverse again
+
+	ptr1 = &intTemp[DataLen - 1];
+	ptr2 = bytRcv; // Last Byte of Data
+
+	for (i = 0; i < DataLen; i++)
+	{
+		*(ptr2++) = *(ptr1--);
+	}
+
+	// ?? Do we need to return the check bytes ??
+
+	// Yes, so we can redo RS Check on supposedly connected frame
+
+	ptr1 = &intTemp[254];	// End of Check Bytes
+
+	for (i = 0; i < CheckLen; i++)
+	{
+		*(ptr2++) = *(ptr1--);
+	}
+
+	return TRUE;
+}
+
+extern TStringList detect_list[5];
+extern TStringList detect_list_c[5];
+
+void ProcessPktFrame(int snd_ch, UCHAR * Data, int frameLen)
+{
+	string * pkt = newString();
+
+	stringAdd(pkt, Data, frameLen + 2);			// 2 for crc (not actually there)
+
+	analiz_frame(snd_ch, pkt, "ARDOP", 1);
+
+}
diff --git a/SMMain.c b/SMMain.c
index 652911e..3c87359 100644
--- a/SMMain.c
+++ b/SMMain.c
@@ -27,6 +27,7 @@ along with QtSoundModem.  If not, see http://www.gnu.org/licenses
 #include "hidapi.h"
 #include <fcntl.h>
 #include <errno.h>
+#include <stdint.h>   
 
 BOOL KISSServ;
 int KISSPort;
@@ -40,6 +41,9 @@ int SoundIsPlaying = 0;
 int UDPSoundIsPlaying = 0;
 int Capturing = 0;
 
+int txmin = 0;
+int txmax = 0;
+
 extern unsigned short buffer[2][1200];
 extern int SoundMode;
 extern int needRSID[4];
@@ -62,13 +66,24 @@ extern int pnt_change[5];				// Freq Changed Flag
 fftwf_complex *in, *out;
 fftwf_plan p;
 
-#define N 2048
+int FFTSize = 4096;
+
+char * Wisdom;
 
 void initfft()
 {
-	in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * N);
-	out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * N);
-	p = fftwf_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
+	fftwf_import_wisdom_from_string(Wisdom); 
+	fftwf_set_timelimit(30);
+
+#ifndef WIN32
+	printf("It may take up to 30 seconds for the program to start for the first time\n");
+#endif
+
+	in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * 10000);
+	out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * 10000);
+	p = fftwf_plan_dft_1d(FFTSize, in, out, FFTW_FORWARD, FFTW_PATIENT);
+
+	Wisdom = fftwf_export_wisdom_to_string();
 }
 
 void dofft(short * inp, float * outr, float * outi)
@@ -77,7 +92,7 @@ void dofft(short * inp, float * outr, float * outi)
 	
 	fftwf_complex * fft = in;
 
-	for (i = 0; i < N; i++)
+	for (i = 0; i < FFTSize; i++)
 	{
 		fft[0][0] = inp[0] * 1.0f;
 		fft[0][1] = 0;
@@ -89,7 +104,7 @@ void dofft(short * inp, float * outr, float * outi)
 
 	fft = out;
 
-	for (i = 0; i < N; i++)
+	for (i = 0; i < FFTSize; i++)
 	{
 		outr[0] = fft[0][0];
 		outi[0] = fft[0][1];
@@ -165,6 +180,18 @@ void SampleSink(int LR, short Sample)
 			DMABuffer[1 + 2 * Number] = 0;
 		}
 	}
+	if (using48000)
+	{
+		// Need to upsample to 48K. Try just duplicating sample
+
+		uint32_t * ptr = &DMABuffer[2 * Number];
+
+		*(&ptr[1]) = *(ptr);
+		*(&ptr[2]) = *(ptr);
+		*(&ptr[3]) = *(ptr);
+
+		Number += 3;
+	}
 	Number++;
 #endif
 		if (Number >= SendSize)
@@ -321,194 +348,171 @@ extern UCHAR * pixelPointer;
 #endif
 
 extern int blnBusyStatus;
-BusyDet = 0;
+BusyDet = 5;
 
 #define PLOTWATERFALL
 
 int WaterfallActive = 1;
 int SpectrumActive;
 
-/*
+float BinSize;
 
-void UpdateBusyDetector(short * bytNewSamples)
+extern int intLastStart;
+extern int intLastStop;
+
+void SMSortSignals2(float * dblMag, int intStartBin, int intStopBin, int intNumBins, float *  dblAVGSignalPerBin, float *  dblAVGBaselinePerBin);
+
+
+BOOL SMBusyDetect3(float * dblMag, int intStart, int intStop)        // this only called while searching for leader ...once leader detected, no longer called.
 {
-	float dblReF[1024];
-	float dblImF[1024];
-	float dblMag[206];
-#ifdef PLOTSPECTRUM
-	float dblMagMax = 0.0000000001f;
-	float dblMagMin = 10000000000.0f;
-#endif
-	UCHAR Waterfall[256];			// Colour index values to send to GUI
-	int clrTLC = Lime;				// Default Bandwidth lines on waterfall
+	// First sort signals and look at highes signals:baseline ratio..
 
-	static BOOL blnLastBusyStatus;
+	float dblAVGSignalPerBinNarrow, dblAVGSignalPerBinWide, dblAVGBaselineNarrow, dblAVGBaselineWide;
+	float dblSlowAlpha = 0.2f;
+	float dblAvgStoNNarrow = 0, dblAvgStoNWide = 0;
+	int intNarrow = 8;  // 8 x 11.72 Hz about 94 z
+	int intWide = ((intStop - intStart) * 2) / 3; //* 0.66);
+	int blnBusy = FALSE;
+	int  BusyDet4th = BusyDet * BusyDet * BusyDet * BusyDet;
+
+	// First sort signals and look at highest signals:baseline ratio..
+	// First narrow band (~94Hz)
+
+	SMSortSignals2(dblMag, intStart, intStop, intNarrow, &dblAVGSignalPerBinNarrow, &dblAVGBaselineNarrow);
+
+	if (intLastStart == intStart && intLastStop == intStop)
+		dblAvgStoNNarrow = (1 - dblSlowAlpha) * dblAvgStoNNarrow + dblSlowAlpha * dblAVGSignalPerBinNarrow / dblAVGBaselineNarrow;
+	else
+	{
+		// This initializes the Narrow average after a bandwidth change
+
+		dblAvgStoNNarrow = dblAVGSignalPerBinNarrow / dblAVGBaselineNarrow;
+		intLastStart = intStart;
+		intLastStop = intStop;
+	}
+
+	// Wide band (66% of current bandwidth)
+
+	SMSortSignals2(dblMag, intStart, intStop, intWide, &dblAVGSignalPerBinWide, &dblAVGBaselineWide);
+
+	if (intLastStart == intStart && intLastStop == intStop)
+		dblAvgStoNWide = (1 - dblSlowAlpha) * dblAvgStoNWide + dblSlowAlpha * dblAVGSignalPerBinWide / dblAVGBaselineWide;
+	else
+	{
+		// This initializes the Wide average after a bandwidth change
+
+		dblAvgStoNWide = dblAVGSignalPerBinWide / dblAVGBaselineWide;
+		intLastStart = intStart;
+		intLastStop = intStop;
+	}
+
+	// Preliminary calibration...future a function of bandwidth and BusyDet.
+
+
+	blnBusy = (dblAvgStoNNarrow > (3 + 0.008 * BusyDet4th)) || (dblAvgStoNWide > (5 + 0.02 * BusyDet4th));
+
+//	if (BusyDet == 0)
+//		blnBusy = FALSE;		// 0 Disables check ?? Is this the best place to do this?
+
+//	WriteDebugLog(LOGDEBUG, "Busy %d Wide %f Narrow %f", blnBusy, dblAvgStoNWide, dblAvgStoNNarrow); 
+
+	return blnBusy;
+}
+
+extern int compare(const void *p1, const void *p2);
+
+void SMSortSignals2(float * dblMag, int intStartBin, int intStopBin, int intNumBins, float *  dblAVGSignalPerBin, float *  dblAVGBaselinePerBin)
+{
+	// puts the top intNumber of bins between intStartBin and intStopBin into dblAVGSignalPerBin, the rest into dblAvgBaselinePerBin
+	// for decent accuracy intNumBins should be < 75% of intStopBin-intStartBin)
+
+	// This version uses a native sort function which is much faster and reduces CPU loading significantly on wide bandwidths. 
+
+	float dblSort[8192];
+	float dblSum1 = 0, dblSum2 = 0;
+	int numtoSort = (intStopBin - intStartBin) + 1, i;
+
+	memcpy(dblSort, &dblMag[intStartBin], numtoSort * sizeof(float));
+
+	qsort((void *)dblSort, numtoSort, sizeof(float), compare);
+
+	for (i = numtoSort - 1; i >= 0; i--)
+	{
+		if (i >= (numtoSort - intNumBins))
+			dblSum1 += dblSort[i];
+		else
+			dblSum2 += dblSort[i];
+	}
+
+	*dblAVGSignalPerBin = dblSum1 / intNumBins;
+	*dblAVGBaselinePerBin = dblSum2 / (intStopBin - intStartBin - intNumBins - 1);
+}
+
+
+
+void SMUpdateBusyDetector(int LR, float * Real, float *Imag)
+{
+	// Energy based detector for each channel.
+	// Fed from FFT generated for waterfall display
+	// FFT size is 4096
+
+	float dblMag[4096];
+
+	static BOOL blnLastBusyStatus[4];
 
 	float dblMagAvg = 0;
 	int intTuneLineLow, intTuneLineHi, intDelta;
-	int i;
+	int i, chan;
 
-	//	if (State != SearchingForLeader)
-	//		return;						// only when looking for leader
+	return;
 
-	if (Now - LastBusyCheck < 100)
+	if (Now - LastBusyCheck < 100)	// ??
 		return;
 
 	LastBusyCheck = Now;
 
-	FourierTransform(1024, bytNewSamples, &dblReF[0], &dblImF[0], FALSE);
+	// We need to run busy test on the frequncies used by each modem.
 
-	for (i = 0; i < 206; i++)
+	for (chan = 0; chan < 4; chan++)
 	{
-		//	starting at ~300 Hz to ~2700 Hz Which puts the center of the signal in the center of the window (~1500Hz)
+		int Low, High, Start, End;
 
-		dblMag[i] = powf(dblReF[i + 25], 2) + powf(dblImF[i + 25], 2);	 // first pass 
-		dblMagAvg += dblMag[i];
-#ifdef PLOTSPECTRUM		
-		dblMagSpectrum[i] = 0.2f * dblMag[i] + 0.8f * dblMagSpectrum[i];
-		dblMagMax = max(dblMagMax, dblMagSpectrum[i]);
-		dblMagMin = min(dblMagMin, dblMagSpectrum[i]);
-#endif
-	}
+		if (soundChannel[chan] != (LR + 1))	// on this side of soundcard 
+			continue;
 
-	//	LookforPacket(dblMag, dblMagAvg, 206, &dblReF[25], &dblImF[25]);
-	//	packet_process_samples(bytNewSamples, 1200);
+		Low = tx_freq[chan] - txbpf[chan] / 2;
+		High = tx_freq[chan] + txbpf[chan] / 2;
 
-	intDelta = roundf(500 / 2) + 50 / 11.719f;
+		// BinSize is width of each fft bin in Hz
 
-	intTuneLineLow = max((103 - intDelta), 3);
-	intTuneLineHi = min((103 + intDelta), 203);
+		Start = (Low / BinSize);		// First and last bins to process
+		End = (High / BinSize);
 
-//	if (ProtocolState == DISC)		// ' Only process busy when in DISC state
-	{
-	//	blnBusyStatus = BusyDetect3(dblMag, intTuneLineLow, intTuneLineHi);
 
-		if (blnBusyStatus && !blnLastBusyStatus)
+		for (i = Start; i < End; i++)
 		{
-//			QueueCommandToHost("BUSY TRUE");
-//			newStatus = TRUE;				// report to PTC
-
-			if (!WaterfallActive && !SpectrumActive)
-			{
-				UCHAR Msg[2];
-
-//				Msg[0] = blnBusyStatus;
-//				SendtoGUI('B', Msg, 1);
-			}
+			dblMag[i] = powf(Real[i], 2) + powf(Imag[i], 2);	 // first pass
+			dblMagAvg += dblMag[i];
 		}
-		//    stcStatus.Text = "TRUE"
-			//    queTNCStatus.Enqueue(stcStatus)
-			//    'Debug.WriteLine("BUSY TRUE @ " & Format(DateTime.UtcNow, "HH:mm:ss"))
 
-		else if (blnLastBusyStatus && !blnBusyStatus)
+		blnBusyStatus = SMBusyDetect3(dblMag, Start, End);
+
+		if (blnBusyStatus && !blnLastBusyStatus[chan])
 		{
-//			QueueCommandToHost("BUSY FALSE");
-//			newStatus = TRUE;				// report to PTC
-
-			if (!WaterfallActive && !SpectrumActive)
-			{
-				UCHAR Msg[2];
-
-				Msg[0] = blnBusyStatus;
-//				SendtoGUI('B', Msg, 1);
-			}
+			Debugprintf("Ch %d Busy True", chan);
+		}
+		else if (blnLastBusyStatus[chan] && !blnBusyStatus)
+		{
+			Debugprintf("Ch %d Busy False", chan);
 		}
 		//    stcStatus.Text = "FALSE"
 		//    queTNCStatus.Enqueue(stcStatus)
 		//    'Debug.WriteLine("BUSY FALSE @ " & Format(DateTime.UtcNow, "HH:mm:ss"))
 
-		blnLastBusyStatus = blnBusyStatus;
-	}
-
-	if (BusyDet == 0)
-		clrTLC = Goldenrod;
-	else if (blnBusyStatus)
-		clrTLC = Fuchsia;
-
-	// At the moment we only get here what seaching for leader,
-	// but if we want to plot spectrum we should call
-	// it always
-
-
-
-	if (WaterfallActive)
-	{
-#ifdef PLOTWATERFALL
-		dblMagAvg = log10f(dblMagAvg / 5000.0f);
-
-		for (i = 0; i < 206; i++)
-		{
-			// The following provides some AGC over the waterfall to compensate for avg input level.
-
-			float y1 = (0.25f + 2.5f / dblMagAvg) * log10f(0.01 + dblMag[i]);
-			int objColor;
-
-			// Set the pixel color based on the intensity (log) of the spectral line
-			if (y1 > 6.5)
-				objColor = Orange; // Strongest spectral line 
-			else if (y1 > 6)
-				objColor = Khaki;
-			else if (y1 > 5.5)
-				objColor = Cyan;
-			else if (y1 > 5)
-				objColor = DeepSkyBlue;
-			else if (y1 > 4.5)
-				objColor = RoyalBlue;
-			else if (y1 > 4)
-				objColor = Navy;
-			else
-				objColor = Black;
-
-			if (i == 102)
-				Waterfall[i] = Tomato;  // 1500 Hz line (center)
-			else if (i == intTuneLineLow || i == intTuneLineLow - 1 || i == intTuneLineHi || i == intTuneLineHi + 1)
-				Waterfall[i] = clrTLC;
-			else
-				Waterfall[i] = objColor; // ' Else plot the pixel as received
-		}
-
-		// Send Signal level and Busy indicator to save extra packets
-
-		Waterfall[206] = CurrentLevel;
-		Waterfall[207] = blnBusyStatus;
-
-		doWaterfall(Waterfall);
-#endif
-	}
-	else if (SpectrumActive)
-	{
-#ifdef PLOTSPECTRUM
-		// This performs an auto scaling mechansim with fast attack and slow release
-		if (dblMagMin / dblMagMax < 0.0001) // more than 10000:1 difference Max:Min
-			dblMaxScale = max(dblMagMax, dblMaxScale * 0.9f);
-		else
-			dblMaxScale = max(10000 * dblMagMin, dblMagMax);
-
-//		clearDisplay();
-
-		for (i = 0; i < 206; i++)
-		{
-			// The following provides some AGC over the spectrum to compensate for avg input level.
-
-			float y1 = -0.25f * (SpectrumHeight - 1) *  log10f((max(dblMagSpectrum[i], dblMaxScale / 10000)) / dblMaxScale); // ' range should be 0 to bmpSpectrumHeight -1
-			int objColor = Yellow;
-
-			Waterfall[i] = round(y1);
-		}
-
-		// Send Signal level and Busy indicator to save extra packets
-
-		Waterfall[206] = CurrentLevel;
-		Waterfall[207] = blnBusyStatus;
-		Waterfall[208] = intTuneLineLow;
-		Waterfall[209] = intTuneLineHi;
-
-//		SendtoGUI('X', Waterfall, 210);
-#endif
+		blnLastBusyStatus[chan] = blnBusyStatus;
 	}
 }
 
-*/
 
 extern short rawSamples[2400];	// Get Frame Type need 2400 and we may add 1200
 int rawSamplesLength = 0;
@@ -543,9 +547,12 @@ int Freq_Change(int Chan, int Freq)
 {
 	int low, high;
 
-	low = round(rx_shift[1] / 2 + RCVR[Chan] * rcvr_offset[Chan] + 1);
+	low = round(rx_shift[Chan] / 2 + (RCVR[Chan] * rcvr_offset[Chan]));
 	high = round(RX_Samplerate / 2 - (rx_shift[Chan] / 2 + RCVR[Chan] * rcvr_offset[Chan]));
 
+	if (Freq < 300)
+		return rx_freq[Chan];				// Dont allow change
+
 	if (Freq < low)
 		return rx_freq[Chan];				// Dont allow change
 
@@ -600,6 +607,7 @@ int ARDOPSendToCard(int Chan, int Len)
 {
 	// Send Next Block of samples to the soundcard
 
+
 	short * in = &ARDOPTXBuffer[Chan][ARDOPTXPtr[Chan]];		// Enough to hold whole frame of samples
 	short * out = DMABuffer;
 
@@ -628,6 +636,7 @@ int ARDOPSendToCard(int Chan, int Len)
 			}
 		}
 	}
+
 	DMABuffer = SendtoCard(DMABuffer, Len);
 
 	ARDOPTXPtr[Chan] += Len;
@@ -668,7 +677,9 @@ void DoTX(int Chan)
 	{
 		Flush();
 		Debugprintf("TX Complete");
-		RadioPTT(0, 0);
+		RadioPTT(Chan, 0);
+		Continuation[Chan] = 0;
+
 		tx_status[Chan] = TX_SILENCE;
 
 		// We should now send any ackmode acks as the channel is now free for dest to reply
@@ -680,7 +691,7 @@ void DoTX(int Chan)
 	{
 		// Continue the send
 
-		if (modem_mode[Chan] == MODE_ARDOP)
+		if (modem_mode[Chan] == MODE_ARDOP || modem_mode[Chan] == MODE_RUH)
 		{
 //			if (SeeIfCardBusy())
 //				return 0;
@@ -699,6 +710,8 @@ void DoTX(int Chan)
 					SoundIsPlaying = TRUE;
 					Number = 0;
 
+					Continuation[Chan] = 1;
+
 					Debugprintf("TX Continuing");
 
 					string * myTemp = Strings(&all_frame_buf[Chan], 0);			// get message
@@ -728,19 +741,26 @@ void DoTX(int Chan)
 
 					put_frame(Chan, tx_data, "", TRUE, FALSE);
 
-					PktARDOPEncode(tx_data->Data, tx_data->Length - 2, Chan);
+					if (modem_mode[Chan] == MODE_ARDOP)
+						PktARDOPEncode(tx_data->Data, tx_data->Length - 2, Chan);
+					else
+						RUHEncode(tx_data->Data, tx_data->Length - 2, Chan);
 
 					freeString(tx_data);
 
 					// Samples are now in DMABuffer = Send first block
 
+					DMABuffer = SoundInit();
+
 					ARDOPSendToCard(Chan, SendSize);
 					tx_status[Chan] = TX_FRAME;
 					return;
 				}
 
 				Debugprintf("TX Complete");
-				RadioPTT(0, 0);
+				RadioPTT(Chan, 0);
+				Continuation[Chan] = 0;
+
 				tx_status[Chan] = TX_SILENCE;
 
 				// We should now send any ackmode acks as the channel is now free for dest to reply
@@ -845,6 +865,50 @@ void DoTX(int Chan)
 		ARDOPSendToCard(Chan, SendSize);
 		tx_status[Chan] = TX_FRAME;
 
+	}
+	else if (modem_mode[Chan] == MODE_RUH)
+	{
+		// Same as for ARDOP. Generate a whole frame of samples 
+		// then send them out a bit at a time to avoid stopping here
+
+		// We allow two RUH modems
+
+		string * myTemp = Strings(&all_frame_buf[Chan], 0);			// get message
+		string * tx_data;
+
+		if ((myTemp->Data[0] & 0x0f) == 12)			// ACKMODE
+		{
+			// Save copy then copy data up 3 bytes
+
+			Add(&KISS_acked[Chan], duplicateString(myTemp));
+
+			mydelete(myTemp, 0, 3);
+			myTemp->Length -= sizeof(void *);
+		}
+		else
+		{
+			// Just remove control 
+
+			mydelete(myTemp, 0, 1);
+		}
+
+		tx_data = duplicateString(myTemp);		// so can free original below
+
+		Delete(&all_frame_buf[Chan], 0);			// This will invalidate temp
+
+		AGW_AX25_frame_analiz(Chan, FALSE, tx_data);
+
+		put_frame(Chan, tx_data, "", TRUE, FALSE);
+
+		RUHEncode(tx_data->Data, tx_data->Length - 2, Chan);
+
+		freeString(tx_data);
+
+		// Samples are now in DMABuffer = Send first block
+
+		ARDOPSendToCard(Chan, SendSize);
+		tx_status[Chan] = TX_FRAME;
+
 	}
 	else
 		modulator(Chan, tx_bufsize);
@@ -852,16 +916,6 @@ void DoTX(int Chan)
 	return;
 }
 
-void stoptx(int snd_ch)
-{
-	Flush();
-	Debugprintf("TX Complete");
-	RadioPTT(snd_ch, 0);
-	tx_status[snd_ch] = TX_SILENCE;
-
-	snd_status[snd_ch] = SND_IDLE;
-}
-
 void RX2TX(int snd_ch)
 {
 	if (snd_status[snd_ch] == SND_IDLE)
@@ -1142,10 +1196,24 @@ void CM108_set_ptt(int PTTState)
 
 }
 
-
+float amplitudes[4] = { 32767, 32767, 32767, 32767 };
+extern float amplitude;
 
 void RadioPTT(int snd_ch, BOOL PTTState)
 {
+	snd_status[snd_ch] = PTTState; // SND_IDLE = 0 SND_TX = 1 
+
+	if (PTTState)
+	{
+		txmax = txmin = 0;
+		amplitude = amplitudes[snd_ch];
+	}
+	else
+	{
+		Debugprintf("Output peaks = %d, %d, amp %f", txmin, txmax, amplitude);
+		amplitudes[snd_ch] = amplitude;
+	}
+
 #ifdef __ARM_ARCH
 	if (useGPIO)
 	{
diff --git a/SoundInput.c b/SoundInput.c
index 5d7e7bc..32366f5 100644
--- a/SoundInput.c
+++ b/SoundInput.c
@@ -322,7 +322,7 @@ VOID Track1Car4FSK(short * intSamples, int * intPtr, int intSampPerSymbol, float
 VOID Decode1CarPSK(int Carrier, BOOL OFDM);
 int EnvelopeCorrelator();
 int EnvelopeCorrelatorNew();
-BOOL DecodeFrame(int intFrameType, UCHAR * bytData);
+BOOL DecodeFrame(int chan, int intFrameType, UCHAR * bytData);
 
 void Update4FSKConstellation(int * intToneMags, int * intQuality);
 void Update16FSKConstellation(int * intToneMags, int * intQuality);
@@ -864,7 +864,7 @@ float dblFreqBin[MAXCAR];
 
 BOOL CheckFrameTypeParity(int intTonePtr, int * intToneMags);
 
-void ARDOPProcessNewSamples(short * Samples, int nSamples)
+void ARDOPProcessNewSamples(int chan, short * Samples, int nSamples)
 {
 	BOOL blnFrameDecodedOK = FALSE;
 
@@ -1264,7 +1264,7 @@ else if (intPhaseError > 2)
 		// This mechanism is to skip actual decoding and reply/change state...no need to decode 
 
      
-		blnFrameDecodedOK = DecodeFrame(intFrameType, bytData);
+		blnFrameDecodedOK = DecodeFrame(chan, intFrameType, bytData);
 
 ProcessFrame:	
 
@@ -3047,7 +3047,7 @@ void DemodulateFrame(int intFrameType)
 int intSNdB = 0, intQuality = 0;
 
 
-BOOL DecodeFrame(int xxx, UCHAR * bytData)
+BOOL DecodeFrame(int chan, int xxx, UCHAR * bytData)
 {
 	BOOL blnDecodeOK = FALSE;
 	char strCallerCallsign[10] = "";
@@ -3176,7 +3176,7 @@ BOOL DecodeFrame(int xxx, UCHAR * bytData)
 
 			// Data in bytData  len in frameLen
 
-			ProcessPktFrame(0, bytData, frameLen);
+			ProcessPktFrame(chan, bytData, frameLen);
 //				else
 //			L2Routine(bytData, frameLen, intLastRcvdFrameQuality, totalRSErrors, intNumCar, pktRXMode);
 
diff --git a/UZ7HOStuff-DESKTOP-MHE5LO8.h b/UZ7HOStuff-DESKTOP-MHE5LO8.h
new file mode 100644
index 0000000..20721cc
--- /dev/null
+++ b/UZ7HOStuff-DESKTOP-MHE5LO8.h
@@ -0,0 +1,1110 @@
+#pragma once
+
+//
+//	 My port of UZ7HO's Soundmodem
+//
+
+#define VersionString "0.0.0.67alpha-6"
+#define VersionBytes {0, 0, 0, 67}
+
+// Added FX25. 4x100 FEC and V27 not Working and disabled
+
+// 0.8 V27 now OK.
+
+// 0.9 Digipeating added
+
+// 0.10 Fix second channel tones and calibrate
+
+// 0.11 Fix allocation of sessions to correct modem
+//		Fix DCD
+//		Fix Monitoring of Multiline packets
+//		Fix possible saving of wrong center freq
+//		Limit TX sample Q in Linux
+//
+
+// 0.12	Add AGWPE monitoring of received frames
+//		Fix DCD Threshold
+//		Fix KISS transparency issue
+
+// 0.13 Fix sending last few bits in FX.25 Mode
+
+// 0.14 Add "Copy on Select" to Trace Window
+
+// 0.15 Limit Trace window to 10000 lines
+
+// 0.16 Fix overwriting monitor window after scrollback
+
+// 0.17	Add GPIO and CAT PTT
+
+// 0.18	Add CM108/119 PTT
+
+// 0.19 Fix scheduling KISS frames
+
+// 0.20 Debug code added to RR processing
+
+// 0.21	Fix AGW monitor of multiple line packets
+//		Close ax.25 sessions if AGW Host session closes
+
+// 0.22	Add FEC Count to Session Stats
+
+// 0.23 Retry DISC until UA received or retry count exceeded
+
+// 0.24	More fixes to DISC handling
+
+// 0.26 Add OSS PulseAudio and HAMLIB support
+
+// 0.27 Dynamically load PulseAudio modules
+
+// 0.28 Add ARDOPPacket Mode
+
+// 0.29 Fix saving settings and geometry on close
+// 0.30 Retructure code to build with Qt 5.3
+//      Fix crash in nogui mode if pulse requested but not available
+//		Try to fix memory leaks
+
+// 0.31 Add option to run modems in seprate threads
+
+// 0.32	Fix timing problem with AGW connect at startup
+//		Add Memory ARQ
+//		Add Single bit "Correction"
+//		Fix error in 31 when using multiple decoders
+
+// 0.33 Fix Single bit correction
+//		More memory leak fixes
+
+// 0.34 Add API to set Modem and Center Frequency
+//		Fix crash in delete_incoming_mycalls
+
+// 0.35 Return Version in AGW Extended g response
+
+// 0.36 Fix timing problem on startup
+
+// 0.37 Add scrollbars to Device and Modem dialogs
+
+// 0.38 Change default CM108 name to /dev/hidraw0 on Linux
+
+// 0.39	Dont try to display Message Boxes in nogui mode.
+//		Close Device and Modem dialogs on Accept or Reject
+//		Fix using HAMLIB in nogui mode
+
+// 0.40	Fix bug in frame optimize when using 6 char calls
+
+// 0.41	Fix "glitch" on waterfall markers when changing modem freqs 
+
+// 0.42	Add "Minimize to Tray" option
+
+// 0.43 Add Andy's on_SABM fix.
+//		Fix Crash if KISS Data sent to AGW port
+
+// 0.44 Add UDP bridge.
+
+// 0.45 Add two more modems.
+// 0.46 Fix two more modems.
+
+// 0.47 Fix suprious DM when host connection lost
+//		Add CWID
+
+// 0.48 Send FRMR for unrecognised frame types
+
+// 0.49 Add Andy's FEC Tag correlation coode
+
+// 0.50 Fix Waterfall display when only using right channel
+//		Allow 1200 baud fsk at other center freqs
+//		Add Port numbers to Window title and Try Icon tooltip
+//		Fix calculation of filters for multiple decoders
+//		Add RX Offset setting (for satellite operation
+
+// 0.51	Fix Multithreading with more that 2 modems
+
+// 0.52	Add Stdin as source on Linux
+
+// 0.53	Use Byte instead of byte as byte is defined in newer versions of gcc
+
+// 0.54 Fix for ALSA problem on new pi OS
+
+// 0.55 Fix for compiler error with newer compiler
+
+// 0.56	Fix errors in Config.cpp			June 22
+
+// 0.57	Add Restart Waterfall action		August 22
+
+// 0.58 Add RSID							Sept 2022
+
+// 0.59 Add config of Digi Calls			Dec 2022
+
+// 0.60 Allow ARDOP Packet on modems 2 to 4 March 2023
+
+// 0.61 Add il2p support					April 2023
+
+// 0.62										April 2023
+//	Add option to specify sound devices that aren't in list
+//	Add Save button to Modem dialog to save current tab without closing dialog
+//	Don't add plug: to Linux device addresses unless addr contains : (allows use of eg ARDOP)
+
+// 0.64 Fix sending ax.25 (broken in .61)
+
+// 0.65	Allow Set Modem command to use modem index as well as modem name
+
+// 0.66 Allow configuration of waterfall span	June 23
+//		Add Exclude
+
+// .67 Add extra modes RUH 4800 RUH 9600 QPSK 600 QPSK 2400	July 23
+//	   8PSK 900 added but not working
+//	   Fix loading txtail
+//	   Fix digipeating
+//	   Add MaxFrame to Modem Dialog
+
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UNUSED(x) (void)(x)
+
+#ifdef M_PI
+#undef M_PI
+#endif
+
+#define M_PI       3.1415926f
+
+#define pi M_PI
+
+#ifndef WIN32
+#define _strdup strdup
+#endif
+
+	//#define NULL ((void *)0)
+
+	//Delphi Types remember case insensitive
+
+#define single float
+#define boolean int
+#define Byte unsigned char		//                  0 to 255
+#define Word unsigned short	//                        0 to 65,535
+#define SmallInt short 		//                  -32,768 to 32,767
+#define LongWord unsigned int	//                        0 to 4,294,967,295
+ //  Int6 : Cardinal; //                        0 to 4,294,967,295
+#define LongInt int			//           -2,147,483,648 to 2,147,483,647
+#define Integer int  //           -2,147,483,648 to 2,147,483,647
+//#define Int64 long long		 // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
+
+//#define Byte unsigned char		//                  0 to 255
+#define word unsigned short	//                        0 to 65,535
+#define smallint short 		//                  -32,768 to 32,767
+#define longword unsigned int	//                        0 to 4,294,967,295
+ //  Int6 : Cardinal; //                        0 to 4,294,967,295
+#define longint int			//           -2,147,483,648 to 2,147,483,647
+#define integer int  //           -2,147,483,648 to 2,147,483,647
+
+typedef unsigned long ULONG;
+
+#define UCHAR unsigned char
+#define UINT unsigned int
+#define BOOL int
+#define TRUE 1
+#define FALSE 0
+
+// Soundcard Channels
+
+#define NONE 0
+#define LEFT 1
+#define RIGHT 2
+
+#define nr_emph 2
+
+#define decodedNormal 4 //'-'
+#define decodedFEC    3 //'F'
+#define decodedMEM	  2 //'#'
+#define decodedSingle 1 //'$'
+
+
+// Think about implications of changing this !!
+extern int FFTSize;
+
+// Seems to use Delphi TStringList for a lot of queues. This seems to be a list of pointers and a count
+// Each pointer is to a Data/Length pair
+//Maybe something like
+
+typedef struct string_T
+{
+	unsigned char * Data;
+	int Length;
+	int AllocatedLength;				// A reasonable sized block is allocated at the start to speed up adding chars
+
+}string;
+
+typedef struct TStringList_T
+{
+	int Count;
+	string ** Items;
+
+} TStringList;
+
+// QPSK struct
+
+typedef struct TQPSK_t
+{
+	UCHAR tx[4];
+	int count[4];
+	UCHAR rx[4];
+	UCHAR mode;
+} TPQSK;
+
+
+typedef struct TKISSMode_t
+{
+	string * data_in;
+	void * Socket;				// Used as a key
+
+	// Not sure what rest are used for. Seems to be one per channel
+
+	TStringList buffer[4];			// Outgoing Frames
+
+} TKISSMode;
+
+typedef struct  TMChannel_t
+{
+
+	single prev_LPF1I_buf[4096];
+	single prev_LPF1Q_buf[4096];
+	single prev_dLPFI_buf[4096];
+	single prev_dLPFQ_buf[4096];
+	single prev_AFCI_buf[4096];
+	single prev_AFCQ_buf[4096];
+	single AngleCorr;
+	single MUX_osc;
+	single AFC_IZ1;
+	single AFC_IZ2;
+	single AFC_QZ1;
+	single AFC_QZ2;
+	single AFC_bit_buf1I[1024];
+	single AFC_bit_buf1Q[1024];
+	single AFC_bit_buf2[1024];
+	single AFC_IIZ1;
+	single AFC_QQZ1;
+
+} TMChannel;
+
+typedef struct TFX25_t
+{
+	string  data;
+	Byte  status;
+	Byte  bit_cnt;
+	Byte  byte_rx;
+	unsigned long long tag;
+	Byte  size;
+	Byte  rs_size;
+	Byte size_cnt;
+} TFX25;
+
+
+
+typedef struct TDetector_t
+{
+	struct TFX25_t fx25[4];
+	TStringList	mem_ARQ_F_buf[5];
+	TStringList mem_ARQ_buf[5];
+	float pll_loop[5];
+	float last_sample[5];
+	UCHAR ones[5];
+	UCHAR zeros[5];
+	float bit_buf[5][1024];
+	float bit_buf1[5][1024];
+	UCHAR sample_cnt[5];
+	UCHAR last_bit[5];
+	float PSK_IZ1[5];
+	float PSK_QZ1[5];
+	float PkAmpI[5];
+	float PkAmpQ[5];
+	float PkAmp[5];
+	float PkAmpMax[5];
+	int newpkpos[5];
+	float AverageAmp[5];
+	float AngleCorr[5];
+	float MinAmp[5];
+	float MaxAmp[5];
+	float MUX3_osc[5];
+	float MUX3_1_osc[5];
+	float MUX3_2_osc[5];
+	float Preemphasis6[5];
+	float Preemphasis12[5];
+	float PSK_AGC[5];
+	float AGC[5];
+	float AGC1[5];
+	float AGC2[5];
+	float AGC3[5];
+	float AGC_max[5];
+	float AGC_min[5];
+	float AFC_IZ1[5];
+	float AFC_IZ2[5];
+	float AFC_QZ1[5];
+	float AFC_QZ2[5];
+
+	UCHAR last_rx_bit[5];
+	UCHAR bit_stream[5];
+	UCHAR byte_rx[5];
+	UCHAR bit_stuff_cnt[5];
+	UCHAR bit_cnt[5];
+	float bit_osc[5];
+	UCHAR frame_status[5];
+	string rx_data[5];
+	string FEC_rx_data[5];
+	//
+	UCHAR FEC_pol[5];
+	unsigned short FEC_err[5];
+	unsigned long long FEC_header1[5][2];
+	unsigned short FEC_blk_int[5];
+	unsigned short FEC_len_int[5];
+	unsigned short FEC_len[5];
+
+	unsigned short FEC_len_cnt[5];
+	
+	UCHAR rx_intv_tbl[5][4];
+	UCHAR rx_intv_sym[5];
+	UCHAR rx_viterbi[5];
+	UCHAR viterbi_cnt[5];
+	//	  SurvivorStates [1..4,0..511] of TSurvivor;
+		  //
+	TMChannel MChannel[5][4];
+
+	float AFC_dF_avg[5];
+	float AFC_dF[5];
+	float AFC_bit_osc[5];
+	float AFC_bit_buf[5][1024];
+	unsigned short AFC_cnt[5];
+
+	string raw_bits1[5];
+	string raw_bits[5];
+	UCHAR last_nrzi_bit[5];
+
+	float BPF_core[5][2048];
+	float LPF_core[5][2048];
+
+	float src_INTR_buf[5][8192];
+	float src_INTRI_buf[5][8192];
+	float src_INTRQ_buf[5][8192];
+	float src_LPF1I_buf[5][8192];
+	float src_LPF1Q_buf[5][8192];
+
+	float src_BPF_buf[5][2048];
+	float src_Loop_buf[5][8192];
+	float prev_BPF_buf[5][4096];
+
+	float prev_LPF1I_buf[5][4096];
+	float prev_LPF1Q_buf[5][4096];
+	float prev_INTR_buf[5][16384];
+	float prev_INTRI_buf[5][16384];
+	float prev_INTRQ_buf[5][16384];
+
+	Byte emph_decoded;	
+	Byte rx_decoded;
+	Byte errors;
+
+} TDetector;
+
+
+
+typedef struct AGWUser_t
+{
+	void *socket;
+	string * data_in;
+	TStringList AGW_frame_buf;
+	boolean	Monitor;
+	boolean	Monitor_raw;
+	boolean reportFreqAndModem;			// Can report modem and frequency to host
+
+} AGWUser;
+
+typedef struct  TAX25Info_t
+{
+	longint	stat_s_pkt;
+	longint stat_s_byte;
+	longint stat_r_pkt;
+	longint stat_r_byte;
+	longint stat_r_fc;
+	longint stat_fec_count;
+	time_t stat_begin_ses;
+	time_t stat_end_ses;
+	longint stat_l_r_byte;
+	longint stat_l_s_byte;
+
+} TAX25Info;
+
+typedef struct TAX25Port_t
+{
+	Byte hi_vs;
+	Byte vs;
+	Byte vr;
+	Byte PID;
+	TStringList in_data_buf;
+	TStringList frm_collector;
+	string frm_win[8];
+	string out_data_buf;
+	word t1;
+	word t2;
+	word t3;
+	Byte i_lo;
+	Byte i_hi;
+	word n1;
+	word n2;
+	word IPOLL_cnt;
+	TStringList frame_buf; //����� ������ �� ��������
+	TStringList I_frame_buf;
+	Byte status;
+	word clk_frack;
+	char corrcall[10];
+	char mycall[10];
+	UCHAR digi[56];
+	UCHAR Path[80];				// Path in ax25 format - added to save building it each time
+	UCHAR ReversePath[80];
+	int snd_ch;					// Simplifies parameter passing
+	int port;
+	int pathLen;
+	void * socket;
+	char kind[16];
+	TAX25Info info;
+} TAX25Port;
+
+
+#define LOGEMERGENCY 0 
+#define LOGALERT 1
+#define LOGCRIT 2 
+#define LOGERROR 3 
+#define LOGWARNING 4
+#define LOGNOTICE 5
+#define LOGINFO 6
+#define LOGDEBUG 7
+
+#define PTTRTS		1
+#define PTTDTR		2
+#define PTTCAT		4
+#define PTTCM108	8
+#define PTTHAMLIB	16
+
+// Status flags
+
+#define STAT_NO_LINK  0
+#define STAT_LINK 1
+#define STAT_CHK_LINK 2
+#define STAT_WAIT_ANS 3
+#define STAT_TRY_LINK 4
+#define STAT_TRY_UNLINK 5
+
+
+	// �md,Resp,Poll,Final,Digipeater flags
+#define 	SET_P 1
+#define 	SET_F 0
+#define 	SET_C 1
+#define 	SET_R 0
+#define 	SET_NO_RPT 0
+#define 	SET_RPT 1
+	// Frame ID flags
+#define 	I_FRM 0
+#define 	S_FRM 1
+#define 	U_FRM 2
+#define 	I_I 0
+#define 	S_RR 1
+#define 	S_RNR 5
+#define 	S_REJ 9
+#define		S_SREJ 0x0D
+#define 	U_SABM 47
+#define 	U_DISC 67
+#define 	U_DM 15
+#define 	U_UA 99
+#define 	U_FRMR 135
+#define 	U_UI 3
+	// PID flags
+#define 	PID_X25 0x01       // 00000001-CCIT X25 PLP
+#define 	PID_SEGMENT 0x08   // 00001000-Segmentation fragment
+#define 	PID_TEXNET 0xC3    // 11000011-TEXNET Datagram Protocol
+#define 	PID_LQ 0xC4        // 11001000-Link Quality Protocol
+#define 	PID_APPLETALK 0xCA // 11001010-Appletalk
+#define 	PID_APPLEARP 0xCB  // 11001011-Appletalk ARP
+#define 	PID_IP 0xCC        // 11001100-ARPA Internet Protocol
+#define 	PID_ARP 0xCD       // 11001101-ARPA Address Resolution Protocol
+#define 	PID_NET_ROM 0xCF   // 11001111-NET/ROM
+
+
+//	Sound interface buffer sizes
+
+extern int ReceiveSize;
+extern int SendSize;
+
+#define NumberofinBuffers 4
+
+#define Now getTicks()
+
+// #defines from all modules (?? is this a good idaa ??
+
+#define WIN_MAXIMIZED 0
+#define WIN_MINIMIZED 1
+#define MODEM_CAPTION 'SoundModem by UZ7HO'
+#define MODEM_VERSION '1.06'
+#define SND_IDLE 0
+#define SND_TX 1
+#define BUF_EMPTY 0
+#define BUF_FULL 1
+#define DISP_MONO FALSE
+#define DISP_RGB TRUE
+#define MOD_IDLE 0
+#define MOD_RX 1
+#define MOD_TX 2
+#define MOD_WAIT 3
+#define TIMER_FREE 0
+#define TIMER_BUSY 1
+#define TIMER_OFF 2
+#define TIMER_EVENT_ON 3
+#define TIMER_EVENT_OFF 4
+#define DEBUG_TIMER 1
+#define DEBUG_WATERFALL 2
+#define DEBUG_DECODE 4
+#define DEBUG_SOUND 8
+#define IS_LAST TRUE
+#define IS_NOT_LAST FALSE
+#define modes_count 20
+#define SPEED_300 0
+#define SPEED_1200 1
+#define SPEED_600 2
+#define SPEED_2400 3
+#define SPEED_P1200 4
+#define SPEED_P600 5
+#define SPEED_P300 6
+#define SPEED_P2400 7
+#define SPEED_Q4800 8
+#define SPEED_Q3600 9
+#define SPEED_Q2400 10
+#define SPEED_MP400 11
+#define SPEED_DW2400 12
+#define SPEED_8P4800 13
+#define SPEED_2400V26B 14
+#define SPEED_ARDOP 15
+#define SPEED_Q300 16
+#define SPEED_8PSK300 17
+#define SPEED_RUH48 18
+#define SPEED_RUH96 19
+
+#define MODE_FSK 0
+#define MODE_BPSK 1
+#define MODE_QPSK 2
+#define MODE_MPSK 3
+#define MODE_8PSK 4
+#define MODE_PI4QPSK 5
+#define MODE_ARDOP 6
+#define MODE_RUH 7
+
+#define QPSK_SM 0
+#define QPSK_V26 1
+
+#define MODEM_8P4800_BPF 3200
+#define MODEM_8P4800_TXBPF 3400
+#define MODEM_8P4800_LPF 1000
+#define MODEM_8P4800_BPF_TAP 64
+#define MODEM_8P4800_LPF_TAP 8
+ //
+#define MODEM_MP400_BPF 775
+#define MODEM_MP400_TXBPF 850
+#define MODEM_MP400_LPF 70
+#define MODEM_MP400_BPF_TAP 256
+#define MODEM_MP400_LPF_TAP 128
+ //
+#define MODEM_DW2400_BPF 2400
+#define MODEM_DW2400_TXBPF 2500
+#define MODEM_DW2400_LPF 900
+#define MODEM_DW2400_BPF_TAP 256 //256
+#define MODEM_DW2400_LPF_TAP 32  //128
+ //
+#define MODEM_Q2400_BPF 2400
+#define MODEM_Q2400_TXBPF 2500
+#define MODEM_Q2400_LPF 900
+#define MODEM_Q2400_BPF_TAP 256 //256
+#define MODEM_Q2400_LPF_TAP 128  //128
+ //
+#define MODEM_Q3600_BPF 3600
+#define MODEM_Q3600_TXBPF 3750
+#define MODEM_Q3600_LPF 1350
+#define MODEM_Q3600_BPF_TAP 256
+#define MODEM_Q3600_LPF_TAP 128
+ //
+#define MODEM_Q4800_BPF 4800
+#define MODEM_Q4800_TXBPF 5000
+#define MODEM_Q4800_LPF 1800
+#define MODEM_Q4800_BPF_TAP 256
+#define MODEM_Q4800_LPF_TAP 128
+ //
+#define MODEM_P2400_BPF 4800
+#define MODEM_P2400_TXBPF 5000
+#define MODEM_P2400_LPF 1800
+#define MODEM_P2400_BPF_TAP 256
+#define MODEM_P2400_LPF_TAP 128
+ //
+#define MODEM_P1200_BPF 2400
+#define MODEM_P1200_TXBPF 2500
+#define MODEM_P1200_LPF 900
+#define MODEM_P1200_BPF_TAP 256
+#define MODEM_P1200_LPF_TAP 128
+ //
+#define MODEM_P600_BPF 1200
+#define MODEM_P600_TXBPF 1250
+#define MODEM_P600_LPF 400
+#define MODEM_P600_BPF_TAP 256
+#define MODEM_P600_LPF_TAP 128
+ //
+#define MODEM_P300_BPF 600
+#define MODEM_P300_TXBPF 625
+#define MODEM_P300_LPF 200
+#define MODEM_P300_BPF_TAP 256
+#define MODEM_P300_LPF_TAP 128
+ //
+#define MODEM_300_BPF 500
+#define MODEM_300_TXBPF 500
+#define MODEM_300_LPF 155
+#define MODEM_300_BPF_TAP 256
+#define MODEM_300_LPF_TAP 128
+ //
+#define MODEM_600_BPF 800
+#define MODEM_600_TXBPF 900
+#define MODEM_600_LPF 325
+#define MODEM_600_BPF_TAP 256
+#define MODEM_600_LPF_TAP 128
+ //
+#define MODEM_1200_BPF 1400
+#define MODEM_1200_TXBPF 1600
+#define MODEM_1200_LPF 650
+#define MODEM_1200_BPF_TAP 256
+#define MODEM_1200_LPF_TAP 128
+ //
+#define MODEM_2400_BPF 3200
+#define MODEM_2400_TXBPF 3200
+#define MODEM_2400_LPF 1400
+#define MODEM_2400_BPF_TAP 256
+#define MODEM_2400_LPF_TAP 128
+
+#define TX_SILENCE 0
+#define TX_DELAY 1
+#define TX_TAIL 2
+#define TX_NO_DATA 3
+#define TX_FRAME 4
+#define TX_WAIT_BPF 5
+
+
+#define FRAME_WAIT 0
+#define FRAME_LOAD 1
+#define RX_BIT0 0
+#define RX_BIT1 128
+#define DCD_WAIT_SLOT 0
+#define DCD_WAIT_PERSIST 1
+
+#define FX25_MODE_NONE  0
+#define FX25_MODE_RX  1
+#define FX25_MODE_TXRX 2
+#define FX25_TAG 0
+#define FX25_LOAD 1
+
+#define IL2P_MODE_NONE  0
+#define IL2P_MODE_RX  1				// RX il2p + HDLC
+#define IL2P_MODE_TXRX 2
+#define IL2P_MODE_ONLY 3			// RX only il2p, TX il2p
+
+
+#define    MODE_OUR 0
+#define    MODE_OTHER 1
+#define    MODE_RETRY 2
+
+#define FRAME_FLAG 126		// 7e
+
+#define port_num 32		// ?? Max AGW sessions
+#define PKT_ERR 17		// Minimum packet size, bytes
+#define I_MAX 7			// Maximum number of packets
+
+
+	// externs for all modules
+
+#define ARDOPBufferSize 12000 * 100
+
+extern short ARDOPTXBuffer[4][ARDOPBufferSize];	// Enough to hold whole frame of samples
+
+extern int ARDOPTXLen[4];				// Length of frame
+extern int ARDOPTXPtr[4];				// Tx Pointer
+
+extern BOOL KISSServ;
+extern int KISSPort;
+
+extern BOOL AGWServ;
+extern int AGWPort;
+
+extern TStringList KISS_acked[];
+extern TStringList KISS_iacked[];
+
+extern TStringList all_frame_buf[5];
+
+extern unsigned short pkt_raw_min_len;
+extern int stat_r_mem;
+
+extern UCHAR diddles;
+
+extern int stdtones;
+extern int fullduplex;
+
+extern struct TQPSK_t qpsk_set[4];
+
+extern int NonAX25[5];
+
+extern short txtail[5];
+extern short txdelay[5];
+
+extern short modem_def[5];
+
+extern int emph_db[5];
+extern UCHAR emph_all[5];
+
+extern UCHAR modem_mode[5];
+
+extern UCHAR RCVR[5];
+extern int soundChannel[5];
+extern int modemtoSoundLR[4];
+
+extern short rx_freq[5];
+extern short active_rx_freq[5];
+extern short rx_shift[5];
+extern short rx_baudrate[5];
+extern short rcvr_offset[5];
+
+extern int tx_hitoneraisedb[5];
+extern float tx_hitoneraise[5];
+
+
+extern UCHAR tx_status[5];
+extern float tx_freq[5];
+extern float tx_shift[5];
+extern unsigned short tx_baudrate[5];
+extern unsigned short tx_bitrate[5];
+
+extern unsigned short bpf[5];
+extern unsigned short lpf[5];
+
+extern unsigned short txbpf[5];
+
+extern unsigned short  tx_BPF_tap[5];
+extern unsigned short  tx_BPF_timer[5];
+
+extern unsigned short  BPF_tap[5];
+extern unsigned short  LPF_tap[5];
+
+extern float tx_BPF_core[5][32768];
+extern float LPF_core[5][2048];
+
+extern UCHAR xData[256];
+extern UCHAR xEncoded[256];
+extern UCHAR xDecoded[256];
+
+extern float PI125;
+extern float PI375;
+extern float PI625;
+extern float PI875;
+extern 	float PI5;
+extern float PI25;
+extern float PI75;
+
+extern int max_frame_collector[4];
+extern boolean KISS_opt[4];
+
+#define MaxErrors 4
+
+extern BOOL MinOnStart;
+
+//RS TReedSolomon;
+//  Form1 TForm1;
+//  WaveFormat TWaveFormatEx;
+
+extern int UDPServ;
+extern long long udpServerSeqno;
+
+extern int Channels;
+extern int BitsPerSample;
+extern float TX_Samplerate;
+extern float RX_Samplerate;
+extern int RX_SR;
+extern int TX_SR;
+extern int RX_PPM;
+extern int TX_PPM;
+extern int tx_bufsize;
+extern int rx_bufsize;
+extern int tx_bufcount;
+extern int rx_bufcount;
+extern int fft_size;
+extern int  mouse_down[2];
+//UCHAR * RX_pBuf array[257];
+//  RX_header array[1..256] of TWaveHdr;
+//  TX_pBuf array[1..4,1..256] of pointer;
+//TX_header array[1..4,1..256] of TWaveHdr;
+extern UCHAR calib_mode[5];
+extern UCHAR snd_status[5];
+extern UCHAR buf_status[5];
+extern UCHAR tx_buf_num1[5];
+extern UCHAR tx_buf_num[5];
+extern int speed[5];
+extern int panels[6];
+
+extern int FFTSize;
+#define fft_size FFTSize
+
+extern float fft_window_arr[2048];
+//  fft_s,fft_d array[0..2047] of TComplex;
+extern short fft_buf[2][8192];
+extern UCHAR fft_disp[2][1024];
+//  bm array[1..4] of TBitMap;
+//  bm1,bm2,bm3 TBitMap;
+
+//  WaveInHandle hWaveIn;
+//  WaveOutHandle array[1..4] of hWaveOut;
+extern int RXBufferLength;
+
+// data1 PData16;
+
+extern int grid_time;
+extern int fft_mult;
+extern int fft_spd;
+extern int grid_timer;
+extern int stop_wf;
+extern int raduga;
+extern char snd_rx_device_name[32];
+extern char snd_tx_device_name[32];
+extern int snd_rx_device;
+extern int snd_tx_device;
+extern UCHAR mod_icon_status;
+extern UCHAR last_mod_icon_status;
+extern UCHAR icon_timer;
+//  TelIni TIniFile;
+extern char cur_dir[];
+//  TimerId1 cardinal;
+//  TimerId2 cardinal;
+extern UCHAR TimerStat1;
+extern UCHAR TimerStat2;
+extern int stat_log;
+
+extern char PTTPort[80];			// Port for Hardware PTT - may be same as control port.
+extern int PTTMode;
+extern int PTTBAUD ;
+
+extern char PTTOnString[128];
+extern char PTTOffString[128];
+
+extern UCHAR PTTOnCmd[64];
+extern UCHAR PTTOnCmdLen;
+
+extern UCHAR PTTOffCmd[64];
+extern UCHAR PTTOffCmdLen;
+
+extern int PTT_device;
+extern int RX_device;
+extern int TX_device;
+extern int TX_rotate;
+extern int UsingLeft;
+extern int UsingRight;
+extern int UsingBothChannels;
+extern int pttGPIOPin;
+extern int pttGPIOPinR;
+extern BOOL pttGPIOInvert;
+extern BOOL useGPIO;
+extern BOOL gotGPIO;
+extern int VID;
+extern int PID;
+extern char CM108Addr[80];
+extern int HamLibPort;
+extern char HamLibHost[];
+
+extern int SCO;
+extern int DualPTT;
+extern UCHAR  DebugMode;
+extern UCHAR TimerEvent;
+extern int nr_monitor_lines;
+extern int UTC_Tim;
+extern int MainPriority;
+//  MainThreadHandle THandle;
+extern UCHAR w_state;
+
+extern BOOL Firstwaterfall;
+extern BOOL Secondwaterfall;
+
+extern int dcd_threshold;
+extern int rxOffset;
+extern int chanOffset[4];
+extern int Continuation[4];	// Sending 2nd or more packet of burst
+
+extern boolean busy;
+extern boolean dcd[5];
+
+extern struct TKISSMode_t  KISS;
+
+extern boolean dyn_frack[4] ;
+extern Byte recovery[4];
+extern Byte users[4];
+
+extern int resptime[4];
+extern int slottime[4];
+extern int persist[4];
+extern int fracks[4];
+extern int frack_time[4];
+extern int idletime[4];
+extern int redtime[4];
+extern int IPOLL[4];
+extern int maxframe[4];
+extern int TXFrmMode[4];
+
+extern char MyDigiCall[4][512];
+extern char exclude_callsigns[4][512];
+extern char exclude_APRS_frm[4][512];
+
+extern TStringList  list_exclude_callsigns[4];
+extern TStringList list_exclude_APRS_frm[4];
+extern TStringList list_digi_callsigns[4];
+
+
+extern int SoundIsPlaying;
+extern int Capturing;
+
+extern struct TDetector_t  DET[nr_emph + 1][16];
+
+extern char CaptureDevice[80];
+extern char PlaybackDevice[80];
+
+extern TAX25Port AX25Port[4][port_num];
+
+extern int fx25_mode[4];
+extern int il2p_mode[4];
+
+extern int tx_fx25_size[4];
+extern int tx_fx25_size_cnt[4];
+extern int tx_fx25_mode[4];
+
+extern int SatelliteMode;
+
+extern int using48000;			// Set if using 48K sample rate (ie RUH Modem active)
+
+
+// Function prototypes
+
+void KISS_send_ack(UCHAR port, string * data);
+void AGW_AX25_frame_analiz(int snd_ch, int RX, string * frame);
+void FIR_filter(float * src, unsigned short buf_size, unsigned short tap, float * core, float * dest, float * prev);
+void make_core_TXBPF(UCHAR snd_ch, float freq, float width);
+void OpenPTTPort();
+void ClosePTTPort();
+
+void RadioPTT(int snd_ch, BOOL PTTState);
+void put_frame(int snd_ch, string * frame, char * code, int  tx_stat, int excluded);
+void CloseCOMPort(int fd);
+void COMClearRTS(int fd);
+void COMClearDTR(int fd);
+unsigned int getTicks();
+char * ShortDateTime();
+void  write_ax25_info(TAX25Port * AX25Sess);
+void reverse_addr(Byte * path, Byte * revpath, int Len);
+string * get_mycall(string * path);
+TAX25Port * get_user_port_by_calls(int snd_ch, char *  CallFrom, char *  CallTo);
+TAX25Port * get_free_port(int snd_ch);
+void * in_list_incoming_mycall(Byte * path);
+boolean add_incoming_mycalls(void * socket, char * src_call);
+int get_addr(char * Calls, UCHAR * AXCalls);
+void reverse_addr(Byte * path, Byte * revpath, int Len);
+void set_link(TAX25Port * AX25Sess, UCHAR * axpath);
+void rst_timer(TAX25Port * AX25Sess);
+void set_unlink(TAX25Port * AX25Sess, Byte * path);
+unsigned short get_fcs(UCHAR * Data, unsigned short len);
+void KISSSendtoServer(void * sock, Byte * Msg, int Len);
+int ConvFromAX25(unsigned char * incall, char * outcall);
+BOOL ConvToAX25(char * callsign, unsigned char * ax25call);
+void Debugprintf(const char * format, ...);
+
+double pila(double x);
+
+void AGW_Raw_monitor(int snd_ch, string * data);
+
+// Delphi emulation functions
+
+string * Strings(TStringList * Q, int Index);
+void Clear(TStringList * Q);
+int Count(TStringList * List);
+
+string * newString();
+string * copy(string * Source, int StartChar, int Count);
+TStringList * newTStringList();
+
+void freeString(string * Msg);
+
+void initString(string * S);
+void initTStringList(TStringList* T);
+
+// Two delete() This is confusing!!
+// Not really - one acts on String, other TStringList
+
+void Delete(TStringList * Q, int Index);
+void mydelete(string * Source, int StartChar, int Count);
+
+void move(UCHAR * SourcePointer, UCHAR * DestinationPointer, int CopyCount);
+void fmove(float * SourcePointer, float * DestinationPointer, int CopyCount);
+
+void setlength(string * Msg, int Count);		// Set string length
+
+string * stringAdd(string * Msg, UCHAR * Chars, int Count);		// Extend string 
+
+void Assign(TStringList * to, TStringList * from);	// Duplicate from to to
+
+string * duplicateString(string * in);
+
+// This looks for a string in a stringlist. Returns inhex if found, otherwise -1
+
+int  my_indexof(TStringList * l, string * s);
+
+boolean compareStrings(string * a, string * b);
+
+int Add(TStringList * Q, string * Entry);
+
+
+#define IL2P_SYNC_WORD_SIZE 3
+#define IL2P_HEADER_SIZE 13	// Does not include 2 parity.
+#define IL2P_HEADER_PARITY 2
+
+#define IL2P_MAX_PAYLOAD_SIZE 1023
+#define IL2P_MAX_PAYLOAD_BLOCKS 5
+#define IL2P_MAX_PARITY_SYMBOLS 16		// For payload only.
+#define IL2P_MAX_ENCODED_PAYLOAD_SIZE (IL2P_MAX_PAYLOAD_SIZE + IL2P_MAX_PAYLOAD_BLOCKS * IL2P_MAX_PARITY_SYMBOLS)
+
+struct il2p_context_s {
+
+	enum { IL2P_SEARCHING = 0, IL2P_HEADER, IL2P_PAYLOAD, IL2P_DECODE } state;
+
+	unsigned int acc;	// Accumulate most recent 24 bits for sync word matching.
+				// Lower 8 bits are also used for accumulating bytes for
+				// the header and payload.
+
+	int bc;			// Bit counter so we know when a complete byte has been accumulated.
+
+	int polarity;		// 1 if opposite of expected polarity.
+
+	unsigned char shdr[IL2P_HEADER_SIZE + IL2P_HEADER_PARITY];
+	// Scrambled header as received over the radio.  Includes parity.
+	int hc;			// Number if bytes placed in above.
+
+	unsigned char uhdr[IL2P_HEADER_SIZE];  // Header after FEC and unscrambling.
+
+	int eplen;		// Encoded payload length.  This is not the nuumber from
+				// from the header but rather the number of encoded bytes to gather.
+
+	unsigned char spayload[IL2P_MAX_ENCODED_PAYLOAD_SIZE];
+	// Scrambled and encoded payload as received over the radio.
+	int pc;			// Number of bytes placed in above.
+
+	int corrected;		// Number of symbols corrected by RS FEC.
+};
+
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/UZ7HOStuff.h b/UZ7HOStuff.h
index 5105c78..586edce 100644
--- a/UZ7HOStuff.h
+++ b/UZ7HOStuff.h
@@ -1,9 +1,11 @@
+#pragma once
+
 //
 //	 My port of UZ7HO's Soundmodem
 //
 
-#define VersionString "0.0.0.65"
-#define VersionBytes {0, 0, 0, 65}
+#define VersionString "0.0.0.67-2"
+#define VersionBytes {0, 0, 0, 67}
 
 // Added FX25. 4x100 FEC and V27 not Working and disabled
 
@@ -143,6 +145,21 @@
 
 // 0.65	Allow Set Modem command to use modem index as well as modem name
 
+// 0.66 Allow configuration of waterfall span	June 23
+//		Add Exclude
+
+// .67 Add extra modes 8PSK 900 RUH 4800 RUH 9600 QPSK 600 QPSK 2400	August 23
+//	   Fix loading txtail
+//	   Fix digipeating
+//	   Add MaxFrame to Modem Dialog
+//	   Fix 64 bit compatibility in ackmode
+//	   Add option to change CWID tones
+//	   Fix minimum centre freq validation
+
+// .68 Monitor XID and TEST
+//	   Flag active interface in title bar
+//	   Improve header validation in il2p
+
 
 
 #include <string.h>
@@ -215,6 +232,9 @@ typedef unsigned long ULONG;
 #define decodedSingle 1 //'$'
 
 
+// Think about implications of changing this !!
+extern int FFTSize;
+
 // Seems to use Delphi TStringList for a lot of queues. This seems to be a list of pointers and a count
 // Each pointer is to a Data/Length pair
 //Maybe something like
@@ -394,7 +414,6 @@ typedef struct TDetector_t
 	Byte rx_decoded;
 	Byte errors;
 
-
 } TDetector;
 
 
@@ -508,6 +527,10 @@ typedef struct TAX25Port_t
 #define 	U_UA 99
 #define 	U_FRMR 135
 #define 	U_UI 3
+
+#define		U_XID 0xAF
+#define		U_TEST 0xE3
+
 	// PID flags
 #define 	PID_X25 0x01       // 00000001-CCIT X25 PLP
 #define 	PID_SEGMENT 0x08   // 00001000-Segmentation fragment
@@ -520,10 +543,11 @@ typedef struct TAX25Port_t
 #define 	PID_NET_ROM 0xCF   // 11001111-NET/ROM
 
 
-//	Sound interface buffer size
+//	Sound interface buffer sizes
+
+extern int ReceiveSize;
+extern int SendSize;
 
-#define SendSize 1024		// 100 mS for now
-#define ReceiveSize 512	// try 100 mS for now
 #define NumberofinBuffers 4
 
 #define Now getTicks()
@@ -535,8 +559,7 @@ typedef struct TAX25Port_t
 #define MODEM_CAPTION 'SoundModem by UZ7HO'
 #define MODEM_VERSION '1.06'
 #define SND_IDLE 0
-#define SND_RX 1
-#define SND_TX 2
+#define SND_TX 1
 #define BUF_EMPTY 0
 #define BUF_FULL 1
 #define DISP_MONO FALSE
@@ -556,7 +579,7 @@ typedef struct TAX25Port_t
 #define DEBUG_SOUND 8
 #define IS_LAST TRUE
 #define IS_NOT_LAST FALSE
-#define modes_count 16
+#define modes_count 20
 #define SPEED_300 0
 #define SPEED_1200 1
 #define SPEED_600 2
@@ -571,8 +594,12 @@ typedef struct TAX25Port_t
 #define SPEED_MP400 11
 #define SPEED_DW2400 12
 #define SPEED_8P4800 13
-#define SPEED_AE2400 14
+#define SPEED_2400V26B 14
 #define SPEED_ARDOP 15
+#define SPEED_Q300 16
+#define SPEED_8PSK300 17
+#define SPEED_RUH48 18
+#define SPEED_RUH96 19
 
 #define MODE_FSK 0
 #define MODE_BPSK 1
@@ -581,11 +608,11 @@ typedef struct TAX25Port_t
 #define MODE_8PSK 4
 #define MODE_PI4QPSK 5
 #define MODE_ARDOP 6
+#define MODE_RUH 7
 
 #define QPSK_SM 0
 #define QPSK_V26 1
 
-
 #define MODEM_8P4800_BPF 3200
 #define MODEM_8P4800_TXBPF 3400
 #define MODEM_8P4800_LPF 1000
@@ -712,7 +739,7 @@ typedef struct TAX25Port_t
 
 #define ARDOPBufferSize 12000 * 100
 
-extern short ARDOPTXBuffer[4][12000 * 100];	// Enough to hold whole frame of samples
+extern short ARDOPTXBuffer[4][ARDOPBufferSize];	// Enough to hold whole frame of samples
 
 extern int ARDOPTXLen[4];				// Length of frame
 extern int ARDOPTXPtr[4];				// Tx Pointer
@@ -755,6 +782,7 @@ extern int soundChannel[5];
 extern int modemtoSoundLR[4];
 
 extern short rx_freq[5];
+extern short active_rx_freq[5];
 extern short rx_shift[5];
 extern short rx_baudrate[5];
 extern short rcvr_offset[5];
@@ -767,6 +795,7 @@ extern UCHAR tx_status[5];
 extern float tx_freq[5];
 extern float tx_shift[5];
 extern unsigned short tx_baudrate[5];
+extern unsigned short tx_bitrate[5];
 
 extern unsigned short bpf[5];
 extern unsigned short lpf[5];
@@ -834,10 +863,13 @@ extern UCHAR tx_buf_num[5];
 extern int speed[5];
 extern int panels[6];
 
+extern int FFTSize;
+#define fft_size FFTSize
+
 extern float fft_window_arr[2048];
 //  fft_s,fft_d array[0..2047] of TComplex;
-extern short fft_buf[5][2048];
-extern UCHAR fft_disp[5][2048];
+extern short fft_buf[2][8192];
+extern UCHAR fft_disp[2][1024];
 //  bm array[1..4] of TBitMap;
 //  bm1,bm2,bm3 TBitMap;
 
@@ -915,6 +947,7 @@ extern BOOL Secondwaterfall;
 extern int dcd_threshold;
 extern int rxOffset;
 extern int chanOffset[4];
+extern int Continuation[4];	// Sending 2nd or more packet of burst
 
 extern boolean busy;
 extern boolean dcd[5];
@@ -964,6 +997,10 @@ extern int tx_fx25_mode[4];
 
 extern int SatelliteMode;
 
+extern int using48000;			// Set if using 48K sample rate (ie RUH Modem active)
+
+extern int txmin, txmax;
+
 // Function prototypes
 
 void KISS_send_ack(UCHAR port, string * data);
@@ -1002,7 +1039,7 @@ double pila(double x);
 
 void AGW_Raw_monitor(int snd_ch, string * data);
 
-// Dephi emulation functions
+// Delphi emulation functions
 
 string * Strings(TStringList * Q, int Index);
 void Clear(TStringList * Q);
@@ -1041,6 +1078,46 @@ int  my_indexof(TStringList * l, string * s);
 boolean compareStrings(string * a, string * b);
 
 int Add(TStringList * Q, string * Entry);
+
+
+#define IL2P_SYNC_WORD_SIZE 3
+#define IL2P_HEADER_SIZE 13	// Does not include 2 parity.
+#define IL2P_HEADER_PARITY 2
+
+#define IL2P_MAX_PAYLOAD_SIZE 1023
+#define IL2P_MAX_PAYLOAD_BLOCKS 5
+#define IL2P_MAX_PARITY_SYMBOLS 16		// For payload only.
+#define IL2P_MAX_ENCODED_PAYLOAD_SIZE (IL2P_MAX_PAYLOAD_SIZE + IL2P_MAX_PAYLOAD_BLOCKS * IL2P_MAX_PARITY_SYMBOLS)
+
+struct il2p_context_s {
+
+	enum { IL2P_SEARCHING = 0, IL2P_HEADER, IL2P_PAYLOAD, IL2P_DECODE } state;
+
+	unsigned int acc;	// Accumulate most recent 24 bits for sync word matching.
+				// Lower 8 bits are also used for accumulating bytes for
+				// the header and payload.
+
+	int bc;			// Bit counter so we know when a complete byte has been accumulated.
+
+	int polarity;		// 1 if opposite of expected polarity.
+
+	unsigned char shdr[IL2P_HEADER_SIZE + IL2P_HEADER_PARITY];
+	// Scrambled header as received over the radio.  Includes parity.
+	int hc;			// Number if bytes placed in above.
+
+	unsigned char uhdr[IL2P_HEADER_SIZE];  // Header after FEC and unscrambling.
+
+	int eplen;		// Encoded payload length.  This is not the nuumber from
+				// from the header but rather the number of encoded bytes to gather.
+
+	unsigned char spayload[IL2P_MAX_ENCODED_PAYLOAD_SIZE];
+	// Scrambled and encoded payload as received over the radio.
+	int pc;			// Number of bytes placed in above.
+
+	int corrected;		// Number of symbols corrected by RS FEC.
+};
+
+
 #ifdef __cplusplus
 }
 #endif
\ No newline at end of file
diff --git a/UZ7HOStuff.h.bak b/UZ7HOStuff.h.bak
new file mode 100644
index 0000000..b7e5ec8
--- /dev/null
+++ b/UZ7HOStuff.h.bak
@@ -0,0 +1,1049 @@
+//
+//	 My port of UZ7HO's Soundmodem
+//
+
+#define VersionString "0.0.0.65"
+#define VersionBytes {0, 0, 0, 65}
+
+// Added FX25. 4x100 FEC and V27 not Working and disabled
+
+// 0.8 V27 now OK.
+
+// 0.9 Digipeating added
+
+// 0.10 Fix second channel tones and calibrate
+
+// 0.11 Fix allocation of sessions to correct modem
+//		Fix DCD
+//		Fix Monitoring of Multiline packets
+//		Fix possible saving of wrong center freq
+//		Limit TX sample Q in Linux
+//
+
+// 0.12	Add AGWPE monitoring of received frames
+//		Fix DCD Threshold
+//		Fix KISS transparency issue
+
+// 0.13 Fix sending last few bits in FX.25 Mode
+
+// 0.14 Add "Copy on Select" to Trace Window
+
+// 0.15 Limit Trace window to 10000 lines
+
+// 0.16 Fix overwriting monitor window after scrollback
+
+// 0.17	Add GPIO and CAT PTT
+
+// 0.18	Add CM108/119 PTT
+
+// 0.19 Fix scheduling KISS frames
+
+// 0.20 Debug code added to RR processing
+
+// 0.21	Fix AGW monitor of multiple line packets
+//		Close ax.25 sessions if AGW Host session closes
+
+// 0.22	Add FEC Count to Session Stats
+
+// 0.23 Retry DISC until UA received or retry count exceeded
+
+// 0.24	More fixes to DISC handling
+
+// 0.26 Add OSS PulseAudio and HAMLIB support
+
+// 0.27 Dynamically load PulseAudio modules
+
+// 0.28 Add ARDOPPacket Mode
+
+// 0.29 Fix saving settings and geometry on close
+// 0.30 Retructure code to build with Qt 5.3
+//      Fix crash in nogui mode if pulse requested but not available
+//		Try to fix memory leaks
+
+// 0.31 Add option to run modems in seprate threads
+
+// 0.32	Fix timing problem with AGW connect at startup
+//		Add Memory ARQ
+//		Add Single bit "Correction"
+//		Fix error in 31 when using multiple decoders
+
+// 0.33 Fix Single bit correction
+//		More memory leak fixes
+
+// 0.34 Add API to set Modem and Center Frequency
+//		Fix crash in delete_incoming_mycalls
+
+// 0.35 Return Version in AGW Extended g response
+
+// 0.36 Fix timing problem on startup
+
+// 0.37 Add scrollbars to Device and Modem dialogs
+
+// 0.38 Change default CM108 name to /dev/hidraw0 on Linux
+
+// 0.39	Dont try to display Message Boxes in nogui mode.
+//		Close Device and Modem dialogs on Accept or Reject
+//		Fix using HAMLIB in nogui mode
+
+// 0.40	Fix bug in frame optimize when using 6 char calls
+
+// 0.41	Fix "glitch" on waterfall markers when changing modem freqs 
+
+// 0.42	Add "Minimize to Tray" option
+
+// 0.43 Add Andy's on_SABM fix.
+//		Fix Crash if KISS Data sent to AGW port
+
+// 0.44 Add UDP bridge.
+
+// 0.45 Add two more modems.
+// 0.46 Fix two more modems.
+
+// 0.47 Fix suprious DM when host connection lost
+//		Add CWID
+
+// 0.48 Send FRMR for unrecognised frame types
+
+// 0.49 Add Andy's FEC Tag correlation coode
+
+// 0.50 Fix Waterfall display when only using right channel
+//		Allow 1200 baud fsk at other center freqs
+//		Add Port numbers to Window title and Try Icon tooltip
+//		Fix calculation of filters for multiple decoders
+//		Add RX Offset setting (for satellite operation
+
+// 0.51	Fix Multithreading with more that 2 modems
+
+// 0.52	Add Stdin as source on Linux
+
+// 0.53	Use Byte instead of byte as byte is defined in newer versions of gcc
+
+// 0.54 Fix for ALSA problem on new pi OS
+
+// 0.55 Fix for compiler error with newer compiler
+
+// 0.56	Fix errors in Config.cpp			June 22
+
+// 0.57	Add Restart Waterfall action		August 22
+
+// 0.58 Add RSID							Sept 2022
+
+// 0.59 Add config of Digi Calls			Dec 2022
+
+// 0.60 Allow ARDOP Packet on modems 2 to 4 March 2023
+
+// 0.61 Add il2p support					April 2023
+
+// 0.62										April 2023
+//	Add option to specify sound devices that aren't in list
+//	Add Save button to Modem dialog to save current tab without closing dialog
+//	Don't add plug: to Linux device addresses unless addr contains : (allows use of eg ARDOP)
+
+// 0.64 Fix sending ax.25 (broken in .61)
+
+// 0.65	Allow Set Modem command to use modem index as well as modem name
+
+
+
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UNUSED(x) (void)(x)
+
+#ifdef M_PI
+#undef M_PI
+#endif
+
+#define M_PI       3.1415926f
+
+#define pi M_PI
+
+#ifndef WIN32
+#define _strdup strdup
+#endif
+
+	//#define NULL ((void *)0)
+
+	//Delphi Types remember case insensitive
+
+#define single float
+#define boolean int
+#define Byte unsigned char		//                  0 to 255
+#define Word unsigned short	//                        0 to 65,535
+#define SmallInt short 		//                  -32,768 to 32,767
+#define LongWord unsigned int	//                        0 to 4,294,967,295
+ //  Int6 : Cardinal; //                        0 to 4,294,967,295
+#define LongInt int			//           -2,147,483,648 to 2,147,483,647
+#define Integer int  //           -2,147,483,648 to 2,147,483,647
+//#define Int64 long long		 // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
+
+//#define Byte unsigned char		//                  0 to 255
+#define word unsigned short	//                        0 to 65,535
+#define smallint short 		//                  -32,768 to 32,767
+#define longword unsigned int	//                        0 to 4,294,967,295
+ //  Int6 : Cardinal; //                        0 to 4,294,967,295
+#define longint int			//           -2,147,483,648 to 2,147,483,647
+#define integer int  //           -2,147,483,648 to 2,147,483,647
+
+typedef unsigned long ULONG;
+
+#define UCHAR unsigned char
+#define UINT unsigned int
+#define BOOL int
+#define TRUE 1
+#define FALSE 0
+
+// Soundcard Channels
+
+#define NONE 0
+#define LEFT 1
+#define RIGHT 2
+
+#define nr_emph 2
+
+#define decodedNormal 4 //'-'
+#define decodedFEC    3 //'F'
+#define decodedMEM	  2 //'#'
+#define decodedSingle 1 //'$'
+
+
+// Seems to use Delphi TStringList for a lot of queues. This seems to be a list of pointers and a count
+// Each pointer is to a Data/Length pair
+//Maybe something like
+
+typedef struct string_T
+{
+	unsigned char * Data;
+	int Length;
+	int AllocatedLength;				// A reasonable sized block is allocated at the start to speed up adding chars
+
+}string;
+
+typedef struct TStringList_T
+{
+	int Count;
+	string ** Items;
+
+} TStringList;
+
+// QPSK struct
+
+typedef struct TQPSK_t
+{
+	UCHAR tx[4];
+	int count[4];
+	UCHAR rx[4];
+	UCHAR mode;
+} TPQSK;
+
+
+typedef struct TKISSMode_t
+{
+	string * data_in;
+	void * Socket;				// Used as a key
+
+	// Not sure what rest are used for. Seems to be one per channel
+
+	TStringList buffer[4];			// Outgoing Frames
+
+} TKISSMode;
+
+typedef struct  TMChannel_t
+{
+
+	single prev_LPF1I_buf[4096];
+	single prev_LPF1Q_buf[4096];
+	single prev_dLPFI_buf[4096];
+	single prev_dLPFQ_buf[4096];
+	single prev_AFCI_buf[4096];
+	single prev_AFCQ_buf[4096];
+	single AngleCorr;
+	single MUX_osc;
+	single AFC_IZ1;
+	single AFC_IZ2;
+	single AFC_QZ1;
+	single AFC_QZ2;
+	single AFC_bit_buf1I[1024];
+	single AFC_bit_buf1Q[1024];
+	single AFC_bit_buf2[1024];
+	single AFC_IIZ1;
+	single AFC_QQZ1;
+
+} TMChannel;
+
+typedef struct TFX25_t
+{
+	string  data;
+	Byte  status;
+	Byte  bit_cnt;
+	Byte  byte_rx;
+	unsigned long long tag;
+	Byte  size;
+	Byte  rs_size;
+	Byte size_cnt;
+} TFX25;
+
+
+
+typedef struct TDetector_t
+{
+	struct TFX25_t fx25[4];
+	TStringList	mem_ARQ_F_buf[5];
+	TStringList mem_ARQ_buf[5];
+	float pll_loop[5];
+	float last_sample[5];
+	UCHAR ones[5];
+	UCHAR zeros[5];
+	float bit_buf[5][1024];
+	float bit_buf1[5][1024];
+	UCHAR sample_cnt[5];
+	UCHAR last_bit[5];
+	float PSK_IZ1[5];
+	float PSK_QZ1[5];
+	float PkAmpI[5];
+	float PkAmpQ[5];
+	float PkAmp[5];
+	float PkAmpMax[5];
+	int newpkpos[5];
+	float AverageAmp[5];
+	float AngleCorr[5];
+	float MinAmp[5];
+	float MaxAmp[5];
+	float MUX3_osc[5];
+	float MUX3_1_osc[5];
+	float MUX3_2_osc[5];
+	float Preemphasis6[5];
+	float Preemphasis12[5];
+	float PSK_AGC[5];
+	float AGC[5];
+	float AGC1[5];
+	float AGC2[5];
+	float AGC3[5];
+	float AGC_max[5];
+	float AGC_min[5];
+	float AFC_IZ1[5];
+	float AFC_IZ2[5];
+	float AFC_QZ1[5];
+	float AFC_QZ2[5];
+
+	UCHAR last_rx_bit[5];
+	UCHAR bit_stream[5];
+	UCHAR byte_rx[5];
+	UCHAR bit_stuff_cnt[5];
+	UCHAR bit_cnt[5];
+	float bit_osc[5];
+	UCHAR frame_status[5];
+	string rx_data[5];
+	string FEC_rx_data[5];
+	//
+	UCHAR FEC_pol[5];
+	unsigned short FEC_err[5];
+	unsigned long long FEC_header1[5][2];
+	unsigned short FEC_blk_int[5];
+	unsigned short FEC_len_int[5];
+	unsigned short FEC_len[5];
+
+	unsigned short FEC_len_cnt[5];
+	
+	UCHAR rx_intv_tbl[5][4];
+	UCHAR rx_intv_sym[5];
+	UCHAR rx_viterbi[5];
+	UCHAR viterbi_cnt[5];
+	//	  SurvivorStates [1..4,0..511] of TSurvivor;
+		  //
+	TMChannel MChannel[5][4];
+
+	float AFC_dF_avg[5];
+	float AFC_dF[5];
+	float AFC_bit_osc[5];
+	float AFC_bit_buf[5][1024];
+	unsigned short AFC_cnt[5];
+
+	string raw_bits1[5];
+	string raw_bits[5];
+	UCHAR last_nrzi_bit[5];
+
+	float BPF_core[5][2048];
+	float LPF_core[5][2048];
+
+	float src_INTR_buf[5][8192];
+	float src_INTRI_buf[5][8192];
+	float src_INTRQ_buf[5][8192];
+	float src_LPF1I_buf[5][8192];
+	float src_LPF1Q_buf[5][8192];
+
+	float src_BPF_buf[5][2048];
+	float src_Loop_buf[5][8192];
+	float prev_BPF_buf[5][4096];
+
+	float prev_LPF1I_buf[5][4096];
+	float prev_LPF1Q_buf[5][4096];
+	float prev_INTR_buf[5][16384];
+	float prev_INTRI_buf[5][16384];
+	float prev_INTRQ_buf[5][16384];
+
+	Byte emph_decoded;	
+	Byte rx_decoded;
+	Byte errors;
+
+
+} TDetector;
+
+
+
+typedef struct AGWUser_t
+{
+	void *socket;
+	string * data_in;
+	TStringList AGW_frame_buf;
+	boolean	Monitor;
+	boolean	Monitor_raw;
+	boolean reportFreqAndModem;			// Can report modem and frequency to host
+
+} AGWUser;
+
+typedef struct  TAX25Info_t
+{
+	longint	stat_s_pkt;
+	longint stat_s_byte;
+	longint stat_r_pkt;
+	longint stat_r_byte;
+	longint stat_r_fc;
+	longint stat_fec_count;
+	time_t stat_begin_ses;
+	time_t stat_end_ses;
+	longint stat_l_r_byte;
+	longint stat_l_s_byte;
+
+} TAX25Info;
+
+typedef struct TAX25Port_t
+{
+	Byte hi_vs;
+	Byte vs;
+	Byte vr;
+	Byte PID;
+	TStringList in_data_buf;
+	TStringList frm_collector;
+	string frm_win[8];
+	string out_data_buf;
+	word t1;
+	word t2;
+	word t3;
+	Byte i_lo;
+	Byte i_hi;
+	word n1;
+	word n2;
+	word IPOLL_cnt;
+	TStringList frame_buf; //����� ������ �� ��������
+	TStringList I_frame_buf;
+	Byte status;
+	word clk_frack;
+	char corrcall[10];
+	char mycall[10];
+	UCHAR digi[56];
+	UCHAR Path[80];				// Path in ax25 format - added to save building it each time
+	UCHAR ReversePath[80];
+	int snd_ch;					// Simplifies parameter passing
+	int port;
+	int pathLen;
+	void * socket;
+	char kind[16];
+	TAX25Info info;
+} TAX25Port;
+
+
+#define LOGEMERGENCY 0 
+#define LOGALERT 1
+#define LOGCRIT 2 
+#define LOGERROR 3 
+#define LOGWARNING 4
+#define LOGNOTICE 5
+#define LOGINFO 6
+#define LOGDEBUG 7
+
+#define PTTRTS		1
+#define PTTDTR		2
+#define PTTCAT		4
+#define PTTCM108	8
+#define PTTHAMLIB	16
+
+// Status flags
+
+#define STAT_NO_LINK  0
+#define STAT_LINK 1
+#define STAT_CHK_LINK 2
+#define STAT_WAIT_ANS 3
+#define STAT_TRY_LINK 4
+#define STAT_TRY_UNLINK 5
+
+
+	// �md,Resp,Poll,Final,Digipeater flags
+#define 	SET_P 1
+#define 	SET_F 0
+#define 	SET_C 1
+#define 	SET_R 0
+#define 	SET_NO_RPT 0
+#define 	SET_RPT 1
+	// Frame ID flags
+#define 	I_FRM 0
+#define 	S_FRM 1
+#define 	U_FRM 2
+#define 	I_I 0
+#define 	S_RR 1
+#define 	S_RNR 5
+#define 	S_REJ 9
+#define		S_SREJ 0x0D
+#define 	U_SABM 47
+#define 	U_DISC 67
+#define 	U_DM 15
+#define 	U_UA 99
+#define 	U_FRMR 135
+#define 	U_UI 3
+	// PID flags
+#define 	PID_X25 0x01       // 00000001-CCIT X25 PLP
+#define 	PID_SEGMENT 0x08   // 00001000-Segmentation fragment
+#define 	PID_TEXNET 0xC3    // 11000011-TEXNET Datagram Protocol
+#define 	PID_LQ 0xC4        // 11001000-Link Quality Protocol
+#define 	PID_APPLETALK 0xCA // 11001010-Appletalk
+#define 	PID_APPLEARP 0xCB  // 11001011-Appletalk ARP
+#define 	PID_IP 0xCC        // 11001100-ARPA Internet Protocol
+#define 	PID_ARP 0xCD       // 11001101-ARPA Address Resolution Protocol
+#define 	PID_NET_ROM 0xCF   // 11001111-NET/ROM
+
+
+//	Sound interface buffer size
+
+#define SendSize 1024		// 100 mS for now
+#define ReceiveSize 512	// try 100 mS for now
+#define NumberofinBuffers 4
+
+#define Now getTicks()
+
+// #defines from all modules (?? is this a good idaa ??
+
+#define WIN_MAXIMIZED 0
+#define WIN_MINIMIZED 1
+#define MODEM_CAPTION 'SoundModem by UZ7HO'
+#define MODEM_VERSION '1.06'
+#define SND_IDLE 0
+#define SND_RX 1
+#define SND_TX 2
+#define BUF_EMPTY 0
+#define BUF_FULL 1
+#define DISP_MONO FALSE
+#define DISP_RGB TRUE
+#define MOD_IDLE 0
+#define MOD_RX 1
+#define MOD_TX 2
+#define MOD_WAIT 3
+#define TIMER_FREE 0
+#define TIMER_BUSY 1
+#define TIMER_OFF 2
+#define TIMER_EVENT_ON 3
+#define TIMER_EVENT_OFF 4
+#define DEBUG_TIMER 1
+#define DEBUG_WATERFALL 2
+#define DEBUG_DECODE 4
+#define DEBUG_SOUND 8
+#define IS_LAST TRUE
+#define IS_NOT_LAST FALSE
+#define modes_count 16
+#define SPEED_300 0
+#define SPEED_1200 1
+#define SPEED_600 2
+#define SPEED_2400 3
+#define SPEED_P1200 4
+#define SPEED_P600 5
+#define SPEED_P300 6
+#define SPEED_P2400 7
+#define SPEED_Q4800 8
+#define SPEED_Q3600 9
+#define SPEED_Q2400 10
+#define SPEED_MP400 11
+#define SPEED_DW2400 12
+#define SPEED_8P4800 13
+#define SPEED_AE2400 14
+#define SPEED_ARDOP 15
+
+#define MODE_FSK 0
+#define MODE_BPSK 1
+#define MODE_QPSK 2
+#define MODE_MPSK 3
+#define MODE_8PSK 4
+#define MODE_PI4QPSK 5
+#define MODE_ARDOP 6
+
+#define QPSK_SM 0
+#define QPSK_V26 1
+
+
+#define MODEM_8P4800_BPF 3200
+#define MODEM_8P4800_TXBPF 3400
+#define MODEM_8P4800_LPF 1000
+#define MODEM_8P4800_BPF_TAP 64
+#define MODEM_8P4800_LPF_TAP 8
+ //
+#define MODEM_MP400_BPF 775
+#define MODEM_MP400_TXBPF 850
+#define MODEM_MP400_LPF 70
+#define MODEM_MP400_BPF_TAP 256
+#define MODEM_MP400_LPF_TAP 128
+ //
+#define MODEM_DW2400_BPF 2400
+#define MODEM_DW2400_TXBPF 2500
+#define MODEM_DW2400_LPF 900
+#define MODEM_DW2400_BPF_TAP 256 //256
+#define MODEM_DW2400_LPF_TAP 32  //128
+ //
+#define MODEM_Q2400_BPF 2400
+#define MODEM_Q2400_TXBPF 2500
+#define MODEM_Q2400_LPF 900
+#define MODEM_Q2400_BPF_TAP 256 //256
+#define MODEM_Q2400_LPF_TAP 128  //128
+ //
+#define MODEM_Q3600_BPF 3600
+#define MODEM_Q3600_TXBPF 3750
+#define MODEM_Q3600_LPF 1350
+#define MODEM_Q3600_BPF_TAP 256
+#define MODEM_Q3600_LPF_TAP 128
+ //
+#define MODEM_Q4800_BPF 4800
+#define MODEM_Q4800_TXBPF 5000
+#define MODEM_Q4800_LPF 1800
+#define MODEM_Q4800_BPF_TAP 256
+#define MODEM_Q4800_LPF_TAP 128
+ //
+#define MODEM_P2400_BPF 4800
+#define MODEM_P2400_TXBPF 5000
+#define MODEM_P2400_LPF 1800
+#define MODEM_P2400_BPF_TAP 256
+#define MODEM_P2400_LPF_TAP 128
+ //
+#define MODEM_P1200_BPF 2400
+#define MODEM_P1200_TXBPF 2500
+#define MODEM_P1200_LPF 900
+#define MODEM_P1200_BPF_TAP 256
+#define MODEM_P1200_LPF_TAP 128
+ //
+#define MODEM_P600_BPF 1200
+#define MODEM_P600_TXBPF 1250
+#define MODEM_P600_LPF 400
+#define MODEM_P600_BPF_TAP 256
+#define MODEM_P600_LPF_TAP 128
+ //
+#define MODEM_P300_BPF 600
+#define MODEM_P300_TXBPF 625
+#define MODEM_P300_LPF 200
+#define MODEM_P300_BPF_TAP 256
+#define MODEM_P300_LPF_TAP 128
+ //
+#define MODEM_300_BPF 500
+#define MODEM_300_TXBPF 500
+#define MODEM_300_LPF 155
+#define MODEM_300_BPF_TAP 256
+#define MODEM_300_LPF_TAP 128
+ //
+#define MODEM_600_BPF 800
+#define MODEM_600_TXBPF 900
+#define MODEM_600_LPF 325
+#define MODEM_600_BPF_TAP 256
+#define MODEM_600_LPF_TAP 128
+ //
+#define MODEM_1200_BPF 1400
+#define MODEM_1200_TXBPF 1600
+#define MODEM_1200_LPF 650
+#define MODEM_1200_BPF_TAP 256
+#define MODEM_1200_LPF_TAP 128
+ //
+#define MODEM_2400_BPF 3200
+#define MODEM_2400_TXBPF 3200
+#define MODEM_2400_LPF 1400
+#define MODEM_2400_BPF_TAP 256
+#define MODEM_2400_LPF_TAP 128
+
+#define TX_SILENCE 0
+#define TX_DELAY 1
+#define TX_TAIL 2
+#define TX_NO_DATA 3
+#define TX_FRAME 4
+#define TX_WAIT_BPF 5
+
+
+#define FRAME_WAIT 0
+#define FRAME_LOAD 1
+#define RX_BIT0 0
+#define RX_BIT1 128
+#define DCD_WAIT_SLOT 0
+#define DCD_WAIT_PERSIST 1
+
+#define FX25_MODE_NONE  0
+#define FX25_MODE_RX  1
+#define FX25_MODE_TXRX 2
+#define FX25_TAG 0
+#define FX25_LOAD 1
+
+#define IL2P_MODE_NONE  0
+#define IL2P_MODE_RX  1				// RX il2p + HDLC
+#define IL2P_MODE_TXRX 2
+#define IL2P_MODE_ONLY 3			// RX only il2p, TX il2p
+
+
+#define    MODE_OUR 0
+#define    MODE_OTHER 1
+#define    MODE_RETRY 2
+
+#define FRAME_FLAG 126		// 7e
+
+#define port_num 32		// ?? Max AGW sessions
+#define PKT_ERR 17		// Minimum packet size, bytes
+#define I_MAX 7			// Maximum number of packets
+
+
+	// externs for all modules
+
+#define ARDOPBufferSize 12000 * 100
+
+extern short ARDOPTXBuffer[4][12000 * 100];	// Enough to hold whole frame of samples
+
+extern int ARDOPTXLen[4];				// Length of frame
+extern int ARDOPTXPtr[4];				// Tx Pointer
+
+extern BOOL KISSServ;
+extern int KISSPort;
+
+extern BOOL AGWServ;
+extern int AGWPort;
+
+extern TStringList KISS_acked[];
+extern TStringList KISS_iacked[];
+
+extern TStringList all_frame_buf[5];
+
+extern unsigned short pkt_raw_min_len;
+extern int stat_r_mem;
+
+extern UCHAR diddles;
+
+extern int stdtones;
+extern int fullduplex;
+
+extern struct TQPSK_t qpsk_set[4];
+
+extern int NonAX25[5];
+
+extern short txtail[5];
+extern short txdelay[5];
+
+extern short modem_def[5];
+
+extern int emph_db[5];
+extern UCHAR emph_all[5];
+
+extern UCHAR modem_mode[5];
+
+extern UCHAR RCVR[5];
+extern int soundChannel[5];
+extern int modemtoSoundLR[4];
+
+extern short rx_freq[5];
+extern short rx_shift[5];
+extern short rx_baudrate[5];
+extern short rcvr_offset[5];
+
+extern int tx_hitoneraisedb[5];
+extern float tx_hitoneraise[5];
+
+
+extern UCHAR tx_status[5];
+extern float tx_freq[5];
+extern float tx_shift[5];
+extern unsigned short tx_baudrate[5];
+
+extern unsigned short bpf[5];
+extern unsigned short lpf[5];
+
+extern unsigned short txbpf[5];
+
+extern unsigned short  tx_BPF_tap[5];
+extern unsigned short  tx_BPF_timer[5];
+
+extern unsigned short  BPF_tap[5];
+extern unsigned short  LPF_tap[5];
+
+extern float tx_BPF_core[5][32768];
+extern float LPF_core[5][2048];
+
+extern UCHAR xData[256];
+extern UCHAR xEncoded[256];
+extern UCHAR xDecoded[256];
+
+extern float PI125;
+extern float PI375;
+extern float PI625;
+extern float PI875;
+extern 	float PI5;
+extern float PI25;
+extern float PI75;
+
+extern int max_frame_collector[4];
+extern boolean KISS_opt[4];
+
+#define MaxErrors 4
+
+extern BOOL MinOnStart;
+
+//RS TReedSolomon;
+//  Form1 TForm1;
+//  WaveFormat TWaveFormatEx;
+
+extern int UDPServ;
+extern long long udpServerSeqno;
+
+extern int Channels;
+extern int BitsPerSample;
+extern float TX_Samplerate;
+extern float RX_Samplerate;
+extern int RX_SR;
+extern int TX_SR;
+extern int RX_PPM;
+extern int TX_PPM;
+extern int tx_bufsize;
+extern int rx_bufsize;
+extern int tx_bufcount;
+extern int rx_bufcount;
+extern int fft_size;
+extern int  mouse_down[2];
+//UCHAR * RX_pBuf array[257];
+//  RX_header array[1..256] of TWaveHdr;
+//  TX_pBuf array[1..4,1..256] of pointer;
+//TX_header array[1..4,1..256] of TWaveHdr;
+extern UCHAR calib_mode[5];
+extern UCHAR snd_status[5];
+extern UCHAR buf_status[5];
+extern UCHAR tx_buf_num1[5];
+extern UCHAR tx_buf_num[5];
+extern int speed[5];
+extern int panels[6];
+
+extern int FFTSize;
+#define fft_size FFTSize
+
+extern float fft_window_arr[2048];
+//  fft_s,fft_d array[0..2047] of TComplex;
+extern short fft_buf[5][4096];
+extern UCHAR fft_disp[5][4096];
+//  bm array[1..4] of TBitMap;
+//  bm1,bm2,bm3 TBitMap;
+
+//  WaveInHandle hWaveIn;
+//  WaveOutHandle array[1..4] of hWaveOut;
+extern int RXBufferLength;
+
+// data1 PData16;
+
+extern int grid_time;
+extern int fft_mult;
+extern int fft_spd;
+extern int grid_timer;
+extern int stop_wf;
+extern int raduga;
+extern char snd_rx_device_name[32];
+extern char snd_tx_device_name[32];
+extern int snd_rx_device;
+extern int snd_tx_device;
+extern UCHAR mod_icon_status;
+extern UCHAR last_mod_icon_status;
+extern UCHAR icon_timer;
+//  TelIni TIniFile;
+extern char cur_dir[];
+//  TimerId1 cardinal;
+//  TimerId2 cardinal;
+extern UCHAR TimerStat1;
+extern UCHAR TimerStat2;
+extern int stat_log;
+
+extern char PTTPort[80];			// Port for Hardware PTT - may be same as control port.
+extern int PTTMode;
+extern int PTTBAUD ;
+
+extern char PTTOnString[128];
+extern char PTTOffString[128];
+
+extern UCHAR PTTOnCmd[64];
+extern UCHAR PTTOnCmdLen;
+
+extern UCHAR PTTOffCmd[64];
+extern UCHAR PTTOffCmdLen;
+
+extern int PTT_device;
+extern int RX_device;
+extern int TX_device;
+extern int TX_rotate;
+extern int UsingLeft;
+extern int UsingRight;
+extern int UsingBothChannels;
+extern int pttGPIOPin;
+extern int pttGPIOPinR;
+extern BOOL pttGPIOInvert;
+extern BOOL useGPIO;
+extern BOOL gotGPIO;
+extern int VID;
+extern int PID;
+extern char CM108Addr[80];
+extern int HamLibPort;
+extern char HamLibHost[];
+
+extern int SCO;
+extern int DualPTT;
+extern UCHAR  DebugMode;
+extern UCHAR TimerEvent;
+extern int nr_monitor_lines;
+extern int UTC_Tim;
+extern int MainPriority;
+//  MainThreadHandle THandle;
+extern UCHAR w_state;
+
+extern BOOL Firstwaterfall;
+extern BOOL Secondwaterfall;
+
+extern int dcd_threshold;
+extern int rxOffset;
+extern int chanOffset[4];
+
+extern boolean busy;
+extern boolean dcd[5];
+
+extern struct TKISSMode_t  KISS;
+
+extern boolean dyn_frack[4] ;
+extern Byte recovery[4];
+extern Byte users[4];
+
+extern int resptime[4];
+extern int slottime[4];
+extern int persist[4];
+extern int fracks[4];
+extern int frack_time[4];
+extern int idletime[4];
+extern int redtime[4];
+extern int IPOLL[4];
+extern int maxframe[4];
+extern int TXFrmMode[4];
+
+extern char MyDigiCall[4][512];
+extern char exclude_callsigns[4][512];
+extern char exclude_APRS_frm[4][512];
+
+extern TStringList  list_exclude_callsigns[4];
+extern TStringList list_exclude_APRS_frm[4];
+extern TStringList list_digi_callsigns[4];
+
+
+extern int SoundIsPlaying;
+extern int Capturing;
+
+extern struct TDetector_t  DET[nr_emph + 1][16];
+
+extern char CaptureDevice[80];
+extern char PlaybackDevice[80];
+
+extern TAX25Port AX25Port[4][port_num];
+
+extern int fx25_mode[4];
+extern int il2p_mode[4];
+
+extern int tx_fx25_size[4];
+extern int tx_fx25_size_cnt[4];
+extern int tx_fx25_mode[4];
+
+extern int SatelliteMode;
+
+// Function prototypes
+
+void KISS_send_ack(UCHAR port, string * data);
+void AGW_AX25_frame_analiz(int snd_ch, int RX, string * frame);
+void FIR_filter(float * src, unsigned short buf_size, unsigned short tap, float * core, float * dest, float * prev);
+void make_core_TXBPF(UCHAR snd_ch, float freq, float width);
+void OpenPTTPort();
+void ClosePTTPort();
+
+void RadioPTT(int snd_ch, BOOL PTTState);
+void put_frame(int snd_ch, string * frame, char * code, int  tx_stat, int excluded);
+void CloseCOMPort(int fd);
+void COMClearRTS(int fd);
+void COMClearDTR(int fd);
+unsigned int getTicks();
+char * ShortDateTime();
+void  write_ax25_info(TAX25Port * AX25Sess);
+void reverse_addr(Byte * path, Byte * revpath, int Len);
+string * get_mycall(string * path);
+TAX25Port * get_user_port_by_calls(int snd_ch, char *  CallFrom, char *  CallTo);
+TAX25Port * get_free_port(int snd_ch);
+void * in_list_incoming_mycall(Byte * path);
+boolean add_incoming_mycalls(void * socket, char * src_call);
+int get_addr(char * Calls, UCHAR * AXCalls);
+void reverse_addr(Byte * path, Byte * revpath, int Len);
+void set_link(TAX25Port * AX25Sess, UCHAR * axpath);
+void rst_timer(TAX25Port * AX25Sess);
+void set_unlink(TAX25Port * AX25Sess, Byte * path);
+unsigned short get_fcs(UCHAR * Data, unsigned short len);
+void KISSSendtoServer(void * sock, Byte * Msg, int Len);
+int ConvFromAX25(unsigned char * incall, char * outcall);
+BOOL ConvToAX25(char * callsign, unsigned char * ax25call);
+void Debugprintf(const char * format, ...);
+
+double pila(double x);
+
+void AGW_Raw_monitor(int snd_ch, string * data);
+
+// Dephi emulation functions
+
+string * Strings(TStringList * Q, int Index);
+void Clear(TStringList * Q);
+int Count(TStringList * List);
+
+string * newString();
+string * copy(string * Source, int StartChar, int Count);
+TStringList * newTStringList();
+
+void freeString(string * Msg);
+
+void initString(string * S);
+void initTStringList(TStringList* T);
+
+// Two delete() This is confusing!!
+// Not really - one acts on String, other TStringList
+
+void Delete(TStringList * Q, int Index);
+void mydelete(string * Source, int StartChar, int Count);
+
+void move(UCHAR * SourcePointer, UCHAR * DestinationPointer, int CopyCount);
+void fmove(float * SourcePointer, float * DestinationPointer, int CopyCount);
+
+void setlength(string * Msg, int Count);		// Set string length
+
+string * stringAdd(string * Msg, UCHAR * Chars, int Count);		// Extend string 
+
+void Assign(TStringList * to, TStringList * from);	// Duplicate from to to
+
+string * duplicateString(string * in);
+
+// This looks for a string in a stringlist. Returns inhex if found, otherwise -1
+
+int  my_indexof(TStringList * l, string * s);
+
+boolean compareStrings(string * a, string * b);
+
+int Add(TStringList * Q, string * Entry);
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/Waveout.c b/Waveout.c
index 31a6c72..77c8b10 100644
--- a/Waveout.c
+++ b/Waveout.c
@@ -54,12 +54,19 @@ void GetSoundDevices();
 
 // Currently use 1200 samples for TX but 480 for RX to reduce latency
 
-short buffer[2][SendSize * 2];		// Two Transfer/DMA buffers of 0.1 Sec  (x2 for Stereo)
-short inbuffer[5][ReceiveSize * 2];	// Input Transfer/ buffers of 0.1 Sec (x2 for Stereo)
+#define MaxReceiveSize 2048		// Enough for 9600
+#define MaxSendSize 4096
+
+short buffer[2][MaxSendSize * 2];		// Two Transfer/DMA buffers of 0.1 Sec  (x2 for Stereo)
+short inbuffer[5][MaxReceiveSize * 2];	// Input Transfer/ buffers of 0.1 Sec (x2 for Stereo)
 
 extern short * DMABuffer;
 extern int Number;
 
+int ReceiveSize = 512;
+int SendSize = 1024;
+int using48000 = 0;
+
 int SoundMode = 0;
 int stdinMode = 0;
 
@@ -248,7 +255,7 @@ short * SendtoCard(unsigned short * buf, int n)
 
 	while (!(header[!Index].dwFlags & WHDR_DONE))
 	{
-		txSleep(10);				// Run buckground while waiting 
+		txSleep(5);				// Run buckground while waiting 
 	}
 
 	waveOutUnprepareHeader(hWaveOut, &header[!Index], sizeof(WAVEHDR));
@@ -404,6 +411,21 @@ int InitSound(BOOL Report)
 		}
 	}
 
+	if (using48000)
+	{
+		wfx.nSamplesPerSec = 48000;
+		wfx.nAvgBytesPerSec = 48000 * 4;
+		ReceiveSize = 2048;
+		SendSize = 4096;		// 100 mS for now
+	}
+	else
+	{
+		wfx.nSamplesPerSec = 12000;
+		wfx.nAvgBytesPerSec = 12000 * 4;
+		ReceiveSize = 512;
+		SendSize = 1024;
+	}
+
     ret = waveOutOpen(&hWaveOut, PlayBackIndex, &wfx, 0, 0, CALLBACK_NULL); //WAVE_MAPPER
 
 	if (ret)
@@ -507,7 +529,7 @@ void PollReceivedSamples()
 		return;
 	}
 
-	if (inheader[inIndex].dwFlags & WHDR_DONE)
+	while (inheader[inIndex].dwFlags & WHDR_DONE)
 	{
 		short * ptr = &inbuffer[inIndex][0];
 		int i;
@@ -656,6 +678,7 @@ VOID WriteSamples(short * buffer, int len)
 short * SoundInit()
 {
 	Index = 0;
+	inIndex = 0;
 	return &buffer[0][0];
 
 
diff --git a/agwlib.h b/agwlib.h
new file mode 100644
index 0000000..688f918
--- /dev/null
+++ b/agwlib.h
@@ -0,0 +1,45 @@
+
+#ifndef AGWLIB_H
+#define AGWLIB_H 1
+
+
+// Call at beginning to start it up.
+
+int agwlib_init (char *host, char *port, int (*init_func)(void));
+
+
+
+// Send commands to TNC.
+
+
+int agwlib_X_register_callsign (int chan, char *call_from);
+
+int agwlib_x_unregister_callsign (int chan, char *call_from);
+
+int agwlib_G_ask_port_information (void);
+
+int agwlib_C_connect (int chan, char *call_from, char *call_to);
+
+int agwlib_d_disconnect (int chan, char *call_from, char *call_to);
+
+int agwlib_D_send_connected_data (int chan, int pid, char *call_from, char *call_to, int data_len, char *data);
+
+int agwlib_Y_outstanding_frames_for_station (int chan, char *call_from, char *call_to);
+
+
+
+// The application must define these.
+
+void agw_cb_C_connection_received (int chan, char *call_from, char *call_to, int data_len, char *data);
+void on_C_connection_received (int chan, char *call_from, char *call_to, int incoming, char *data);
+
+void agw_cb_d_disconnected (int chan, char *call_from, char *call_to, int data_len, char *data);
+
+void agw_cb_D_connected_data (int chan, char *call_from, char *call_to, int data_len, char *data);
+
+void agw_cb_G_port_information (int num_chan, char *chan_descriptions[]);
+
+void agw_cb_Y_outstanding_frames_for_station (int chan, char *call_from, char *call_to, int frame_count);
+
+
+#endif
\ No newline at end of file
diff --git a/ais.h b/ais.h
new file mode 100644
index 0000000..6b96288
--- /dev/null
+++ b/ais.h
@@ -0,0 +1,8 @@
+
+
+void ais_to_nmea (unsigned char *ais, int ais_len, char *nema, int nema_size);
+
+int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mssi, int mssi_size, double *odlat, double *odlon,
+			float *ofknots, float *ofcourse, float *ofalt_m, char *symtab, char *symbol, char *comment, int comment_size);
+
+int ais_check_length (int type, int length);
diff --git a/aprs_tt.h b/aprs_tt.h
new file mode 100644
index 0000000..4d33f48
--- /dev/null
+++ b/aprs_tt.h
@@ -0,0 +1,191 @@
+
+/* aprs_tt.h */
+
+#ifndef APRS_TT_H
+#define APRS_TT_H 1
+
+
+
+/*
+ * For holding location format specifications from config file.
+ * Same thing is also useful for macro definitions.
+ * We have exactly the same situation of looking for a pattern
+ * match and extracting fixed size groups of digits.
+ */
+
+struct ttloc_s {
+	enum { TTLOC_POINT, TTLOC_VECTOR, TTLOC_GRID, TTLOC_UTM, TTLOC_MGRS, TTLOC_USNG, TTLOC_MACRO, TTLOC_MHEAD, TTLOC_SATSQ, TTLOC_AMBIG } type;
+
+	char pattern[20];	/* e.g. B998, B5bbbdddd, B2xxyy, Byyyxxx, BAxxxx */
+				/* For macros, it should be all fixed digits, */
+				/* and the letters x, y, z.  e.g.  911, xxyyyz */
+
+	union {
+
+	  struct {	
+	    double lat;		/* Specific locations. */
+	    double lon;
+	  } point;
+
+	  struct {
+	    double lat;		/* For bearing/direction. */
+	    double lon;
+	    double scale;	/* conversion to meters */
+	  } vector;
+
+	  struct {
+	    double lat0;	/* yyy all zeros. */
+	    double lon0;	/* xxx */
+	    double lat9;	/* yyy all nines. */
+	    double lon9;	/* xxx */
+	  } grid;
+
+	  struct {
+	    double scale;
+	    double x_offset;
+	    double y_offset;
+	    long lzone;		/* UTM zone, should be 1-60 */
+	    char latband;	/* Latitude band if specified, otherwise space or - */
+	    char hemi;		/* UTM Hemisphere, should be 'N' or 'S'. */
+	  } utm;
+
+	  struct {
+	    char zone[8];	/* Zone and square for USNG/MGRS */
+	  } mgrs;
+
+	  struct {
+	    char prefix[24];	/* should be 10, 6, or 4 digits to be */
+				/* prepended to the received sequence. */
+	  } mhead;
+
+	  struct {
+	    char *definition;
+	  } macro;
+
+	};
+};
+
+
+/* Error codes for sending responses to user. */
+
+#define TT_ERROR_OK		0	/* Success. */
+#define TT_ERROR_D_MSG		1	/* D was first char of field.  Not implemented yet. */
+#define TT_ERROR_INTERNAL	2	/* Internal error.  Shouldn't be here. */
+#define TT_ERROR_MACRO_NOMATCH	3	/* No definition for digit sequence. */
+#define TT_ERROR_BAD_CHECKSUM	4	/* Bad checksum on call. */
+#define TT_ERROR_INVALID_CALL	5	/* Invalid callsign. */
+#define TT_ERROR_INVALID_OBJNAME 6	/* Invalid object name. */
+#define TT_ERROR_INVALID_SYMBOL	7	/* Invalid symbol specification. */
+#define TT_ERROR_INVALID_LOC	8	/* Invalid location. */
+#define TT_ERROR_NO_CALL	9	/* No call or object name included. */
+#define TT_ERROR_INVALID_MHEAD	10	/* Invalid Maidenhead Locator. */
+#define TT_ERROR_INVALID_SATSQ	11	/* Satellite square must be 4 digits. */
+#define TT_ERROR_SUFFIX_NO_CALL 12	/* No known callsign for suffix. */
+
+#define TT_ERROR_MAXP1		13	/* Number of items above.  i.e. Last number plus 1. */
+
+
+#if CONFIG_C		/* Is this being included from config.c? */
+
+/* Must keep in sync with above !!! */
+
+static const char *tt_msg_id[TT_ERROR_MAXP1] = {
+	"OK",
+	"D_MSG",
+	"INTERNAL",
+	"MACRO_NOMATCH",
+	"BAD_CHECKSUM",
+	"INVALID_CALL",
+	"INVALID_OBJNAME",
+	"INVALID_SYMBOL",
+	"INVALID_LOC",
+	"NO_CALL",
+	"INVALID_MHEAD",
+	"INVALID_SATSQ",
+	"SUFFIX_NO_CALL"
+};
+
+#endif
+
+/* 
+ * Configuration options for APRStt.
+ */
+
+#define TT_MAX_XMITS 10
+
+#define TT_MTEXT_LEN 64
+
+
+struct tt_config_s {
+
+	int gateway_enabled;		/* Send DTMF sequences to APRStt gateway. */
+
+	int obj_recv_chan;		/* Channel to listen for tones. */
+
+	int obj_xmit_chan;		/* Channel to transmit object report. */
+					/* -1 for none.  This could happen if we */
+					/* are only sending to application */
+					/* and/or IGate. */
+
+	int obj_send_to_app;		/* send to attached application(s). */
+
+	int obj_send_to_ig;		/* send to IGate. */
+
+	char obj_xmit_via[AX25_MAX_REPEATERS * (AX25_MAX_ADDR_LEN+1)];	
+					/* e.g.  empty or "WIDE2-1,WIDE1-1" */
+	
+	int retain_time;		/* Seconds to keep information about a user. */
+
+	int num_xmits;			/* Number of times to transmit object report. */
+				
+	int xmit_delay[TT_MAX_XMITS];	/* Delay between them. */
+					/* e.g.  3 seconds before first transmission then */
+					/* delays of 16, 32, seconds etc. in between repeats. */
+
+	struct ttloc_s *ttloc_ptr;	/* Pointer to variable length array of above. */
+	int ttloc_size;			/* Number of elements allocated. */
+	int ttloc_len;			/* Number of elements actually used. */
+
+	double corral_lat;		/* The "corral" for unknown locations. */
+	double corral_lon;
+	double corral_offset;
+	int corral_ambiguity;
+
+	char status[10][TT_MTEXT_LEN];		/* Up to 9 status messages. e.g.  "/enroute" */
+						/* Position 0 means none and can't be changed. */
+
+	struct {
+	  char method[AX25_MAX_ADDR_LEN];	/* SPEECH or MORSE[-n] */
+	  char mtext[TT_MTEXT_LEN];		/* Message text. */
+	} response[TT_ERROR_MAXP1];
+
+	char ttcmd[80];			/* Command to generate custom audible response. */
+};
+
+
+
+	
+void aprs_tt_init (struct tt_config_s *p_config, int debug);
+
+void aprs_tt_button (int chan, char button);
+
+
+
+
+
+#define APRSTT_LOC_DESC_LEN 32		/* Need at least 26 */
+
+#define APRSTT_DEFAULT_SYMTAB '\\'
+#define APRSTT_DEFAULT_SYMBOL 'A'
+
+
+void aprs_tt_dao_to_desc (char *dao, char *str);
+
+void aprs_tt_sequence (int chan, char *msg);
+
+int dw_run_cmd (char *cmd, int oneline, char *result, size_t resultsiz);
+
+
+#endif
+
+/* end aprs_tt.h */
\ No newline at end of file
diff --git a/audio_stats.h b/audio_stats.h
new file mode 100644
index 0000000..4cf8ad0
--- /dev/null
+++ b/audio_stats.h
@@ -0,0 +1,7 @@
+
+
+/* audio_stats.h */
+
+
+extern void audio_stats (int adev, int nchan, int nsamp, int interval);
+
diff --git a/ax25.c b/ax25.c
index edf86c7..e55e286 100644
--- a/ax25.c
+++ b/ax25.c
@@ -1491,6 +1491,7 @@ boolean is_correct_path(Byte * path, Byte pid)
 void get_exclude_list(char * line, TStringList * list)
 {
 	// Convert comma separated list of calls to ax25 format in list
+	// Convert to 6 chars - SSID is ignored
 
 	string axcall;
 
@@ -1504,7 +1505,44 @@ void get_exclude_list(char * line, TStringList * list)
 	strcpy(copy, line);						// copy as strtok messes with it
 	strcat(copy, ",");
 
-	axcall.Length = 8;
+	axcall.Length = 6;
+	axcall.AllocatedLength = 8;
+	axcall.Data = malloc(8);
+
+	memset(axcall.Data, 0, 8);
+
+	ptr = strtok_s(copy, " ,", &Context);
+
+	while (ptr)
+	{
+		if (ConvToAX25(ptr, axcall.Data) == 0)
+			return;
+
+		axcall.Data[6] = 0;
+
+		Add(list, duplicateString(&axcall));
+
+		ptr = strtok_s(NULL, " ,", &Context);
+	}
+}
+
+void get_digi_list(char * line, TStringList * list)
+{
+	// Convert comma separated list of calls to ax25 format in list
+
+	string axcall;
+
+	char copy[512];
+
+	char * ptr, *Context;
+
+	if (line[0] == 0)
+		return;
+
+	strcpy(copy, line);						// copy as strtok messes with it
+	strcat(copy, ",");
+
+	axcall.Length = 7;
 	axcall.AllocatedLength = 8;
 	axcall.Data = malloc(8);
 
@@ -1518,7 +1556,6 @@ void get_exclude_list(char * line, TStringList * list)
 			return;
 
 		Add(list, duplicateString(&axcall));
-
 		ptr = strtok_s(NULL, " ,", &Context);
 	}
 }
@@ -1659,6 +1696,36 @@ begin
 end;
 */
 
+
+
+int is_excluded_call(int snd_ch, unsigned char * path)
+{
+	string * call = newString();
+	int Excluded = FALSE;
+
+	stringAdd(call, &path[7], 6);
+	
+	if (list_exclude_callsigns[snd_ch].Count > 0)
+		if (my_indexof(&list_exclude_callsigns[snd_ch], call) > -1)
+			Excluded = TRUE;
+	
+	freeString(call);
+	return Excluded;
+}
+
+
+int  is_excluded_frm(int snd_ch, int f_id, string * data)
+{
+	if (f_id == U_UI)
+		if (data->Length > 0)
+			if (my_indexof(&list_exclude_APRS_frm[snd_ch], data) >= 0)
+				return TRUE;
+
+	return FALSE;
+}
+
+
+
 int number_digi(string path)
 {
 	int n = 0;
@@ -1926,7 +1993,7 @@ void ax25_init()
 		initTStringList(&list_digi_callsigns[i]);
 		initTStringList(&KISS_acked[i]);
 
-		get_exclude_list(MyDigiCall[i], &list_digi_callsigns[i]);
+		get_digi_list(MyDigiCall[i], &list_digi_callsigns[i]);
 		get_exclude_list(exclude_callsigns[i], &list_exclude_callsigns[i]);
 		get_exclude_frm(exclude_APRS_frm[i], &list_exclude_APRS_frm[i]);
 
diff --git a/ax25_agw.c b/ax25_agw.c
index 00574ec..5432195 100644
--- a/ax25_agw.c
+++ b/ax25_agw.c
@@ -22,7 +22,7 @@ along with QtSoundModem.  If not, see http://www.gnu.org/licenses
 
 #include "UZ7HOStuff.h"
 
-extern char modes_name[modes_count][20];
+extern char modes_name[modes_count][21];
 extern int RSID_SABM[4];
 extern int RSID_UI[4];
 extern int RSID_SetModem[4];
diff --git a/ax25_demod.c b/ax25_demod.c
index eec2f9f..3e1b5bb 100644
--- a/ax25_demod.c
+++ b/ax25_demod.c
@@ -29,6 +29,10 @@ void  make_rx_frame_FX25(int snd_ch, int rcvr_nr, int emph, string * data);
 string * memory_ARQ(TStringList * buf, string * data);
 
 float GuessCentreFreq(int i);
+void ProcessRXFrames(int snd_ch);
+
+extern struct il2p_context_s *il2p_context[4][16][3];
+
 
 /*
 
@@ -115,6 +119,8 @@ longword DCD_header[5] = { 0 };
 int dcd_on_hdr[5] = { 0 };
 
 extern int centreFreq[4];
+
+float lastangle[4];			// pevious value for differential modes
  
 
 unsigned short n_INTR[5] = { 1,1,1,1,1 };
@@ -154,6 +160,12 @@ int modemtoSoundLR[4] = { 0 };
 
 struct TDetector_t  DET[nr_emph + 1][16];
 
+// Chan, Decoder, Emph
+
+float Phases[4][16][nr_emph + 1][4096];
+float Mags[4][16][nr_emph + 1][4096];
+int nPhases[4][16][nr_emph + 1];
+
 TStringList detect_list_l[5];
 TStringList detect_list[5];
 TStringList detect_list_c[5];
@@ -341,6 +353,10 @@ void chk_dcd1(int snd_ch, int buf_size)
 	{
 		dcd_bit_sync[snd_ch] = blnBusyStatus;
 	}
+	else if (modem_mode[snd_ch] == MODE_RUH)
+	{
+		dcd_bit_sync[snd_ch] = blnBusyStatus;
+	}
 	else
 	{
 		if (dcd_bit_cnt[snd_ch] > 0)
@@ -1341,7 +1357,7 @@ int stats[2] = { 0 };
 void decode_stream_MPSK(int snd_ch, int rcvr_nr, float *  src, int buf_size, int  last)
 {
 
-#ifndef WIN32
+#ifndef XXXX
 
 	// Until ASM is converted
 
@@ -2787,6 +2803,9 @@ void decode_stream_BPSK(int last, int snd_ch, int rcvr_nr, int emph, float * src
 			angle = atan2f(sumIQ2, sumIQ1);
 			PSK_IZ1 = PkAmpI;
 			PSK_QZ1 = PkAmpQ;
+
+			float Mag = sqrtf(powf(PSK_IZ1, 2) + powf(PSK_QZ1, 2));
+
 			// Phase corrector
 
 			if (fabsf(angle) < PI5)
@@ -2808,11 +2827,24 @@ void decode_stream_BPSK(int last, int snd_ch, int rcvr_nr, int emph, float * src
 				bit = RX_BIT0;
 			//
 
+			//	is this the best place to store phase for constellation?
+			// only for ilp2 for now
+
 			if (il2p_mode[snd_ch])
+			{
+				struct il2p_context_s * il2p = il2p_context[snd_ch][rcvr_nr][emph];
+
+				if (il2p && il2p->state > IL2P_SEARCHING)
+				{
+					Phases[snd_ch][rcvr_nr][emph][nPhases[snd_ch][rcvr_nr][emph]] = angle;
+					Mags[snd_ch][rcvr_nr][emph][nPhases[snd_ch][rcvr_nr][emph]++] = Mag;
+					if (nPhases[snd_ch][rcvr_nr][emph] > 4090)
+						nPhases[snd_ch][rcvr_nr][emph]--;
+				}
 				il2p_rec_bit(snd_ch, rcvr_nr, emph, bit);
 				if (il2p_mode[snd_ch] == IL2P_MODE_ONLY)		// Dont try HDLC decode
 					continue;
-
+			}
 			if (bit)
 				stats[1]++;
 			else
@@ -2949,7 +2981,6 @@ void decode_stream_QPSK(int last, int snd_ch, int rcvr_nr, int emph, float * src
 
 	struct TDetector_t * pDET = &DET[emph][rcvr_nr];
 
-
 	bit_stuff_cnt = pDET->bit_stuff_cnt[snd_ch];
 	last_rx_bit = pDET->last_rx_bit[snd_ch];
 	sample_cnt = pDET->sample_cnt[snd_ch];
@@ -3073,6 +3104,8 @@ void decode_stream_QPSK(int last, int snd_ch, int rcvr_nr, int emph, float * src
 			PSK_IZ1 = PkAmpI;
 			PSK_QZ1 = PkAmpQ;
 
+			float Mag = sqrtf(powf(PSK_IZ1, 2) + powf(PSK_QZ1, 2));
+
 			if (angle > pi || angle < -pi)
 				angle = angle;
 
@@ -3120,6 +3153,8 @@ void decode_stream_QPSK(int last, int snd_ch, int rcvr_nr, int emph, float * src
 			}
 			else
 			{
+				// "Normal" QPSK
+	
 				// Phase corrector
 
 				// I think this sends 0 90 180 270
@@ -3158,6 +3193,26 @@ void decode_stream_QPSK(int last, int snd_ch, int rcvr_nr, int emph, float * src
 			{
 				dibit = dibit << 1;
 
+				//	is this the best place to store phase for constellation?
+				// only for ilp2 for now
+
+				if (il2p_mode[snd_ch])
+				{
+					struct il2p_context_s * il2p = il2p_context[snd_ch][rcvr_nr][emph];
+
+					if (il2p && il2p->state > IL2P_SEARCHING)
+					{
+						Phases[snd_ch][rcvr_nr][emph][nPhases[snd_ch][rcvr_nr][emph]] = angle;
+						Mags[snd_ch][rcvr_nr][emph][nPhases[snd_ch][rcvr_nr][emph]++] = Mag;
+						if (nPhases[snd_ch][rcvr_nr][emph] > 4090)
+							nPhases[snd_ch][rcvr_nr][emph]--;
+					}
+
+					il2p_rec_bit(snd_ch, rcvr_nr, emph, (dibit & RX_BIT1));
+					if (il2p_mode[snd_ch] == IL2P_MODE_ONLY)		// Dont try HDLC decode
+						continue;
+				}
+
 				// NRZI
 
 				if (last_rx_bit == (dibit & RX_BIT1))
@@ -3328,7 +3383,13 @@ void decode_stream_8PSK(int last, int snd_ch, int rcvr_nr, int emph, float * src
 		dcd_bit_cnt[snd_ch] = 0;
 	}
 
-	baudrate = 1600 / 6;
+	// Not sure how this works
+
+	if (tx_baudrate[snd_ch] == 300)
+		baudrate = 300;
+	else
+		baudrate = 1600 / 6;
+
 	div_bit_afc = 1.0 / round(BIT_AFC*(RX_Samplerate / 11025));
 	x = baudrate / RX_Samplerate;
 	max_cnt = round(RX_Samplerate / baudrate) + 1;
@@ -3365,7 +3426,7 @@ void decode_stream_8PSK(int last, int snd_ch, int rcvr_nr, int emph, float * src
 		bit_osc = bit_osc + x;
 
 		if (bit_osc >= 1)
-		{
+		{			
 			if (sample_cnt <= max_cnt)
 				for (k = sample_cnt; k <= max_cnt; k++)
 					bit_buf[k] = 0.95*bit_buf[k];
@@ -3403,6 +3464,8 @@ void decode_stream_8PSK(int last, int snd_ch, int rcvr_nr, int emph, float * src
 			PSK_IZ1 = PkAmpI;
 			PSK_QZ1 = PkAmpQ;
 
+			float Mag = sqrtf(powf(PSK_IZ1, 2) + powf(PSK_QZ1, 2));
+
 			// Phase corrector
 
 			if (fabsf(angle) < PI125)
@@ -3431,8 +3494,7 @@ void decode_stream_8PSK(int last, int snd_ch, int rcvr_nr, int emph, float * src
 
 			AngleCorr = AngleCorr * 0.95 - KCorr * 0.05;
 			angle = angle + AngleCorr;
-			//
-
+			
 			if (fabsf(angle) < PI125)
 				tribit = 1;
 			if (angle >= PI125 && angle < PI375)
@@ -3455,6 +3517,29 @@ void decode_stream_8PSK(int last, int snd_ch, int rcvr_nr, int emph, float * src
 			for (j = 0; j < 3; j++)
 			{
 				tribit = tribit << 1;
+
+				// look for il2p before nrzi 
+
+				//	is this the best place to store phase for constellation?
+				// only for ilp2 for now
+
+				if (il2p_mode[snd_ch])
+				{
+					struct il2p_context_s * il2p = il2p_context[snd_ch][rcvr_nr][emph];
+					
+					if (il2p && il2p->state > IL2P_SEARCHING)
+					{
+						Phases[snd_ch][rcvr_nr][emph][nPhases[snd_ch][rcvr_nr][emph]] = angle;
+						Mags[snd_ch][rcvr_nr][emph][nPhases[snd_ch][rcvr_nr][emph]++] = Mag;
+					if (nPhases[snd_ch][rcvr_nr][emph] > 4090)
+						nPhases[snd_ch][rcvr_nr][emph]--;
+					}
+
+					il2p_rec_bit(snd_ch, rcvr_nr, emph, tribit & RX_BIT1);
+					if (il2p_mode[snd_ch] == IL2P_MODE_ONLY)		// Dont try HDLC decode
+						continue;
+				}
+
 				//NRZI
 
 				if (last_rx_bit == (tribit & RX_BIT1))
@@ -3733,6 +3818,14 @@ void make_core_INTR(UCHAR snd_ch)
 		n_INTR[snd_ch] = 1;
 		break;
 
+
+	case SPEED_Q300:
+	case SPEED_8PSK300:
+
+		width = roundf(RX_Samplerate / 2);
+		n_INTR[snd_ch] = 1;
+		break;
+
 	case SPEED_600:
 
 		width = roundf(RX_Samplerate / 4);
@@ -3755,6 +3848,11 @@ void make_core_INTR(UCHAR snd_ch)
 		n_INTR[snd_ch] = 4;
 		break;
 
+//	case SPEED_Q1200:
+//		width = roundf(RX_Samplerate / 8);
+//		n_INTR[snd_ch] = 4;
+//		break;
+
 	case SPEED_Q2400:
 		width = 300;
 		n_INTR[snd_ch] = 4;
@@ -3766,7 +3864,7 @@ void make_core_INTR(UCHAR snd_ch)
 		n_INTR[snd_ch] = 4;
 		break;
 
-	case SPEED_AE2400:
+	case SPEED_2400V26B:
 
 		width = 300;
 		n_INTR[snd_ch] = 4;
@@ -3784,6 +3882,7 @@ void make_core_INTR(UCHAR snd_ch)
 		break;
 
 	case SPEED_8P4800:
+
 		width = 100;
 		n_INTR[snd_ch] = 6;
 		break;
@@ -4121,7 +4220,7 @@ void Demodulator(int snd_ch, int rcvr_nr, float * src_buf, int last, int xcenter
 			QPSK_Demodulator(snd_ch, rcvr_nr, emph_db[snd_ch], last);
 	}
 
-	// QPSK demodulator
+	// 8PSK demodulator
 
 	if (modem_mode[snd_ch]==MODE_8PSK)
 	{
@@ -4147,120 +4246,125 @@ void Demodulator(int snd_ch, int rcvr_nr, float * src_buf, int last, int xcenter
 // I think this handles multiple decoders and passes packet on to next level
 
 // Packet manager
-
 	if (last)
+		ProcessRXFrames(snd_ch);
+}
+
+void ProcessRXFrames(int snd_ch)
+{
+	boolean fecflag = 0;
+	char indicators[5] = "-$#F+"; // None, Single, MEM, FEC, Normal
+
+	// Work out which decoder and which emph settings worked. 
+
+	if (snd_ch < 0 || snd_ch >3)
+		return;
+
+	if (detect_list[snd_ch].Count > 0)		// no point if nothing decoded
 	{
-		boolean fecflag = 0;
+		char decoded[32] = "";
 		char indicators[5] = "-$#F+"; // None, Single, MEM, FEC, Normal
+		char s_emph[4] = "";
+		int emph[4] = { 0 };
+		char report[32] = "";
+		int il2perrors = 255;
 
-		// Work out which decoder and which emph settings worked. 
+		// The is one DET for each Decoder for each Emph setting
 
-		if (detect_list[snd_ch].Count > 0)		// no point if nothing decoded
+		struct TDetector_t * pDET;
+		int i = 0, j, found;
+		int maxemph = nr_emph;
+
+		for (i = 0; i <= nr_emph; i++)
 		{
-			char decoded[32] = "";
-			char indicators[5] = "-$#F+"; // None, Single, MEM, FEC, Normal
-			char s_emph[4] = "";
-			int emph[4] = { 0 };
-			char report[32] = "";
-			int il2perrors = 255;
+			for (j = 0; j <= RCVR[snd_ch] * 2; j++)
+			{
+				pDET = &DET[i][j];
 
-			// The is one DET for each Decoder for each Emph setting
+				if (pDET->rx_decoded > decoded[j])		// Better than other one (| is higher than F)
+					decoded[j] = pDET->rx_decoded;
 
-			struct TDetector_t * pDET;
-			int i = 0, j;
-			int maxemph = nr_emph;
+				if (pDET->emph_decoded > emph[i])
+					emph[i] = pDET->emph_decoded;
 
+				if (il2perrors > pDET->errors)
+					il2perrors = pDET->errors;
+
+				pDET->rx_decoded = 0;
+				pDET->emph_decoded = 0;					// Ready for next time
+				pDET->errors = 255;
+			}
+			if (emph_all[snd_ch] == 0)
+				break;
+		}
+
+		decoded[j] = 0;
+
+		for (j--; j >= 0; j--)
+			decoded[j] = indicators[decoded[j]];
+
+		if (emph_all[snd_ch])
+		{
 			for (i = 0; i <= nr_emph; i++)
 			{
-				for (j = 0; j <= RCVR[snd_ch] * 2; j++)
-				{
-					pDET = &DET[i][j];
-
-					if (pDET->rx_decoded > decoded[j])		// Better than other one (| is higher than F)
-						decoded[j] = pDET->rx_decoded;
-
-					if (pDET->emph_decoded > emph[i])
-						emph[i] = pDET->emph_decoded;
-
-					if (il2perrors > pDET->errors)
-						il2perrors = pDET->errors;
-
-					pDET->rx_decoded = 0;
-					pDET->emph_decoded = 0;					// Ready for next time
-					pDET->errors = 255;
-				}
-				if (emph_all[snd_ch] == 0)
-					break;
+				s_emph[i] = indicators[emph[i]];
 			}
+			sprintf(report, "%s][%s", s_emph, decoded);
+		}
 
-			decoded[j] = 0;
+		else
+			strcpy(report, decoded);
 
-			for (j--; j >= 0; j--)
-				decoded[j] = indicators[decoded[j]];
+		if (detect_list_c[snd_ch].Items[0]->Length)
+		{
+			if (il2perrors < 255 && il2perrors > 0)
+				sprintf(detect_list_c[snd_ch].Items[0]->Data, "%s-%d", detect_list_c[snd_ch].Items[0]->Data, il2perrors);
 
-			if (emph_all[snd_ch])
+			strcat(report, "][");
+			strcat(report, detect_list_c[snd_ch].Items[0]->Data);
+		}
+
+		if (detect_list[snd_ch].Count > 0)
+		{
+			for (i = 0; i < detect_list[snd_ch].Count; i++)
 			{
-				for (i = 0; i <= nr_emph; i++)
+				found = 0;
+
+				//					if (detect_list_l[snd_ch].Count > 0)
+				//						if (my_indexof(&detect_list_l[snd_ch], detect_list[snd_ch].Items[i]) > -1)
+				//							found = 1;
+
+				if (found == 0)
 				{
-					s_emph[i] = indicators[emph[i]];
-				}
-				sprintf(report, "%s][%s", s_emph, decoded);
-			}
-
-			else
-				strcpy(report, decoded);
-
-			if (detect_list_c[snd_ch].Items[0]->Length)
-			{
-				if (il2perrors < 255 && il2perrors > 0)
-					sprintf(detect_list_c[snd_ch].Items[0]->Data, "%s-%d", detect_list_c[snd_ch].Items[0]->Data, il2perrors);
-
-				strcat(report, "][");
-				strcat(report, detect_list_c[snd_ch].Items[0]->Data);
-			}
-
-			if (detect_list[snd_ch].Count > 0)
-			{
-				for (i = 0; i < detect_list[snd_ch].Count; i++)
-				{
-					found = 0;
-
-					//					if (detect_list_l[snd_ch].Count > 0)
-					//						if (my_indexof(&detect_list_l[snd_ch], detect_list[snd_ch].Items[i]) > -1)
-					//							found = 1;
-
-					if (found == 0)
+					if (modem_mode[snd_ch] == MODE_MPSK)
 					{
-						if (modem_mode[snd_ch] == MODE_MPSK)
-						{
-							//					analiz_frame(snd_ch, detect_list[snd_ch].Items[i]->Data, [snd_ch].Items[i]->Data + ' dF: ' + FloatToStrF(DET[0, 0].AFC_dF[snd_ch], ffFixed, 0, 1));
-						}
-						else
-						{
-							analiz_frame(snd_ch, detect_list[snd_ch].Items[i], report, fecflag);
-						}
+						//					analiz_frame(snd_ch, detect_list[snd_ch].Items[i]->Data, [snd_ch].Items[i]->Data + ' dF: ' + FloatToStrF(DET[0, 0].AFC_dF[snd_ch], ffFixed, 0, 1));
+					}
+					else
+					{
+						analiz_frame(snd_ch, detect_list[snd_ch].Items[i], report, fecflag);
 					}
 				}
-
-				// Cancel FX25 decode
-
-				if (fx25_mode[snd_ch] != FX25_MODE_NONE)
-				{
-					int e;
-
-					for (i = 0; i < 16; i++)
-						for (e = 0; e <= nr_emph; e++)
-							DET[e][i].fx25[snd_ch].status = FX25_TAG;
-				}
 			}
 
-			//			Assign(&detect_list_l[snd_ch], &detect_list[snd_ch]);	// Duplicate detect_list to detect_list_l
+			// Cancel FX25 decode
 
-			Clear(&detect_list[snd_ch]);
-			Clear(&detect_list_c[snd_ch]);
+			if (fx25_mode[snd_ch] != FX25_MODE_NONE)
+			{
+				int e;
+
+				for (i = 0; i < 16; i++)
+					for (e = 0; e <= nr_emph; e++)
+						DET[e][i].fx25[snd_ch].status = FX25_TAG;
+			}
 		}
-		chk_dcd1(snd_ch, rx_bufsize);
+
+		//			Assign(&detect_list_l[snd_ch], &detect_list[snd_ch]);	// Duplicate detect_list to detect_list_l
+
+		Clear(&detect_list[snd_ch]);
+		Clear(&detect_list_c[snd_ch]);
 	}
+	chk_dcd1(snd_ch, rx_bufsize);
 }
 
 string * memory_ARQ(TStringList * buf, string * data)
diff --git a/ax25_l2.c b/ax25_l2.c
index 1a2bb06..a99a613 100644
--- a/ax25_l2.c
+++ b/ax25_l2.c
@@ -1410,8 +1410,10 @@ void analiz_frame(int snd_ch, string * frame, char * code, boolean fecflag)
 
 	decode_frame(frame->Data, frame->Length, path, data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr);
 
-	//  if is_excluded_call(snd_ch,path) then excluded:=TRUE;
-	 // if is_excluded_frm(snd_ch,f_id,data) then excluded:=TRUE;
+	if (is_excluded_call(snd_ch, path))
+		excluded =TRUE;
+
+	// if is_excluded_frm(snd_ch,f_id,data) then excluded:=TRUE;
 	
 
 	if (excluded)
diff --git a/ax25_link.h b/ax25_link.h
new file mode 100644
index 0000000..40fa401
--- /dev/null
+++ b/ax25_link.h
@@ -0,0 +1,88 @@
+
+/* ax25_link.h */
+
+
+#ifndef AX25_LINK_H
+#define AX25_LINK_H 1
+
+#include "ax25_pad.h"		// for AX25_MAX_INFO_LEN
+
+#include "dlq.h"		// for dlq_item_t
+
+#include "config.h"		// for struct misc_config_s
+
+
+
+// Limits and defaults for parameters.
+
+
+#define AX25_N1_PACLEN_MIN 1		// Max bytes in Information part of frame.
+#define AX25_N1_PACLEN_DEFAULT 256	// some v2.0 implementations have 128
+#define AX25_N1_PACLEN_MAX AX25_MAX_INFO_LEN	// from ax25_pad.h
+
+
+#define AX25_N2_RETRY_MIN 1		// Number of times to retry before giving up.
+#define AX25_N2_RETRY_DEFAULT 10
+#define AX25_N2_RETRY_MAX 15
+
+
+#define AX25_T1V_FRACK_MIN 1		// Number of seconds to wait before retrying.
+#define AX25_T1V_FRACK_DEFAULT 3	// KPC-3+ has 4.  TM-D710A has 3.
+#define AX25_T1V_FRACK_MAX 15
+
+
+#define AX25_K_MAXFRAME_BASIC_MIN 1		// Window size - number of I frames to send before waiting for ack.
+#define AX25_K_MAXFRAME_BASIC_DEFAULT 4
+#define AX25_K_MAXFRAME_BASIC_MAX 7
+
+#define AX25_K_MAXFRAME_EXTENDED_MIN 1
+#define AX25_K_MAXFRAME_EXTENDED_DEFAULT 32
+#define AX25_K_MAXFRAME_EXTENDED_MAX 63		// In theory 127 but I'm restricting as explained in SREJ handling.
+
+
+
+// Call once at startup time.
+
+void ax25_link_init (struct misc_config_s *pconfig);
+
+
+
+// IMPORTANT:
+
+// These functions must be called on a single thread, one at a time.
+// The Data Link Queue (DLQ) is used to serialize events from multiple sources.
+
+// Maybe the dispatch switch should be moved to ax25_link.c so they can all
+// be made static and they can't be called from the wrong place accidentally.
+
+void dl_connect_request (dlq_item_t *E);
+
+void dl_disconnect_request (dlq_item_t *E);
+
+void dl_data_request (dlq_item_t *E);
+
+void dl_register_callsign (dlq_item_t *E);
+
+void dl_unregister_callsign (dlq_item_t *E);
+
+void dl_outstanding_frames_request (dlq_item_t *E);
+
+void dl_client_cleanup (dlq_item_t *E);
+
+
+void lm_data_indication (dlq_item_t *E);
+
+void lm_seize_confirm (dlq_item_t *E);
+
+void lm_channel_busy (dlq_item_t *E);
+
+
+void dl_timer_expiry (void);
+
+
+double ax25_link_get_next_timer_expiry (void);
+
+
+#endif
+
+/* end ax25_link.h */
\ No newline at end of file
diff --git a/ax25_mod-DESKTOP-MHE5LO8.c b/ax25_mod-DESKTOP-MHE5LO8.c
new file mode 100644
index 0000000..4c803c2
--- /dev/null
+++ b/ax25_mod-DESKTOP-MHE5LO8.c
@@ -0,0 +1,1810 @@
+/*
+Copyright (C) 2019-2020 Andrei Kopanchuk UZ7HO
+
+This file is part of QtSoundModem
+
+QtSoundModem is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+QtSoundModem is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with QtSoundModem.  If not, see http://www.gnu.org/licenses
+
+*/
+
+// UZ7HO Soundmodem Port by John Wiseman G8BPQ
+
+
+#include "UZ7HOStuff.h"
+
+// I assume this modulates (and sends?} frames
+
+int RSEncode(UCHAR * bytToRS, UCHAR * RSBytes, int DataLen, int RSLen);
+
+//unit ax25_mod;
+
+//interface
+
+//uses sysutils,classes,math
+
+extern int SampleNo;
+
+extern BOOL KISSServ;
+
+extern TStringList KISS_acked[];
+extern TStringList KISS_iacked[];
+
+extern UCHAR modem_mode[];
+
+#define sbc 175
+
+extern single  ch_offset[4];
+int Continuation[4] = { 0, 0, 0, 0 };	// Sending 2nd or more packet of burst
+
+#define COS45 0.70710676908493f
+
+#define TX_SILENCE 0
+#define TX_DELAY 1
+#define TX_TAIL 2
+#define TX_NO_DATA 3
+#define TX_FRAME 4
+#define TX_WAIT_BPF 5
+
+
+#define TX_BIT0 0
+#define TX_BIT1 1
+#define FRAME_EMPTY 0
+#define FRAME_FULL 1
+#define FRAME_NO_FRAME 2
+#define FRAME_NEW_FRAME 3
+#define BYTE_EMPTY 0
+#define BYTE_FULL 1
+
+
+UCHAR gray_8PSK[8] = {7,0,6,5,2,1,3,4};		// ?? was 1::8
+
+UCHAR gray_PI4QPSK[4] = {3,1,5,7};
+
+
+float audio_buf[5][32768];  // [1..4,0..32767]
+float tx_src_BPF_buf[5][32768];
+float tx_BPF_buf[5][32768];
+float tx_prev_BPF_buf[5][32768];
+float tx_BPF_core[5][32768];
+
+long tx_delay_cnt[5] = {0};		//			 : array[1..4] of longword=(0,0,0,0};
+long tx_tail_cnt[5] = {0};
+
+int tx_hitoneraisedb[5] = {0};		//   : array[1..4] of integer=(0,0,0,0};
+float tx_hitoneraise[5] = {0};		//     : array[1..4] of single=(0,0,0,0};
+float tx_freq[5] = { 1000, 1000, 1000, 1000, 1000};			//			     : array[1..4] of single=(1000,1000,1000,1000};
+float tx_shift[5] = { 200, 200, 200, 200, 200};				  //   : array[1..4] of single=(200,200,200,200};
+float tx_bit_mod[5] = {1, 1, 1, 1, 1};		//			   : array[1..4] of single=(1,1,1,1};
+float tx_osc[5] = {0};		//						 : array[1..4] of single=(0,0,0,0};
+float tx_bit_osc[5] = {0};		//			   : array[1..4] of single=(0,0,0,0};
+unsigned short txbpf[5] = { 400, 400, 400, 400, 400};		//						  : array[1..4] of word=(400,400,400,400};
+unsigned short  tx_BPF_tap[5] = { 256, 256, 256, 256, 256};		//			   : array[1..4] of word=(256,256,256,256};
+unsigned short  tx_baudrate[5] = { 300, 300, 300, 300, 300 };		//			  : array[1..4] of word=(300,300,300,300};
+unsigned short  tx_bitrate[5] = { 300, 300, 300, 300, 300 };		//			  : array[1..4] of word=(300,300,300,300};
+unsigned short  tx_BPF_timer[5] = {0};		//			 : array[1..4] of word=(0,0,0,0};
+UCHAR tx_pol[5] = {0};		//						 : array[1..4] of byte=(0,0,0,0};
+UCHAR tx_last_pol[5] = {0};		//			  : array[1..4] of byte=(0,0,0,0};
+UCHAR tx_last_diddle[5] = {0};		//     : array[1..4] of byte=(0,0,0,0};
+UCHAR tx_flag_cnt[5] = {0};		//			  : array[1..4] of byte=(0,0,0,0};
+UCHAR tx_frame_status[5] = {0};		//    : array[1..4] of byte=(0,0,0,0};
+UCHAR tx_byte_status[5] = {0};		//     : array[1..4] of byte=(0,0,0,0};
+UCHAR tx_status[5] = {0};		//			     : array[1..4] of byte=(0,0,0,0};
+UCHAR tx_bit_stuff_cnt[5] = {0};		//    : array[1..4] of byte=(0,0,0,0};
+UCHAR tx_bit_cnt[5] = {0};		//			    : array[1..4] of byte=(0,0,0,0};
+UCHAR tx_last_bit[5] = {0};		//			   : array[1..4] of byte=(0,0,0,0};
+UCHAR tx_bit_stream[5] = {0};		//			 : array[1..4] of byte=(0,0,0,0};
+
+UCHAR tx_8PSK[5] = {0};		//						 : array[1..4] of byte=(0,0,0,0};
+UCHAR tx_QPSK[5] = {0};		//						 : array[1..4] of byte=(0,0,0,0};
+
+float tx_I_mod[5] = {1, 1, 1, 1, 1};		//						: array[1..4] of single=(1,1,1,1};
+float tx_Q_mod[5] = {1, 1, 1, 1, 1};		//						: array[1..4] of single=(1,1,1,1};
+float tx_QPSK_avg_I[5] = {0};		//			 : array[1..4] of single=(0,0,0,0};
+float tx_QPSK_avg_Q[5] = {0};		//			 : array[1..4] of single=(0,0,0,0};
+float tx_QPSK_df_I[5] = {0};		//			  : array[1..4] of single=(0,0,0,0};
+float tx_QPSK_df_Q [5] = {0};		//			 : array[1..4] of single=(0,0,0,0};
+float tx_QPSK_I[5] = {0};		//			     : array[1..4] of single=(0,0,0,0};
+float tx_QPSK_Q[5] = {0};		//			     : array[1..4] of single=(0,0,0,0};
+float tx_QPSK_old_I[5] = {0};		//			 : array[1..4] of single=(0,0,0,0};
+float tx_QPSK_old_Q[5] = {0};		//			 : array[1..4] of single=(0,0,0,0};
+float tx_8PSK_avg_I[5] = {0};		//			 : array[1..4] of single=(0,0,0,0};
+float tx_8PSK_avg_Q[5] = {0};		//			 : array[1..4] of single=(0,0,0,0};
+float tx_8PSK_df_I[5] = {0};		//			  : array[1..4] of single=(0,0,0,0};
+float tx_8PSK_df_Q[5] = {0};		//			  : array[1..4] of single=(0,0,0,0};
+float tx_8PSK_I[5] = {0};		//			     : array[1..4] of single=(0,0,0,0};
+float tx_8PSK_Q[5] = {0};		//			     : array[1..4] of single=(0,0,0,0};
+float tx_8PSK_old_I[5] = {0};		//			 : array[1..4] of single=(0,0,0,0};
+float tx_8PSK_old_Q[5] = {0};		//			 : array[1..4] of single=(0,0,0,0};
+
+float tx_osc1[5] = {0};		//						 : array[1..4] of single=(0,0,0,0};
+float tx_osc2[5] = {0};		//						 : array[1..4] of single=(0,0,0,0};
+float tx_osc3[5] = {0};		//						 : array[1..4] of single=(0,0,0,0};
+float tx_osc4[5] = {0};		//						 : array[1..4] of single=(0,0,0,0};
+short tx_inv1[5] = {1, 1, 1, 1, 1};		//						: array[1..4] of shortint=(1,1,1,1};
+short tx_inv2[5] = {1, 1, 1, 1, 1};		//						: array[1..4] of shortint=(1,1,1,1};
+short tx_inv3[5] = {1, 1, 1, 1, 1};		//						: array[1..4] of shortint=(1,1,1,1};
+short tx_inv4[5] = {1, 1, 1, 1, 1};		//						: array[1..4] of shortint=(1,1,1,1};
+short tx_old_inv1[5] = {1, 1, 1, 1, 1};		//			  : array[1..4] of shortint=(1,1,1,1};
+short tx_old_inv2[5] = {1, 1, 1, 1, 1};		//			  : array[1..4] of shortint=(1,1,1,1};
+short tx_old_inv3[5] = {1, 1, 1, 1, 1};		//			  : array[1..4] of shortint=(1,1,1,1};
+short tx_old_inv4[5] = {1, 1, 1, 1, 1};		//			  : array[1..4] of shortint=(1,1,1,1};
+float tx_bit1_mod[5] = {1, 1, 1, 1, 1};		//			  : array[1..4] of single=(1,1,1,1};
+float tx_bit2_mod[5] = {1, 1, 1, 1, 1};		//			  : array[1..4] of single=(1,1,1,1};
+float tx_bit3_mod[5] = {1, 1, 1, 1, 1};		//			  : array[1..4] of single=(1,1,1,1};
+float tx_bit4_mod[5] = {1, 1, 1, 1, 1};		//			  : array[1..4] of single=(1,1,1,1};
+UINT tx_viterbi[5] = {0};		//			     : array[1..4] of word=(0,0,0,0};
+UCHAR tx_intv_tbl[5][4];		//			  : array[1..4,0..3] of byte;
+
+short tx_inv[5] = {1, 1, 1, 1, 1};		//						    : array[1..4] of shortint=(1,1,1,1};
+BOOL tx_change_phase[5] = {0};		//    : array[1..4] of boolean=(FALSE,FALSE,FALSE,FALSE};
+BOOL tx_bs_bit[5] = {0};		//			    : array[1..4] of boolean=(FALSE,FALSE,FALSE,FALSE};
+
+string * tx_data[5] = {0};		//						: array[1..4] of string=('','','',''};
+int tx_data_len[5] = {0};
+
+int tx_fx25_size[4] = { 0, 0, 0, 0 };
+int tx_fx25_size_cnt[4] = { 0, 0, 0, 0 };
+int tx_fx25_mode[4] = { 0, 0, 0, 0 };
+
+
+//  uses sm_main,ax25,ax25_agw,ax25_demod,rsunit;
+
+UCHAR tx_nrzi(UCHAR snd_ch, UCHAR bit)
+{
+//	Debugprintf("Before NRZI %d", bit);
+
+	if (bit == TX_BIT0)
+	{
+		// Zero so switch bit
+
+		tx_last_bit[snd_ch] ^= 1;
+	}
+	return tx_last_bit[snd_ch];	
+}
+
+BOOL tx_bit_stuffing(UCHAR snd_ch, UCHAR bit)
+{					 
+ // result = FALSE;
+ // if bit=TX_BIT1 then inc(tx_bit_stuff_cnt[snd_ch]};
+ // if bit=TX_BIT0 then tx_bit_stuff_cnt[snd_ch] = 0;
+ // if tx_bit_stuff_cnt[snd_ch]=5 then begin tx_bit_stuff_cnt[snd_ch] = 0; result = TRUE; end;
+//end;
+
+	if (bit == TX_BIT1)
+		tx_bit_stuff_cnt[snd_ch]++;
+
+	if (bit == TX_BIT0)
+		tx_bit_stuff_cnt[snd_ch] = 0;
+
+	if (tx_bit_stuff_cnt[snd_ch] == 5)
+	{
+		tx_bit_stuff_cnt[snd_ch] = 0;
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+
+
+void interleave(char *s, int len)
+{
+//	var
+ // data: string;
+ // i,k,len: word;
+ // nr_blocks: word;
+//begin{
+//  data = '';
+ // len = length(s};
+ // if len>0 then nr_blocks = ((len-1} div 16}+1 else nr_blocks = 1;
+ // for i = 1 to 16 do
+ //   for k = 0 to nr_blocks-1 do
+  //   if (i+k*16}<=len then data = data+s[i+k*16];
+ // result = data;
+//end;
+
+	char data[1024];
+
+	UINT i,k;
+	UINT nr_blocks;
+	int n = 0;
+
+	if (len > 0)
+		nr_blocks = ((len - 1) / 16) + 1;
+	else
+		nr_blocks = 1;
+
+	for (i = 0; i < 16; i++)
+	{
+		for (k = 0; k < nr_blocks; k++)
+		{
+			if ((i + k * 16) <= len)
+				data[n++] = s[i + k * 16];
+		}
+	}
+
+	memcpy(s, data, len);
+}
+
+//procedure get_new_frame(snd_ch: byte; var frame_stream: TStringList};
+//var
+//  header,line,temp: string;
+//  len,i,size: word;
+ // crc: word;
+//begin
+
+void get_new_frame(UCHAR snd_ch, TStringList * frame_stream)
+{
+	UCHAR header[256];
+	UCHAR line[1024];
+
+	int LineLen;
+
+	string ** Items;
+
+	string * myTemp;
+
+	UCHAR temp[1024];
+
+	UINT len, i, size;
+	UINT crc;
+
+	tx_bs_bit[snd_ch] = FALSE;
+	tx_bit_cnt[snd_ch] = 0;
+	tx_flag_cnt[snd_ch] = 0;
+	tx_bit_stuff_cnt[snd_ch] = 0;
+	tx_bit_stream[snd_ch] = FRAME_FLAG;
+	tx_frame_status[snd_ch] = FRAME_NEW_FRAME;
+	tx_byte_status[snd_ch] = BYTE_EMPTY;
+
+	if (frame_stream->Count == 0)
+	{
+		tx_frame_status[snd_ch] = FRAME_NO_FRAME;
+		return;
+	}
+
+	// We now pass control byte and ack bytes on front and pointer to socket on end if ackmode
+
+	myTemp = Strings(frame_stream, 0);			// get message
+
+	if ((myTemp->Data[0] & 0x0f) == 12)			// ACKMODE
+	{
+		// Save copy then copy data up 3 bytes
+
+		Add(&KISS_acked[snd_ch], duplicateString(myTemp));
+
+		mydelete(myTemp, 0, 3);
+		myTemp->Length -= sizeof(void *);
+	}
+	else
+	{
+		// Just remove control 
+
+		mydelete(myTemp, 0, 1);
+	}
+
+	tx_data[snd_ch] = duplicateString(myTemp);		// so can free original below
+
+	Delete(frame_stream, 0);			// This will invalidate temp
+
+	AGW_AX25_frame_analiz(snd_ch, FALSE, tx_data[snd_ch]);
+
+	put_frame(snd_ch, tx_data[snd_ch], "", TRUE, FALSE);
+
+	if (tx_data[snd_ch]->Length == 0 || modem_mode[snd_ch] != MODE_MPSK)
+		return;
+
+	// Reformat MPSK Data
+
+	//Take data 8 bytes at a time and add 8 bytes of RS data
+
+	LineLen = 0;
+
+	while (tx_data[snd_ch]->Length > 0)
+	{
+		size = tx_data[snd_ch]->Length;
+
+		if (size > 8)
+			size = 8;
+
+		memcpy(temp, tx_data[snd_ch]->Data, size);
+
+		// Delete the chars from tx_data
+
+		mydelete(tx_data[snd_ch], 0, 8);
+
+		memset(xData, 0, sizeof(xData));
+		memset(xEncoded, 0, sizeof(xEncoded));
+
+		memcpy(xData, temp, size);
+
+		InitBuffers();
+		EncodeRS(xData, xEncoded);			// This puts the 8 RS bytes in xEncoded
+
+		memcpy(&line[LineLen], xData, size);
+		memcpy(&line[LineLen + size], xEncoded, MaxErrors * 2);
+
+		LineLen += size + (MaxErrors * 2);
+	}
+
+
+
+
+	len = LineLen;
+
+	interleave(line, LineLen);
+	scrambler(line, LineLen);
+
+	header[0] = 0x7e;
+	header[1] = 0x7e;
+	header[2] = len >> 8;
+	header[3] = len;
+
+	crc = get_fcs(header, 4);
+
+	header[4] = crc >> 8;
+	header[5] = crc;
+
+	memset(xData, 0, sizeof(xData));
+	memset(xEncoded, 0, sizeof(xEncoded));
+	memmove(xData, header, 6);
+
+
+	//		RSEncode(xData, xEncoded, 6 + (MaxErrors * 2), MaxErrors * 2);
+
+	InitBuffers();
+	EncodeRS(xData, xEncoded);
+
+	fx25_encode_rs(xData, xEncoded, 0, 8);
+
+
+	// We should now have RS Encoded Header in xEncoded;
+
+	// I think we send encoded header then line
+
+	tx_data[snd_ch]->Length = 0;
+
+	stringAdd(tx_data[snd_ch], xData, 6);
+	stringAdd(tx_data[snd_ch], xEncoded, MaxErrors * 2);
+	stringAdd(tx_data[snd_ch], line, LineLen);
+
+	// For testing, descramble and de-interleve
+
+	scrambler(line, LineLen); // should look like interleaved
+	{
+		Byte unscrambled[1024];
+		int count, len;
+		int origlen;
+
+		len = LineLen;
+		count = (len + 15) / 16;
+
+		int j1, j2, j3, i, j;
+
+		j3 = 0;
+
+		for (j1 = 0; j1 < 16; j1++)
+		{
+			// Each char in block
+
+			for (j2 = 0; j2 < count; j2++)
+			{
+				// Blocks
+
+				unscrambled[j2 * 16 + j1] = line[j3];
+				j3++;
+			}
+		}
+
+		// Now remove RS (will check later)
+
+		i = 0;
+		j = 0;
+
+		while (j < len)
+		{
+			Byte line1[256];
+			int nErr, eras_pos = 0;
+			Byte rs_block[256];
+
+			memcpy(line1, &unscrambled[j], 16);
+
+			memset(xEncoded, 0, sizeof(xEncoded));
+			memset(xDecoded, 0, sizeof(xDecoded));
+
+			memcpy(xEncoded, &unscrambled[j], 16);
+
+//			nErr = DecodeRS(xEncoded, xDecoded);
+
+			memset(rs_block, 0, 255);
+			memcpy(rs_block, &unscrambled[j], 8);
+			memcpy(&rs_block[255 - 8], &unscrambled[j+8], 8);
+
+			nErr = fx25_decode_rs(rs_block, &eras_pos, 0, 0, 8);
+
+
+//			line1 = '';
+//			for j1 = MaxErrors * 2 to size - 1 do line1 = line1 + chr(xDecoded[j1]);
+
+
+			memcpy(&unscrambled[i], &unscrambled[j], 8);
+			i += 8;
+			j += 16;
+		}
+
+		j3 = j3;
+
+	}
+
+}
+  
+
+
+int get_new_bit(Byte snd_ch, Byte bit)
+{
+	unsigned short len;
+	string * s;
+
+	if (tx_frame_status[snd_ch] == FRAME_FULL)
+	{
+		if (tx_byte_status[snd_ch] == BYTE_EMPTY)
+		{
+			len = tx_data[snd_ch]->Length;
+
+			if (len > 0)
+			{
+				s = tx_data[snd_ch];
+				tx_bit_stream[snd_ch] = (s->Data[0]);
+				tx_frame_status[snd_ch] = FRAME_FULL;
+				tx_byte_status[snd_ch] = BYTE_FULL;
+				tx_bit_cnt[snd_ch] = 0;
+				mydelete(tx_data[snd_ch], 0, 1);
+			}
+
+			else tx_frame_status[snd_ch] = FRAME_EMPTY;
+		}
+
+		if (tx_byte_status[snd_ch] == BYTE_FULL)
+			bit = tx_bit_stream[snd_ch] & TX_BIT1;
+
+		if (modem_mode[snd_ch] == MODE_MPSK)
+		{
+			tx_bit_cnt[snd_ch]++;
+			tx_bit_stream[snd_ch] = tx_bit_stream[snd_ch] >> 1;
+			if (tx_bit_cnt[snd_ch] >= 8)
+				tx_byte_status[snd_ch] = BYTE_EMPTY;
+
+		}
+		else
+		{
+			if (tx_bs_bit[snd_ch])
+				bit = TX_BIT0;
+
+			tx_bs_bit[snd_ch] = tx_bit_stuffing(snd_ch, bit);
+
+			if (!tx_bs_bit[snd_ch])
+			{
+				tx_bit_cnt[snd_ch]++;
+				tx_bit_stream[snd_ch] >>= 1;
+				if (tx_bit_cnt[snd_ch] >= 8 && !tx_bs_bit[snd_ch])
+					tx_byte_status[snd_ch] = BYTE_EMPTY;
+			}
+		}
+	}
+
+	if (tx_frame_status[snd_ch] == FRAME_EMPTY)
+		get_new_frame(snd_ch, &all_frame_buf[snd_ch]);
+
+	if ((tx_frame_status[snd_ch] == FRAME_NEW_FRAME) || (tx_frame_status[snd_ch] == FRAME_NO_FRAME))
+	{
+		bit = tx_bit_stream[snd_ch] & TX_BIT1;
+		tx_flag_cnt[snd_ch]++;
+		tx_bit_stream[snd_ch] >>= 1;
+
+		if (tx_flag_cnt[snd_ch] == 8)
+		{
+			switch (tx_frame_status[snd_ch])
+			{
+			case FRAME_NEW_FRAME:
+
+				tx_frame_status[snd_ch] = FRAME_FULL;
+				break;
+
+			case FRAME_NO_FRAME:
+
+				tx_tail_cnt[snd_ch] = 0;
+				tx_frame_status[snd_ch] = FRAME_EMPTY;
+				tx_status[snd_ch] = TX_TAIL;
+
+				break;
+			}
+		}
+	}
+	return bit;
+}
+
+////// FX.25 //////
+
+
+void bit_to_fx25(Byte * tx_byte,  Byte * bit_cnt, Byte bit, string * data, int * data_cnt)
+{
+	*tx_byte = (*tx_byte >> 1) | (bit << 7);
+	(*bit_cnt)++;
+
+	if (*bit_cnt == 8)
+	{
+		stringAdd(data, tx_byte, 1);
+		*bit_cnt = 0;
+	}
+	(*data_cnt)++;
+}
+
+string * fill_fx25_data(int snd_ch, string * data)
+{
+#define nr_tags 5
+
+	string * result;
+
+	Byte rs_roots[nr_tags + 1] = { 16, 32, 64, 32, 16, 16 };
+	word rs_payload[nr_tags + 1] = { 1912, 1784, 1528, 1024, 512, 256 }; // 239, 233, 191, 128, 64, 32
+
+	unsigned long long rs_tag[nr_tags + 1] =
+	{
+		0xB74DB7DF8A532F3E,			// 255 / 16 (239)
+		0x6E260B1AC5835FAE,			// 255 / 32 (223)
+		0x3ADB0C13DEAE2836,			// 255 / 64 (191)
+		0xFF94DC634F1CFF4E,			// 160 / 32 (128)
+		0xC7DC0508F3D9B09E,			// 80 / 16	(64)	
+		0x8F056EB4369660EE 		// 48 / 16	(32)
+	};
+
+//	0x26FF60A600CC8FDE) 144; = 16;
+//	0x1EB7B9CDBC09C00E) 96; 32;
+//  0xDBF869BD2DBB1776) 64;= 32;
+//	0xAB69DB6A543188D6) 192; = 64;
+//	0x4A4ABEC4A724B796) 128; = 64;
+
+	string * ax25_data = newString();
+
+	int i, ax25_size;
+	Byte a, bit, bit_cnt, bit_cnt1, bs, tx_byte;
+	Byte rs_id;
+	Byte rs_block[256], parity[256];
+
+	ax25_size = 0;
+	bs = 0;
+	tx_byte = 0;
+	bit_cnt = 0;
+
+	// Load start flag
+	a = FRAME_FLAG;
+
+	for (i = 0; i < 8; i++)
+	{
+		bit = a & 1;
+		a = a >> 1;
+		bit_to_fx25(&tx_byte, &bit_cnt, bit, ax25_data, &ax25_size);
+	}
+
+	// Load body
+	for (i = 0; i < data->Length; i++)
+	{
+		bit_cnt1 = 0;
+		a = data->Data[i];
+		do
+		{
+			if (bs == 5)
+			{
+				bit = TX_BIT0;
+				bs = 0;
+			}
+			else
+			{
+				bit = a & 1;
+				a = a >> 1;
+				bit_cnt1++;
+
+				if (bit == TX_BIT1)
+					bs++;
+				else
+					bs = 0;
+			}
+
+			bit_to_fx25(&tx_byte, &bit_cnt, bit, ax25_data, &ax25_size);
+
+		} while (bit_cnt1 != 8 || bs == 5);
+	}
+
+	// Load close flag
+
+	a = FRAME_FLAG;
+
+	for (i = 0; i < 8; i++)
+	{
+		bit = a & 1;
+		a = a >> 1;
+		bit_to_fx25(&tx_byte, &bit_cnt, bit, ax25_data, &ax25_size);
+	}
+
+	a = FRAME_FLAG;
+
+	// if too short or too long 
+
+	if (ax25_size < 168 || ax25_size > 1912)	// < 21 or > 239
+	{
+		// Send as normal ax25 packet
+
+		if (bit_cnt > 0)
+		{
+			do
+			{
+				tx_byte = tx_byte >> 1;
+				bit_cnt++;
+				if (bit_cnt == 8)
+					stringAdd(ax25_data, &tx_byte, 1);
+			} while (bit_cnt < 8);
+		}
+		tx_fx25_size[snd_ch] = ax25_size;
+		return ax25_data;
+	}
+
+	// Send as FX25 Message
+
+	// find RS block size
+
+	rs_id = 0;
+
+	for (i = 0; i <= nr_tags; i++)
+		if (ax25_size <= rs_payload[i])
+			rs_id = i;
+
+	// Padding to block size
+
+	while (ax25_size != rs_payload[rs_id])
+	{
+		bit = a & 1;
+		a = (a >> 1) | (bit << 7);
+		bit_to_fx25(&tx_byte, &bit_cnt, bit, ax25_data, &ax25_size);
+	}
+
+	memset(rs_block, 0, 255);
+	move(&ax25_data->Data[0], &rs_block[0], ax25_data->Length);
+
+	fx25_encode_rs(rs_block, parity, 0, rs_roots[rs_id]);
+
+	result = newString();
+
+	stringAdd(result, (Byte *)&rs_tag[rs_id], 8);
+	stringAdd(result, ax25_data->Data, ax25_data->Length);
+	stringAdd(result, parity, rs_roots[rs_id]);
+
+	tx_fx25_size[snd_ch] = result->Length << 3;
+
+	freeString(ax25_data);
+	return result;
+}
+
+void fx25_get_new_frame(int snd_ch, TStringList * frame_stream)
+{
+	string * myTemp;
+
+	tx_bs_bit[snd_ch] = 0;
+	tx_bit_cnt[snd_ch] = 0;
+	tx_flag_cnt[snd_ch] = 0;
+	tx_bit_stuff_cnt[snd_ch] = 0;
+	tx_fx25_size_cnt[snd_ch] = 0;
+	tx_fx25_size[snd_ch] = 1;
+	tx_frame_status[snd_ch] = FRAME_NEW_FRAME;
+	tx_byte_status[snd_ch] = BYTE_EMPTY;
+	if (frame_stream->Count == 0)
+		tx_frame_status[snd_ch] = FRAME_NO_FRAME;
+	else
+	{
+		// We now pass control byte and ack bytes on front and pointer to socket on end if ackmode
+
+		myTemp = Strings(frame_stream, 0);			// get message
+
+		if ((myTemp->Data[0] & 0x0f) == 12)			// ACKMODE
+		{
+			// Save copy then copy data up 3 bytes
+
+			Add(&KISS_acked[snd_ch], duplicateString(myTemp));
+
+			mydelete(myTemp, 0, 3);
+			myTemp->Length -= sizeof(void *);
+		}
+		else
+		{
+			// Just remove control 
+
+			mydelete(myTemp, 0, 1);
+		}
+
+		AGW_AX25_frame_analiz(snd_ch, FALSE, myTemp);
+		put_frame(snd_ch, myTemp, "", TRUE, FALSE);
+
+		tx_data[snd_ch] = fill_fx25_data(snd_ch, myTemp);
+
+		Delete(frame_stream, 0);			// This will invalidate temp
+	}
+}
+
+int fx25_get_new_bit(int snd_ch, Byte bit)
+{
+	string *s;
+
+	if (tx_frame_status[snd_ch] == FRAME_EMPTY)
+	{
+		fx25_get_new_frame(snd_ch, &all_frame_buf[snd_ch]);
+		if (tx_frame_status[snd_ch] == FRAME_NEW_FRAME)
+			tx_frame_status[snd_ch] = FRAME_FULL;
+	}
+
+	if (tx_frame_status[snd_ch] == FRAME_FULL)
+	{
+		if (tx_byte_status[snd_ch] == BYTE_EMPTY)
+		{
+			if (tx_data[snd_ch]->Length)
+			{
+				s = tx_data[snd_ch];
+
+				tx_bit_stream[snd_ch] = s->Data[0];
+				tx_frame_status[snd_ch] = FRAME_FULL;
+				tx_byte_status[snd_ch] = BYTE_FULL;
+				tx_bit_cnt[snd_ch] = 0;
+				mydelete(tx_data[snd_ch], 0, 1);
+			}
+			else
+				tx_frame_status[snd_ch] = FRAME_EMPTY;
+		}
+		if (tx_byte_status[snd_ch] == BYTE_FULL)
+		{
+			bit = tx_bit_stream[snd_ch] & TX_BIT1;
+			tx_bit_stream[snd_ch] = tx_bit_stream[snd_ch] >> 1;
+			tx_bit_cnt[snd_ch]++;
+			tx_fx25_size_cnt[snd_ch]++;
+			if (tx_bit_cnt[snd_ch] >= 8)
+				tx_byte_status[snd_ch] = BYTE_EMPTY;
+			if (tx_fx25_size_cnt[snd_ch] == tx_fx25_size[snd_ch])
+				tx_frame_status[snd_ch] = FRAME_EMPTY;
+		}
+	}
+
+	if (tx_frame_status[snd_ch] == FRAME_EMPTY)
+	{
+		fx25_get_new_frame(snd_ch, &all_frame_buf[snd_ch]);
+
+		switch (tx_frame_status[snd_ch])
+		{
+		case FRAME_NEW_FRAME:
+			tx_frame_status[snd_ch] = FRAME_FULL;
+			break;
+
+		case FRAME_NO_FRAME:
+			tx_tail_cnt[snd_ch] = 0;
+			tx_frame_status[snd_ch] = FRAME_EMPTY;
+			tx_status[snd_ch] = TX_TAIL;
+			break;
+		}
+	}
+	return bit;
+}
+
+
+//////////////////
+
+
+int get_new_bit_tail(UCHAR snd_ch, UCHAR bit)
+{
+	long _txtail = 0;
+	UCHAR _diddles;
+
+	if (modem_mode[snd_ch] == MODE_FSK)
+		_diddles = diddles;
+	else
+		_diddles = 0;
+
+	if (modem_mode[snd_ch] == MODE_FSK)
+		_txtail = txtail[snd_ch];
+
+	else if (modem_mode[snd_ch] == MODE_BPSK)
+		_txtail = txtail[snd_ch];
+
+	else if (modem_mode[snd_ch] == MODE_8PSK)
+		_txtail = txtail[snd_ch] * 3;
+
+	else if (modem_mode[snd_ch] == MODE_QPSK || modem_mode[snd_ch] == MODE_PI4QPSK)
+	  _txtail = txtail[snd_ch] << 1;
+  
+	else if (modem_mode[snd_ch] == MODE_MPSK)
+		_txtail = txtail[snd_ch] << 2;
+
+	_txtail = (_txtail * tx_baudrate[snd_ch]) / 1000;
+
+	if (qpsk_set[snd_ch].mode == QPSK_V26 || modem_mode[snd_ch] == MODE_8PSK)
+		_diddles = 2;
+
+	switch (_diddles)
+	{
+	case 0:
+    
+		if (tx_tail_cnt[snd_ch] < _txtail)
+		{
+			bit = TX_BIT0;
+			tx_tail_cnt[snd_ch]++;
+		}
+		else
+		{
+			tx_status[snd_ch] = TX_WAIT_BPF;
+		}
+
+		break;
+
+	case 1:
+
+		if (tx_tail_cnt[snd_ch] < _txtail)
+		{
+			if (tx_last_diddle[snd_ch] == TX_BIT0)
+				bit = TX_BIT1;
+			else
+				bit = TX_BIT0;
+
+			tx_tail_cnt[snd_ch]++;
+			tx_last_diddle[snd_ch] = bit;
+		}
+		else
+		{
+			Debugprintf("End TXTAIL %d", SampleNo);
+			tx_status[snd_ch] = TX_WAIT_BPF;
+		}
+		
+		break;
+	
+	case 2:
+    
+		if (tx_tail_cnt[snd_ch] < _txtail)
+		{
+			bit = FRAME_FLAG >> (tx_tail_cnt[snd_ch] % 8) & 1;
+			tx_tail_cnt[snd_ch]++;
+		}
+		else
+		{
+			Debugprintf("End TXTAIL %d", SampleNo);
+			tx_status[snd_ch] = TX_WAIT_BPF;
+		}
+		break;
+	}
+	return bit;
+}
+
+int get_new_bit_delay(UCHAR snd_ch, UCHAR bit)
+{
+	ULONG _txdelay = 0;
+	UCHAR _diddles;
+
+	_diddles = 0;
+
+	switch (modem_mode[snd_ch])
+	{
+	case MODE_FSK:
+	
+		_diddles = diddles;
+		break;
+
+	case MODE_PI4QPSK:
+	case MODE_8PSK:
+
+		_diddles = 2;
+		break;
+
+    case MODE_QPSK:
+		
+		if (qpsk_set[snd_ch].mode == QPSK_V26)
+			_diddles = 2;
+		break;
+	}
+
+	if (modem_mode[snd_ch] == MODE_FSK)
+		_txdelay = txdelay[snd_ch];
+
+	else if (modem_mode[snd_ch] == MODE_BPSK)
+		_txdelay = txdelay[snd_ch];
+
+	else if (modem_mode[snd_ch] == MODE_8PSK)
+		_txdelay = txdelay[snd_ch] * 3;
+
+	else if (modem_mode[snd_ch] == MODE_QPSK || modem_mode[snd_ch] == MODE_PI4QPSK)
+		_txdelay = txdelay[snd_ch] << 1;
+
+	else if (modem_mode[snd_ch] == MODE_MPSK)
+	{
+		if (txdelay[snd_ch] < 400)
+			_txdelay = 400 << 2;		 //AFC delay
+		else
+			_txdelay = txdelay[snd_ch] << 2;
+	}
+	
+	_txdelay = (_txdelay * tx_baudrate[snd_ch]) / 1000;
+
+	switch (_diddles)
+	{
+	case 0:
+   
+		if (tx_delay_cnt[snd_ch] < _txdelay)
+		{
+			bit = TX_BIT0;
+			tx_delay_cnt[snd_ch]++;
+		}
+		else
+		{
+			tx_status[snd_ch] = TX_FRAME;
+		}
+
+		break;	
+	
+	case 1:
+    
+		if (tx_delay_cnt[snd_ch] < _txdelay)
+		{
+			if (tx_last_diddle[snd_ch] == TX_BIT0)
+				bit = TX_BIT1;
+			else
+				bit = TX_BIT0;
+		
+			tx_delay_cnt[snd_ch]++;
+			tx_last_diddle[snd_ch] = bit;
+		}
+		else
+		{
+			tx_status[snd_ch] = TX_FRAME;
+			Debugprintf("End TXD %d", SampleNo);
+		}
+		break;
+
+	case 2:
+
+		// Send Flags
+
+		if (tx_delay_cnt[snd_ch] < _txdelay)
+		{
+			bit = FRAME_FLAG >> ((8 - (_txdelay % 8) + tx_delay_cnt[snd_ch]) % 8) & 1;
+			tx_delay_cnt[snd_ch]++;
+		}
+		else
+		{
+			tx_status[snd_ch] = TX_FRAME;
+			Debugprintf("End TXD %d", SampleNo);
+		}
+		break;
+	}
+	return bit;
+}
+
+// is this waiting for the filter to fill?
+// No, flushing BPF
+
+void get_wait_bpf(UCHAR snd_ch)
+{
+	tx_BPF_timer[snd_ch]++;
+
+	if (tx_BPF_timer[snd_ch] == tx_BPF_tap[snd_ch] )
+	{
+		tx_status[snd_ch] = TX_NO_DATA;
+		tx_BPF_timer[snd_ch] = 0;
+	}
+}
+
+
+//procedure modulator(snd_ch: byte; var buf: array of single; buf_size: word};
+//{
+/*
+function filter(x,k: single}: single;
+begin
+  result = k*cos(x};
+  if result>1 then result = 1;
+  if result<-1 then result = -1;
+end;
+}
+*/
+
+single filter(single x)
+{
+	if (x <= PI25)
+		return 1.0f;
+
+	if (x >= PI75)
+		return  -1.0f;
+
+     return cosf(2.0f * x -PI5);
+}
+
+
+// make_samples return one sample of the waveform
+
+// But seems to be called only once per bit ??
+
+// No, but needs to preserve bit between calls 
+
+float make_samples(unsigned char  snd_ch, unsigned char * bitptr)
+{
+	float pi2, x1, x;
+	Byte i,qbit,tribit,dibit;
+	float z1,z2,z3,z4;
+	unsigned short b, msb, lsb;
+	unsigned char bit = *bitptr;
+
+	float amp = 0;
+
+	pi2 = 2 * pi / TX_Samplerate;
+	x1 = pi * tx_baudrate[snd_ch] / TX_Samplerate;
+
+	if (modem_mode[snd_ch] == MODE_FSK)
+	{
+		if (bit == TX_BIT0)
+			x = pi2*(tx_freq[snd_ch] + 0.5f * tx_shift[snd_ch]);
+		else
+			x = pi2*(tx_freq[snd_ch] - 0.5f * tx_shift[snd_ch]);
+
+	   amp = 1.0f;
+
+	    if (tx_baudrate[snd_ch] > 600)
+		{
+			if (tx_hitoneraisedb[snd_ch] < 0 && bit == TX_BIT0)
+				amp = tx_hitoneraise[snd_ch];
+
+			if (tx_hitoneraisedb[snd_ch] > 0 && bit == TX_BIT1)
+				amp = tx_hitoneraise[snd_ch];
+		}
+
+		tx_osc[snd_ch] = tx_osc[snd_ch] + x;
+
+		if (tx_osc[snd_ch] > 2*pi)
+			tx_osc[snd_ch] = tx_osc[snd_ch] - 2*pi;
+	}
+
+	else if (modem_mode[snd_ch] == MODE_BPSK)
+	{
+		if (tx_change_phase[snd_ch])
+			tx_bit_mod[snd_ch] = tx_inv[snd_ch] * cos(tx_bit_osc[snd_ch]);
+
+		x = pi2 * (tx_freq[snd_ch]);
+
+		tx_osc[snd_ch] = tx_osc[snd_ch] + x;
+
+		if (tx_osc[snd_ch] > 2 * pi)
+			tx_osc[snd_ch] = tx_osc[snd_ch] - 2 * pi;
+	}
+
+	else if (modem_mode[snd_ch] == MODE_QPSK)
+	{
+		if (tx_QPSK_old_I[snd_ch] != tx_QPSK_I[snd_ch])
+
+			tx_I_mod[snd_ch] = tx_QPSK_avg_I[snd_ch] + tx_QPSK_df_I[snd_ch] * filter(tx_bit_osc[snd_ch]);
+		else
+			tx_I_mod[snd_ch] = tx_QPSK_I[snd_ch];
+
+		if (tx_QPSK_old_Q[snd_ch] != tx_QPSK_Q[snd_ch])
+			tx_Q_mod[snd_ch] = tx_QPSK_avg_Q[snd_ch] + tx_QPSK_df_Q[snd_ch] * filter(tx_bit_osc[snd_ch]);
+		else
+			tx_Q_mod[snd_ch] = tx_QPSK_Q[snd_ch];
+
+		x = pi2 * (tx_freq[snd_ch]);
+		tx_osc[snd_ch] = tx_osc[snd_ch] + x;
+		if (tx_osc[snd_ch] > 2 * pi)
+			tx_osc[snd_ch] = tx_osc[snd_ch] - 2 * pi;
+	}
+  
+	else if (modem_mode[snd_ch] == MODE_8PSK || modem_mode[snd_ch] == MODE_PI4QPSK)
+	{
+		if (tx_8PSK_old_I[snd_ch] != tx_8PSK_I[snd_ch])
+			tx_I_mod[snd_ch] = tx_8PSK_avg_I[snd_ch] + tx_8PSK_df_I[snd_ch] * filter(tx_bit_osc[snd_ch]);
+		else
+			tx_I_mod[snd_ch] = tx_8PSK_I[snd_ch];
+
+		if (tx_8PSK_old_Q[snd_ch] != tx_8PSK_Q[snd_ch])
+			tx_Q_mod[snd_ch] = tx_8PSK_avg_Q[snd_ch] + tx_8PSK_df_Q[snd_ch] * filter(tx_bit_osc[snd_ch]);
+		else
+			tx_Q_mod[snd_ch] = tx_8PSK_Q[snd_ch];
+
+		x = pi2 * (tx_freq[snd_ch]);
+		tx_osc[snd_ch] = tx_osc[snd_ch] + x;
+
+		if (tx_osc[snd_ch] > 2 * pi)
+			tx_osc[snd_ch] = tx_osc[snd_ch] - 2 * pi;
+
+	}
+
+	else if (modem_mode[snd_ch] == MODE_MPSK)
+	{
+		z1 = pi2 * (tx_freq[snd_ch] + ch_offset[0]);
+		z2 = pi2 * (tx_freq[snd_ch] + ch_offset[1]);
+		z3 = pi2 * (tx_freq[snd_ch] + ch_offset[2]);
+		z4 = pi2 * (tx_freq[snd_ch] + ch_offset[3]);
+
+		tx_osc1[snd_ch] = tx_osc1[snd_ch] + z1;
+		tx_osc2[snd_ch] = tx_osc2[snd_ch] + z2;
+		tx_osc3[snd_ch] = tx_osc3[snd_ch] + z3;
+		tx_osc4[snd_ch] = tx_osc4[snd_ch] + z4;
+
+		if (tx_osc1[snd_ch] > 2 * pi)
+			tx_osc1[snd_ch] = tx_osc1[snd_ch] - 2 * pi;
+
+		if (tx_osc2[snd_ch] > 2 * pi)
+			tx_osc2[snd_ch] = tx_osc2[snd_ch] - 2 * pi;
+
+		if (tx_osc3[snd_ch] > 2 * pi)
+			tx_osc3[snd_ch] = tx_osc3[snd_ch] - 2 * pi;
+
+		if (tx_osc4[snd_ch] > 2 * pi)
+			tx_osc4[snd_ch] = tx_osc4[snd_ch] - 2 * pi;
+
+		if (tx_old_inv1[snd_ch] != tx_inv1[snd_ch])
+			tx_bit1_mod[snd_ch] = tx_inv1[snd_ch] * cos(tx_bit_osc[snd_ch]);
+		else
+			tx_bit1_mod[snd_ch] = -tx_inv1[snd_ch];
+
+		if (tx_old_inv2[snd_ch] != tx_inv2[snd_ch])
+			tx_bit2_mod[snd_ch] = tx_inv2[snd_ch] * cos(tx_bit_osc[snd_ch]);
+		else 
+			tx_bit2_mod[snd_ch] = -tx_inv2[snd_ch];
+
+		if (tx_old_inv3[snd_ch] != tx_inv3[snd_ch])
+			tx_bit3_mod[snd_ch] = tx_inv3[snd_ch] * cos(tx_bit_osc[snd_ch]);
+		else 
+			tx_bit3_mod[snd_ch] = -tx_inv3[snd_ch];
+
+		if (tx_old_inv4[snd_ch] != tx_inv4[snd_ch]) 
+			tx_bit4_mod[snd_ch] = tx_inv4[snd_ch] * cos(tx_bit_osc[snd_ch]);
+		else
+			tx_bit4_mod[snd_ch] = -tx_inv4[snd_ch];
+	}
+
+	tx_bit_osc[snd_ch] = tx_bit_osc[snd_ch] + x1;
+
+	if (tx_bit_osc[snd_ch] > pi)
+	{
+		// This seems to get the next bit,
+		// but why?? - end of samples for last bit
+
+		tx_bit_osc[snd_ch] = tx_bit_osc[snd_ch] - pi;
+
+		// FSK Mode
+		if (modem_mode[snd_ch] == MODE_FSK)
+		{
+			bit = 0;
+
+			if (tx_status[snd_ch] == TX_SILENCE)
+			{
+				tx_delay_cnt[snd_ch] = 0;
+				tx_status[snd_ch] = TX_DELAY;
+			}
+	
+			if (il2p_mode[snd_ch] >= IL2P_MODE_TXRX)
+			{
+				// il2p generates TXDELAY as part of the frame, so go straight to TX_FRAME
+			
+				if (tx_status[snd_ch] == TX_DELAY)
+					tx_status[snd_ch] = TX_FRAME;
+	
+				if (tx_status[snd_ch] == TX_TAIL)
+					bit = get_new_bit_tail(snd_ch, bit);
+
+				if (tx_status[snd_ch] == TX_FRAME)
+					bit = il2p_get_new_bit(snd_ch, bit);
+
+
+				// No nrzi for il2p
+
+				*bitptr = bit;
+			}
+			else
+			{
+				// ax25/fx25
+
+				if (tx_status[snd_ch] == TX_DELAY)
+					bit = get_new_bit_delay(snd_ch, bit);
+
+				if (tx_status[snd_ch] == TX_TAIL)
+					bit = get_new_bit_tail(snd_ch, bit);
+
+				if (tx_status[snd_ch] == TX_FRAME)
+				{
+					if (tx_fx25_mode[snd_ch])
+						bit = fx25_get_new_bit(snd_ch, bit);
+					else
+						bit = get_new_bit(snd_ch, bit);
+				}
+				
+				*bitptr = tx_nrzi(snd_ch, bit);
+
+			}
+		}
+
+		// BPSK Mode
+		if (modem_mode[snd_ch] == MODE_BPSK)
+		{
+			bit = 0;
+
+			if (tx_status[snd_ch] == TX_SILENCE)
+			{
+				tx_delay_cnt[snd_ch] = 0;
+				Debugprintf("Start TXD");
+				tx_status[snd_ch] = TX_DELAY;
+			}
+
+
+
+			// il2p generates TXDELAY as part of the frame, so go straight too TX_FRAME
+
+			if (tx_status[snd_ch] == TX_DELAY)
+				if (il2p_mode[snd_ch] >= IL2P_MODE_TXRX)
+					tx_status[snd_ch] = TX_FRAME;
+				else
+					bit = get_new_bit_delay(snd_ch, bit);
+
+			if (tx_status[snd_ch] == TX_TAIL)
+				bit = get_new_bit_tail(snd_ch, bit);
+
+			if (tx_status[snd_ch] == TX_FRAME)
+			{
+				if (il2p_mode[snd_ch] >= IL2P_MODE_TXRX)
+					bit = il2p_get_new_bit(snd_ch, bit);
+				else if (tx_fx25_mode[snd_ch])
+					bit = fx25_get_new_bit(snd_ch, bit);
+				else
+					bit = get_new_bit(snd_ch, bit);
+			}
+			// ??			*bitptr = tx_nrzi(snd_ch, bit);
+
+			if (bit == 0)
+			{
+				tx_inv[snd_ch] = -tx_inv[snd_ch];
+				tx_change_phase[snd_ch] = TRUE;
+			}
+			else
+				tx_change_phase[snd_ch] = FALSE;
+		}
+
+		// QPSK Mode
+
+		else if (modem_mode[snd_ch] == MODE_QPSK)
+		{
+			dibit = 0;
+			for (i = 0; i < 2; i++)
+			{
+				bit = 0;
+				if (tx_status[snd_ch] == TX_SILENCE)
+				{
+					tx_delay_cnt[snd_ch] = 0;
+					tx_status[snd_ch] = TX_DELAY;
+				}
+
+				if (il2p_mode[snd_ch] >= IL2P_MODE_TXRX)
+				{
+					if (tx_status[snd_ch] == TX_DELAY)
+						tx_status[snd_ch] = TX_FRAME;			// il2p generates TXDELAY as part of the frame, so go straight to TX_FRAME
+
+					if (tx_status[snd_ch] == TX_TAIL)
+						bit = get_new_bit_tail(snd_ch, bit);
+
+					if (tx_status[snd_ch] == TX_FRAME)
+						bit = il2p_get_new_bit(snd_ch, bit);
+
+					// No nrzi for il2p
+
+					dibit = (dibit << 1) | bit;
+				}
+				else
+				{
+					// ax25/fx25
+
+					if (tx_status[snd_ch] == TX_DELAY)
+						bit = get_new_bit_delay(snd_ch, bit);
+					if (tx_status[snd_ch] == TX_TAIL)
+						bit = get_new_bit_tail(snd_ch, bit);
+					if (tx_status[snd_ch] == TX_FRAME)
+						bit = get_new_bit(snd_ch, bit);
+					dibit = (dibit << 1) | tx_nrzi(snd_ch, bit);
+
+				}
+			}
+
+
+			dibit = qpsk_set[snd_ch].tx[dibit & 3];
+			tx_QPSK[snd_ch] = (tx_QPSK[snd_ch] + dibit) & 3;
+			tx_QPSK_old_I[snd_ch] = tx_QPSK_I[snd_ch];
+			tx_QPSK_old_Q[snd_ch] = tx_QPSK_Q[snd_ch];
+
+			switch (tx_QPSK[snd_ch])
+			{
+			case 0:
+
+				tx_QPSK_I[snd_ch] = COS45;
+				tx_QPSK_Q[snd_ch] = COS45;
+				break;
+
+			case 1:
+
+				tx_QPSK_I[snd_ch] = -COS45;
+				tx_QPSK_Q[snd_ch] = COS45;
+				break;
+
+			case 2:
+
+				tx_QPSK_I[snd_ch] = -COS45;
+				tx_QPSK_Q[snd_ch] = -COS45;
+				break;
+
+			case 3:
+
+				tx_QPSK_I[snd_ch] = COS45;
+				tx_QPSK_Q[snd_ch] = -COS45;
+				break;
+			}
+
+			tx_QPSK_avg_I[snd_ch] = 0.5f*(tx_QPSK_old_I[snd_ch] + tx_QPSK_I[snd_ch]);
+			tx_QPSK_df_I[snd_ch] = 0.5f*(tx_QPSK_old_I[snd_ch] - tx_QPSK_I[snd_ch]);
+			tx_QPSK_avg_Q[snd_ch] = 0.5f*(tx_QPSK_old_Q[snd_ch] + tx_QPSK_Q[snd_ch]);
+			tx_QPSK_df_Q[snd_ch] = 0.5f*(tx_QPSK_old_Q[snd_ch] - tx_QPSK_Q[snd_ch]);
+		}
+
+		// PI/4 QPSK Mode
+
+		if (modem_mode[snd_ch] == MODE_PI4QPSK)
+		{
+			dibit = 0;
+
+			for (i = 0; i < 2; i++)
+			{
+				bit = 0;
+				if (tx_status[snd_ch] == TX_SILENCE)
+				{
+					tx_delay_cnt[snd_ch] = 0;
+					Debugprintf("Start TXD");
+					tx_status[snd_ch] = TX_DELAY;
+				}
+
+				if (il2p_mode[snd_ch] >= IL2P_MODE_TXRX)
+				{
+					if (tx_status[snd_ch] == TX_DELAY)
+						tx_status[snd_ch] = TX_FRAME;			// il2p generates TXDELAY as part of the frame, so go straight to TX_FRAME
+
+					if (tx_status[snd_ch] == TX_TAIL)
+						bit = get_new_bit_tail(snd_ch, bit);
+
+					if (tx_status[snd_ch] == TX_FRAME)
+						bit = il2p_get_new_bit(snd_ch, bit);
+
+					// No nrzi for il2p
+
+					dibit = (dibit << 1) | bit;
+				}
+				else
+				{
+					// ax25/fx25
+
+					if (tx_status[snd_ch] == TX_DELAY)
+						bit = get_new_bit_delay(snd_ch, bit);
+
+					if (tx_status[snd_ch] == TX_TAIL)
+						bit = get_new_bit_tail(snd_ch, bit);
+
+					if (tx_status[snd_ch] == TX_FRAME)
+						bit = get_new_bit(snd_ch, bit);
+
+					*bitptr = tx_nrzi(snd_ch, bit);
+			
+					dibit = (dibit << 1) | *bitptr;
+				}
+			}
+
+			// This returns 3,1,5 or 7 so we use the odd enties in the 8PSK table
+
+			dibit = gray_PI4QPSK[dibit & 3];
+
+			tx_8PSK[snd_ch] = (tx_8PSK[snd_ch] + dibit) & 7;
+			tx_8PSK_old_I[snd_ch] = tx_8PSK_I[snd_ch];
+			tx_8PSK_old_Q[snd_ch] = tx_8PSK_Q[snd_ch];
+
+			switch (tx_8PSK[snd_ch])
+			{
+			case 0:
+				tx_8PSK_I[snd_ch] = 0;
+				tx_8PSK_Q[snd_ch] = 1;
+				break;
+
+			case 1:
+				tx_8PSK_I[snd_ch] = COS45;
+				tx_8PSK_Q[snd_ch] = COS45;
+				break;
+
+			case 2:
+				tx_8PSK_I[snd_ch] = 1;
+				tx_8PSK_Q[snd_ch] = 0;
+				break;
+
+			case 3:
+				tx_8PSK_I[snd_ch] = COS45;
+				tx_8PSK_Q[snd_ch] = -COS45;
+				break;
+
+			case 4:
+				tx_8PSK_I[snd_ch] = 0;
+				tx_8PSK_Q[snd_ch] = -1;
+				break;
+
+			case 5:
+				tx_8PSK_I[snd_ch] = -COS45;
+				tx_8PSK_Q[snd_ch] = -COS45;
+				break;
+
+			case 6:
+				tx_8PSK_I[snd_ch] = -1;
+				tx_8PSK_Q[snd_ch] = 0;
+				break;
+
+			case 7:
+				tx_8PSK_I[snd_ch] = -COS45;
+				tx_8PSK_Q[snd_ch] = COS45;
+				break;
+
+			}
+
+			tx_8PSK_avg_I[snd_ch] = 0.5*(tx_8PSK_old_I[snd_ch] + tx_8PSK_I[snd_ch]);
+			tx_8PSK_df_I[snd_ch] = 0.5*(tx_8PSK_old_I[snd_ch] - tx_8PSK_I[snd_ch]);
+			tx_8PSK_avg_Q[snd_ch] = 0.5*(tx_8PSK_old_Q[snd_ch] + tx_8PSK_Q[snd_ch]);
+			tx_8PSK_df_Q[snd_ch] = 0.5*(tx_8PSK_old_Q[snd_ch] - tx_8PSK_Q[snd_ch]);
+
+		}
+		
+		// 8PSK Mode
+
+		if (modem_mode[snd_ch] == MODE_8PSK)
+		{
+			tribit = 0;
+			for (i = 0; i < 3; i++)
+			{
+				bit = 0;
+
+				if (tx_status[snd_ch] == TX_SILENCE)
+				{
+					tx_delay_cnt[snd_ch] = 0;
+					tx_status[snd_ch] = TX_DELAY;
+				}
+
+				if (il2p_mode[snd_ch] >= IL2P_MODE_TXRX)
+				{
+					if (tx_status[snd_ch] == TX_DELAY)
+						tx_status[snd_ch] = TX_FRAME;			// il2p generates TXDELAY as part of the frame, so go straight to TX_FRAME
+
+					if (tx_status[snd_ch] == TX_TAIL)
+						bit = get_new_bit_tail(snd_ch, bit);
+
+					if (tx_status[snd_ch] == TX_FRAME)
+						bit = il2p_get_new_bit(snd_ch, bit);
+
+					// No nrzi for il2p
+
+					tribit = (tribit << 1) | bit;
+				}
+				else
+				{
+					// ax25/fx25
+
+					if (tx_status[snd_ch] == TX_DELAY)
+						bit = get_new_bit_delay(snd_ch, bit);
+					if (tx_status[snd_ch] == TX_TAIL)
+						bit = get_new_bit_tail(snd_ch, bit);
+					if (tx_status[snd_ch] == TX_FRAME)
+						bit = get_new_bit(snd_ch, bit);
+
+					tribit = (tribit << 1) | tx_nrzi(snd_ch, bit);
+				}
+			}
+			tribit = gray_8PSK[tribit & 7];
+
+			tx_8PSK[snd_ch] = (tx_8PSK[snd_ch] + tribit) & 7;
+			tx_8PSK_old_I[snd_ch] = tx_8PSK_I[snd_ch];
+			tx_8PSK_old_Q[snd_ch] = tx_8PSK_Q[snd_ch];
+
+			switch (tx_8PSK[snd_ch])
+			{
+			case 0:
+
+				tx_8PSK_I[snd_ch] = 0;
+				tx_8PSK_Q[snd_ch] = 1;
+				break;
+
+			case 1:
+
+				tx_8PSK_I[snd_ch] = COS45;
+				tx_8PSK_Q[snd_ch] = COS45;
+				break;
+
+			case 2:
+
+				tx_8PSK_I[snd_ch] = 1;
+				tx_8PSK_Q[snd_ch] = 0;
+				break;
+
+			case 3:
+
+				tx_8PSK_I[snd_ch] = COS45;
+				tx_8PSK_Q[snd_ch] = -COS45;
+				break;
+
+			case 4:
+
+				tx_8PSK_I[snd_ch] = 0;
+				tx_8PSK_Q[snd_ch] = -1;
+				break;
+
+			case 5:
+
+				tx_8PSK_I[snd_ch] = -COS45;
+				tx_8PSK_Q[snd_ch] = -COS45;
+				break;
+
+			case 6:
+
+				tx_8PSK_I[snd_ch] = -1;
+				tx_8PSK_Q[snd_ch] = 0;
+				break;
+
+			case 7:
+
+				tx_8PSK_I[snd_ch] = -COS45;
+				tx_8PSK_Q[snd_ch] = COS45;
+				break;
+
+			}
+
+			tx_8PSK_avg_I[snd_ch] = 0.5f*(tx_8PSK_old_I[snd_ch] + tx_8PSK_I[snd_ch]);
+			tx_8PSK_df_I[snd_ch] = 0.5f*(tx_8PSK_old_I[snd_ch] - tx_8PSK_I[snd_ch]);
+			tx_8PSK_avg_Q[snd_ch] = 0.5f*(tx_8PSK_old_Q[snd_ch] + tx_8PSK_Q[snd_ch]);
+			tx_8PSK_df_Q[snd_ch] = 0.5f*(tx_8PSK_old_Q[snd_ch] - tx_8PSK_Q[snd_ch]);
+		}
+					
+		if (modem_mode[snd_ch] == MODE_MPSK)
+		{
+			qbit = 0;
+
+			// get the bits for each of 4 carriers
+
+			for (i = 1; i <= 4; i++)
+			{
+				bit = 0;
+
+				if (tx_status[snd_ch] == TX_SILENCE)
+				{
+					tx_delay_cnt[snd_ch] = 0;
+					Debugprintf("Start TXD");
+					tx_status[snd_ch] = TX_DELAY;
+				}
+
+				if (tx_status[snd_ch] == TX_DELAY)
+					bit = get_new_bit_delay(snd_ch, bit);
+
+				if (tx_status[snd_ch] == TX_TAIL)
+					bit = get_new_bit_tail(snd_ch, bit);
+
+				if (tx_status[snd_ch] == TX_FRAME)
+					bit = get_new_bit(snd_ch, bit);
+
+				qbit = (qbit << 1) | bit;
+			}
+
+			tx_old_inv1[snd_ch] = tx_inv1[snd_ch];
+			tx_old_inv2[snd_ch] = tx_inv2[snd_ch];
+			tx_old_inv3[snd_ch] = tx_inv3[snd_ch];
+			tx_old_inv4[snd_ch] = tx_inv4[snd_ch];
+
+			if ((qbit & 8) == 0)
+				tx_inv1[snd_ch] = -tx_inv1[snd_ch];
+			if ((qbit & 4) == 0)
+				tx_inv2[snd_ch] = -tx_inv2[snd_ch];
+			if ((qbit & 2) == 0)
+				tx_inv3[snd_ch] = -tx_inv3[snd_ch];
+			if ((qbit & 1) == 0)
+				tx_inv4[snd_ch] = -tx_inv4[snd_ch];
+
+		}
+	}
+
+	if (tx_status[snd_ch] == TX_WAIT_BPF)
+		get_wait_bpf(snd_ch);
+
+	if (modem_mode[snd_ch] == MODE_FSK)
+		return amp * sinf(tx_osc[snd_ch]);
+
+	if (modem_mode[snd_ch] == MODE_BPSK)
+		return sinf(tx_osc[snd_ch]) * tx_bit_mod[snd_ch];
+
+	if (modem_mode[snd_ch] == MODE_QPSK || modem_mode[snd_ch] == MODE_8PSK || modem_mode[snd_ch] == MODE_PI4QPSK)
+		return sin(tx_osc[snd_ch]) * tx_I_mod[snd_ch] + cos(tx_osc[snd_ch]) * tx_Q_mod[snd_ch];
+
+	if (modem_mode[snd_ch] == MODE_MPSK)
+		return 0.35*(sinf(tx_osc1[snd_ch])*tx_bit1_mod[snd_ch] +
+			sinf(tx_osc2[snd_ch])*tx_bit2_mod[snd_ch] +
+			sinf(tx_osc3[snd_ch])*tx_bit3_mod[snd_ch] +
+			sinf(tx_osc4[snd_ch])*tx_bit4_mod[snd_ch]);
+
+	return 0.0f;
+}
+
+float make_samples_calib(UCHAR snd_ch, UCHAR tones)
+{
+	float amp, pi2, x, x1;
+
+	x1 = pi * tx_baudrate[snd_ch] / TX_Samplerate;
+	pi2 = 2 * pi / TX_Samplerate;
+
+	switch (tones)
+	{
+	case 1:
+
+		tx_last_bit[snd_ch] = 1;
+		break;
+
+	case 2:
+
+		tx_last_bit[snd_ch] = 0;
+		break;
+
+	case 3:
+
+		tx_bit_osc[snd_ch] = tx_bit_osc[snd_ch] + x1;
+
+		if (tx_bit_osc[snd_ch] > pi)
+		{
+			tx_bit_osc[snd_ch] = tx_bit_osc[snd_ch] - pi;
+			tx_last_bit[snd_ch] = tx_last_bit[snd_ch] ^ 1;
+		}
+		break;
+	}
+
+	amp = 1;
+
+	if (tx_baudrate[snd_ch] > 600)
+	{
+		if (tx_hitoneraisedb[snd_ch] < 0 && tx_last_bit[snd_ch] == 0)
+			amp = tx_hitoneraise[snd_ch];
+
+		if (tx_hitoneraisedb[snd_ch] > 0 && tx_last_bit[snd_ch] == 1)
+			amp = tx_hitoneraise[snd_ch];
+	}
+	
+	if (tx_last_bit[snd_ch] == 0)
+		x = pi2*(tx_freq[snd_ch] + 0.5f * tx_shift[snd_ch]);
+	else
+		x = pi2*(tx_freq[snd_ch] - 0.5f * tx_shift[snd_ch]);
+
+	tx_osc[snd_ch] = tx_osc[snd_ch] + x;
+	
+	if (tx_osc[snd_ch] > 2*pi)
+		tx_osc[snd_ch] = tx_osc[snd_ch] - 2 * pi;
+
+	return amp * sinf(tx_osc[snd_ch]);
+}
+
+int amplitude = 22000;
+
+void modulator(UCHAR snd_ch, int buf_size)
+{
+	// We feed samples to samplesink instead of buffering them
+
+	// I think this is the top of the TX hierarchy
+
+	int i;
+	short Sample;
+
+	if (calib_mode[snd_ch] > 0)
+	{
+		if (calib_mode[snd_ch] == 4)		// CWID
+		{
+			if (tx_status[snd_ch] == TX_SILENCE)
+			{
+				SoundIsPlaying = TRUE;
+				Debugprintf("Start CWID Chan %d", snd_ch);
+				RadioPTT(snd_ch, 1);
+
+				tx_status[snd_ch] = 6;
+			}
+
+			if (ARDOPSendToCard(snd_ch, SendSize) == 1)
+			{
+				// End of TX
+
+				tx_status[snd_ch] = TX_SILENCE;		// Stop TX
+				Flush();
+				RadioPTT(snd_ch, 0);
+				Debugprintf("End CWID");
+				calib_mode[snd_ch] = 0;
+			}
+			return;
+		}
+
+
+		if (tx_status[snd_ch] == TX_SILENCE)
+		{
+			SoundIsPlaying = TRUE;
+			Debugprintf("Start Calib Chan %d", snd_ch);
+			RadioPTT(snd_ch, 1);
+
+			tx_bit_osc[snd_ch] = 0;
+			tx_last_bit[snd_ch] = 0;
+	
+			// fill filter 
+
+			for (i = 0; i < tx_BPF_tap[snd_ch]; i++)
+				tx_prev_BPF_buf[snd_ch][buf_size + i] = make_samples_calib(snd_ch,calib_mode[snd_ch]);
+		}
+		tx_status[snd_ch] = TX_WAIT_BPF;
+	
+		for (i = 0; i < buf_size; i++)
+			tx_src_BPF_buf[snd_ch][i] = make_samples_calib(snd_ch, calib_mode[snd_ch]);
+
+		FIR_filter(tx_src_BPF_buf[snd_ch],buf_size,tx_BPF_tap[snd_ch],tx_BPF_core[snd_ch],tx_BPF_buf[snd_ch],tx_prev_BPF_buf[snd_ch]);
+    
+		for (i = 0; i < buf_size; i++)
+		{
+			Sample = tx_BPF_buf[snd_ch][i] * amplitude;
+			SampleSink(modemtoSoundLR[snd_ch], Sample);
+		}
+	}
+	else
+	{
+		if (tx_status[snd_ch] == TX_SILENCE)
+		{
+			if (fx25_mode[snd_ch] == FX25_MODE_TXRX)
+				tx_fx25_mode[snd_ch] = 1;
+			else
+				tx_fx25_mode[snd_ch] = 0;
+
+			tx_bit_osc[snd_ch] = 0;
+			tx_8PSK[snd_ch] = 0;
+			tx_QPSK[snd_ch] = 0;
+			tx_last_bit[snd_ch] = 0;
+			tx_inv1[snd_ch] = 1;
+			tx_inv2[snd_ch] = 1;
+			tx_inv3[snd_ch] = 1;
+			tx_inv4[snd_ch] = 1;
+			tx_8PSK_I[snd_ch] =  0;
+			tx_8PSK_Q[snd_ch] =  1;
+			tx_8PSK_old_I[snd_ch] =  0;
+			tx_8PSK_old_Q[snd_ch] =  1;
+			tx_QPSK_I[snd_ch] =  COS45;
+			tx_QPSK_Q[snd_ch] =  COS45;
+			tx_QPSK_old_I[snd_ch] =  COS45;
+			tx_QPSK_old_Q[snd_ch] =  COS45;
+
+			for (i = 0; i < tx_BPF_tap[snd_ch]; i++)
+				tx_prev_BPF_buf[snd_ch][buf_size+i] = make_samples(snd_ch, &tx_pol[snd_ch]);
+		}
+		
+		for (i = 0; i < buf_size; i++)
+			tx_src_BPF_buf[snd_ch][i] = make_samples(snd_ch, &tx_pol[snd_ch]);
+		
+		FIR_filter(tx_src_BPF_buf[snd_ch], buf_size, tx_BPF_tap[snd_ch], tx_BPF_core[snd_ch], tx_BPF_buf[snd_ch], tx_prev_BPF_buf[snd_ch]);
+
+		for (i = 0; i < buf_size; i++)
+		{
+			Sample = tx_BPF_buf[snd_ch][i] * 20000.0f;
+			SampleSink(modemtoSoundLR[snd_ch], Sample);
+		}
+	}
+}
+
+
diff --git a/ax25_mod.c b/ax25_mod.c
index 64d21e1..f8fd4bc 100644
--- a/ax25_mod.c
+++ b/ax25_mod.c
@@ -45,6 +45,7 @@ extern UCHAR modem_mode[];
 #define sbc 175
 
 extern single  ch_offset[4];
+int Continuation[4] = { 0, 0, 0, 0 };	// Sending 2nd or more packet of burst
 
 #define COS45 0.70710676908493f
 
@@ -89,7 +90,8 @@ float tx_osc[5] = {0};		//						 : array[1..4] of single=(0,0,0,0};
 float tx_bit_osc[5] = {0};		//			   : array[1..4] of single=(0,0,0,0};
 unsigned short txbpf[5] = { 400, 400, 400, 400, 400};		//						  : array[1..4] of word=(400,400,400,400};
 unsigned short  tx_BPF_tap[5] = { 256, 256, 256, 256, 256};		//			   : array[1..4] of word=(256,256,256,256};
-unsigned short  tx_baudrate[5] = { 300, 300, 300, 300, 300};		//			  : array[1..4] of word=(300,300,300,300};
+unsigned short  tx_baudrate[5] = { 300, 300, 300, 300, 300 };		//			  : array[1..4] of word=(300,300,300,300};
+unsigned short  tx_bitrate[5] = { 300, 300, 300, 300, 300 };		//			  : array[1..4] of word=(300,300,300,300};
 unsigned short  tx_BPF_timer[5] = {0};		//			 : array[1..4] of word=(0,0,0,0};
 UCHAR tx_pol[5] = {0};		//						 : array[1..4] of byte=(0,0,0,0};
 UCHAR tx_last_pol[5] = {0};		//			  : array[1..4] of byte=(0,0,0,0};
@@ -1191,9 +1193,11 @@ float make_samples(unsigned char  snd_ch, unsigned char * bitptr)
 	
 			if (il2p_mode[snd_ch] >= IL2P_MODE_TXRX)
 			{
+				// il2p generates TXDELAY as part of the frame, so go straight to TX_FRAME
+			
 				if (tx_status[snd_ch] == TX_DELAY)
-					tx_status[snd_ch] = TX_FRAME;			// il2p generates TXDELAY as part of the frame, so go straight to TX_FRAME
-
+					tx_status[snd_ch] = TX_FRAME;
+	
 				if (tx_status[snd_ch] == TX_TAIL)
 					bit = get_new_bit_tail(snd_ch, bit);
 
@@ -1240,6 +1244,8 @@ float make_samples(unsigned char  snd_ch, unsigned char * bitptr)
 				tx_status[snd_ch] = TX_DELAY;
 			}
 
+
+
 			// il2p generates TXDELAY as part of the frame, so go straight too TX_FRAME
 
 			if (tx_status[snd_ch] == TX_DELAY)
@@ -1273,7 +1279,7 @@ float make_samples(unsigned char  snd_ch, unsigned char * bitptr)
 
 		// QPSK Mode
 
-		if (modem_mode[snd_ch] == MODE_QPSK)
+		else if (modem_mode[snd_ch] == MODE_QPSK)
 		{
 			dibit = 0;
 			for (i = 0; i < 2; i++)
@@ -1284,13 +1290,35 @@ float make_samples(unsigned char  snd_ch, unsigned char * bitptr)
 					tx_delay_cnt[snd_ch] = 0;
 					tx_status[snd_ch] = TX_DELAY;
 				}
-				if (tx_status[snd_ch] == TX_DELAY)
-					bit = get_new_bit_delay(snd_ch, bit);
-				if (tx_status[snd_ch] == TX_TAIL)
-					bit = get_new_bit_tail(snd_ch, bit);
-				if (tx_status[snd_ch] == TX_FRAME)
-					bit = get_new_bit(snd_ch, bit);
-				dibit = (dibit << 1) | tx_nrzi(snd_ch, bit);
+
+				if (il2p_mode[snd_ch] >= IL2P_MODE_TXRX)
+				{
+					if (tx_status[snd_ch] == TX_DELAY)
+						tx_status[snd_ch] = TX_FRAME;			// il2p generates TXDELAY as part of the frame, so go straight to TX_FRAME
+
+					if (tx_status[snd_ch] == TX_TAIL)
+						bit = get_new_bit_tail(snd_ch, bit);
+
+					if (tx_status[snd_ch] == TX_FRAME)
+						bit = il2p_get_new_bit(snd_ch, bit);
+
+					// No nrzi for il2p
+
+					dibit = (dibit << 1) | bit;
+				}
+				else
+				{
+					// ax25/fx25
+
+					if (tx_status[snd_ch] == TX_DELAY)
+						bit = get_new_bit_delay(snd_ch, bit);
+					if (tx_status[snd_ch] == TX_TAIL)
+						bit = get_new_bit_tail(snd_ch, bit);
+					if (tx_status[snd_ch] == TX_FRAME)
+						bit = get_new_bit(snd_ch, bit);
+					dibit = (dibit << 1) | tx_nrzi(snd_ch, bit);
+
+				}
 			}
 
 
@@ -1348,19 +1376,38 @@ float make_samples(unsigned char  snd_ch, unsigned char * bitptr)
 					tx_status[snd_ch] = TX_DELAY;
 				}
 
-				if (tx_status[snd_ch] == TX_DELAY)
-					bit = get_new_bit_delay(snd_ch, bit);
+				if (il2p_mode[snd_ch] >= IL2P_MODE_TXRX)
+				{
+					if (tx_status[snd_ch] == TX_DELAY)
+						tx_status[snd_ch] = TX_FRAME;			// il2p generates TXDELAY as part of the frame, so go straight to TX_FRAME
 
-				if (tx_status[snd_ch] == TX_TAIL)
-					bit = get_new_bit_tail(snd_ch, bit);
+					if (tx_status[snd_ch] == TX_TAIL)
+						bit = get_new_bit_tail(snd_ch, bit);
 
-				if (tx_status[snd_ch] == TX_FRAME)
-					bit = get_new_bit(snd_ch, bit);
+					if (tx_status[snd_ch] == TX_FRAME)
+						bit = il2p_get_new_bit(snd_ch, bit);
 
-				*bitptr = tx_nrzi(snd_ch, bit);
+					// No nrzi for il2p
 
-				dibit = (dibit << 1) | *bitptr;
+					dibit = (dibit << 1) | bit;
+				}
+				else
+				{
+					// ax25/fx25
 
+					if (tx_status[snd_ch] == TX_DELAY)
+						bit = get_new_bit_delay(snd_ch, bit);
+
+					if (tx_status[snd_ch] == TX_TAIL)
+						bit = get_new_bit_tail(snd_ch, bit);
+
+					if (tx_status[snd_ch] == TX_FRAME)
+						bit = get_new_bit(snd_ch, bit);
+
+					*bitptr = tx_nrzi(snd_ch, bit);
+			
+					dibit = (dibit << 1) | *bitptr;
+				}
 			}
 
 			// This returns 3,1,5 or 7 so we use the odd enties in the 8PSK table
@@ -1436,16 +1483,36 @@ float make_samples(unsigned char  snd_ch, unsigned char * bitptr)
 					tx_delay_cnt[snd_ch] = 0;
 					tx_status[snd_ch] = TX_DELAY;
 				}
-				if (tx_status[snd_ch] == TX_DELAY)
-					bit = get_new_bit_delay(snd_ch, bit);
-				if (tx_status[snd_ch] == TX_TAIL)
-					bit = get_new_bit_tail(snd_ch, bit);
-				if (tx_status[snd_ch] == TX_FRAME)
-					bit = get_new_bit(snd_ch, bit);
 
-				tribit = (tribit << 1) | tx_nrzi(snd_ch, bit);
+				if (il2p_mode[snd_ch] >= IL2P_MODE_TXRX)
+				{
+					if (tx_status[snd_ch] == TX_DELAY)
+						tx_status[snd_ch] = TX_FRAME;			// il2p generates TXDELAY as part of the frame, so go straight to TX_FRAME
+
+					if (tx_status[snd_ch] == TX_TAIL)
+						bit = get_new_bit_tail(snd_ch, bit);
+
+					if (tx_status[snd_ch] == TX_FRAME)
+						bit = il2p_get_new_bit(snd_ch, bit);
+
+					// No nrzi for il2p
+
+					tribit = (tribit << 1) | bit;
+				}
+				else
+				{
+					// ax25/fx25
+
+					if (tx_status[snd_ch] == TX_DELAY)
+						bit = get_new_bit_delay(snd_ch, bit);
+					if (tx_status[snd_ch] == TX_TAIL)
+						bit = get_new_bit_tail(snd_ch, bit);
+					if (tx_status[snd_ch] == TX_FRAME)
+						bit = get_new_bit(snd_ch, bit);
+
+					tribit = (tribit << 1) | tx_nrzi(snd_ch, bit);
+				}
 			}
-
 			tribit = gray_8PSK[tribit & 7];
 
 			tx_8PSK[snd_ch] = (tx_8PSK[snd_ch] + tribit) & 7;
@@ -1632,7 +1699,7 @@ float make_samples_calib(UCHAR snd_ch, UCHAR tones)
 	return amp * sinf(tx_osc[snd_ch]);
 }
 
-int amplitude = 22000;
+float amplitude = 32000;
 
 void modulator(UCHAR snd_ch, int buf_size)
 {
@@ -1641,7 +1708,7 @@ void modulator(UCHAR snd_ch, int buf_size)
 	// I think this is the top of the TX hierarchy
 
 	int i;
-	short Sample;
+	int Sample;
 
 	if (calib_mode[snd_ch] > 0)
 	{
@@ -1694,6 +1761,12 @@ void modulator(UCHAR snd_ch, int buf_size)
 		for (i = 0; i < buf_size; i++)
 		{
 			Sample = tx_BPF_buf[snd_ch][i] * amplitude;
+
+			if (Sample < txmin)
+				txmin = Sample;
+			else if (Sample > txmax)
+				txmax = Sample;
+
 			SampleSink(modemtoSoundLR[snd_ch], Sample);
 		}
 	}
@@ -1734,7 +1807,22 @@ void modulator(UCHAR snd_ch, int buf_size)
 
 		for (i = 0; i < buf_size; i++)
 		{
-			Sample = tx_BPF_buf[snd_ch][i] * 20000.0f;
+			Sample = tx_BPF_buf[snd_ch][i] * amplitude;
+
+			if (Sample < txmin)
+				txmin = Sample;
+			else if (Sample > txmax)
+			{
+				txmax = Sample;
+
+				if (txmax > 32767)
+				{
+					amplitude = amplitude * 32767 / txmax;
+					txmax = 32767;
+					Sample = 32767;
+				}
+			}
+
 			SampleSink(modemtoSoundLR[snd_ch], Sample);
 		}
 	}
diff --git a/ax25_pad2.h b/ax25_pad2.h
new file mode 100644
index 0000000..c6dc17a
--- /dev/null
+++ b/ax25_pad2.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------
+ *
+ * Name:	ax25_pad2.h
+ *
+ * Purpose:	Header file for using ax25_pad2.c
+ *		ax25_pad dealt only with UI frames.
+ *		This adds a facility for the other types: U, s, I.
+ *
+ *------------------------------------------------------------------*/
+
+#ifndef AX25_PAD2_H
+#define AX25_PAD2_H 1
+
+#include "ax25_pad.h"
+
+
+
+
+#if AX25MEMDEBUG	// to investigate a memory leak problem
+
+
+
+packet_t ax25_u_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line);
+
+packet_t ax25_s_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, unsigned char *pinfo, int info_len, char *src_file, int src_line);
+
+packet_t ax25_i_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line);
+
+
+#define ax25_u_frame(a,n,c,f,p,q,i,l) ax25_u_frame_debug(a,n,c,f,p,q,i,l,__FILE__,__LINE__)
+
+#define ax25_s_frame(a,n,c,f,m,r,p,i,l) ax25_s_frame_debug(a,n,c,f,m,r,p,i,l,__FILE__,__LINE__)
+
+#define ax25_i_frame(a,n,c,m,r,s,p,q,i,l) ax25_i_frame_debug(a,n,c,m,r,s,p,q,i,l,__FILE__,__LINE__)
+
+
+#else
+
+packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len);
+
+packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, unsigned char *pinfo, int info_len);
+
+packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len);
+
+
+#endif
+
+
+
+
+#endif /* AX25_PAD2_H */
+
+/* end ax25_pad2.h */
+
+
diff --git a/beacon.h b/beacon.h
new file mode 100644
index 0000000..f7d2a56
--- /dev/null
+++ b/beacon.h
@@ -0,0 +1,6 @@
+
+/* beacon.h */
+
+void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct igate_config_s *pigate);
+
+void beacon_tracker_set_debug (int level);
diff --git a/cdigipeater.h b/cdigipeater.h
new file mode 100644
index 0000000..69a4b8c
--- /dev/null
+++ b/cdigipeater.h
@@ -0,0 +1,62 @@
+
+
+#ifndef CDIGIPEATER_H
+#define CDIGIPEATER_H 1
+
+#include "regex.h"
+
+#include "direwolf.h"		/* for MAX_CHANS */
+#include "ax25_pad.h"		/* for packet_t */
+#include "audio.h"		/* for radio channel properties */
+
+
+/*
+ * Information required for Connected mode digipeating.
+ *
+ * The configuration file reader fills in this information
+ * and it is passed to cdigipeater_init at application start up time.
+ */
+
+
+struct cdigi_config_s {
+
+/*
+ * Rules for each of the [from_chan][to_chan] combinations.
+ */
+	int	enabled[MAX_CHANS][MAX_CHANS];	// Is it enabled for from/to pair?
+
+	int has_alias[MAX_CHANS][MAX_CHANS];	// If there was no alias in the config file,
+						// the structure below will not be set up
+						// properly and an attempt to use it could
+						// result in a crash.  (fixed v1.5)
+						// Not needed for [APRS] DIGIPEAT because
+						// the alias is mandatory there.
+	regex_t	alias[MAX_CHANS][MAX_CHANS];
+
+	char *cfilter_str[MAX_CHANS][MAX_CHANS];
+						// NULL or optional Packet Filter strings such as "t/m".
+};
+
+/*
+ * Call once at application start up time.
+ */
+
+extern void cdigipeater_init (struct audio_s *p_audio_config, struct cdigi_config_s *p_cdigi_config);
+
+/*
+ * Call this for each packet received.
+ * Suitable packets will be queued for transmission.
+ */
+
+extern void cdigipeater (int from_chan, packet_t pp);
+
+
+/* Make statistics available. */
+
+int cdigipeater_get_count (int from_chan, int to_chan);
+
+
+#endif 
+
+/* end cdigipeater.h */
+
diff --git a/cm108.h b/cm108.h
new file mode 100644
index 0000000..2def77a
--- /dev/null
+++ b/cm108.h
@@ -0,0 +1,5 @@
+/* Dire Wolf cm108.h */
+
+extern void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_device_size);
+
+extern int cm108_set_gpio_pin (char *name, int num, int state);
\ No newline at end of file
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..360ac49
--- /dev/null
+++ b/config.h
@@ -0,0 +1,262 @@
+
+/*----------------------------------------------------------------------------
+ * 
+ * Name:	config.h
+ *
+ * Purpose:	
+ *
+ * Description:	
+ *
+ *-----------------------------------------------------------------------------*/
+
+
+#ifndef CONFIG_H
+#define CONFIG_H 1
+
+#include "audio.h"		/* for struct audio_s */
+#include "digipeater.h"		/* for struct digi_config_s */
+#include "cdigipeater.h"		/* for struct cdigi_config_s */
+#include "aprs_tt.h"		/* for struct tt_config_s */
+#include "igate.h"		/* for struct igate_config_s */
+
+/*
+ * All the leftovers.
+ * This wasn't thought out.  It just happened.
+ */
+
+enum beacon_type_e { BEACON_IGNORE, BEACON_POSITION, BEACON_OBJECT, BEACON_TRACKER, BEACON_CUSTOM, BEACON_IGATE };
+
+enum sendto_type_e { SENDTO_XMIT, SENDTO_IGATE, SENDTO_RECV };
+
+
+#define MAX_BEACONS 30
+#define MAX_KISS_TCP_PORTS (MAX_CHANS+1)
+
+struct misc_config_s {
+
+	int agwpe_port;		/* TCP Port number for the "AGW TCPIP Socket Interface" */
+
+	// Previously we allowed only a single TCP port for KISS.
+	// An increasing number of people want to run multiple radios.
+	// Unfortunately, most applications don't know how to deal with multi-radio TNCs.
+	// They ignore the channel on receive and always transmit to channel 0.
+	// Running multiple instances of direwolf is a work-around but this leads to
+	// more complex configuration and we lose the cross-channel digipeating capability.
+	// In release 1.7 we add a new feature to assign a single radio channel to a TCP port.
+	// e.g.
+	//	KISSPORT 8001		# default, all channels.  Radio channel = KISS channel.
+	//
+	//	KISSPORT 7000 0		# Only radio channel 0 for receive.
+	//				# Transmit to radio channel 0, ignoring KISS channel.
+	//
+	//	KISSPORT 7001 1		# Only radio channel 1 for receive.  KISS channel set to 0.
+	//				# Transmit to radio channel 1, ignoring KISS channel.
+
+	int kiss_port[MAX_KISS_TCP_PORTS];	/* TCP Port number for the "TCP KISS" protocol. */
+	int kiss_chan[MAX_KISS_TCP_PORTS];	/* Radio Channel number for this port or -1 for all.  */
+
+	int kiss_copy;		/* Data from network KISS client is copied to all others. */
+	int enable_kiss_pt;	/* Enable pseudo terminal for KISS. */
+				/* Want this to be off by default because it hangs */
+				/* after a while if nothing is reading from other end. */
+
+	char kiss_serial_port[20];
+				/* Serial port name for our end of the */
+				/* virtual null modem for native Windows apps. */
+				/* Version 1.5 add same capability for Linux. */
+
+	int kiss_serial_speed;	/* Speed, in bps, for the KISS serial port. */
+				/* If 0, just leave what was already there. */
+
+	int kiss_serial_poll;	/* When using Bluetooth KISS, the /dev/rfcomm0 device */
+				/* will appear and disappear as the remote application */
+				/* opens and closes the virtual COM port. */
+				/* When this is non-zero, we will check periodically to */
+				/* see if the device has appeared and we will open it. */
+
+	char gpsnmea_port[20];	/* Serial port name for reading NMEA sentences from GPS. */
+				/* e.g. COM22, /dev/ttyACM0 */
+
+	int gpsnmea_speed;	/* Speed for above, baud, default 4800. */
+
+	char gpsd_host[20];	/* Host for gpsd server. */
+				/* e.g. localhost, 192.168.1.2 */
+
+	int gpsd_port;		/* Port number for gpsd server. */
+				/* Default is  2947. */
+
+				
+	char waypoint_serial_port[20];	/* Serial port name for sending NMEA waypoint sentences */
+				/* to a GPS map display or other mapping application. */
+				/* e.g. COM22, /dev/ttyACM0 */
+				/* Currently no option for setting non-standard speed. */
+				/* This was done in 2014 and no one has complained yet. */
+
+	char waypoint_udp_hostname[80];	/* Destination host when using UDP. */
+
+	int waypoint_udp_portnum;	/* UDP port. */
+
+	int waypoint_formats;	/* Which sentence formats should be generated? */
+
+#define WPL_FORMAT_NMEA_GENERIC 0x01		/* N	$GPWPL */
+#define WPL_FORMAT_GARMIN       0x02		/* G	$PGRMW */
+#define WPL_FORMAT_MAGELLAN     0x04		/* M	$PMGNWPL */
+#define WPL_FORMAT_KENWOOD      0x08		/* K	$PKWDWPL */
+#define WPL_FORMAT_AIS          0x10		/* A	!AIVDM */
+
+
+	int log_daily_names;	/* True to generate new log file each day. */
+
+	char log_path[80];	/* Either directory or full file name depending on above. */
+
+	int dns_sd_enabled;	/* DNS Service Discovery announcement enabled. */
+	char dns_sd_name[64];	/* Name announced on dns-sd; defaults to "Dire Wolf on <hostname>" */
+
+	int sb_configured;	/* TRUE if SmartBeaconing is configured. */
+	int sb_fast_speed;	/* MPH */
+	int sb_fast_rate;	/* seconds */
+	int sb_slow_speed;	/* MPH */
+	int sb_slow_rate;	/* seconds */
+	int sb_turn_time;	/* seconds */
+	int sb_turn_angle;	/* degrees */
+	int sb_turn_slope;	/* degrees * MPH */
+
+// AX.25 connected mode.
+
+	int frack;		/* Number of seconds to wait for ack to transmission. */
+
+	int retry;		/* Number of times to retry before giving up. */
+
+	int paclen;		/* Max number of bytes in information part of frame. */
+
+	int maxframe_basic;	/* Max frames to send before ACK.  mod 8 "Window" size. */
+
+	int maxframe_extended;	/* Max frames to send before ACK.  mod 128 "Window" size. */
+
+	int maxv22;		/* Maximum number of unanswered SABME frames sent before */
+				/* switching to SABM.  This is to handle the case of an old */
+				/* TNC which simply ignores SABME rather than replying with FRMR. */
+
+	char **v20_addrs;	/* Stations known to understand only AX.25 v2.0 so we don't */
+				/* waste time trying v2.2 first. */
+
+	int v20_count;		/* Number of station addresses in array above. */
+
+	char **noxid_addrs;	/* Stations known not to understand XID command so don't */
+				/* waste time sending it and eventually giving up. */
+				/* AX.25 for Linux is the one known case, so far, where */
+				/* SABME is implemented but XID is not. */
+
+	int noxid_count;	/* Number of station addresses in array above. */
+
+
+// Beacons.
+ 			
+	int num_beacons;	/* Number of beacons defined. */
+
+	struct beacon_s {
+
+	  enum beacon_type_e btype;	/* Position or object. */
+
+	  int lineno;		/* Line number from config file for later error messages. */
+
+	  enum sendto_type_e sendto_type;
+
+				/* SENDTO_XMIT	- Usually beacons go to a radio transmitter. */
+				/*		  chan, below is the channel number. */
+				/* SENDTO_IGATE	- Send to IGate, probably to announce my position */
+				/* 		  rather than relying on someone else to hear */
+				/* 		  me on the radio and report me. */
+				/* SENDTO_RECV	- Pretend this was heard on the specified */
+				/* 		  radio channel.  Mostly for testing. It is a */
+				/* 		  convenient way to send packets to attached apps. */
+
+	  int sendto_chan;	/* Transmit or simulated receive channel for above.  Should be 0 for IGate. */
+
+	  int delay;		/* Seconds to delay before first transmission. */
+
+	  int slot;		/* Seconds after hour for slotted time beacons. */
+				/* If specified, it overrides any 'delay' value. */
+
+	  int every;		/* Time between transmissions, seconds. */
+				/* Remains fixed for PBEACON and OBEACON. */
+				/* Dynamically adjusted for TBEACON. */
+
+	  time_t next;		/* Unix time to transmit next one. */
+
+	  char *source;		/* NULL or explicit AX.25 source address to use */
+				/* instead of the mycall value for the channel. */
+
+	  char *dest;		/* NULL or explicit AX.25 destination to use */
+				/* instead of the software version such as APDW11. */
+
+	  int compress;		/* Use more compact form? */
+
+	  char objname[10];	/* Object name.  Any printable characters. */
+
+	  char *via;		/* Path, e.g. "WIDE1-1,WIDE2-1" or NULL. */
+
+	  char *custom_info;	/* Info part for handcrafted custom beacon. */
+				/* Ignore the rest below if this is set. */
+
+	  char *custom_infocmd;	/* Command to generate info part. */
+				/* Again, other options below are then ignored. */
+
+	  int messaging;	/* Set messaging attribute for position report. */
+				/* i.e. Data Type Indicator of '=' rather than '!' */
+
+	  double lat;		/* Latitude and longitude. */
+	  double lon;
+	  int ambiguity;	/* Number of lower digits to trim from location. 0 (default), 1, 2, 3, 4. */
+	  float alt_m;		/* Altitude in meters. */
+
+	  char symtab;		/* Symbol table: / or \ or overlay character. */
+	  char symbol;		/* Symbol code. */
+
+	  float power;		/* For PHG. */
+	  float height;		/* HAAT in feet */
+	  float gain;		/* Original protocol spec was unclear. */
+				/* Addendum 1.1 clarifies it is dBi not dBd. */
+
+	  char dir[3];		/* 1 or 2 of N,E,W,S, or empty for omni. */
+
+	  float freq;		/* MHz. */
+	  float tone;		/* Hz. */
+	  float offset;		/* MHz. */
+	
+	  char *comment;	/* Comment or NULL. */
+	  char *commentcmd;	/* Command to append more to Comment or NULL. */
+
+
+	} beacon[MAX_BEACONS];
+
+};
+
+
+#define MIN_IP_PORT_NUMBER 1024
+#define MAX_IP_PORT_NUMBER 49151
+
+
+#define DEFAULT_AGWPE_PORT 8000		/* Like everyone else. */
+#define DEFAULT_KISS_PORT 8001		/* Above plus 1. */
+
+
+#define DEFAULT_NULLMODEM "COM3"  	/* should be equiv. to /dev/ttyS2 on Cygwin */
+
+
+
+
+extern void config_init (char *fname, struct audio_s *p_modem, 
+			struct digi_config_s *digi_config,
+			struct cdigi_config_s *cdigi_config,
+			struct tt_config_s *p_tt_config,
+			struct igate_config_s *p_igate_config,
+			struct misc_config_s *misc_config);
+
+
+
+#endif /* CONFIG_H */
+
+/* end config.h */
+
+
diff --git a/debug/QtSoundModem.ini b/debug/QtSoundModem.ini
index 5202a17..9747798 100644
--- a/debug/QtSoundModem.ini
+++ b/debug/QtSoundModem.ini
@@ -1,6 +1,7 @@
 [General]
-geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\x9e\0\0\0\x64\0\0\x4\x61\0\0\x3T\0\0\0\x9f\0\0\0\x83\0\0\x4`\0\0\x3S\0\0\0\0\0\0\0\0\x5\0\0\0\0\x9f\0\0\0\x83\0\0\x4`\0\0\x3S)
+geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\a\0\0\0\0\0\0\x3\xca\0\0\x2\xf0\0\0\0\b\0\0\0\x1f\0\0\x3\xc9\0\0\x2\xef\0\0\0\0\0\0\0\0\x5\0\0\0\0\b\0\0\0\x1f\0\0\x3\xc9\0\0\x2\xef)
 windowState=@ByteArray(\0\0\0\xff\0\0\0\0\xfd\0\0\0\0\0\0\x3\xc2\0\0\x2\xbc\0\0\0\x4\0\0\0\x4\0\0\0\b\0\0\0\b\xfc\0\0\0\0)
+PSKWindow=@Rect(46 499 366 140)
 
 [AX25_A]
 Retries=15
@@ -23,7 +24,7 @@ MEMRecovery=200
 IPOLL=80
 MyDigiCall=
 FX25=1
-IL2P=0
+IL2P=2
 RSID_UI=0
 RSID_SABM=0
 RSID_SetModem=0
@@ -37,15 +38,15 @@ UDPServer=0
 UDPHost=192.168.1.255
 TXSampleRate=12000
 RXSampleRate=12000
-SndRXDeviceName="HW:1,0"
-SndTXDeviceName="HW:1,0"
+SndRXDeviceName="CABLE-B OUTPUT (VB-AUDIO CABLE "
+SndTXDeviceName=CABLE-B INPUT (VB-AUDIO CABLE B
 SCO=0
 DualPTT=1
 TXRotate=0
 DispMode=1
 PTT=
 PTTBAUD=19200
-PTTMode=19200
+PTTMode=1
 PTTOffString=
 PTTOnString=
 pttGPIOPin=17
@@ -54,7 +55,10 @@ CM108Addr=0xD8C:0x08
 HamLibPort=4532
 HamLibHost=127.0.0.1
 MinimizetoTray=1
-multiCore=0
+multiCore=1
+Wisdom=(fftw-3.3.5 fftwf_wisdom #x08ac4c16 #x457005cc #xea102cf7 #xd7ff9038\n  (fftwf_dft_vrank_geq1_register 0 #x10048 #x10048 #x0 #x2fce15e1 #x178d4f4d #x1e956a41 #xf3fd6b80)\n  (fftwf_dft_buffered_register 0 #x11048 #x11048 #x0 #x4f8e87b4 #xec4f2fa0 #x79fe76a1 #xa16e32a5)\n  (fftwf_codelet_t1fv_6_sse2 0 #x10048 #x10048 #x0 #xd9db29d8 #x3302fcf3 #x19ce6e5d #x869fc341)\n  (fftwf_codelet_t1fv_12_sse2 0 #x10048 #x10048 #x0 #x0b0d3933 #x08267d12 #x45613873 #xde496efe)\n  (fftwf_codelet_n2fv_12_sse2 0 #x10048 #x10048 #x0 #xb0767e46 #x8d41dd22 #x439264a0 #x18435a99)\n  (fftwf_dft_bluestein_register 0 #x11048 #x11048 #x0 #x5e17c068 #x1682c5d6 #x89dd79be #x9b951c0f)\n  (fftwf_codelet_t1fuv_8_sse2 0 #x11048 #x11048 #x0 #x34057a74 #x664db78f #xa9524ebc #x606afd88)\n  (fftwf_dft_r2hc_register 0 #x11048 #x11048 #x0 #x576d5db6 #xa6a15f8a #x875d87d5 #x7561a866)\n  (fftwf_dft_vrank_geq1_register 0 #x11048 #x11048 #x0 #x1e354940 #xac45f390 #x8260fb76 #x1a44862e)\n  (fftwf_rdft_rank0_register 0 #x11048 #x11048 #x0 #xff75c762 #x3a0ee093 #x5b78d592 #x6b6be60e)\n  (fftwf_dft_nop_register 0 #x11048 #x11048 #x0 #x4a593e24 #xb5f06ddf #xf11fe7f2 #xc010b545)\n)\n
+WaterfallMin=0
+WaterfallMax=3300
 
 [AX25_B]
 Retries=15
@@ -77,30 +81,30 @@ MEMRecovery=200
 IPOLL=80
 MyDigiCall=
 FX25=1
-IL2P=0
+IL2P=2
 RSID_UI=0
 RSID_SABM=0
 RSID_SetModem=0
 
 [Modem]
-NRRcvrPairs1=0
-NRRcvrPairs2=0
+NRRcvrPairs1=2
+NRRcvrPairs2=2
 NRRcvrPairs3=0
-NRRcvrPairs4=0
-RcvrShift1=30
+NRRcvrPairs4=2
+RcvrShift1=50
 RcvrShift2=30
 RcvrShift3=30
 RcvrShift4=30
-ModemType1=1
-ModemType2=1
-ModemType3=1
-ModemType4=1
+ModemType1=4
+ModemType2=14
+ModemType3=0
+ModemType4=14
 soundChannel1=1
-soundChannel2=0
+soundChannel2=1
 soundChannel3=0
 soundChannel4=0
 DCDThreshold=40
-rxOffset=0
+rxOffset=-100
 PreEmphasisAll1=0
 PreEmphasisAll2=0
 PreEmphasisAll3=0
@@ -122,6 +126,11 @@ CWIDInterval=0
 CWIDLeft=0
 CWIDRight=0
 CWIDType=1
+RXFreq1=1700
+RXFreq2=1700
+RXFreq3=500
+RXFreq4=1700
+CWIDMark=
 
 [AGWHost]
 Server=1
diff --git a/debug/moc_predefs-DESKTOP-MHE5LO8.h b/debug/moc_predefs-DESKTOP-MHE5LO8.h
new file mode 100644
index 0000000..2c1557b
--- /dev/null
+++ b/debug/moc_predefs-DESKTOP-MHE5LO8.h
@@ -0,0 +1,12 @@
+#define _MSC_EXTENSIONS 
+#define _INTEGRAL_MAX_BITS 64
+#define _MSC_VER 1916
+#define _MSC_FULL_VER 191627050
+#define _MSC_BUILD 0
+#define _WIN32 
+#define _M_IX86 600
+#define _M_IX86_FP 2
+#define _CPPRTTI 
+#define _DEBUG 
+#define _MT 
+#define _DLL 
diff --git a/decode_aprs.h b/decode_aprs.h
new file mode 100644
index 0000000..f25d1e9
--- /dev/null
+++ b/decode_aprs.h
@@ -0,0 +1,150 @@
+
+/* decode_aprs.h */
+
+
+#ifndef DECODE_APRS_H
+
+#define DECODE_APRS_H 1
+
+
+
+#ifndef G_UNKNOWN
+#include "latlong.h"
+#endif
+
+#ifndef AX25_MAX_ADDR_LEN
+#include "ax25_pad.h"
+#endif 
+
+#ifndef APRSTT_LOC_DESC_LEN
+#include "aprs_tt.h"
+#endif
+
+typedef struct decode_aprs_s {
+
+	int g_quiet;			/* Suppress error messages when decoding. */
+
+        char g_src[AX25_MAX_ADDR_LEN];
+
+        char g_dest[AX25_MAX_ADDR_LEN];
+
+        char g_data_type_desc[100];	/* APRS data type description.  Telemetry descriptions get pretty long. */
+
+        char g_symbol_table;		/* The Symbol Table Identifier character selects one */
+					/* of the two Symbol Tables, or it may be used as */
+					/* single-character (alpha or numeric) overlay, as follows: */
+					
+					/*	/ 	Primary Symbol Table (mostly stations) */
+
+					/* 	\ 	Alternate Symbol Table (mostly Objects) */
+
+					/*	0-9 	Numeric overlay. Symbol from Alternate Symbol */
+					/*		Table (uncompressed lat/long data format) */
+
+					/*	a-j	Numeric overlay. Symbol from Alternate */
+					/*		Symbol Table (compressed lat/long data */
+					/*		format only). i.e. a-j maps to 0-9 */
+
+					/*	A-Z	Alpha overlay. Symbol from Alternate Symbol Table */
+
+
+        char g_symbol_code;		/* Where the Symbol Table Identifier is 0-9 or A-Z (or a-j */
+					/* with compressed position data only), the symbol comes from */
+					/* the Alternate Symbol Table, and is overlaid with the */
+					/* identifier (as a single digit or a capital letter). */
+
+	char g_aprstt_loc[APRSTT_LOC_DESC_LEN];		/* APRStt location from !DAO! */
+
+        double g_lat, g_lon;		/* Location, degrees.  Negative for South or West. */
+					/* Set to G_UNKNOWN if missing or error. */
+
+        char g_maidenhead[12];		/* 4 or 6 (or 8?) character maidenhead locator. */
+
+        char g_name[12];		/* Object or item name. Max. 9 characters. */
+
+	char g_addressee[12];		/* Addressee for a "message."  Max. 9 characters. */
+					/* Also for Directed Station Query which is a */
+					/* special case of message. */
+
+	enum message_subtype_e { message_subtype_invalid = 0,
+				message_subtype_message,
+				message_subtype_ack,
+				message_subtype_rej,
+				message_subtype_telem_parm,
+				message_subtype_telem_unit,
+				message_subtype_telem_eqns,
+				message_subtype_telem_bits,
+				message_subtype_directed_query
+		} g_message_subtype;	/* Various cases of the overloaded "message." */
+
+	char g_message_number[12];	/* Message number.  Should be 1 - 5 alphanumeric characters if used. */
+					/* Addendum 1.1 has new format {mm} or {mm}aa with only two */
+					/* characters for message number and an ack riding piggyback. */
+
+        float g_speed_mph;		/* Speed in MPH.  */
+					/* The APRS transmission uses knots so watch out for */
+					/* conversions when sending and receiving APRS packets. */
+
+        float g_course;			/* 0 = North, 90 = East, etc. */
+	
+        int g_power;			/* Transmitter power in watts. */
+
+        int g_height;			/* Antenna height above average terrain, feet. */
+
+        int g_gain;			/* Antenna gain in dB. */
+
+        char g_directivity[12];		/* Direction of max signal strength */
+
+        float g_range;			/* Precomputed radio range in miles. */
+
+        float g_altitude_ft;		/* Feet above median sea level.  */
+					/* I used feet here because the APRS specification */
+					/* has units of feet for alititude.  Meters would be */
+					/* more natural to the other 96% of the world. */
+
+        char g_mfr[80];			/* Manufacturer or application. */
+
+        char g_mic_e_status[32];	/* MIC-E message. */
+
+        double g_freq;			/* Frequency, MHz */
+
+        float g_tone;			/* CTCSS tone, Hz, one fractional digit */
+
+        int g_dcs;			/* Digital coded squelch, print as 3 octal digits. */
+
+        int g_offset;			/* Transmit offset, kHz */
+
+
+	char g_query_type[12];		/* General Query: APRS, IGATE, WX, ... */
+					/* Addressee is NOT set. */
+
+					/* Directed Station Query: exactly 5 characters. */
+					/* APRSD, APRST, PING?, ... */
+					/* Addressee is set. */
+	
+	double g_footprint_lat;		/* A general query may contain a foot print. */
+	double g_footprint_lon;		/* Set all to G_UNKNOWN if not used. */
+	float g_footprint_radius;	/* Radius in miles. */
+
+	char g_query_callsign[12];	/* Directed query may contain callsign.  */
+					/* e.g. tell me all objects from that callsign. */
+
+
+        char g_weather[500];		/* Weather.  Can get quite long. Rethink max size. */
+
+        char g_telemetry[256];		/* Telemetry data.  Rethink max size. */
+
+        char g_comment[256];		/* Comment. */
+
+} decode_aprs_t;
+
+
+
+
+
+extern void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_src);
+
+extern void decode_aprs_print (decode_aprs_t *A);
+
+
+#endif
diff --git a/dedupe.h b/dedupe.h
new file mode 100644
index 0000000..9c0613c
--- /dev/null
+++ b/dedupe.h
@@ -0,0 +1,10 @@
+
+
+void dedupe_init (int ttl);
+
+void dedupe_remember (packet_t pp, int chan);
+
+int dedupe_check (packet_t pp, int chan);
+
+
+/* end dedupe.h */
diff --git a/demod.h b/demod.h
new file mode 100644
index 0000000..3233b9b
--- /dev/null
+++ b/demod.h
@@ -0,0 +1,17 @@
+
+
+/* demod.h */
+
+#include "audio.h" 	/* for struct audio_s */
+#include "ax25_pad.h"	/* for alevel_t */
+
+
+int demod_init (struct audio_s *pa);
+
+int demod_get_sample (int a);
+
+void demod_process_sample (int chan, int subchan, int sam);
+
+void demod_print_agc (int chan, int subchan);
+
+alevel_t demod_get_audio_level (int chan, int subchan);
\ No newline at end of file
diff --git a/demod_afsk.h b/demod_afsk.h
new file mode 100644
index 0000000..e44a44e
--- /dev/null
+++ b/demod_afsk.h
@@ -0,0 +1,8 @@
+
+/* demod_afsk.h */
+
+
+void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
+			int space_freq, char profile, struct demodulator_state_s *D);
+
+void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D);
diff --git a/demod_psk.h b/demod_psk.h
new file mode 100644
index 0000000..134b199
--- /dev/null
+++ b/demod_psk.h
@@ -0,0 +1,7 @@
+
+/* demod_psk.h */
+
+
+void demod_psk_init (enum modem_t modem_type, enum v26_e v26_alt, int samples_per_sec, int bps, char profile, struct demodulator_state_s *D);
+
+void demod_psk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D);
diff --git a/devicesDialog.ui b/devicesDialog.ui
index c50549b..0f46639 100644
--- a/devicesDialog.ui
+++ b/devicesDialog.ui
@@ -125,7 +125,7 @@
        <rect>
         <x>20</x>
         <y>254</y>
-        <width>231</width>
+        <width>143</width>
         <height>17</height>
        </rect>
       </property>
@@ -491,6 +491,97 @@
        <string>Port</string>
       </property>
      </widget>
+     <widget class="QComboBox" name="WaterfallMin">
+      <property name="geometry">
+       <rect>
+        <x>262</x>
+        <y>252</y>
+        <width>63</width>
+        <height>22</height>
+       </rect>
+      </property>
+      <item>
+       <property name="text">
+        <string>0</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string>100</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string>200</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string>300</string>
+       </property>
+      </item>
+     </widget>
+     <widget class="QComboBox" name="WaterfallMax">
+      <property name="geometry">
+       <rect>
+        <x>376</x>
+        <y>252</y>
+        <width>63</width>
+        <height>22</height>
+       </rect>
+      </property>
+      <item>
+       <property name="text">
+        <string>2500</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string>2800</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string>3000</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string>3300</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string>5500</string>
+       </property>
+      </item>
+     </widget>
+     <widget class="QLabel" name="label_11">
+      <property name="geometry">
+       <rect>
+        <x>158</x>
+        <y>254</y>
+        <width>107</width>
+        <height>17</height>
+       </rect>
+      </property>
+      <property name="text">
+       <string>Waterfall Range</string>
+      </property>
+     </widget>
+     <widget class="QLabel" name="label_16">
+      <property name="geometry">
+       <rect>
+        <x>348</x>
+        <y>254</y>
+        <width>31</width>
+        <height>17</height>
+       </rect>
+      </property>
+      <property name="text">
+       <string>to</string>
+      </property>
+     </widget>
     </widget>
     <widget class="QGroupBox" name="groupBox_2">
      <property name="geometry">
@@ -767,8 +858,8 @@
       <property name="geometry">
        <rect>
         <x>18</x>
-        <y>50</y>
-        <width>83</width>
+        <y>52</y>
+        <width>99</width>
         <height>18</height>
        </rect>
       </property>
@@ -815,7 +906,7 @@
      <widget class="QLineEdit" name="VIDPID">
       <property name="geometry">
        <rect>
-        <x>120</x>
+        <x>122</x>
         <y>50</y>
         <width>96</width>
         <height>20</height>
@@ -826,7 +917,7 @@
       <property name="geometry">
        <rect>
         <x>20</x>
-        <y>50</y>
+        <y>52</y>
         <width>95</width>
         <height>18</height>
        </rect>
diff --git a/digipeater.h b/digipeater.h
new file mode 100644
index 0000000..5c84976
--- /dev/null
+++ b/digipeater.h
@@ -0,0 +1,78 @@
+
+#ifndef DIGIPEATER_H
+#define DIGIPEATER_H 1
+
+#include "regex.h"
+
+#include "direwolf.h"		/* for MAX_CHANS */
+#include "ax25_pad.h"		/* for packet_t */
+#include "audio.h"		/* for radio channel properties */
+
+
+/*
+ * Information required for digipeating.
+ *
+ * The configuration file reader fills in this information
+ * and it is passed to digipeater_init at application start up time.
+ */
+
+
+struct digi_config_s {
+
+
+	int	dedupe_time;	/* Don't digipeat duplicate packets */
+				/* within this number of seconds. */
+
+#define DEFAULT_DEDUPE 30
+
+/*
+ * Rules for each of the [from_chan][to_chan] combinations.
+ */
+
+	regex_t	alias[MAX_CHANS][MAX_CHANS];
+
+	regex_t	wide[MAX_CHANS][MAX_CHANS];
+
+	int	enabled[MAX_CHANS][MAX_CHANS];
+
+	enum preempt_e { PREEMPT_OFF, PREEMPT_DROP, PREEMPT_MARK, PREEMPT_TRACE } preempt[MAX_CHANS][MAX_CHANS];
+
+	// ATGP is an ugly hack for the specific need of ATGP which needs more that 8 digipeaters.
+	// DO NOT put this in the User Guide.  On a need to know basis.
+
+	char atgp[MAX_CHANS][MAX_CHANS][AX25_MAX_ADDR_LEN];
+
+	char *filter_str[MAX_CHANS+1][MAX_CHANS+1];
+						// NULL or optional Packet Filter strings such as "t/m".
+						// Notice the size of arrays is one larger than normal.
+						// That extra position is for the IGate.
+
+	int regen[MAX_CHANS][MAX_CHANS];	// Regenerate packet.  
+						// Sort of like digipeating but passed along unchanged.
+};
+
+/*
+ * Call once at application start up time.
+ */
+
+extern void digipeater_init (struct audio_s *p_audio_config, struct digi_config_s *p_digi_config);
+
+/*
+ * Call this for each packet received.
+ * Suitable packets will be queued for transmission.
+ */
+
+extern void digipeater (int from_chan, packet_t pp);
+
+void digi_regen (int from_chan, packet_t pp);
+
+
+/* Make statistics available. */
+
+int digipeater_get_count (int from_chan, int to_chan);
+
+
+#endif 
+
+/* end digipeater.h */
+
diff --git a/dlq.h b/dlq.h
new file mode 100644
index 0000000..8771636
--- /dev/null
+++ b/dlq.h
@@ -0,0 +1,148 @@
+
+/*------------------------------------------------------------------
+ *
+ * Module:      dlq.h
+ *
+ *---------------------------------------------------------------*/
+
+#ifndef DLQ_H
+#define DLQ_H 1
+
+#include "ax25_pad.h"
+#include "audio.h"
+
+
+/* A transmit or receive data block for connected mode. */
+
+typedef struct cdata_s {
+	int magic;			/* For integrity checking. */
+
+#define TXDATA_MAGIC 0x09110911
+
+	struct cdata_s *next;		/* Pointer to next when part of a list. */
+
+	int pid;			/* Protocol id. */
+
+	int size;			/* Number of bytes allocated. */
+
+	int len;			/* Number of bytes actually used. */
+
+	char data[];			/* Variable length data. */
+
+} cdata_t;
+
+
+
+/* Types of things that can be in queue. */
+
+typedef enum dlq_type_e {DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST, DLQ_REGISTER_CALLSIGN, DLQ_UNREGISTER_CALLSIGN, DLQ_OUTSTANDING_FRAMES_REQUEST, DLQ_CHANNEL_BUSY, DLQ_SEIZE_CONFIRM, DLQ_CLIENT_CLEANUP} dlq_type_t;
+
+
+/* A queue item. */
+
+// TODO: call this event rather than item.
+// TODO: should add fences.
+
+typedef struct dlq_item_s {
+
+	struct dlq_item_s *nextp;	/* Next item in queue. */
+
+	dlq_type_t type;		/* Type of item. */
+					/* See enum definition above. */
+
+	int chan;			/* Radio channel of origin. */
+
+// I'm not worried about amount of memory used but this might be a
+// little clearer if a union was used for the different event types.
+
+// Used for received frame.
+
+	int subchan;			/* Winning "subchannel" when using multiple */
+					/* decoders on one channel.  */
+					/* Special case, -1 means DTMF decoder. */
+					/* Maybe we should have a different type in this case? */
+
+	int slice;			/* Winning slicer. */
+
+	packet_t pp;			/* Pointer to frame structure. */
+
+	alevel_t alevel;		/* Audio level. */
+
+	int is_fx25;			/* Was it from FX.25? */
+
+	retry_t retries;		/* Effort expended to get a valid CRC. */
+					/* Bits changed for regular AX.25. */
+					/* Number of bytes fixed for FX.25. */
+
+	char spectrum[MAX_SUBCHANS*MAX_SLICERS+1];	/* "Spectrum" display for multi-decoders. */
+
+// Used by requests from a client application, connect, etc.
+
+	char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
+
+	int num_addr;			/* Range 2 .. 10. */
+
+	int client;
+
+
+// Used only by client request to transmit connected data.
+
+	cdata_t *txdata;
+
+// Used for channel activity change.
+// It is useful to know when the channel is busy either for carrier detect
+// or when we are transmitting.
+
+	int activity;			/* OCTYPE_PTT for my transmission start/end. */
+					/* OCTYPE_DCD if we hear someone else. */
+
+	int status;			/* 1 for active or 0 for quiet. */
+
+} dlq_item_t;
+
+
+
+void dlq_init (void);
+
+
+
+void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum);
+
+void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid);
+
+void dlq_disconnect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client);
+
+void dlq_outstanding_frames_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client);
+
+void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid, char *xdata_ptr, int xdata_len);
+
+void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client);
+
+void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client);
+
+void dlq_channel_busy (int chan, int activity, int status);
+
+void dlq_seize_confirm (int chan);
+
+void dlq_client_cleanup (int client);
+
+
+
+int dlq_wait_while_empty (double timeout_val);
+
+struct dlq_item_s *dlq_remove (void);
+
+void dlq_delete (struct dlq_item_s *pitem);
+
+
+
+cdata_t *cdata_new (int pid, char *data, int len);
+
+void cdata_delete (cdata_t *txdata);
+
+void cdata_check_leak (void);
+
+
+#endif
+
+/* end dlq.h */
diff --git a/dns_sd_common.h b/dns_sd_common.h
new file mode 100644
index 0000000..f104bf8
--- /dev/null
+++ b/dns_sd_common.h
@@ -0,0 +1,7 @@
+
+#if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD)
+
+char *dns_sd_default_service_name(void);
+
+#endif
+
diff --git a/dns_sd_dw.h b/dns_sd_dw.h
new file mode 100644
index 0000000..79f4b86
--- /dev/null
+++ b/dns_sd_dw.h
@@ -0,0 +1,10 @@
+
+#if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD)
+
+#include "config.h"
+
+#define DNS_SD_SERVICE "_kiss-tnc._tcp"
+
+void dns_sd_announce (struct misc_config_s *mc);
+
+#endif // USE_AVAHI_CLIENT
diff --git a/dtime_now.h b/dtime_now.h
new file mode 100644
index 0000000..411534b
--- /dev/null
+++ b/dtime_now.h
@@ -0,0 +1,18 @@
+
+
+extern double dtime_realtime (void);
+
+extern double dtime_monotonic (void);
+
+
+void timestamp_now (char *result, int result_size, int show_ms);
+
+void timestamp_user_format (char *result, int result_size, char *user_format);
+
+void timestamp_filename (char *result, int result_size);
+
+
+// FIXME:  remove temp workaround.
+// Needs many scattered updates.
+
+#define dtime_now dtime_realtime
diff --git a/dtmf.h b/dtmf.h
new file mode 100644
index 0000000..c1b52b9
--- /dev/null
+++ b/dtmf.h
@@ -0,0 +1,14 @@
+/* dtmf.h */
+
+
+#include "audio.h"
+
+void dtmf_init (struct audio_s *p_audio_config, int amp);
+
+char dtmf_sample (int c, float input);
+
+int dtmf_send (int chan, char *str, int speed, int txdelay, int txtail);
+
+
+/* end dtmf.h */
+
diff --git a/dw9600.c b/dw9600.c
new file mode 100644
index 0000000..8307342
--- /dev/null
+++ b/dw9600.c
@@ -0,0 +1,4184 @@
+// 4800/9600 RHU Mode code for QtSoundModem
+
+// Based on code from Dire Wolf Copyright (C) 2011, 2012, 2013, 2014, 2015  John Langner, WB2OSZ
+
+
+#define MODE_RUH 7
+
+typedef struct string_T
+{
+	unsigned char * Data;
+	int Length;
+	int AllocatedLength;				// A reasonable sized block is allocated at the start to speed up adding chars
+
+}string;
+
+typedef struct TStringList_T
+{
+	int Count;
+	string ** Items;
+
+} TStringList;
+
+#include <stddef.h>
+#include "dw9600.h"
+
+extern int fx25_mode[4];
+extern int il2p_mode[4];
+extern short rx_baudrate[5];
+
+#define FX25_MODE_NONE  0
+#define FX25_MODE_RX  1
+#define FX25_MODE_TXRX 2
+#define FX25_TAG 0
+#define FX25_LOAD 1
+
+#define IL2P_MODE_NONE  0
+#define IL2P_MODE_RX  1				// RX il2p + HDLC
+#define IL2P_MODE_TXRX 2
+#define IL2P_MODE_ONLY 3			// RX only il2p, TX il2p
+
+
+extern unsigned short * DMABuffer;
+extern int SampleNo;
+
+
+void ProcessRXFrames(int snd_ch);
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  John Langner, WB2OSZ
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU General Public License as published by
+//    the Free Software Foundation, either version 2 of the License, or
+//    (at your option) any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+#include <math.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <stdint.h>          // uint64_t
+#define RRBB_C 1
+
+/********************************************************************************
+ *
+ * File:	hdlc_rec.c
+ *
+ * Purpose:	Extract HDLC frames from a stream of bits.
+ *
+ *******************************************************************************/
+
+
+//#define TEST 1				/* Define for unit testing. */
+
+//#define DEBUG3 1				/* monitor the data detect signal. */
+
+
+
+/*
+ * Minimum & maximum sizes of an AX.25 frame including the 2 octet FCS.
+ */
+
+#define MIN_FRAME_LEN ((AX25_MIN_PACKET_LEN) + 2)
+
+#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)	
+
+ /*
+  * This is the current state of the HDLC decoder.
+  *
+  * It is possible to run multiple decoders concurrently by
+  * having a separate set of state variables for each.
+  *
+  * Should have a reset function instead of initializations here.
+  */
+
+struct hdlc_state_s {
+
+
+	int prev_raw;			/* Keep track of previous bit so */
+					/* we can look for transitions. */
+					/* Should be only 0 or 1. */
+
+	int lfsr;			/* Descrambler shift register for 9600 baud. */
+
+	int prev_descram;		/* Previous descrambled for 9600 baud. */
+
+	unsigned char pat_det; 		/* 8 bit pattern detector shift register. */
+					/* See below for more details. */
+
+	unsigned int flag4_det;		/* Last 32 raw bits to look for 4 */
+					/* flag patterns in a row. */
+
+	unsigned char oacc;		/* Accumulator for building up an octet. */
+
+	int olen;			/* Number of bits in oacc. */
+					/* When this reaches 8, oacc is copied */
+					/* to the frame buffer and olen is zeroed. */
+					/* The value of -1 is a special case meaning */
+					/* bits should not be accumulated. */
+
+	unsigned char frame_buf[MAX_FRAME_LEN];
+	/* One frame is kept here. */
+
+	int frame_len;			/* Number of octets in frame_buf. */
+					/* Should be in range of 0 .. MAX_FRAME_LEN. */
+
+	rrbb_t rrbb;			/* Handle for bit array for raw received bits. */
+
+	uint64_t eas_acc;		/* Accumulate most recent 64 bits received for EAS. */
+
+	int eas_gathering;		/* Decoding in progress. */
+
+	int eas_plus_found;		/* "+" seen, indicating end of geographical area list. */
+
+	int eas_fields_after_plus;	/* Number of "-" characters after the "+". */
+};
+
+static struct hdlc_state_s hdlc_state[MAX_CHANS];
+
+static int num_subchan[MAX_CHANS];		//TODO1.2 use ptr rather than copy.
+
+static int composite_dcd[MAX_CHANS][MAX_SUBCHANS + 1];
+
+
+/***********************************************************************************
+ *
+ * Name:	hdlc_rec_init
+ *
+ * Purpose:	Call once at the beginning to initialize.
+ *
+ * Inputs:	None.
+ *
+ ***********************************************************************************/
+
+int was_init[4] = { 0, 0, 0, 0 };
+
+struct audio_s *g_audio_p;
+extern struct audio_s pa[4];
+
+
+void hdlc_rec_init(struct audio_s *pa)
+{
+	int ch;
+	struct hdlc_state_s *H;
+
+	//text_color_set(DW_COLOR_DEBUG);
+	//dw_printf ("hdlc_rec_init (%p) \n", pa);
+
+	assert(pa != NULL);
+	g_audio_p = &pa;
+
+	memset(composite_dcd, 0, sizeof(composite_dcd));
+
+	for (ch = 0; ch < MAX_CHANS; ch++)
+	{
+		pa->achan[ch].num_subchan = 1;
+		num_subchan[ch] = pa->achan[ch].num_subchan;
+
+		assert(num_subchan[ch] >= 1 && num_subchan[ch] <= MAX_SUBCHANS);
+
+		H = &hdlc_state[ch];
+
+		H->olen = -1;
+
+		H->rrbb = rrbb_new(ch, 0, 0, pa->achan[ch].modem_type == MODEM_SCRAMBLE, H->lfsr, H->prev_descram);
+	}
+	hdlc_rec2_init(pa);
+
+}
+
+/* Own copy of random number generator so we can get */
+/* same predictable results on different operating systems. */
+/* TODO: Consolidate multiple copies somewhere. */
+
+#define MY_RAND_MAX 0x7fffffff
+static int seed = 1;
+
+static int my_rand(void) {
+	// Perform the calculation as unsigned to avoid signed overflow error.
+	seed = (int)(((unsigned)seed * 1103515245) + 12345) & MY_RAND_MAX;
+	return (seed);
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	eas_rec_bit
+ *
+ * Purpose:	Extract EAS trasmissions from a stream of bits.
+ *
+ * Inputs:	chan	- Channel number.
+ *
+ *		subchan	- This allows multiple demodulators per channel.
+ *
+ *		slice	- Allows multiple slicers per demodulator (subchannel).
+ *
+ *		raw 	- One bit from the demodulator.
+ *			  should be 0 or 1.
+ *
+ *		future_use - Not implemented yet.  PSK already provides it.
+ *
+ *
+ * Description:	This is called once for each received bit.
+ *		For each valid transmission, process_rec_frame()
+ *		is called for further processing.
+ *
+ ***********************************************************************************/
+
+#define PREAMBLE      0xababababababababULL
+#define PREAMBLE_ZCZC 0x435a435aababababULL
+#define PREAMBLE_NNNN 0x4e4e4e4eababababULL
+#define EAS_MAX_LEN 268  	// Not including preamble.  Up to 31 geographic areas.
+
+
+static void eas_rec_bit(int chan, int subchan, int slice, int raw, int future_use)
+{
+	struct hdlc_state_s *H;
+
+	/*
+	 * Different state information for each channel / subchannel / slice.
+	 */
+	H = &hdlc_state[chan];
+
+	//dw_printf ("slice %d = %d\n", slice, raw);
+
+// Accumulate most recent 64 bits.
+
+	H->eas_acc >>= 1;
+	if (raw) {
+		H->eas_acc |= 0x8000000000000000ULL;
+	}
+
+	int done = 0;
+
+	if (H->eas_acc == PREAMBLE_ZCZC) {
+		//dw_printf ("ZCZC\n");
+		H->olen = 0;
+		H->eas_gathering = 1;
+		H->eas_plus_found = 0;
+		H->eas_fields_after_plus = 0;
+		strcpy((char*)(H->frame_buf), "ZCZC");
+		H->frame_len = 4;
+	}
+	else if (H->eas_acc == PREAMBLE_NNNN) {
+		//dw_printf ("NNNN\n");
+		H->olen = 0;
+		H->eas_gathering = 1;
+		strcpy((char*)(H->frame_buf), "NNNN");
+		H->frame_len = 4;
+		done = 1;
+	}
+	else if (H->eas_gathering) {
+		H->olen++;
+		if (H->olen == 8) {
+			H->olen = 0;
+			char ch = H->eas_acc >> 56;
+			H->frame_buf[H->frame_len++] = ch;
+			H->frame_buf[H->frame_len] = '\0';
+			//dw_printf ("frame_buf = %s\n", H->frame_buf);
+
+			// What characters are acceptable?
+			// Only ASCII is allowed.  i.e. the MSB must be 0.
+			// The examples show only digits but the geographical area can
+			// contain anything in range of '!' to DEL or CR or LF.
+			// There are no restrictions listed for the originator and
+			// examples contain a slash.
+			// It's not clear if a space can occur in other places.
+
+			if (!((ch >= ' ' && ch <= 0x7f) || ch == '\r' || ch == '\n')) {
+				//#define DEBUG_E 1
+#ifdef DEBUG_E
+				dw_printf("reject %d invalid character = %s\n", slice, H->frame_buf);
+#endif
+				H->eas_gathering = 0;
+				return;
+			}
+			if (H->frame_len > EAS_MAX_LEN) {		// FIXME: look for other places with max length
+#ifdef DEBUG_E
+				dw_printf("reject %d too long = %s\n", slice, H->frame_buf);
+#endif
+				H->eas_gathering = 0;
+				return;
+			}
+			if (ch == '+') {
+				H->eas_plus_found = 1;
+				H->eas_fields_after_plus = 0;
+			}
+			if (H->eas_plus_found && ch == '-') {
+				H->eas_fields_after_plus++;
+				if (H->eas_fields_after_plus == 3) {
+					done = 1;	// normal case
+				}
+			}
+		}
+	}
+
+	if (done) {
+#ifdef DEBUG_E
+		dw_printf("frame_buf %d = %s\n", slice, H->frame_buf);
+#endif
+		alevel_t alevel = demod_get_audio_level(chan, subchan);
+		multi_modem_process_rec_frame(chan, subchan, slice, H->frame_buf, H->frame_len, alevel, 0, 0);
+		H->eas_gathering = 0;
+	}
+
+} // end eas_rec_bit
+
+
+/*
+
+EAS has no error detection.
+Maybe that doesn't matter because we would normally be dealing with a reasonable
+VHF FM or TV signal.
+Let's see what happens when we intentionally introduce errors.
+When some match and others don't, the multislice voting should give preference
+to those matching others.
+
+	$ src/atest -P+ -B EAS -e 3e-3 ../../ref-doc/EAS/same.wav
+	Demodulator profile set to "+"
+	96000 samples per second.  16 bits per sample.  1 audio channels.
+	2079360 audio bytes in file.  Duration = 10.8 seconds.
+	Fix Bits level = 0
+	Channel 0: 521 baud, AFSK 2083 & 1563 Hz, D+, 96000 sample rate / 3.
+
+case 1:  Slice 6 is different than others (EQS vs. EAS) so we want one of the others that match.
+	 Slice 3 has an unexpected character (in 0120u7) so it is a mismatch.
+	 At this point we are not doing validity checking other than all printable characters.
+
+	 We are left with 0 & 4 which don't match (012057 vs. 012077).
+	 So I guess we don't have any two that match so it is a toss up.
+
+	reject 7 invalid character = ZCZC-EAS-RWT-0120▒
+	reject 5 invalid character = ZCZC-ECW-RWT-012057-012081-012101-012103-012115+003
+	frame_buf 6 = ZCZC-EQS-RWT-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV-
+	frame_buf 4 = ZCZC-EAS-RWT-012077-012081-012101-012103-012115+0030-2780415-WTSP/TV-
+	frame_buf 3 = ZCZC-EAS-RWT-0120u7-012281-012101-012103-092115+0038-2780415-VTSP/TV-
+	frame_buf 0 = ZCZC-EAS-RWT-012057-412081-012101-012103-012115+0030-2780415-WTSP/TV-
+
+	DECODED[1] 0:01.313 EAS audio level = 194(106/108)     |__||_|__
+	[0.0] EAS>APDW16:{DEZCZC-EAS-RWT-012057-412081-012101-012103-012115+0030-2780415-WTSP/TV-
+
+Case 2: We have two that match so pick either one.
+
+	reject 5 invalid character = ZCZC-EAS-RW▒
+	reject 7 invalid character = ZCZC-EAS-RWT-0
+	reject 3 invalid character = ZCZC-EAS-RWT-012057-012080-012101-012103-01211
+	reject 0 invalid character = ZCZC-EAS-RWT-012057-012081-012101-012103-012115+0030-2780415-W▒
+	frame_buf 6 = ZCZC-EAS-RWT-012057-012081-012!01-012103-012115+0030-2780415-WTSP/TV-
+	frame_buf 1 = ZCZC-EAS-RWT-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV-
+
+	DECODED[2] 0:03.617 EAS audio level = 194(106/108)     _|____|__
+	[0.1] EAS>APDW16:{DEZCZC-EAS-RWT-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV-
+
+Case 3: Slice 6 is a mismatch (EAs vs. EAS).
+	Slice 7 has RST rather than RWT.
+	2 & 4 don't match either (012141 vs. 012101).
+	We have another case where no two match so there is no clear winner.
+
+
+	reject 5 invalid character = ZCZC-EAS-RWT-012057-012081-012101-012103-012115+▒
+	frame_buf 7 = ZCZC-EAS-RST-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV-
+	frame_buf 6 = ZCZC-EAs-RWT-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV-
+	frame_buf 4 = ZCZC-EAS-RWT-112057-012081-012101-012103-012115+0030-2780415-WTSP/TV-
+	frame_buf 2 = ZCZC-EAS-RWT-012057-012081-012141-012103-012115+0030-2780415-WTSP/TV-
+
+	DECODED[3] 0:05.920 EAS audio level = 194(106/108)     __|_|_||_
+	[0.2] EAS>APDW16:{DEZCZC-EAS-RWT-012057-012081-012141-012103-012115+0030-2780415-WTSP/TV-
+
+Conclusions:
+
+	(1) The existing algorithm gives a higher preference to those frames matching others.
+	We didn't see any cases here where that would be to our advantage.
+
+	(2) A partial solution would be more validity checking.  (i.e. non-digit where
+	digit is expected.)  But wait... We might want to keep it for consideration:
+
+	(3) If I got REALLY ambitious, some day, we could compare all of them one column
+	at a time and take the most popular (and valid for that column) character and
+	use all of the most popular characters. Better yet, at the bit level.
+
+Of course this is probably all overkill because we would normally expect to have pretty
+decent signals.  The designers didn't even bother to add any sort of checksum for error checking.
+
+The random errors injected are also not realistic. Actual noise would probably wipe out the
+same bit(s) for all of the slices.
+
+The protocol specification suggests comparing all 3 transmissions and taking the best 2 out of 3.
+I think that would best be left to an external application and we just concentrate on being
+a good modem here and providing a result when it is received.
+
+*/
+
+
+/***********************************************************************************
+ *
+ * Name:	hdlc_rec_bit
+ *
+ * Purpose:	Extract HDLC frames from a stream of bits.
+ *
+ * Inputs:	chan	- Channel number.
+ *
+ *		subchan	- This allows multiple demodulators per channel.
+ *
+ *		slice	- Allows multiple slicers per demodulator (subchannel).
+ *
+ *		raw 	- One bit from the demodulator.
+ *			  should be 0 or 1.
+ *
+ *		is_scrambled - Is the data scrambled?
+ *
+ *		descram_state - Current descrambler state.  (not used - remove)
+ *				Not so fast - plans to add new parameter.  PSK already provides it.
+ *
+ *
+ * Description:	This is called once for each received bit.
+ *		For each valid frame, process_rec_frame()
+ *		is called for further processing.
+ *
+ ***********************************************************************************/
+
+void hdlc_rec_bit(int chan, int subchan, int slice, int raw, int is_scrambled, int not_used_remove)
+{
+
+	int dbit;			/* Data bit after undoing NRZI. */
+					/* Should be only 0 or 1. */
+	struct hdlc_state_s *H;
+
+	/*
+	 * Different state information for each channel / subchannel / slice.
+	 */
+	H = &hdlc_state[chan];
+
+
+	/*
+	 * Using NRZI encoding,
+	 *   A '0' bit is represented by an inversion since previous bit.
+	 *   A '1' bit is represented by no change.
+	 */
+
+	if (is_scrambled) {
+		int descram;
+
+		descram = descramble(raw, &(H->lfsr));
+
+		dbit = (descram == H->prev_descram);
+		H->prev_descram = descram;
+		H->prev_raw = raw;
+	}
+	else {
+
+		dbit = (raw == H->prev_raw);
+
+		H->prev_raw = raw;
+	}
+
+	// After BER insertion, NRZI, and any descrambling, feed into FX.25 decoder as well.
+
+//	fx25_rec_bit (chan, subchan, slice, dbit);
+
+	il2p_rec_bit (chan, subchan, slice, raw);	// Note: skip NRZI.
+
+	/*
+	 * Octets are sent LSB first.
+	 * Shift the most recent 8 bits thru the pattern detector.
+	 */
+	H->pat_det >>= 1;
+	if (dbit) {
+		H->pat_det |= 0x80;
+	}
+
+	H->flag4_det >>= 1;
+	if (dbit) {
+		H->flag4_det |= 0x80000000;
+	}
+
+	rrbb_append_bit(H->rrbb, raw);
+
+	if (H->pat_det == 0x7e) {
+
+		rrbb_chop8(H->rrbb);
+
+		/*
+		 * The special pattern 01111110 indicates beginning and ending of a frame.
+		 * If we have an adequate number of whole octets, it is a candidate for
+		 * further processing.
+		 *
+		 * It might look odd that olen is being tested for 7 instead of 0.
+		 * This is because oacc would already have 7 bits from the special
+		 * "flag" pattern before it is detected here.
+		 */
+
+
+#if OLD_WAY
+
+#if TEST
+		text_color_set(DW_COLOR_DEBUG);
+		dw_printf("\nfound flag, olen = %d, frame_len = %d\n", olen, frame_len);
+#endif
+		if (H->olen == 7 && H->frame_len >= MIN_FRAME_LEN) {
+
+			unsigned short actual_fcs, expected_fcs;
+
+#if TEST
+			int j;
+			dw_printf("TRADITIONAL: frame len = %d\n", H->frame_len);
+			for (j = 0; j < H->frame_len; j++) {
+				dw_printf("  %02x", H->frame_buf[j]);
+			}
+			dw_printf("\n");
+
+#endif
+			/* Check FCS, low byte first, and process... */
+
+			/* Alternatively, it is possible to include the two FCS bytes */
+			/* in the CRC calculation and look for a magic constant.  */
+			/* That would be easier in the case where the CRC is being */
+			/* accumulated along the way as the octets are received. */
+			/* I think making a second pass over it and comparing is */
+			/* easier to understand. */
+
+			actual_fcs = H->frame_buf[H->frame_len - 2] | (H->frame_buf[H->frame_len - 1] << 8);
+
+			expected_fcs = fcs_calc(H->frame_buf, H->frame_len - 2);
+
+			if (actual_fcs == expected_fcs) {
+				alevel_t alevel = demod_get_audio_level(chan, subchan);
+
+				multi_modem_process_rec_frame(chan, subchan, slice, H->frame_buf, H->frame_len - 2, alevel, RETRY_NONE, 0);   /* len-2 to remove FCS. */
+			}
+			else {
+
+#if TEST
+				dw_printf("*** actual fcs = %04x, expected fcs = %04x ***\n", actual_fcs, expected_fcs);
+#endif
+
+			}
+
+	  }
+
+#else
+
+		 /*
+		  * New way - Decode the raw bits in later step.
+		  */
+
+#if TEST
+		text_color_set(DW_COLOR_DEBUG);
+		dw_printf("\nfound flag, channel %d.%d, %d bits in frame\n", chan, subchan, rrbb_get_len(H->rrbb) - 1);
+#endif
+		if (rrbb_get_len(H->rrbb) >= MIN_FRAME_LEN * 8) {
+
+			alevel_t alevel = demod_get_audio_level(chan, subchan);
+
+			rrbb_set_audio_level(H->rrbb, alevel);
+			hdlc_rec2_block(H->rrbb);
+			/* Now owned by someone else who will free it. */
+
+			H->rrbb = rrbb_new(chan, 0, 0, is_scrambled, H->lfsr, H->prev_descram); /* Allocate a new one. */
+		}
+		else {
+			rrbb_clear(H->rrbb, is_scrambled, H->lfsr, H->prev_descram);
+		}
+
+		H->olen = 0;		/* Allow accumulation of octets. */
+		H->frame_len = 0;
+
+
+		rrbb_append_bit(H->rrbb, H->prev_raw); /* Last bit of flag.  Needed to get first data bit. */
+						  /* Now that we are saving other initial state information, */
+						  /* it would be sensible to do the same for this instead */
+						  /* of lumping it in with the frame data bits. */
+#endif
+
+	}
+
+	//#define EXPERIMENT12B 1
+
+#if EXPERIMENT12B
+
+	else if (H->pat_det == 0xff) {
+
+		/*
+		 * Valid data will never have seven 1 bits in a row.
+		 *
+		 *	11111110
+		 *
+		 * This indicates loss of signal.
+		 * But we will let it slip thru because it might diminish
+		 * our single bit fixup effort.   Instead give up on frame
+		 * only when we see eight 1 bits in a row.
+		 *
+		 *	11111111
+		 *
+		 * What is the impact?  No difference.
+		 *
+		 *  Before:	atest -P E -F 1 ../02_Track_2.wav	= 1003
+		 *  After:	atest -P E -F 1 ../02_Track_2.wav	= 1003
+		 */
+
+#else
+	else if (H->pat_det == 0xfe) {
+
+		/*
+		 * Valid data will never have 7 one bits in a row.
+		 *
+		 *	11111110
+		 *
+		 * This indicates loss of signal.
+		 */
+
+#endif
+
+		H->olen = -1;		/* Stop accumulating octets. */
+		H->frame_len = 0;	/* Discard anything in progress. */
+
+		rrbb_clear(H->rrbb, is_scrambled, H->lfsr, H->prev_descram);
+
+	}
+	else if ((H->pat_det & 0xfc) == 0x7c) {
+
+		/*
+		 * If we have five '1' bits in a row, followed by a '0' bit,
+		 *
+		 *	0111110xx
+		 *
+		 * the current '0' bit should be discarded because it was added for
+		 * "bit stuffing."
+		 */
+		;
+
+	}
+	else {
+
+		/*
+		 * In all other cases, accumulate bits into octets, and complete octets
+		 * into the frame buffer.
+		 */
+		if (H->olen >= 0) {
+
+			H->oacc >>= 1;
+			if (dbit) {
+				H->oacc |= 0x80;
+			}
+			H->olen++;
+
+			if (H->olen == 8) {
+				H->olen = 0;
+
+				if (H->frame_len < MAX_FRAME_LEN) {
+					H->frame_buf[H->frame_len] = H->oacc;
+					H->frame_len++;
+				}
+			}
+		}
+	}
+}
+
+// TODO:  Data Carrier Detect (DCD) is now based on DPLL lock
+// rather than data patterns found here.
+// It would make sense to move the next 2 functions to demod.c
+// because this is done at the modem level, rather than HDLC decoder.
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dcd_change
+ *
+ * Purpose:     Combine DCD states of all subchannels/ into an overall
+ *		state for the channel.
+ *
+ * Inputs:	chan
+ *
+ *		subchan		0 to MAX_SUBCHANS-1 for HDLC.
+ *				SPECIAL CASE --> MAX_SUBCHANS for DTMF decoder.
+ *
+ *		slice		slicer number, 0 .. MAX_SLICERS - 1.
+ *
+ *		state		1 for active, 0 for not.
+ *
+ * Returns:	None.  Use hdlc_rec_data_detect_any to retrieve result.
+ *
+ * Description:	DCD for the channel is active if ANY of the subchannels/slices
+ *		are active.  Update the DCD indicator.
+ *
+ * version 1.3:	Add DTMF detection into the final result.
+ *		This is now called from dtmf.c too.
+ *
+ *--------------------------------------------------------------------*/
+
+void dcd_change(int chan, int subchan, int slice, int state)
+{
+
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        hdlc_rec_data_detect_any
+ *
+ * Purpose:     Determine if the radio channel is currently busy
+ *		with packet data.
+ *		This version doesn't care about voice or other sounds.
+ *		This is used by the transmit logic to transmit only
+ *		when the channel is clear.
+ *
+ * Inputs:	chan	- Audio channel.
+ *
+ * Returns:	True if channel is busy (data detected) or
+ *		false if OK to transmit.
+ *
+ *
+ * Description:	We have two different versions here.
+ *
+ *		hdlc_rec_data_detect_any sees if ANY of the decoders
+ *		for this channel are receiving a signal.   This is
+ *		used to determine whether the channel is clear and
+ *		we can transmit.  This would apply to the 300 baud
+ *		HF SSB case where we have multiple decoders running
+ *		at the same time.  The channel is busy if ANY of them
+ *		thinks the channel is busy.
+ *
+ * Version 1.3: New option for input signal to inhibit transmit.
+ *
+ *--------------------------------------------------------------------*/
+
+int hdlc_rec_data_detect_any(int chan)
+{
+
+	int sc;
+	assert(chan >= 0 && chan < MAX_CHANS);
+
+	for (sc = 0; sc < num_subchan[chan]; sc++) {
+		if (composite_dcd[chan][sc] != 0)
+			return (1);
+	}
+
+	return (0);
+
+} /* end hdlc_rec_data_detect_any */
+
+/* end hdlc_rec.c */
+
+
+
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  John Langner, WB2OSZ
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU General Public License as published by
+//    the Free Software Foundation, either version 2 of the License, or
+//    (at your option) any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+
+/********************************************************************************
+ *
+ * File:	hdlc_rec2.c
+ *
+ * Purpose:	Extract HDLC frame from a block of bits after someone
+ *		else has done the work of pulling it out from between
+ *		the special "flag" sequences.
+ *
+ *
+ * New in version 1.1:
+ *
+ *		Several enhancements provided by Fabrice FAURE:
+ *
+ *		- Additional types of attempts to fix a bad CRC.
+ *		- Optimized code to reduce execution time.
+ *		- Improved detection of duplicate packets from different fixup attempts.
+ *		- Set limit on number of packets in fix up later queue.
+ *
+ *		One of the new recovery attempt cases recovers three additional
+ *		packets that were lost before.  The one thing I disagree with is
+ *		use of the word "swap" because that sounds like two things
+ *		are being exchanged for each other.  I would prefer "flip"
+ *		or "invert" to describe changing a bit to the opposite state.
+ *		I took "swap" out of the user-visible messages but left the
+ *		rest of the source code as provided.
+ *
+ * Test results:	We intentionally use the worst demodulator so there
+ *			is more opportunity to try to fix the frames.
+ *
+ *		atest -P A -F n 02_Track_2.wav
+ *
+ *		n   	description	frames	sec
+ *		--  	----------- 	------	---
+ *		0	no attempt	963	40	error-free frames
+ *		1	invert 1	979	41	16 more
+ *		2	invert 2	982	42	3 more
+ *		3	invert 3	982	42	no change
+ *		4	remove 1	982	43	no change
+ *		5	remove 2	982	43	no change
+ *		6	remove 3	982	43	no change
+ *		7	insert 1	982	45	no change
+ *		8	insert 2	982	47	no change
+ *		9	invert two sep	993	178	11 more, some visually obvious errors.
+ *		10	invert many?	993	190	no change
+ *		11	remove many	995	190	2 more, need to investigate in detail.
+ *		12	remove two sep	995	201	no change
+ *
+ * Observations:	The "insert" and "remove" techniques had no benefit.  I would not expect them to.
+ *			We have a phase locked loop that attempts to track any slight variations in the
+ *			timing so we sample near the middle of the bit interval.  Bits can get corrupted
+ *			by noise but not disappear or just appear.  That would be a gap in the timing.
+ *			These should probably be removed in a future version.
+ *
+ *
+ * Version 1.2:	Now works for 9600 baud.
+ *		This was more complicated due to the data scrambling.
+ *		It was necessary to retain more initial state information after
+ *		the start flag octet.
+ *
+ * Version 1.3: Took out all of the "insert" and "remove" cases because they
+ *		offer no benenfit.
+ *
+ *		Took out the delayed processing and just do it realtime.
+ *		Changed SWAP to INVERT because it is more descriptive.
+ *
+ *******************************************************************************/
+
+
+//#define DEBUG 1
+//#define DEBUGx 1
+//#define DEBUG_LATER 1
+
+/* Audio configuration. */
+
+static struct audio_s          *save_audio_config_p;
+
+
+/*
+ * Minimum & maximum sizes of an AX.25 frame including the 2 octet FCS.
+ */
+
+#define MIN_FRAME_LEN ((AX25_MIN_PACKET_LEN) + 2)
+
+#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)	
+
+
+ /*
+  * This is the current state of the HDLC decoder.
+  *
+  * It is possible to run multiple decoders concurrently by
+  * having a separate set of state variables for each.
+  *
+  * Should have a reset function instead of initializations here.
+  */
+
+  // TODO: Clean up. This is a remnant of splitting hdlc_rec.c into 2 parts.
+  // This is not the same as hdlc_state_s in hdlc_rec.c
+  // "2" was added to reduce confusion.  Can be trimmed down.
+
+struct hdlc_state2_s {
+
+	int prev_raw;			/* Keep track of previous bit so */
+					/* we can look for transitions. */
+					/* Should be only 0 or 1. */
+
+	int is_scrambled;		/* Set for 9600 baud. */
+	int lfsr;			/* Descrambler shift register for 9600 baud. */
+	int prev_descram;		/* Previous unscrambled for 9600 baud. */
+
+
+	unsigned char pat_det; 		/* 8 bit pattern detector shift register. */
+					/* See below for more details. */
+
+	unsigned char oacc;		/* Accumulator for building up an octet. */
+
+	int olen;			/* Number of bits in oacc. */
+					/* When this reaches 8, oacc is copied */
+					/* to the frame buffer and olen is zeroed. */
+
+	unsigned char frame_buf[MAX_FRAME_LEN];
+	/* One frame is kept here. */
+
+	int frame_len;			/* Number of octets in frame_buf. */
+					/* Should be in range of 0 .. MAX_FRAME_LEN. */
+
+};
+
+
+
+
+typedef enum retry_mode_e {
+	RETRY_MODE_CONTIGUOUS = 0,
+	RETRY_MODE_SEPARATED = 1,
+}  retry_mode_t;
+
+typedef enum retry_type_e {
+	RETRY_TYPE_NONE = 0,
+	RETRY_TYPE_SWAP = 1
+}  retry_type_t;
+
+typedef struct retry_conf_s {
+	retry_t      retry;
+	retry_mode_t mode;
+	retry_type_t type;
+	union {
+		struct {
+			int bit_idx_a; /*  */
+			int bit_idx_b; /*  */
+			int bit_idx_c; /*  */
+		} sep;       /* RETRY_MODE_SEPARATED */
+
+		struct {
+			int bit_idx;
+			int nr_bits;
+		} contig;  /* RETRY_MODE_CONTIGUOUS */
+
+	} u_bits;
+	int insert_value;
+
+} retry_conf_t;
+
+
+
+
+#if defined(DIREWOLF_C) || defined(ATEST_C) || defined(UDPTEST_C)
+
+static const char * retry_text[] = {
+		"NONE",
+		"SINGLE",
+		"DOUBLE",
+		"TRIPLE",
+		"TWO_SEP",
+		"PASSALL" };
+#endif
+
+
+
+static int try_decode(rrbb_t block, int chan, int subchan, int slice, alevel_t alevel, retry_conf_t retry_conf, int passall);
+
+static int try_to_fix_quick_now(rrbb_t block, int chan, int subchan, int slice, alevel_t alevel);
+
+static int sanity_check(unsigned char *buf, int blen, retry_t bits_flipped, enum sanity_e sanity_test);
+
+
+/***********************************************************************************
+ *
+ * Name:	hdlc_rec2_init
+ *
+ * Purpose:	Initialization.
+ *
+ * Inputs:	p_audio_config	 - Pointer to configuration settings.
+ *				   This is what we care about for each channel.
+ *
+ *	   			enum retry_e fix_bits;
+ *					Level of effort to recover from
+ *					a bad FCS on the frame.
+ *					0 = no effort
+ *					1 = try inverting a single bit
+ *					2... = more techniques...
+ *
+ *	    			enum sanity_e sanity_test;
+ *					Sanity test to apply when finding a good
+ *					CRC after changing one or more bits.
+ *					Must look like APRS, AX.25, or anything.
+ *
+ *	    			int passall;
+ *					Allow thru even with bad CRC after exhausting
+ *					all fixup attempts.
+ *
+ * Description:	Save pointer to configuration for later use.
+ *
+ ***********************************************************************************/
+
+void hdlc_rec2_init(struct audio_s *p_audio_config)
+{
+	save_audio_config_p = p_audio_config;
+}
+
+
+
+/***********************************************************************************
+ *
+ * Name:	hdlc_rec2_block
+ *
+ * Purpose:	Extract HDLC frame from a stream of bits.
+ *
+ * Inputs:	block 		- Handle for bit array.
+ *
+ * Description:	The other (original) hdlc decoder took one bit at a time
+ *		right out of the demodulator.
+ *
+ *		This is different in that it processes a block of bits
+ *		previously extracted from between two "flag" patterns.
+ *
+ *		This allows us to try decoding the same received data more
+ *		than once.
+ *
+ * Version 1.2:	Now works properly for G3RUH type scrambling.
+ *
+ ***********************************************************************************/
+
+
+void hdlc_rec2_block(rrbb_t block)
+{
+	int chan = rrbb_get_chan(block);
+	int subchan = rrbb_get_subchan(block);
+	int slice = rrbb_get_slice(block);
+	alevel_t alevel = rrbb_get_audio_level(block);
+	retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
+	int passall = save_audio_config_p->achan[chan].passall;
+	int ok;
+
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf("\n--- try to decode ---\n");
+#endif
+
+	/* Create an empty retry configuration */
+	retry_conf_t retry_cfg;
+
+	memset(&retry_cfg, 0, sizeof(retry_cfg));
+
+	/*
+	 * For our first attempt we don't try to alter any bits.
+	 * Still let it thru if passall AND no retries are desired.
+	 */
+
+	retry_cfg.type = RETRY_TYPE_NONE;
+	retry_cfg.mode = RETRY_MODE_CONTIGUOUS;
+	retry_cfg.retry = RETRY_NONE;
+	retry_cfg.u_bits.contig.nr_bits = 0;
+	retry_cfg.u_bits.contig.bit_idx = 0;
+
+	ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, passall & (fix_bits == RETRY_NONE));
+	if (ok) {
+#if DEBUG
+		text_color_set(DW_COLOR_INFO);
+		dw_printf("Got it the first time.\n");
+#endif
+		rrbb_delete(block);
+		return;
+	}
+
+	/*
+	 * Not successful with frame in original form.
+	 * See if we can "fix" it.
+	 */
+	if (try_to_fix_quick_now(block, chan, subchan, slice, alevel)) {
+		rrbb_delete(block);
+		return;
+	}
+
+
+	if (passall) {
+		/* Exhausted all desired fix up attempts. */
+		/* Let thru even with bad CRC.  Of course, it still */
+		/* needs to be a minimum number of whole octets. */
+		ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, 1);
+		rrbb_delete(block);
+	}
+	else {
+		rrbb_delete(block);
+	}
+
+} /* end hdlc_rec2_block */
+
+
+/***********************************************************************************
+ *
+ * Name:	try_to_fix_quick_now
+ *
+ * Purpose:	Attempt some quick fixups that don't take very long.
+ *
+ * Inputs:	block	- Stream of bits that might be a frame.
+ *		chan	- Radio channel from which it was received.
+ *		subchan	- Which demodulator when more than one per channel.
+ *		alevel	- Audio level for later reporting.
+ *
+ * Global In:	configuration fix_bits - Maximum level of fix up to attempt.
+ *
+ *				RETRY_NONE (0)	- Don't try any.
+ *				RETRY_INVERT_SINGLE (1)  - Try inverting single bits.
+ *				etc.
+ *
+ *		configuration passall - Let it thru with bad CRC after exhausting
+ *				all fixup attempts.
+ *
+ *
+ * Returns:	1 for success.  "try_decode" has passed the result along to the
+ *				processing step.
+ *		0 for failure.  Caller might continue with more aggressive attempts.
+ *
+ * Original:	Some of the attempted fix up techniques are quick.
+ *		We will attempt them immediately after receiving the frame.
+ *		Others, that take time order N**2, will be done in a later section.
+ *
+ * Version 1.2:	Now works properly for G3RUH type scrambling.
+ *
+ * Version 1.3: Removed the extra cases that didn't help.
+ *		The separated bit case is now handled immediately instead of
+ *		being thrown in a queue for later processing.
+ *
+ ***********************************************************************************/
+
+static int try_to_fix_quick_now(rrbb_t block, int chan, int subchan, int slice, alevel_t alevel)
+{
+	int ok;
+	int len, i;
+	retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
+	//int passall = save_audio_config_p->achan[chan].passall;
+
+
+	len = rrbb_get_len(block);
+	/* Prepare the retry configuration */
+	retry_conf_t retry_cfg;
+
+	memset(&retry_cfg, 0, sizeof(retry_cfg));
+
+	/* Will modify only contiguous bits*/
+	retry_cfg.mode = RETRY_MODE_CONTIGUOUS;
+	/*
+	 * Try inverting one bit.
+	 */
+	if (fix_bits < RETRY_INVERT_SINGLE) {
+
+		/* Stop before single bit fix up. */
+
+		return 0;	/* failure. */
+	}
+	/* Try to swap one bit */
+	retry_cfg.type = RETRY_TYPE_SWAP;
+	retry_cfg.retry = RETRY_INVERT_SINGLE;
+	retry_cfg.u_bits.contig.nr_bits = 1;
+
+	for (i = 0; i < len; i++) {
+		/* Set the index of the bit to swap */
+		retry_cfg.u_bits.contig.bit_idx = i;
+		ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, 0);
+		if (ok) {
+#if DEBUG
+			text_color_set(DW_COLOR_ERROR);
+			dw_printf("*** Success by flipping SINGLE bit %d of %d ***\n", i, len);
+#endif
+			return 1;
+		}
+	}
+
+	/*
+	 * Try inverting two adjacent bits.
+	 */
+	if (fix_bits < RETRY_INVERT_DOUBLE) {
+		return 0;
+	}
+	/* Try to swap two contiguous bits */
+	retry_cfg.retry = RETRY_INVERT_DOUBLE;
+	retry_cfg.u_bits.contig.nr_bits = 2;
+
+
+	for (i = 0; i < len - 1; i++) {
+		retry_cfg.u_bits.contig.bit_idx = i;
+		ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, 0);
+		if (ok) {
+#if DEBUG
+			text_color_set(DW_COLOR_ERROR);
+			dw_printf("*** Success by flipping DOUBLE bit %d of %d ***\n", i, len);
+#endif
+			return 1;
+		}
+	}
+
+	/*
+	 * Try inverting adjacent three bits.
+	 */
+	if (fix_bits < RETRY_INVERT_TRIPLE) {
+		return 0;
+	}
+	/* Try to swap three contiguous bits */
+	retry_cfg.retry = RETRY_INVERT_TRIPLE;
+	retry_cfg.u_bits.contig.nr_bits = 3;
+
+	for (i = 0; i < len - 2; i++) {
+		retry_cfg.u_bits.contig.bit_idx = i;
+		ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, 0);
+		if (ok) {
+#if DEBUG
+			text_color_set(DW_COLOR_ERROR);
+			dw_printf("*** Success by flipping TRIPLE bit %d of %d ***\n", i, len);
+#endif
+			return 1;
+		}
+	}
+
+
+	/*
+	 * Two  non-adjacent ("separated") single bits.
+	 * It chews up a lot of CPU time.  Usual test takes 4 times longer to run.
+	 *
+	 * Processing time is order N squared so time goes up rapidly with larger frames.
+	 */
+	if (fix_bits < RETRY_INVERT_TWO_SEP) {
+		return 0;
+	}
+
+	retry_cfg.mode = RETRY_MODE_SEPARATED;
+	retry_cfg.type = RETRY_TYPE_SWAP;
+	retry_cfg.retry = RETRY_INVERT_TWO_SEP;
+	retry_cfg.u_bits.sep.bit_idx_c = -1;
+
+#ifdef DEBUG_LATER
+	tstart = dtime_now();
+	dw_printf("*** Try flipping TWO SEPARATED BITS %d bits\n", len);
+#endif
+	len = rrbb_get_len(block);
+	for (i = 0; i < len - 2; i++) {
+		retry_cfg.u_bits.sep.bit_idx_a = i;
+		int j;
+
+		ok = 0;
+		for (j = i + 2; j < len; j++) {
+			retry_cfg.u_bits.sep.bit_idx_b = j;
+			ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, 0);
+			if (ok) {
+				break;
+			}
+
+		}
+		if (ok) {
+#if DEBUG
+			text_color_set(DW_COLOR_ERROR);
+			dw_printf("*** Success by flipping TWO SEPARATED bits %d and %d of %d \n", i, j, len);
+#endif
+			return (1);
+		}
+	}
+
+	return 0;
+}
+
+
+
+// TODO:  Remove this.  but first figure out what to do in atest.c
+
+
+
+int hdlc_rec2_try_to_fix_later(rrbb_t block, int chan, int subchan, int slice, alevel_t alevel)
+{
+	int ok;
+	//int len;
+	//retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
+	int passall = save_audio_config_p->achan[chan].passall;
+#if DEBUG_LATER
+	double tstart, tend;
+#endif
+	retry_conf_t retry_cfg;
+
+	memset(&retry_cfg, 0, sizeof(retry_cfg));
+
+	//len = rrbb_get_len(block);
+
+
+/*
+ * All fix up attempts have failed.
+ * Should we pass it along anyhow with a bad CRC?
+ * Note that we still need a minimum number of whole octets.
+ */
+	if (passall) {
+
+		retry_cfg.type = RETRY_TYPE_NONE;
+		retry_cfg.mode = RETRY_MODE_CONTIGUOUS;
+		retry_cfg.retry = RETRY_NONE;
+		retry_cfg.u_bits.contig.nr_bits = 0;
+		retry_cfg.u_bits.contig.bit_idx = 0;
+		ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, passall);
+		return (ok);
+	}
+
+	return (0);
+
+}  /* end hdlc_rec2_try_to_fix_later */
+
+
+
+/*
+ * Check if the specified index of bit has been modified with the current type of configuration
+ * Provide a specific implementation for contiguous mode to optimize number of tests done in the loop
+ */
+
+inline static char is_contig_bit_modified(int bit_idx, retry_conf_t retry_conf) {
+	int cont_bit_idx = retry_conf.u_bits.contig.bit_idx;
+	int cont_nr_bits = retry_conf.u_bits.contig.nr_bits;
+
+	if (bit_idx >= cont_bit_idx && (bit_idx < cont_bit_idx + cont_nr_bits))
+		return 1;
+	else
+		return 0;
+}
+
+/*
+ * Check  if the specified index of bit has been modified with the current type of configuration in separated bit index mode
+ * Provide a specific implementation for separated mode to optimize number of tests done in the loop
+ */
+
+inline static char is_sep_bit_modified(int bit_idx, retry_conf_t retry_conf) {
+	if (bit_idx == retry_conf.u_bits.sep.bit_idx_a ||
+		bit_idx == retry_conf.u_bits.sep.bit_idx_b ||
+		bit_idx == retry_conf.u_bits.sep.bit_idx_c)
+		return 1;
+	else
+		return 0;
+}
+
+
+
+/***********************************************************************************
+ *
+ * Name:	try_decode
+ *
+ * Purpose:
+ *
+ * Inputs:	block		- Bit string that was collected between "flag" patterns.
+ *
+ *		chan, subchan	- where it came from.
+ *
+ *		alevel		- audio level for later reporting.
+ *
+ *		retry_conf	- Controls changes that will be attempted to get a good CRC.
+ *
+ *	   			retry:
+ *					Level of effort to recover from a bad FCS on the frame.
+ *				                RETRY_NONE = 0
+ *				                RETRY_INVERT_SINGLE = 1
+ *				                RETRY_INVERT_DOUBLE = 2
+ *		                                RETRY_INVERT_TRIPLE = 3
+ *		                                RETRY_INVERT_TWO_SEP = 4
+ *
+ *	    			mode:	RETRY_MODE_CONTIGUOUS - change adjacent bits.
+ *						contig.bit_idx - first bit position
+ *						contig.nr_bits - number of bits
+ *
+ *				        RETRY_MODE_SEPARATED  - change bits not next to each other.
+ *						sep.bit_idx_a - bit positions
+ *						sep.bit_idx_b - bit positions
+ *						sep.bit_idx_c - bit positions
+ *
+ *				type:	RETRY_TYPE_NONE	- Make no changes.
+ *					RETRY_TYPE_SWAP - Try inverting.
+ *
+ *		passall		- All it thru even with bad CRC.
+ *				  Valid only when no changes make.  i.e.
+ *					retry == RETRY_NONE, type == RETRY_TYPE_NONE
+ *
+ * Returns:	1 = successfully extracted something.
+ *		0 = failure.
+ *
+ ***********************************************************************************/
+
+static int try_decode(rrbb_t block, int chan, int subchan, int slice, alevel_t alevel, retry_conf_t retry_conf, int passall)
+{
+	struct hdlc_state2_s H2;
+	int blen;			/* Block length in bits. */
+	int i;
+	int raw;			/* From demodulator.  Should be 0 or 1. */
+#if DEBUGx
+	int crc_failed = 1;
+#endif
+	int retry_conf_mode = retry_conf.mode;
+	int retry_conf_type = retry_conf.type;
+	int retry_conf_retry = retry_conf.retry;
+
+
+	H2.is_scrambled = rrbb_get_is_scrambled(block);
+	H2.prev_descram = rrbb_get_prev_descram(block);
+	H2.lfsr = rrbb_get_descram_state(block);
+	H2.prev_raw = rrbb_get_bit(block, 0);	  /* Actually last bit of the */
+					/* opening flag so we can derive the */
+					/* first data bit.  */
+
+	/* Does this make sense? */
+	/* This is the last bit of the "flag" pattern. */
+	/* If it was corrupted we wouldn't have detected */
+	/* the start of frame. */
+
+	if ((retry_conf.mode == RETRY_MODE_CONTIGUOUS && is_contig_bit_modified(0, retry_conf)) ||
+		(retry_conf.mode == RETRY_MODE_SEPARATED && is_sep_bit_modified(0, retry_conf))) {
+		H2.prev_raw = !H2.prev_raw;
+	}
+
+	H2.pat_det = 0;
+	H2.oacc = 0;
+	H2.olen = 0;
+	H2.frame_len = 0;
+
+	blen = rrbb_get_len(block);
+
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+	if (retry_conf.type == RETRY_TYPE_NONE)
+		dw_printf("try_decode: blen=%d\n", blen);
+#endif
+	for (i = 1; i < blen; i++) {
+		/* Get the value for the current bit */
+		raw = rrbb_get_bit(block, i);
+		/* If swap two sep mode , swap the bit if needed */
+		if (retry_conf_retry == RETRY_INVERT_TWO_SEP) {
+			if (is_sep_bit_modified(i, retry_conf))
+				raw = !raw;
+		}
+		/* Else handle all the others contiguous modes */
+		else if (retry_conf_mode == RETRY_MODE_CONTIGUOUS) {
+
+			if (retry_conf_type == RETRY_TYPE_SWAP) {
+				/* If this is the bit to swap */
+				if (is_contig_bit_modified(i, retry_conf))
+					raw = !raw;
+			}
+
+		}
+		else {
+		}
+		/*
+		 * Octets are sent LSB first.
+		 * Shift the most recent 8 bits thru the pattern detector.
+		 */
+		H2.pat_det >>= 1;
+
+		/*
+		 * Using NRZI encoding,
+		 *   A '0' bit is represented by an inversion since previous bit.
+		 *   A '1' bit is represented by no change.
+		 *   Note: this code can be factorized with the raw != H2.prev_raw code at the cost of processing time
+		 */
+
+		int dbit;
+
+		if (H2.is_scrambled) {
+			int descram;
+
+			descram = descramble(raw, &(H2.lfsr));
+
+			dbit = (descram == H2.prev_descram);
+			H2.prev_descram = descram;
+			H2.prev_raw = raw;
+		}
+		else {
+
+			dbit = (raw == H2.prev_raw);
+			H2.prev_raw = raw;
+		}
+
+		if (dbit) {
+
+			H2.pat_det |= 0x80;
+			/* Valid data will never have 7 one bits in a row: exit. */
+			if (H2.pat_det == 0xfe) {
+#if DEBUGx
+				text_color_set(DW_COLOR_DEBUG);
+				dw_printf("try_decode: found abort, i=%d\n", i);
+#endif
+				return 0;
+			}
+			H2.oacc >>= 1;
+			H2.oacc |= 0x80;
+		}
+		else {
+
+			/* The special pattern 01111110 indicates beginning and ending of a frame: exit. */
+			if (H2.pat_det == 0x7e) {
+#if DEBUGx
+				text_color_set(DW_COLOR_DEBUG);
+				dw_printf("try_decode: found flag, i=%d\n", i);
+#endif
+				return 0;
+				/*
+				 * If we have five '1' bits in a row, followed by a '0' bit,
+				 *
+				 *	011111xx
+				 *
+				 * the current '0' bit should be discarded because it was added for
+				 * "bit stuffing."
+				 */
+
+			}
+			else if ((H2.pat_det >> 2) == 0x1f) {
+				continue;
+			}
+			H2.oacc >>= 1;
+		}
+
+		/*
+		 * Now accumulate bits into octets, and complete octets
+		 * into the frame buffer.
+		 */
+
+		H2.olen++;
+
+		if (H2.olen & 8) {
+			H2.olen = 0;
+
+			if (H2.frame_len < MAX_FRAME_LEN) {
+				H2.frame_buf[H2.frame_len] = H2.oacc;
+				H2.frame_len++;
+
+			}
+		}
+	}	/* end of loop on all bits in block */
+/*
+ * Do we have a minimum number of complete bytes?
+ */
+
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf("try_decode: olen=%d, frame_len=%d\n", H2.olen, H2.frame_len);
+#endif
+
+	if (H2.olen == 0 && H2.frame_len >= MIN_FRAME_LEN) {
+
+		unsigned short actual_fcs, expected_fcs;
+
+#if DEBUGx 
+		if (retry_conf.type == RETRY_TYPE_NONE) {
+			int j;
+			text_color_set(DW_COLOR_DEBUG);
+			dw_printf("NEW WAY: frame len = %d\n", H2.frame_len);
+			for (j = 0; j < H2.frame_len; j++) {
+				dw_printf("  %02x", H2.frame_buf[j]);
+			}
+			dw_printf("\n");
+
+		}
+#endif
+		/* Check FCS, low byte first, and process... */
+
+		/* Alternatively, it is possible to include the two FCS bytes */
+		/* in the CRC calculation and look for a magic constant.  */
+		/* That would be easier in the case where the CRC is being */
+		/* accumulated along the way as the octets are received. */
+		/* I think making a second pass over it and comparing is */
+		/* easier to understand. */
+
+		actual_fcs = H2.frame_buf[H2.frame_len - 2] | (H2.frame_buf[H2.frame_len - 1] << 8);
+
+		expected_fcs = get_fcs(H2.frame_buf, H2.frame_len - 2);
+
+
+		if (actual_fcs == expected_fcs &&
+			sanity_check(H2.frame_buf, H2.frame_len - 2, retry_conf.retry, save_audio_config_p->achan[chan].sanity_test)) {
+
+			// TODO: Shouldn't be necessary to pass chan, subchan, alevel into
+			// try_decode because we can obtain them from block.
+			// Let's make sure that assumption is good...
+
+			assert(rrbb_get_chan(block) == chan);
+			assert(rrbb_get_subchan(block) == subchan);
+			multi_modem_process_rec_frame(chan, subchan, slice, H2.frame_buf, H2.frame_len - 2, alevel, retry_conf.retry, 0);   /* len-2 to remove FCS. */
+			return 1;		/* success */
+
+		}
+		else if (passall) {
+			if (retry_conf_retry == RETRY_NONE && retry_conf_type == RETRY_TYPE_NONE) {
+
+				//text_color_set(DW_COLOR_ERROR);
+				//dw_printf ("ATTEMPTING PASSALL PROCESSING\n");
+
+				multi_modem_process_rec_frame(chan, subchan, slice, H2.frame_buf, H2.frame_len - 2, alevel, RETRY_MAX, 0);   /* len-2 to remove FCS. */
+				return 1;		/* success */
+			}
+			else {
+				text_color_set(DW_COLOR_ERROR);
+				dw_printf("try_decode: internal error passall = %d, retry_conf_retry = %d, retry_conf_type = %d\n",
+					passall, retry_conf_retry, retry_conf_type);
+			}
+		}
+		else {
+
+			goto failure;
+		}
+	}
+	else {
+#if DEBUGx
+		crc_failed = 0;
+#endif
+		goto failure;
+	}
+failure:
+#if DEBUGx
+	if (retry_conf.type == RETRY_TYPE_NONE) {
+		int j;
+		text_color_set(DW_COLOR_ERROR);
+		if (crc_failed)
+			dw_printf("CRC failed\n");
+		if (H2.olen != 0)
+			dw_printf("Bad olen: %d \n", H2.olen);
+		else if (H2.frame_len < MIN_FRAME_LEN) {
+			dw_printf("Frame too small\n");
+			goto end;
+		}
+
+		dw_printf("FAILURE with frame: frame len = %d\n", H2.frame_len);
+		dw_printf("\n");
+		for (j = 0; j < H2.frame_len; j++) {
+			dw_printf(" %02x", H2.frame_buf[j]);
+		}
+		dw_printf("\nDEC\n");
+		for (j = 0; j < H2.frame_len; j++) {
+			dw_printf("%c", H2.frame_buf[j] >> 1);
+		}
+		dw_printf("\nORIG\n");
+		for (j = 0; j < H2.frame_len; j++) {
+			dw_printf("%c", H2.frame_buf[j]);
+		}
+		dw_printf("\n");
+	}
+end:
+#endif
+	return 0;	/* failure. */
+
+} /* end try_decode */
+
+
+
+/***********************************************************************************
+ *
+ * Name:	sanity_check
+ *
+ * Purpose:	Try to weed out bogus packets from initially failed FCS matches.
+ *
+ * Inputs:	buf
+ *
+ *		blen
+ *
+ *		bits_flipped
+ *
+ *		sanity		How much sanity checking to perform:
+ *					SANITY_APRS - Looks like APRS.  See User Guide,
+ *						section that discusses bad apples.
+ *					SANITY_AX25 - Has valid AX.25 address part.
+ *						No checking of the rest.  Useful for
+ *						connected mode packet.
+ *					SANITY_NONE - No checking.  Would be suitable
+ *						only if using frames that don't conform
+ *						to AX.25 standard.
+ *
+ * Returns:	1 if it passes the sanity test.
+ *
+ * Description:	This is NOT a validity check.
+ *		We don't know if modifying the frame fixed the problem or made it worse.
+ *		We can only test if it looks reasonable.
+ *
+ ***********************************************************************************/
+
+
+static int sanity_check(unsigned char *buf, int blen, retry_t bits_flipped, enum sanity_e sanity_test)
+{
+	int alen;		/* Length of address part. */
+	int j;
+
+	/*
+	 * No sanity check if we didn't try fixing the data.
+	 * Should we have different levels of checking depending on
+	 * how much we try changing the raw data?
+	 */
+	if (bits_flipped == RETRY_NONE) {
+		return 1;
+	}
+
+
+	/*
+	 * If using frames that do not conform to AX.25, it might be
+	 * desirable to skip the sanity check entirely.
+	 */
+	if (sanity_test == SANITY_NONE) {
+		return (1);
+	}
+
+	/*
+	 * Address part must be a multiple of 7.
+	 */
+
+	alen = 0;
+	for (j = 0; j < blen && alen == 0; j++) {
+		if (buf[j] & 0x01) {
+			alen = j + 1;
+		}
+	}
+
+	if (alen % 7 != 0) {
+#if DEBUGx
+		text_color_set(DW_COLOR_ERROR);
+		dw_printf("sanity_check: FAILED.  Address part length %d not multiple of 7.\n", alen);
+#endif
+		return 0;
+	}
+
+	/*
+	 * Need at least 2 addresses and maximum of 8 digipeaters.
+	 */
+
+	if (alen / 7 < 2 || alen / 7 > 10) {
+#if DEBUGx
+		text_color_set(DW_COLOR_ERROR);
+		dw_printf("sanity_check: FAILED.  Too few or many addresses.\n");
+#endif
+		return 0;
+	}
+
+	/*
+	 * Addresses can contain only upper case letters, digits, and space.
+	 */
+
+	for (j = 0; j < alen; j += 7) {
+
+		char addr[7];
+
+		addr[0] = buf[j + 0] >> 1;
+		addr[1] = buf[j + 1] >> 1;
+		addr[2] = buf[j + 2] >> 1;
+		addr[3] = buf[j + 3] >> 1;
+		addr[4] = buf[j + 4] >> 1;
+		addr[5] = buf[j + 5] >> 1;
+		addr[6] = '\0';
+
+
+		if ((!isupper(addr[0]) && !isdigit(addr[0])) ||
+			(!isupper(addr[1]) && !isdigit(addr[1]) && addr[1] != ' ') ||
+			(!isupper(addr[2]) && !isdigit(addr[2]) && addr[2] != ' ') ||
+			(!isupper(addr[3]) && !isdigit(addr[3]) && addr[3] != ' ') ||
+			(!isupper(addr[4]) && !isdigit(addr[4]) && addr[4] != ' ') ||
+			(!isupper(addr[5]) && !isdigit(addr[5]) && addr[5] != ' ')) {
+#if DEBUGx	  
+			text_color_set(DW_COLOR_ERROR);
+			dw_printf("sanity_check: FAILED.  Invalid characters in addresses \"%s\"\n", addr);
+#endif
+			return 0;
+		}
+	}
+
+
+	/*
+	 * That's good enough for the AX.25 sanity check.
+	 * Continue below for additional APRS checking.
+	 */
+	if (sanity_test == SANITY_AX25) {
+		return (1);
+	}
+
+	/*
+	 * The next two bytes should be 0x03 and 0xf0 for APRS.
+	 */
+
+	if (buf[alen] != 0x03 || buf[alen + 1] != 0xf0) {
+		return (0);
+	}
+
+	/*
+	 * Finally, look for bogus characters in the information part.
+	 * In theory, the bytes could have any values.
+	 * In practice, we find only printable ASCII characters and:
+	 *
+	 *	0x0a	line feed
+	 *	0x0d	carriage return
+	 *	0x1c	MIC-E
+	 *	0x1d	MIC-E
+	 *	0x1e	MIC-E
+	 *	0x1f	MIC-E
+	 *	0x7f	MIC-E
+	 *	0x80	"{UIV32N}<0x0d><0x9f><0x80>"
+	 *	0x9f	"{UIV32N}<0x0d><0x9f><0x80>"
+	 *	0xb0	degree symbol, ISO LATIN1
+	 *		  (Note: UTF-8 uses two byte sequence 0xc2 0xb0.)
+	 *	0xbe	invalid MIC-E encoding.
+	 *	0xf8	degree symbol, Microsoft code page 437
+	 *
+	 * So, if we have something other than these (in English speaking countries!),
+	 * chances are that we have bogus data from twiddling the wrong bits.
+	 *
+	 * Notice that we shouldn't get here for good packets.  This extra level
+	 * of checking happens only if we twiddled a couple of bits, possibly
+	 * creating bad data.  We want to be very fussy.
+	 */
+
+	for (j = alen + 2; j < blen; j++) {
+		int ch = buf[j];
+
+		if (!((ch >= 0x1c && ch <= 0x7f)
+			|| ch == 0x0a
+			|| ch == 0x0d
+			|| ch == 0x80
+			|| ch == 0x9f
+			|| ch == 0xc2
+			|| ch == 0xb0
+			|| ch == 0xf8)) {
+#if DEBUGx
+			text_color_set(DW_COLOR_ERROR);
+			dw_printf("sanity_check: FAILED.  Probably bogus info char 0x%02x\n", ch);
+#endif
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+
+/* end hdlc_rec2.c */
+
+
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+// 
+//    Copyright (C) 2011, 2012, 2013, 2015, 2019, 2021  John Langner, WB2OSZ
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU General Public License as published by
+//    the Free Software Foundation, either version 2 of the License, or
+//    (at your option) any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+//#define DEBUG4 1	/* capture 9600 output to log files */
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      demod_9600.c
+ *
+ * Purpose:   	Demodulator for baseband signal.
+ *		This is used for AX.25 (with scrambling) and IL2P without.
+ *
+ * Input:	Audio samples from either a file or the "sound card."
+ *
+ * Outputs:	Calls hdlc_rec_bit() for each bit demodulated.
+ *
+ *---------------------------------------------------------------*/
+
+
+
+ // Fine tuning for different demodulator types.
+ // Don't remove this section.  It is here for a reason.
+
+
+void gen_lowpass(float fc, float *lp_filter, int filter_size, bp_window_t wtype);
+void hdlc_rec_bit(int chan, int subchan, int slice, int raw, int is_scrambled, int not_used_remove);
+
+static float slice_point[MAX_SUBCHANS];
+
+
+/* Add sample to buffer and shift the rest down. */
+
+
+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 *__restrict__ data, const float *__restrict__ filter, int filter_size)
+{
+	float sum = 0.0f;
+	int j;
+
+	//#pragma GCC ivdep				// ignored until gcc 4.9
+	for (j = 0; j < filter_size; j++) {
+		sum += filter[j] * data[j];
+	}
+	return (sum);
+}
+
+/* Automatic gain control. */
+/* Result should settle down to 1 unit peak to peak.  i.e. -0.5 to +0.5 */
+
+
+static inline float agc(float in, float fast_attack, float slow_decay, float *ppeak, float *pvalley)
+{
+	if (in >= *ppeak) {
+		*ppeak = in * fast_attack + *ppeak * (1.0f - fast_attack);
+	}
+	else {
+		*ppeak = in * slow_decay + *ppeak * (1.0f - slow_decay);
+	}
+
+	if (in <= *pvalley) {
+		*pvalley = in * fast_attack + *pvalley * (1.0f - fast_attack);
+	}
+	else {
+		*pvalley = in * slow_decay + *pvalley * (1.0f - slow_decay);
+	}
+
+	if (*ppeak > *pvalley) {
+		return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley));
+	}
+	return (0.0);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        demod_9600_init
+ *
+ * Purpose:     Initialize the 9600 (or higher) baud demodulator.
+ *
+ * Inputs:      modem_type	- Determines whether scrambling is used.
+ *
+ *		samples_per_sec	- Number of samples per second for audio.
+ *
+ *		upsample	- Factor to upsample the incoming stream.
+ *				  After a lot of experimentation, I discovered that
+ *				  it works better if the data is upsampled.
+ *				  This reduces the jitter for PLL synchronization.
+ *
+ *		baud		- Data rate in bits per second.
+ *
+ *		D		- Address of demodulator state.
+ *
+ * Returns:     None
+ *
+ *----------------------------------------------------------------*/
+
+void demod_9600_init(enum modem_t modem_type, int original_sample_rate, int upsample, int baud, struct demodulator_state_s *D)
+{
+	float fc;
+	int j;
+	if (upsample < 1) upsample = 1;
+	if (upsample > 4) upsample = 4;
+
+
+	memset(D, 0, sizeof(struct demodulator_state_s));
+	D->modem_type = modem_type;
+	D->num_slicers = 1;
+
+	// Multiple profiles in future?
+
+	//	switch (profile) {
+
+	//	  case 'J':			// upsample x2 with filtering.
+	//	  case 'K':			// upsample x3 with filtering.
+	//	  case 'L':			// upsample x4 with filtering.
+
+
+	D->lp_filter_len_bits = 1.0;	// -U4 = 61 	4.59 samples/symbol
+
+	// 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)original_sample_rate / (float)baud ))) * 2 + 1;
+
+	// Just round to nearest integer.
+	D->lp_filter_size = (int)((D->lp_filter_len_bits * (float)original_sample_rate / baud) + 0.5f);
+
+	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;
+
+	//	    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
+
+
+	// PLL needs to use the upsampled rate.
+
+	D->pll_step_per_sample =
+		(int)round(TICKS_PER_PLL_CYCLE * (double)baud / (double)(original_sample_rate * upsample));
+
+
+#ifdef TUNE_LP_WINDOW
+	D->lp_window = TUNE_LP_WINDOW;
+#endif
+
+#if TUNE_LP_FILTER_SIZE
+	D->lp_filter_size = TUNE_LP_FILTER_SIZE;
+#endif
+
+#ifdef TUNE_LPF_BAUD
+	D->lpf_baud = TUNE_LPF_BAUD;
+#endif	
+
+#ifdef TUNE_AGC_FAST
+	D->agc_fast_attack = TUNE_AGC_FAST;
+#endif
+
+#ifdef TUNE_AGC_SLOW
+	D->agc_slow_decay = TUNE_AGC_SLOW;
+#endif
+
+#if defined(TUNE_PLL_LOCKED)
+	D->pll_locked_inertia = TUNE_PLL_LOCKED;
+#endif
+
+#if defined(TUNE_PLL_SEARCHING)
+	D->pll_searching_inertia = TUNE_PLL_SEARCHING;
+#endif
+
+	// Initial filter (before scattering) is based on upsampled rate.
+
+	fc = (float)baud * D->lpf_baud / (float)(original_sample_rate * upsample);
+
+	//dw_printf ("demod_9600_init: call gen_lowpass(fc=%.2f, , size=%d, )\n", fc, D->lp_filter_size);
+
+	gen_lowpass(fc, D->u.bb.lp_filter, D->lp_filter_taps * upsample, D->lp_window);
+
+	// New in 1.7 -
+	// Use a polyphase filter to reduce the CPU load.
+	// Originally I used zero stuffing to upsample.
+	// Here is the general idea.
+	//
+	// Suppose the input samples are 1 2 3 4 5 6 7 8 9 ...
+	// Filter coefficients are a b c d e f g h i ...
+	//
+	// With original sampling rate, the filtering would involve multiplying and adding:
+	//
+	// 	1a 2b 3c 4d 5e 6f ...
+	//
+	// When upsampling by 3, each of these would need to be evaluated
+	// for each audio sample:
+	//
+	//	1a 0b 0c 2d 0e 0f 3g 0h 0i ...
+	//	0a 1b 0c 0d 2e 0f 0g 3h 0i ...
+	//	0a 0b 1c 0d 0e 2f 0g 0h 3i ...
+	//
+	// 2/3 of the multiplies are always by a stuffed zero.
+	// We can do this more efficiently by removing them.
+	//
+	//	1a       2d       3g       ...
+	//	   1b       2e       3h    ...
+	//	      1c       2f       3i ...
+	//
+	// We scatter the original filter across multiple shorter filters.
+	// Each input sample cycles around them to produce the upsampled rate.
+	//
+	//	a d g ...
+	//	b e h ...
+	//	c f i ...
+	//
+	// There are countless sources of information DSP but this one is unique
+	// in that it is a college course that mentions APRS.
+	// https://www2.eecs.berkeley.edu/Courses/EE123
+	//
+	// Was the effort worthwhile?  Times on an RPi 3.
+	//
+	// command:   atest -B9600  ~/walkabout9600[abc]-compressed*.wav
+	//
+	// These are 3 recordings of a portable system being carried out of
+	// range and back in again.  It is a real world test for weak signals.
+	//
+	//	options		num decoded	seconds		x realtime
+	//			1.6	1.7	1.6	1.7	1.6	1.7
+	//			---	---	---	---	---	---
+	//	-P-		171	172	23.928	17.967	14.9	19.9
+	//	-P+		180	180	54.688	48.772	6.5	7.3
+	//	-P- -F1		177	178	32.686	26.517	10.9	13.5
+	//
+	// So, it turns out that -P+ doesn't have a dramatic improvement, only
+	// around 4%, for drastically increased CPU requirements.
+	// Maybe we should turn that off by default, especially for ARM.
+	//
+
+	int k = 0;
+	for (int i = 0; i < D->lp_filter_size; i++) {
+		D->u.bb.lp_polyphase_1[i] = D->u.bb.lp_filter[k++];
+		if (upsample >= 2) {
+			D->u.bb.lp_polyphase_2[i] = D->u.bb.lp_filter[k++];
+			if (upsample >= 3) {
+				D->u.bb.lp_polyphase_3[i] = D->u.bb.lp_filter[k++];
+				if (upsample >= 4) {
+					D->u.bb.lp_polyphase_4[i] = D->u.bb.lp_filter[k++];
+				}
+			}
+		}
+	}
+
+
+	/* Version 1.2: Experiment with different slicing levels. */
+	// Really didn't help that much because we should have a symmetrical signal.
+
+	for (j = 0; j < MAX_SUBCHANS; j++) {
+		slice_point[j] = 0.02f * (j - 0.5f * (MAX_SUBCHANS - 1));
+		//dw_printf ("slice_point[%d] = %+5.2f\n", j, slice_point[j]);
+	}
+
+} /* end fsk_demod_init */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        demod_9600_process_sample
+ *
+ * Purpose:     (1) Filter & slice the signal.
+ *		(2) Descramble it.
+ *		(2) Recover clock and data.
+ *
+ * Inputs:	chan	- Audio channel.  0 for left, 1 for right.
+ *
+ *		sam	- One sample of audio.
+ *			  Should be in range of -32768 .. 32767.
+ *
+ * Returns:	None
+ *
+ * Descripion:	"9600 baud" packet is FSK for an FM voice transceiver.
+ *		By the time it gets here, it's really a baseband signal.
+ *		At one extreme, we could have a 4800 Hz square wave.
+ *		A the other extreme, we could go a considerable number
+ *		of bit times without any transitions.
+ *
+ *		The trick is to extract the digital data which has
+ *		been distorted by going thru voice transceivers not
+ *		intended to pass this sort of "audio" signal.
+ *
+ *		For G3RUH mode, data is "scrambled" to reduce the amount of DC bias.
+ *		The data stream must be unscrambled at the receiving end.
+ *
+ *		We also have a digital phase locked loop (PLL)
+ *		to recover the clock and pick out data bits at
+ *		the proper rate.
+ *
+ *		For each recovered data bit, we call:
+ *
+ *			  hdlc_rec (channel, demodulated_bit);
+ *
+ *		to decode HDLC frames from the stream of bits.
+ *
+ * Future:	This could be generalized by passing in the name
+ *		of the function to be called for each bit recovered
+ *		from the demodulator.  For now, it's simply hard-coded.
+ *
+ *		After experimentation, I found that this works better if
+ *		the original signal is upsampled by 2x or even 4x.
+ *
+ * References:	9600 Baud Packet Radio Modem Design
+ *		http://www.amsat.org/amsat/articles/g3ruh/109.html
+ *
+ *		The KD2BD 9600 Baud Modem
+ *		http://www.amsat.org/amsat/articles/kd2bd/9k6modem/
+ *
+ *		9600 Baud Packet Handbook
+ * 		ftp://ftp.tapr.org/general/9600baud/96man2x0.txt
+ *
+ *
+ *--------------------------------------------------------------------*/
+
+inline static void nudge_pll(int chan, int subchan, int slice, float demod_out, struct demodulator_state_s *D);
+
+static void process_filtered_sample(int chan, float fsam, struct demodulator_state_s *D);
+
+
+
+void demod_9600_process_sample(int chan, int sam, int upsample, struct demodulator_state_s *D)
+{
+	float fsam;
+
+#if DEBUG4
+	static FILE *demod_log_fp = NULL;
+	static int log_file_seq = 0;		/* Part of log file name */
+#endif
+
+	int subchan = 0;
+
+	assert(chan >= 0 && chan < MAX_CHANS);
+	assert(subchan >= 0 && subchan < MAX_SUBCHANS);
+
+	/* Scale to nice number for convenience. */
+	/* Consistent with the AFSK demodulator, we'd like to use */
+	/* only half of the dynamic range to have some headroom. */
+	/* i.e.  input range +-16k becomes +-1 here and is */
+	/* displayed in the heard line as audio level 100. */
+
+	fsam = (float)sam / 16384.0f;
+
+	// Low pass filter
+	push_sample(fsam, D->u.bb.audio_in, D->lp_filter_size);
+
+	fsam = convolve(D->u.bb.audio_in, D->u.bb.lp_polyphase_1, D->lp_filter_size);
+	process_filtered_sample(chan, fsam, D);
+	if (upsample >= 2) {
+		fsam = convolve(D->u.bb.audio_in, D->u.bb.lp_polyphase_2, D->lp_filter_size);
+		process_filtered_sample(chan, fsam, D);
+		if (upsample >= 3) {
+			fsam = convolve(D->u.bb.audio_in, D->u.bb.lp_polyphase_3, D->lp_filter_size);
+			process_filtered_sample(chan, fsam, D);
+			if (upsample >= 4) {
+				fsam = convolve(D->u.bb.audio_in, D->u.bb.lp_polyphase_4, D->lp_filter_size);
+				process_filtered_sample(chan, fsam, D);
+			}
+		}
+	}
+}
+
+
+static void process_filtered_sample(int chan, float fsam, struct demodulator_state_s *D)
+{
+
+	int subchan = 0;
+
+	/*
+	 * Version 1.2: Capture the post-filtering amplitude for display.
+	 * This is similar to the AGC without the normalization step.
+	 * We want decay to be substantially slower to get a longer
+	 * range idea of the received audio.
+	 * For AFSK, we keep mark and space amplitudes.
+	 * Here we keep + and - peaks because there could be a DC bias.
+	 */
+
+	 // TODO:  probably no need for this.  Just use  D->m_peak, D->m_valley
+
+	if (fsam >= D->alevel_mark_peak) {
+		D->alevel_mark_peak = fsam * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack);
+	}
+	else {
+		D->alevel_mark_peak = fsam * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay);
+	}
+
+	if (fsam <= D->alevel_space_peak) {
+		D->alevel_space_peak = fsam * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack);
+	}
+	else {
+		D->alevel_space_peak = fsam * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay);
+	}
+
+	/*
+	 * The input level can vary greatly.
+	 * More importantly, there could be a DC bias which we need to remove.
+	 *
+	 * Normalize the signal with automatic gain control (AGC).
+	 * This works by looking at the minimum and maximum signal peaks
+	 * and scaling the results to be roughly in the -1.0 to +1.0 range.
+	 */
+	float demod_out;
+	int demod_data;				/* Still scrambled. */
+
+	demod_out = agc(fsam, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
+
+	// TODO: There is potential for multiple decoders with one filter.
+
+	//dw_printf ("peak=%.2f valley=%.2f fsam=%.2f norm=%.2f\n", D->m_peak, D->m_valley, fsam, norm);
+
+	if (D->num_slicers <= 1) {
+
+		/* Normal case of one demodulator to one HDLC decoder. */
+		/* Demodulator output is difference between response from two filters. */
+		/* AGC should generally keep this around -1 to +1 range. */
+
+		demod_data = demod_out > 0;
+		nudge_pll(chan, subchan, 0, demod_out, D);
+	}
+	else {
+		int slice;
+
+		/* Multiple slicers each feeding its own HDLC decoder. */
+
+		for (slice = 0; slice < D->num_slicers; slice++) {
+			demod_data = demod_out - slice_point[slice] > 0;
+			nudge_pll(chan, subchan, slice, demod_out - slice_point[slice], D);
+		}
+	}
+
+	// demod_data is used only for debug out.
+	// suppress compiler warning about it not being used.
+	(void)demod_data;
+
+#if DEBUG4
+
+	if (chan == 0) {
+
+		if (1) {
+			//if (D->slicer[slice].data_detect) {
+			char fname[30];
+			int slice = 0;
+
+			if (demod_log_fp == NULL) {
+				log_file_seq++;
+				snprintf(fname, sizeof(fname), "demod/%04d.csv", log_file_seq);
+				//if (log_file_seq == 1) mkdir ("demod", 0777);
+				if (log_file_seq == 1) mkdir("demod");
+
+				demod_log_fp = fopen(fname, "w");
+				text_color_set(DW_COLOR_DEBUG);
+				dw_printf("Starting demodulator log file %s\n", fname);
+				fprintf(demod_log_fp, "Audio, Filtered,  Max,  Min, Normalized, Sliced, Clock\n");
+			}
+
+			fprintf(demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.3f, %d, %.2f\n",
+				fsam + 6,
+				fsam + 4,
+				D->m_peak + 4,
+				D->m_valley + 4,
+				demod_out + 2,
+				demod_data + 2,
+				(D->slicer[slice].data_clock_pll & 0x80000000) ? .5 : .0);
+
+			fflush(demod_log_fp);
+		}
+		else {
+			if (demod_log_fp != NULL) {
+				fclose(demod_log_fp);
+				demod_log_fp = NULL;
+			}
+		}
+	}
+#endif
+
+} /* end demod_9600_process_sample */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        nudge_pll
+ *
+ * Purpose:	Update the PLL state for each audio sample.
+ *
+ *		(2) Descramble it.
+ *		(2) Recover clock and data.
+ *
+ * Inputs:	chan	- Audio channel.  0 for left, 1 for right.
+ *
+ *		subchan	- Which demodulator.  We could have several running in parallel.
+ *
+ *		slice	- Determines which Slicing level & HDLC decoder to use.
+ *
+ *		demod_out_f - Demodulator output, possibly shifted by slicing level
+ *				It will be compared with 0.0 to bit binary value out.
+ *
+ *		D	- Demodulator state for this channel / subchannel.
+ *
+ * Returns:	None
+ *
+ * Description:	A PLL is used to sample near the centers of the data bits.
+ *
+ *		D->data_clock_pll is a SIGNED 32 bit variable.
+ *		When it overflows from a large positive value to a negative value, we
+ *		sample a data bit from the demodulated signal.
+ *
+ *		Ideally, the the demodulated signal transitions should be near
+ *		zero we we sample mid way between the transitions.
+ *
+ *		Nudge the PLL by removing some small fraction from the value of
+ *		data_clock_pll, pushing it closer to zero.
+ *
+ *		This adjustment will never change the sign so it won't cause
+ *		any erratic data bit sampling.
+ *
+ *		If we adjust it too quickly, the clock will have too much jitter.
+ *		If we adjust it too slowly, it will take too long to lock on to a new signal.
+ *
+ *		I don't think the optimal value will depend on the audio sample rate
+ *		because this happens for each transition from the demodulator.
+ *
+ * Version 1.4:	Previously, we would always pull the PLL phase toward 0 after
+ *		after a zero crossing was detetected.  This adds extra jitter,
+ *		especially when the ratio of audio sample rate to baud is low.
+ *		Now, we interpolate between the two samples to get an estimate
+ *		on when the zero crossing happened.  The PLL is pulled toward
+ *		this point.
+ *
+ *		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.
+ *
+ *--------------------------------------------------------------------*/
+
+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.
+	D->slicer[slice].data_clock_pll = (signed)((unsigned)(D->slicer[slice].data_clock_pll) + (unsigned)(D->pll_step_per_sample));
+
+	if (D->slicer[slice].prev_d_c_pll > 1000000000 && D->slicer[slice].data_clock_pll < -1000000000) {
+
+		/* Overflow.  Was large positive, wrapped around, now large negative. */
+
+		hdlc_rec_bit(chan, subchan, slice, demod_out_f > 0, D->modem_type == MODEM_SCRAMBLE, D->slicer[slice].lfsr);
+		pll_dcd_each_symbol2(D, chan, subchan, slice);
+	}
+
+	/*
+	 * Zero crossing?
+	 */
+	if ((D->slicer[slice].prev_demod_out_f < 0 && demod_out_f > 0) ||
+		(D->slicer[slice].prev_demod_out_f > 0 && demod_out_f < 0)) {
+
+		// Note:  Test for this demodulator, not overall for channel.
+
+		pll_dcd_signal_transition2(D, slice, D->slicer[slice].data_clock_pll);
+
+		float target = D->pll_step_per_sample * demod_out_f / (demod_out_f - D->slicer[slice].prev_demod_out_f);
+
+		if (D->slicer[slice].data_detect) {
+			D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia + target * (1.0f - D->pll_locked_inertia));
+		}
+		else {
+			D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia + target * (1.0f - D->pll_searching_inertia));
+		}
+	}
+
+
+#if DEBUG5
+
+	//if (chan == 0) {
+	if (D->slicer[slice].data_detect) {
+
+		char fname[30];
+
+
+		if (demod_log_fp == NULL) {
+			seq++;
+			snprintf(fname, sizeof(fname), "demod96/%04d.csv", seq);
+			if (seq == 1) mkdir("demod96"
+#ifndef __WIN32__
+				, 0777
+#endif
+			);
+
+			demod_log_fp = fopen(fname, "w");
+			text_color_set(DW_COLOR_DEBUG);
+			dw_printf("Starting 9600 decoder log file %s\n", fname);
+			fprintf(demod_log_fp, "Audio, Peak, Valley, Demod, SData, Descram, Clock\n");
+		}
+		fprintf(demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n",
+			0.5f * fsam + 3.5,
+			0.5f * D->m_peak + 3.5,
+			0.5f * D->m_valley + 3.5,
+			0.5f * demod_out + 2.0,
+			demod_data ? 1.35 : 1.0,
+			descram ? .9 : .55,
+			(D->data_clock_pll & 0x80000000) ? .1 : .45);
+	}
+	else {
+		if (demod_log_fp != NULL) {
+			fclose(demod_log_fp);
+			demod_log_fp = NULL;
+		}
+	}
+	//}
+
+#endif
+
+
+/*
+ * Remember demodulator output (pre-descrambling) so we can compare next time
+ * for the DPLL sync.
+ */
+	D->slicer[slice].prev_demod_out_f = demod_out_f;
+
+} /* end nudge_pll */
+
+
+/* end demod_9600.c */
+
+
+
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  John Langner, WB2OSZ
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU General Public License as published by
+//    the Free Software Foundation, either version 2 of the License, or
+//    (at your option) any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+/********************************************************************************
+ *
+ * File:	rrbb.c
+ *
+ * Purpose:	Raw Received Bit Buffer.
+ *		An array of bits used to hold data out of
+ *		the demodulator before feeding it into the HLDC decoding.
+ *
+ * Version 1.2: Save initial state of 9600 baud descrambler so we can
+ *		attempt bit fix up on G3RUH/K9NG scrambled data.
+ *
+ * Version 1.3:	Store as bytes rather than packing 8 bits per byte.
+ *
+ *******************************************************************************/
+
+
+
+
+
+#define MAGIC1 0x12344321
+#define MAGIC2 0x56788765
+
+
+static int new_count = 0;
+static int delete_count = 0;
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_new
+ *
+ * Purpose:	Allocate space for an array of samples.
+ *
+ * Inputs:	chan	- Radio channel from whence it came.
+ *
+ *		subchan	- Which demodulator of the channel.
+ *
+ *		slice	- multiple thresholds per demodulator.
+ *
+ *		is_scrambled - Is data scrambled? (true, false)
+ *
+ *		descram_state - State of data descrambler.
+ *
+ *		prev_descram - Previous descrambled bit.
+ *
+ * Returns:	Handle to be used by other functions.
+ *
+ * Description:
+ *
+ ***********************************************************************************/
+
+rrbb_t rrbb_new(int chan, int subchan, int slice, int is_scrambled, int descram_state, int prev_descram)
+{
+	rrbb_t result;
+
+	result = malloc(sizeof(struct rrbb_s));
+	if (result == NULL) {
+		text_color_set(DW_COLOR_ERROR);
+		dw_printf("FATAL ERROR: Out of memory.\n");
+		exit(0);
+	}
+	result->magic1 = MAGIC1;
+	result->chan = chan;
+	result->subchan = subchan;
+	result->slice = slice;
+	result->magic2 = MAGIC2;
+
+	new_count++;
+
+	if (new_count > delete_count + 100) {
+		text_color_set(DW_COLOR_ERROR);
+		dw_printf("MEMORY LEAK, rrbb_new, new_count=%d, delete_count=%d\n", new_count, delete_count);
+	}
+
+	rrbb_clear(result, is_scrambled, descram_state, prev_descram);
+
+	return (result);
+}
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_clear
+ *
+ * Purpose:	Clear by setting length to zero, etc.
+ *
+ * Inputs:	b 		-Handle for sample array.
+ *
+ *		is_scrambled 	- Is data scrambled? (true, false)
+ *
+ *		descram_state 	- State of data descrambler.
+ *
+ *		prev_descram 	- Previous descrambled bit.
+ *
+ ***********************************************************************************/
+
+void rrbb_clear(rrbb_t b, int is_scrambled, int descram_state, int prev_descram)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	assert(is_scrambled == 0 || is_scrambled == 1);
+	assert(prev_descram == 0 || prev_descram == 1);
+
+	b->nextp = NULL;
+
+	b->alevel.rec = 9999;	// TODO: was there some reason for this instead of 0 or -1?
+	b->alevel.mark = 9999;
+	b->alevel.space = 9999;
+
+	b->len = 0;
+
+	b->is_scrambled = is_scrambled;
+	b->descram_state = descram_state;
+	b->prev_descram = prev_descram;
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_append_bit
+ *
+ * Purpose:	Append another bit to the end.
+ *
+ * Inputs:	Handle for sample array.
+ *		Value for the sample.
+ *
+ ***********************************************************************************/
+
+ /* Definition in header file so it can be inlined. */
+
+
+ /***********************************************************************************
+  *
+  * Name:	rrbb_chop8
+  *
+  * Purpose:	Remove 8 from the length.
+  *
+  * Inputs:	Handle for bit array.
+  *
+  * Description:	Back up after appending the flag sequence.
+  *
+  ***********************************************************************************/
+
+void rrbb_chop8(rrbb_t b)
+{
+
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	if (b->len >= 8) {
+		b->len -= 8;
+	}
+}
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_len
+ *
+ * Purpose:	Get number of bits in the array.
+ *
+ * Inputs:	Handle for bit array.
+ *
+ ***********************************************************************************/
+
+int rrbb_get_len(rrbb_t b)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	return (b->len);
+}
+
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_bit
+ *
+ * Purpose:	Get value of bit in specified position.
+ *
+ * Inputs:	Handle for sample array.
+ *		Index into array.
+ *
+ ***********************************************************************************/
+
+ /* Definition in header file so it can be inlined. */
+
+
+
+
+ /***********************************************************************************
+  *
+  * Name:	rrbb_flip_bit
+  *
+  * Purpose:	Complement the value of bit in specified position.
+  *
+  * Inputs:	Handle for bit array.
+  *		Index into array.
+  *
+  ***********************************************************************************/
+
+  //void rrbb_flip_bit (rrbb_t b, unsigned int ind)
+  //{
+  //	unsigned int di, mi;
+  //
+  //	assert (b != NULL);
+  //	assert (b->magic1 == MAGIC1);
+  //	assert (b->magic2 == MAGIC2);
+  //
+  //	assert (ind < b->len);
+  //
+  //	di = ind / SOI;
+  //	mi = ind % SOI;
+  //
+  //	b->data[di] ^= masks[mi];
+  //}
+
+  /***********************************************************************************
+   *
+   * Name:	rrbb_delete
+   *
+   * Purpose:	Free the storage associated with the bit array.
+   *
+   * Inputs:	Handle for bit array.
+   *
+   ***********************************************************************************/
+
+void rrbb_delete(rrbb_t b)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	b->magic1 = 0;
+	b->magic2 = 0;
+
+	free(b);
+
+	delete_count++;
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_set_netxp
+ *
+ * Purpose:	Set the nextp field, used to maintain a queue.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		np	New value for nextp.
+ *
+ ***********************************************************************************/
+
+void rrbb_set_nextp(rrbb_t b, rrbb_t np)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	b->nextp = np;
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_netxp
+ *
+ * Purpose:	Get value of nextp field.
+ *
+ * Inputs:	b	Handle for bit array.
+ *
+ ***********************************************************************************/
+
+rrbb_t rrbb_get_nextp(rrbb_t b)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	return (b->nextp);
+}
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_chan
+ *
+ * Purpose:	Get channel from which bit buffer was received.
+ *
+ * Inputs:	b	Handle for bit array.
+ *
+ ***********************************************************************************/
+
+int rrbb_get_chan(rrbb_t b)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	assert(b->chan >= 0 && b->chan < MAX_CHANS);
+
+	return (b->chan);
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_subchan
+ *
+ * Purpose:	Get subchannel from which bit buffer was received.
+ *
+ * Inputs:	b	Handle for bit array.
+ *
+ ***********************************************************************************/
+
+int rrbb_get_subchan(rrbb_t b)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	assert(b->subchan >= 0 && b->subchan < MAX_SUBCHANS);
+
+	return (b->subchan);
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_slice
+ *
+ * Purpose:	Get slice number from which bit buffer was received.
+ *
+ * Inputs:	b	Handle for bit array.
+ *
+ ***********************************************************************************/
+
+int rrbb_get_slice(rrbb_t b)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	assert(b->slice >= 0 && b->slice < MAX_SLICERS);
+
+	return (b->slice);
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_set_audio_level
+ *
+ * Purpose:	Set audio level at time the frame was received.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		alevel	Audio level.
+ *
+ ***********************************************************************************/
+
+void rrbb_set_audio_level(rrbb_t b, alevel_t alevel)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	b->alevel = alevel;
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_audio_level
+ *
+ * Purpose:	Get audio level at time the frame was received.
+ *
+ * Inputs:	b	Handle for bit array.
+ *
+ ***********************************************************************************/
+
+alevel_t rrbb_get_audio_level(rrbb_t b)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	return (b->alevel);
+}
+
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_is_scrambled
+ *
+ * Purpose:	Find out if using scrambled data.
+ *
+ * Inputs:	b	Handle for bit array.
+ *
+ * Returns:	True (for 9600 baud) or false (for slower AFSK).
+ *
+ ***********************************************************************************/
+
+int rrbb_get_is_scrambled(rrbb_t b)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	return (b->is_scrambled);
+}
+
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_descram_state
+ *
+ * Purpose:	Get data descrambler state before first data bit of frame.
+ *
+ * Inputs:	b	Handle for bit array.
+ *
+ ***********************************************************************************/
+
+int rrbb_get_descram_state(rrbb_t b)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	return (b->descram_state);
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_prev_descram
+ *
+ * Purpose:	Get previous descrambled bit before first data bit of frame.
+ *
+ * Inputs:	b	Handle for bit array.
+ *
+ ***********************************************************************************/
+
+int rrbb_get_prev_descram(rrbb_t b)
+{
+	assert(b != NULL);
+	assert(b->magic1 == MAGIC1);
+	assert(b->magic2 == MAGIC2);
+
+	return (b->prev_descram);
+}
+
+
+/* end rrbb.c */
+
+
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2014, 2015, 2016, 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
+//    the Free Software Foundation, either version 2 of the License, or
+//    (at your option) any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      gen_tone.c
+ *
+ * Purpose:     Convert bits to AFSK for writing to .WAV sound file
+ *		or a sound device.
+ *
+ *
+ *---------------------------------------------------------------*/
+
+// Properties of the digitized sound stream & modem.
+
+static struct audio_s *save_audio_config_p = NULL;
+
+/*
+ * 8 bit samples are unsigned bytes in range of 0 .. 255.
+ *
+ * 16 bit samples are signed short in range of -32768 .. +32767.
+ */
+
+
+ /* Constants after initialization. */
+
+#define TICKS_PER_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 )
+
+static int ticks_per_sample[MAX_CHANS];	/* Same for both channels of same soundcard */
+					/* because they have same sample rate */
+					/* but less confusing to have for each channel. */
+
+static int ticks_per_bit[MAX_CHANS];
+static int f1_change_per_sample[MAX_CHANS];
+static int f2_change_per_sample[MAX_CHANS];
+
+
+static short sine_table[256];
+
+
+/* Accumulators. */
+
+static unsigned int tone_phase[MAX_CHANS]; // Phase accumulator for tone generation.
+					   // Upper bits are used as index into sine table.
+
+#define PHASE_SHIFT_180 ( 128u << 24 )
+#define PHASE_SHIFT_90  (  64u << 24 )
+#define PHASE_SHIFT_45  (  32u << 24 )
+
+
+static int bit_len_acc[MAX_CHANS];	// To accumulate fractional samples per bit.
+
+static int lfsr[MAX_CHANS];		// Shift register for scrambler.
+
+static int bit_count[MAX_CHANS];	// Counter incremented for each bit transmitted
+					// on the channel.   This is only used for QPSK.
+					// The LSB determines if we save the bit until
+					// next time, or send this one with the previously saved.
+					// The LSB+1 position determines if we add an
+					// extra 180 degrees to the phase to compensate
+					// for having 1.5 carrier cycles per symbol time.
+
+					// For 8PSK, it has a different meaning.  It is the
+					// number of bits in 'save_bit' so we can accumulate
+					// three for each symbol.
+static int save_bit[MAX_CHANS];
+
+
+static int prev_dat[MAX_CHANS];		// Previous data bit.  Used for G3RUH style.
+
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        gen_tone_init
+ *
+ * Purpose:     Initialize for AFSK tone generation which might
+ *		be used for RTTY or amateur packet radio.
+ *
+ * Inputs:      audio_config_p		- Pointer to modem parameter structure, modem_s.
+ *
+ *				The fields we care about are:
+ *
+ *					samples_per_sec
+ *					baud
+ *					mark_freq
+ *					space_freq
+ *					samples_per_sec
+ *
+ *		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
+ *				  rather than the "direwolf" application.
+ *
+ * Returns:     0 for success.
+ *              -1 for failure.
+ *
+ * Description:	 Calculate various constants for use by the direct digital synthesis
+ * 		audio tone generation.
+ *
+ *----------------------------------------------------------------*/
+
+static int amp16bit;	/* for 9600 baud */
+
+
+int gen_tone_init(struct audio_s *pa, int amp, int gen_packets)
+{
+	int j;
+	int chan = 0;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf("gen_tone_init ( audio_config_p=%p, amp=%d, gen_packets=%d )\n",
+		audio_config_p, amp, gen_packets);
+#endif
+
+	/*
+	 * Save away modem parameters for later use.
+	 */
+
+	// This should be in config somewhere
+
+	pa->adev[0].num_channels = DEFAULT_NUM_CHANNELS;
+	pa->adev[0].samples_per_sec = 48000;
+	pa->adev[0].bits_per_sample = 16;
+
+	pa->achan[0].baud = rx_baudrate[0];
+
+	pa->adev[1].num_channels = DEFAULT_NUM_CHANNELS;
+	pa->adev[1].samples_per_sec = 48000;
+	pa->adev[1].bits_per_sample = 16;
+
+	pa->achan[1].baud = rx_baudrate[1];
+
+	pa->chan_medium[0] = MEDIUM_RADIO;
+	pa->chan_medium[1] = MEDIUM_RADIO;
+
+	pa->achan[0].modem_type = MODEM_SCRAMBLE;
+	pa->achan[1].modem_type = MODEM_SCRAMBLE;
+
+	save_audio_config_p = pa;
+
+	amp16bit = (int)((32767 * amp) / 100);
+
+	for (chan = 0; chan < MAX_CHANS; chan++) {
+
+		if (pa->chan_medium[chan] == MEDIUM_RADIO) {
+
+			int a = ACHAN2ADEV(chan);
+
+#if DEBUG
+			text_color_set(DW_COLOR_DEBUG);
+			dw_printf("gen_tone_init: chan=%d, modem_type=%d, bps=%d, samples_per_sec=%d\n",
+				chan,
+				save_pa->achan[chan].modem_type,
+				pa->achan[chan].baud,
+				pa->adev[a].samples_per_sec);
+#endif
+
+			tone_phase[chan] = 0;
+			bit_len_acc[chan] = 0;
+			lfsr[chan] = 0;
+
+			ticks_per_sample[chan] = (int)((TICKS_PER_CYCLE / (double)pa->adev[a].samples_per_sec) + 0.5);
+
+			// The terminology is all wrong here.  Didn't matter with 1200 and 9600.
+			// The config speed should be bits per second rather than baud.
+			// ticks_per_bit should be ticks_per_symbol.
+
+			switch (pa->achan[chan].modem_type) {
+
+			case MODEM_QPSK:
+
+				pa->achan[chan].mark_freq = 1800;
+				pa->achan[chan].space_freq = pa->achan[chan].mark_freq;	// Not Used.
+
+				// symbol time is 1 / (half of bps)
+				ticks_per_bit[chan] = (int)((TICKS_PER_CYCLE / ((double)pa->achan[chan].baud * 0.5)) + 0.5);
+				f1_change_per_sample[chan] = (int)(((double)pa->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)pa->adev[a].samples_per_sec) + 0.5);
+				f2_change_per_sample[chan] = f1_change_per_sample[chan];	// Not used.
+
+				tone_phase[chan] = PHASE_SHIFT_45;	// Just to mimic first attempt.
+				break;
+
+			case MODEM_8PSK:
+
+				pa->achan[chan].mark_freq = 1800;
+				pa->achan[chan].space_freq = pa->achan[chan].mark_freq;	// Not Used.
+
+				// symbol time is 1 / (third of bps)
+				ticks_per_bit[chan] = (int)((TICKS_PER_CYCLE / ((double)pa->achan[chan].baud / 3.)) + 0.5);
+				f1_change_per_sample[chan] = (int)(((double)pa->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)pa->adev[a].samples_per_sec) + 0.5);
+				f2_change_per_sample[chan] = f1_change_per_sample[chan];	// Not used.
+				break;
+
+			case MODEM_BASEBAND:
+			case MODEM_SCRAMBLE:
+			case MODEM_AIS:
+
+				// Tone is half baud.
+				ticks_per_bit[chan] = (int)((TICKS_PER_CYCLE / (double)pa->achan[chan].baud) + 0.5);
+				f1_change_per_sample[chan] = (int)(((double)pa->achan[chan].baud * 0.5 * TICKS_PER_CYCLE / (double)pa->adev[a].samples_per_sec) + 0.5);
+				break;
+
+			default:		// AFSK
+
+				ticks_per_bit[chan] = (int)((TICKS_PER_CYCLE / (double)pa->achan[chan].baud) + 0.5);
+				f1_change_per_sample[chan] = (int)(((double)pa->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)pa->adev[a].samples_per_sec) + 0.5);
+				f2_change_per_sample[chan] = (int)(((double)pa->achan[chan].space_freq * TICKS_PER_CYCLE / (double)pa->adev[a].samples_per_sec) + 0.5);
+				break;
+			}
+		}
+	}
+
+	for (j = 0; j < 256; j++) {
+		double a;
+		int s;
+
+		a = ((double)(j) / 256.0) * (2 * M_PI);
+		s = (int)(sin(a) * 32767 * amp / 100.0);
+
+		/* 16 bit sound sample must fit in range of -32768 .. +32767. */
+
+		if (s < -32768) {
+			text_color_set(DW_COLOR_ERROR);
+			dw_printf("gen_tone_init: Excessive amplitude is being clipped.\n");
+			s = -32768;
+		}
+		else if (s > 32767) {
+			text_color_set(DW_COLOR_ERROR);
+			dw_printf("gen_tone_init: Excessive amplitude is being clipped.\n");
+			s = 32767;
+		}
+		sine_table[j] = s;
+	}
+
+	return (0);
+
+} /* end gen_tone_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        tone_gen_put_bit
+ *
+ * Purpose:     Generate tone of proper duration for one data bit.
+ *
+ * Inputs:      chan	- Audio channel, 0 = first.
+ *
+ *		dat	- 0 for f1, 1 for f2.
+ *
+ * 			  	-1 inserts half bit to test data
+ *				recovery PLL.
+ *
+ * Assumption:  fp is open to a file for write.
+ *
+ * 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.
+ *
+ * Version 1.6:	MFJ-2400 compatibility for V.26.
+ *
+ *--------------------------------------------------------------------*/
+
+static const int gray2phase_v26[4] = { 0, 1, 3, 2 };
+static const int gray2phase_v27[8] = { 1, 0, 2, 3, 6, 7, 5, 4 };
+
+// We are only using this for RUH modes
+
+void tone_gen_put_bit(int chan, int dat)
+{
+	int modem_type = MODEM_SCRAMBLE;
+	int a = 0;
+
+	// scramble
+
+	int x;
+
+	x = (dat ^ (lfsr[chan] >> 16) ^ (lfsr[chan] >> 11)) & 1;
+	lfsr[chan] = (lfsr[chan] << 1) | (x & 1);
+	dat = x;
+
+	do {		/* until enough audio samples for this symbol. */
+
+		int sam;
+
+		switch (modem_type)
+		{
+
+		case MODEM_AFSK:
+
+			// v1.7 reversed.
+			// Previously a data '1' selected the second (usually higher) tone.
+			// It never really mattered before because we were using NRZI.
+			// With the addition of IL2P, we need to be more careful.
+			// A data '1' should be the mark tone.
+
+			tone_phase[chan] += dat ? f1_change_per_sample[chan] : f2_change_per_sample[chan];
+			sam = sine_table[(tone_phase[chan] >> 24) & 0xff];
+			gen_tone_put_sample(chan, a, sam);
+			break;
+
+		case MODEM_QPSK:
+		case MODEM_8PSK:
+
+			tone_phase[chan] += f1_change_per_sample[chan];
+			sam = sine_table[(tone_phase[chan] >> 24) & 0xff];
+			gen_tone_put_sample(chan, a, sam);
+			break;
+
+		case MODEM_BASEBAND:
+		case MODEM_SCRAMBLE:
+		case MODEM_AIS:
+
+			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:
+			text_color_set(DW_COLOR_ERROR);
+			dw_printf("INTERNAL ERROR: %s %d achan[%d].modem_type = %d\n",
+				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].modem_type);
+			exit(0);
+		}
+
+		/* Enough for the bit time? */
+
+		bit_len_acc[chan] += ticks_per_sample[chan];
+
+	} 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 */
+
+#define ARDOPBufferSize 12000 * 100					// May need to be bigger for 48K
+
+extern short ARDOPTXBuffer[4][ARDOPBufferSize];	// Enough to hold whole frame of samples
+
+void gen_tone_put_sample(int chan, int a, int sam) 
+{
+	// This replaces the DW code
+
+	ARDOPTXBuffer[chan][SampleNo++] = sam;
+}
+
+
+
+
+/* end gen_tone.c */
+
+
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2013, 2014, 2019, 2021  John Langner, WB2OSZ
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU General Public License as published by
+//    the Free Software Foundation, either version 2 of the License, or
+//    (at your option) any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+static void send_byte_msb_first(int chan, int x, int polarity);
+
+static void send_control_nrzi(int, int);
+static void send_data_nrzi(int, int);
+static void send_bit_nrzi(int, int);
+
+
+
+static int number_of_bits_sent[MAX_CHANS];	// Count number of bits sent by "hdlc_send_frame" or "hdlc_send_flags"
+
+
+
+/*-------------------------------------------------------------
+ *
+ * Name:	layer2_send_frame
+ *
+ * Purpose:	Convert frames to a stream of bits.
+ *		Originally this was for AX.25 only, hence the file name.
+ *		Over time, FX.25 and IL2P were shoehorned in.
+ *
+ * Inputs:	chan	- Audio channel number, 0 = first.
+ *
+ *		pp	- Packet object.
+ *
+ *		bad_fcs	- Append an invalid FCS for testing purposes.
+ *			  Applies only to regular AX.25.
+ *
+ * Outputs:	Bits are shipped out by calling tone_gen_put_bit().
+ *
+ * Returns:	Number of bits sent including "flags" and the
+ *		stuffing bits.
+ *		The required time can be calculated by dividing this
+ *		number by the transmit rate of bits/sec.
+ *
+ * Description:	For AX.25, send:
+ *			start flag
+ *			bit stuffed data
+ *			calculated FCS
+ *			end flag
+ *		NRZI encoding for all but the "flags."
+ *
+ *
+ * Assumptions:	It is assumed that the tone_gen module has been
+ *		properly initialized so that bits sent with
+ *		tone_gen_put_bit() are processed correctly.
+ *
+ *--------------------------------------------------------------*/
+
+static int ax25_only_hdlc_send_frame(int chan, unsigned char *fbuf, int flen, int bad_fcs);
+
+
+int layer2_send_frame(int chan, packet_t pp, int bad_fcs, struct audio_s *audio_config_p)
+{
+	if (audio_config_p->achan[chan].layer2_xmit == LAYER2_IL2P) {
+
+		int n = il2p_send_frame(chan, pp, audio_config_p->achan[chan].il2p_max_fec,
+			audio_config_p->achan[chan].il2p_invert_polarity);
+		if (n > 0) {
+			return (n);
+		}
+		text_color_set(DW_COLOR_ERROR);
+		dw_printf("Unable to send IL2p frame.  Falling back to regular AX.25.\n");
+		// Not sure if we should fall back to AX.25 or not here.
+	}
+	else if (audio_config_p->achan[chan].layer2_xmit == LAYER2_FX25) 
+	{
+		unsigned char fbuf[AX25_MAX_PACKET_LEN + 2];
+		int flen = ax25_pack(pp, fbuf);
+		int n = fx25_send_frame(chan, fbuf, flen, audio_config_p->achan[chan].fx25_strength);
+		if (n > 0) {
+			return (n);
+		}
+		text_color_set(DW_COLOR_ERROR);
+		dw_printf("Unable to send FX.25.  Falling back to regular AX.25.\n");
+		// Definitely need to fall back to AX.25 here because
+		// the FX.25 frame length is so limited.
+	}
+
+	unsigned char fbuf[AX25_MAX_PACKET_LEN + 2];
+	int flen = ax25_pack(pp, fbuf);
+	return (ax25_only_hdlc_send_frame(chan, fbuf, flen, bad_fcs));
+}
+
+
+
+static int ax25_only_hdlc_send_frame(int chan, unsigned char *fbuf, int flen, int bad_fcs)
+{
+	int j, fcs;
+
+
+	number_of_bits_sent[chan] = 0;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d, bad_fcs = %d)\n", chan, fbuf, flen, bad_fcs);
+	fflush(stdout);
+#endif
+
+	send_control_nrzi(chan, 0x7e);	/* Start frame */
+
+	for (j = 0; j < flen; j++) {
+		send_data_nrzi(chan, fbuf[j]);
+	}
+
+	fcs = get_fcs(fbuf, flen);
+
+	if (bad_fcs) {
+		/* For testing only - Simulate a frame getting corrupted along the way. */
+		send_data_nrzi(chan, (~fcs) & 0xff);
+		send_data_nrzi(chan, ((~fcs) >> 8) & 0xff);
+	}
+	else {
+		send_data_nrzi(chan, fcs & 0xff);
+		send_data_nrzi(chan, (fcs >> 8) & 0xff);
+	}
+
+	send_control_nrzi(chan, 0x7e);	/* End frame */
+
+	return (number_of_bits_sent[chan]);
+}
+
+
+
+// The next one is only for IL2P.  No NRZI.
+// MSB first, opposite of AX.25.
+
+static void send_byte_msb_first(int chan, int x, int polarity)
+{
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		int dbit = (x & 0x80) != 0;
+		tone_gen_put_bit(chan, (dbit ^ polarity) & 1);
+		x <<= 1;
+		number_of_bits_sent[chan]++;
+	}
+}
+
+
+// The following are only for HDLC.
+// All bits are sent NRZI.
+// Data (non flags) use bit stuffing.
+
+
+static int stuff[MAX_CHANS];		// Count number of "1" bits to keep track of when we
+					// need to break up a long run by "bit stuffing."
+					// Needs to be array because we could be transmitting
+					// on multiple channels at the same time.
+
+static void send_control_nrzi(int chan, int x)
+{
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		send_bit_nrzi(chan, x & 1);
+		x >>= 1;
+	}
+
+	stuff[chan] = 0;
+}
+
+static void send_data_nrzi(int chan, int x)
+{
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		send_bit_nrzi(chan, x & 1);
+		if (x & 1) {
+			stuff[chan]++;
+			if (stuff[chan] == 5) {
+				send_bit_nrzi(chan, 0);
+				stuff[chan] = 0;
+			}
+		}
+		else {
+			stuff[chan] = 0;
+		}
+		x >>= 1;
+	}
+}
+
+/*
+ * NRZI encoding.
+ * data 1 bit -> no change.
+ * data 0 bit -> invert signal.
+ */
+
+static void send_bit_nrzi(int chan, int b)
+{
+	static int output[MAX_CHANS];
+
+	if (b == 0) {
+		output[chan] = !output[chan];
+	}
+
+	tone_gen_put_bit(chan, output[chan]);
+
+	number_of_bits_sent[chan]++;
+}
+
+/* end hdlc_send.c */
+
+
+
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    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
+//    the Free Software Foundation, either version 2 of the License, or
+//    (at your option) any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        dsp.c
+ *
+ * Purpose:     Generate the filters used by the demodulators.
+ *
+ *----------------------------------------------------------------*/
+
+
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#define MAX(a,b) ((a)>(b)?(a):(b))
+
+
+ // Don't remove this.  It serves as a reminder that an experiment is underway.
+
+#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
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        window
+ *
+ * Purpose:     Filter window shape functions.
+ *
+ * Inputs:   	type	- BP_WINDOW_HAMMING, etc.
+ *		size	- Number of filter taps.
+ *		j	- Index in range of 0 to size-1.
+ *
+ * Returns:     Multiplier for the window shape.
+ *
+ *----------------------------------------------------------------*/
+
+	static float window(bp_window_t type, int size, int j)
+{
+	float center;
+	float w;
+
+	center = 0.5 * (size - 1);
+
+	switch (type) {
+
+	case BP_WINDOW_COSINE:
+		w = cos((j - center) / size * M_PI);
+		//w = sin(j * M_PI / (size - 1));
+		break;
+
+	case BP_WINDOW_HAMMING:
+		w = 0.53836 - 0.46164 * cos((j * 2 * M_PI) / (size - 1));
+		break;
+
+	case BP_WINDOW_BLACKMAN:
+		w = 0.42659 - 0.49656 * cos((j * 2 * M_PI) / (size - 1))
+			+ 0.076849 * cos((j * 4 * M_PI) / (size - 1));
+		break;
+
+	case BP_WINDOW_FLATTOP:
+		w = 1.0 - 1.93  * cos((j * 2 * M_PI) / (size - 1))
+			+ 1.29  * cos((j * 4 * M_PI) / (size - 1))
+			- 0.388 * cos((j * 6 * M_PI) / (size - 1))
+			+ 0.028 * cos((j * 8 * M_PI) / (size - 1));
+		break;
+
+	case BP_WINDOW_TRUNCATED:
+	default:
+		w = 1.0;
+		break;
+	}
+	return (w);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        gen_lowpass
+ *
+ * Purpose:     Generate low pass filter kernel.
+ *
+ * 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 j;
+	float G;
+
+
+#if DEBUG1
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf("Lowpass, size=%d, fc=%.2f\n", filter_size, fc);
+	dw_printf("   j     shape   sinc   final\n");
+#endif
+
+	assert(filter_size >= 3 && filter_size <= MAX_FILTER_SIZE);
+
+	for (j = 0; j < filter_size; j++) {
+		float center;
+		float sinc;
+		float shape;
+
+		center = 0.5 * (filter_size - 1);
+
+		if (j - center == 0) {
+			sinc = 2 * fc;
+		}
+		else {
+			sinc = sin(2 * M_PI * fc * (j - center)) / (M_PI*(j - center));
+		}
+
+		shape = window(wtype, filter_size, j);
+		lp_filter[j] = sinc * shape;
+
+#if DEBUG1
+		dw_printf("%6d  %6.2f  %6.3f  %6.3f\n", j, shape, sinc, lp_filter[j]);
+#endif
+	}
+
+	/*
+	 * Normalize lowpass for unity gain at DC.
+	 */
+	G = 0;
+	for (j = 0; j < filter_size; j++) {
+		G += lp_filter[j];
+	}
+	for (j = 0; j < filter_size; j++) {
+		lp_filter[j] = lp_filter[j] / G;
+	}
+
+	return;
+
+}  /* end gen_lowpass */
+
+
+#undef DEBUG1
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        gen_bandpass
+ *
+ * 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...
+ *		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_bandpass(float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype)
+{
+	int j;
+	float w;
+	float G;
+	float center = 0.5 * (filter_size - 1);
+
+#if DEBUG1
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf("Bandpass, size=%d\n", filter_size);
+	dw_printf("   j     shape   sinc   final\n");
+#endif
+
+	assert(filter_size >= 3 && filter_size <= MAX_FILTER_SIZE);
+
+	for (j = 0; j < filter_size; j++) {
+		float sinc;
+		float shape;
+
+		if (j - center == 0) {
+			sinc = 2 * (f2 - f1);
+		}
+		else {
+			sinc = sin(2 * M_PI * f2 * (j - center)) / (M_PI*(j - center))
+				- sin(2 * M_PI * f1 * (j - center)) / (M_PI*(j - center));
+		}
+
+		shape = window(wtype, filter_size, j);
+		bp_filter[j] = sinc * shape;
+
+#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.
+	 * Can't use same technique as for lowpass.
+	 * Instead compute gain in middle of passband.
+	 * See http://dsp.stackexchange.com/questions/4693/fir-filter-gain
+	 */
+	w = 2 * M_PI * (f1 + f2) / 2;
+	G = 0;
+	for (j = 0; j < filter_size; j++) {
+		G += 2 * bp_filter[j] * cos((j - center)*w);  // is this correct?
+	}
+
+#if DEBUG1
+	dw_printf("Before normalizing, G=%.3f\n", G);
+#endif
+	for (j = 0; j < filter_size; j++) {
+		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 */
+
+
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        rrc
+ *
+ * Purpose:     Root Raised Cosine function.
+ *		Why do they call it that?
+ *		It's mostly the sinc function with cos windowing to taper off edges faster.
+ *
+ * Inputs:      t		- Time in units of symbol duration.
+ *				  i.e. The centers of two adjacent symbols would differ by 1.
+ *
+ *		a		- Roll off factor, between 0 and 1.
+ *
+ * Returns:	Basically the sinc  (sin(x)/x) function with edges decreasing faster.
+ *		Should be 1 for t = 0 and 0 at all other integer values of t.
+ *
+ *----------------------------------------------------------------*/
+
+
+float rrc(float t, float a)
+{
+	float sinc, window, result;
+
+	if (t > -0.001 && t < 0.001) {
+		sinc = 1;
+	}
+	else {
+		sinc = sinf(M_PI * t) / (M_PI * t);
+	}
+
+	if (fabsf(a * t) > 0.499 && fabsf(a * t) < 0.501) {
+		window = M_PI / 4;
+	}
+	else {
+		window = cos(M_PI * a * t) / (1 - powf(2 * a * t, 2));
+		// This made nicer looking waveforms for generating signal.
+		//window = cos(M_PI * a * t);
+		// Do we want to let it go negative?
+		// I think this would happen when a > 0.5 / (filter width in symbol times)
+		if (window < 0) {
+			//printf ("'a' is too large for range of 't'.\n");
+			//window = 0;
+		}
+	}
+
+	result = sinc * window;
+
+#if DEBUGRRC
+	// t should vary from - to + half of filter size in symbols.
+	// Result should be 1 at t=0 and 0 at all other integer values of t.
+
+	printf("%.3f, %.3f, %.3f, %.3f\n", t, sinc, window, result);
+#endif
+	return (result);
+}
+
+// The Root Raised Cosine (RRC) low pass filter is suppposed to minimize Intersymbol Interference (ISI).
+
+void gen_rrc_lowpass(float *pfilter, int filter_taps, float rolloff, float samples_per_symbol)
+{
+	int k;
+	float t;
+
+	for (k = 0; k < filter_taps; k++) {
+		t = (k - ((filter_taps - 1.0) / 2.0)) / samples_per_symbol;
+		pfilter[k] = rrc(t, rolloff);
+	}
+
+	// Scale it for unity gain.
+
+	t = 0;
+	for (k = 0; k < filter_taps; k++) {
+		t += pfilter[k];
+	}
+	for (k = 0; k < filter_taps; k++) {
+		pfilter[k] = pfilter[k] / t;
+	}
+}
+
+/* end dsp.c */
+
+
+int ax25_pack(packet_t this_p, unsigned char * result)
+{
+	int len = 0;
+	//	assert(this_p->magic1 == MAGIC);
+	//	assert(this_p->magic2 == MAGIC);
+
+	//	assert(this_p->frame_len >= 0 && this_p->frame_len <= AX25_MAX_PACKET_LEN);
+
+	//	memcpy(result, this_p->frame_data, this_p->frame_len);
+
+	return (len);
+}
+
+
+int audio_flush(int a)
+{
+	return 0;
+}
+
+int fx25_send_frame(int chan, unsigned char *fbuf, int flen, int fx_mode)
+{
+	return 0;
+}
+
+
+struct demodulator_state_s D[4];
+struct audio_s pa[4];
+
+extern int was_init[4];
+
+extern short rx_baudrate[5];
+extern unsigned char  modem_mode[5];
+
+void dw9600ProcessSample(int snd_ch, short Sample)
+{
+	demod_9600_process_sample(snd_ch, Sample, 1, &D[snd_ch]);
+}
+
+void init_RUH48(int snd_ch)
+{
+	modem_mode[snd_ch] = MODE_RUH;
+	rx_baudrate[snd_ch] = 4800;
+
+	if (was_init[snd_ch] == 0)
+	{
+		hdlc_rec_init(&pa[snd_ch]);
+		gen_tone_init(&pa[snd_ch], 100, 0);
+		was_init[snd_ch] = 1;
+	}
+
+	demod_9600_init(2,
+		48000,
+		1, //upsample
+		4800,
+		&D[snd_ch]);
+
+}
+
+void init_RUH96(int snd_ch)
+{
+	modem_mode[snd_ch] = MODE_RUH;
+	rx_baudrate[snd_ch] = 9600;
+
+	if (was_init[snd_ch] == 0)
+	{
+		hdlc_rec_init(&pa[snd_ch]);
+		gen_tone_init(&pa[snd_ch], 100, 0);
+		was_init[snd_ch];
+	}
+
+	demod_9600_init(2,
+		48000,
+		1, //upsample
+		9600,
+		&D[snd_ch]);
+
+}
+
+
+void text_color_set(dw_color_t c)
+{
+	return;
+}
+
+
+extern TStringList detect_list[5];
+extern TStringList detect_list_c[5];
+
+
+int multi_modem_process_rec_frame(int chan, int subchan, int slice, unsigned char *fbuf, int flen, int alevel, int retries, int is_fx25)
+{
+	// Convert to QtSM internal format
+
+//	struct TDetector_t * pDET = &DET[emph][subchan];
+	string *  data = newString();
+	char Mode[16] = "RUH";
+	int i, found;
+
+	stringAdd(data, fbuf, flen + 2);  // QTSM assumes a CRC
+
+	if (detect_list[chan].Count > 0 &&
+		my_indexof(&detect_list[chan], data) >= 0)
+	{
+		// Already have a copy of this frame
+
+		freeString(data);
+		Debugprintf("Discarding copy rcvr %d emph %d", subchan, 0);
+		return;
+	}
+
+	string * xx = newString();
+	memset(xx->Data, 0, 16);
+
+	Add(&detect_list_c[chan], xx);
+	Add(&detect_list[chan], data);
+
+	//	if (retries)
+	//		sprintf(Mode, "IP2P-%d", retries);
+
+	stringAdd(xx, Mode, strlen(Mode));
+	return 0;
+
+}
+
+extern unsigned short * DMABuffer;
+
+
+extern int Number;
+extern int SampleNo;
+
+extern short txtail[5];
+extern short txdelay[5];
+extern short tx_baudrate[5];
+
+extern int ARDOPTXLen[4];			// Length of frame
+extern int ARDOPTXPtr[4];			// Tx Pointer
+
+
+void RUHEncode(unsigned char * Data, int Len, int chan)
+{
+	//	Generate audio samples from frame data
+
+	int bitcount;
+	int txdcount;
+	int j;
+	unsigned short CRC;
+
+	number_of_bits_sent[chan] = 0;
+
+	SampleNo = 0;
+
+	// First do TX delay
+
+	// Set up txd worth of flags
+
+	txdcount = (txdelay[chan] * tx_baudrate[chan]) / 8000;		// 8 for bits, 1000 for mS
+
+	if (txdcount > 1024)
+		txdcount = 1024;
+
+	while (txdcount--)
+		send_control_nrzi(chan, 0x7e);	// Flag
+
+	for (j = 0; j < Len; j++)
+	{
+		send_data_nrzi(chan, Data[j]);
+	}
+
+	CRC = get_fcs(Data, Len);
+
+	send_data_nrzi(chan, (CRC & 0xff));
+	send_data_nrzi(chan, (CRC >> 8));
+
+	// do we need tail here??
+
+	send_control_nrzi(chan, 0x7e);	// Flag
+
+	ARDOPTXLen[chan] = SampleNo;
+	ARDOPTXPtr[chan] = 0;
+
+	// sampleNo should now contain number of (stereo) samples
+
+}
+
+
+
+
+
+
+
diff --git a/dw9600.h b/dw9600.h
new file mode 100644
index 0000000..e80d2ac
--- /dev/null
+++ b/dw9600.h
@@ -0,0 +1,2042 @@
+#pragma once
+
+// Includes code from Dire Wolf Copyright (C) 2011, 2012, 2013, 2014, 2015  John Langner, WB2OSZ
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU General Public License as published by
+//    the Free Software Foundation, either version 2 of the License, or
+//    (at your option) any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+
+// Dephi emulation functions
+
+string * Strings(TStringList * Q, int Index);
+void Clear(TStringList * Q);
+int Count(TStringList * List);
+
+string * newString();
+string * copy(string * Source, int StartChar, int Count);
+TStringList * newTStringList();
+
+void freeString(string * Msg);
+
+void initString(string * S);
+void initTStringList(TStringList* T);
+
+// Two delete() This is confusing!!
+// Not really - one acts on String, other TStringList
+
+void Delete(TStringList * Q, int Index);
+void mydelete(string * Source, int StartChar, int Count);
+
+void move(unsigned char * SourcePointer, unsigned char * DestinationPointer, int CopyCount);
+void fmove(float * SourcePointer, float * DestinationPointer, int CopyCount);
+
+void setlength(string * Msg, int Count);		// Set string length
+
+string * stringAdd(string * Msg, unsigned char * Chars, int Count);		// Extend string 
+
+void Assign(TStringList * to, TStringList * from);	// Duplicate from to to
+
+string * duplicateString(string * in);
+
+// This looks for a string in a stringlist. Returns inhex if found, otherwise -1
+
+int  my_indexof(TStringList * l, string * s);
+
+int Add(TStringList * Q, string * Entry);
+
+
+#define MAX_FILTER_SIZE 480		/* 401 is needed for profile A, 300 baud & 44100. Revisit someday. */
+// Size comes out to 417 for 1200 bps with 48000 sample rate
+// v1.7 - Was 404.  Bump up to 480.
+
+
+#define MAX_TOTAL_CHANS 16
+#define MAX_ADEVS 3	
+#define AX25_MAX_ADDR_LEN 12
+
+#include <stdint.h>	// for uint64_t
+
+
+
+#define AX25_MAX_REPEATERS 8
+#define AX25_MIN_ADDRS 2	/* Destination & Source. */
+#define AX25_MAX_ADDRS 10	/* Destination, Source, 8 digipeaters. */	
+
+#define AX25_DESTINATION  0	/* Address positions in frame. */
+#define AX25_SOURCE       1	
+#define AX25_REPEATER_1   2
+#define AX25_REPEATER_2   3
+#define AX25_REPEATER_3   4
+#define AX25_REPEATER_4   5
+#define AX25_REPEATER_5   6
+#define AX25_REPEATER_6   7
+#define AX25_REPEATER_7   8
+#define AX25_REPEATER_8   9
+
+#define AX25_MAX_ADDR_LEN 12	/* In theory, you would expect the maximum length */
+				/* to be 6 letters, dash, 2 digits, and nul for a */
+				/* total of 10.  However, object labels can be 10 */
+				/* characters so throw in a couple extra bytes */
+				/* to be safe. */
+
+#define AX25_MIN_INFO_LEN 0	/* Previously 1 when considering only APRS. */
+
+#define AX25_MAX_INFO_LEN 2048	/* Maximum size for APRS. */
+				/* AX.25 starts out with 256 as the default max */
+				/* length but the end stations can negotiate */
+				/* something different. */
+				/* version 0.8:  Change from 256 to 2028 to */
+				/* handle the larger paclen for Linux AX25. */
+
+				/* These don't include the 2 bytes for the */
+				/* HDLC frame FCS. */
+
+/*
+ * Previously, for APRS only.
+ * #define AX25_MIN_PACKET_LEN ( 2 * 7 + 2 + AX25_MIN_INFO_LEN)
+ * #define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + AX25_MAX_INFO_LEN)
+ */
+
+ /* The more general case. */
+ /* An AX.25 frame can have a control byte and no protocol. */
+
+#define AX25_MIN_PACKET_LEN ( 2 * 7 + 1 )
+
+#define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + 3 + AX25_MAX_INFO_LEN)
+
+
+/*
+ * packet_t is a pointer to a packet object.
+ *
+ * The actual implementation is not visible outside ax25_pad.c.
+ */
+
+#define AX25_UI_FRAME 3		/* Control field value. */
+
+#define AX25_PID_NO_LAYER_3 0xf0		/* protocol ID used for APRS */
+#define AX25_PID_SEGMENTATION_FRAGMENT 0x08
+#define AX25_PID_ESCAPE_CHARACTER 0xff
+
+
+struct packet_s {
+
+	int magic1;		/* for error checking. */
+
+	int seq;		/* unique sequence number for debugging. */
+
+	double release_time;	/* Time stamp in format returned by dtime_now(). */
+				/* When to release from the SATgate mode delay queue. */
+
+#define MAGIC 0x41583235
+
+	struct packet_s *nextp;	/* Pointer to next in queue. */
+
+	int num_addr;		/* Number of addresses in frame. */
+				/* Range of AX25_MIN_ADDRS .. AX25_MAX_ADDRS for AX.25. */
+				/* It will be 0 if it doesn't look like AX.25. */
+				/* -1 is used temporarily at allocation to mean */
+				/* not determined yet. */
+
+
+
+				/*
+				 * The 7th octet of each address contains:
+					 *
+				 * Bits:   H  R  R  SSID  0
+				 *
+				 *   H 		for digipeaters set to 0 initially.
+				 *		Changed to 1 when position has been used.
+				 *
+				 *		for source & destination it is called
+				 *		command/response.  Normally both 1 for APRS.
+				 *		They should be opposites for connected mode.
+				 *
+				 *   R	R	Reserved.  Normally set to 1 1.
+				 *
+				 *   SSID	Substation ID.  Range of 0 - 15.
+				 *
+				 *   0		Usually 0 but 1 for last address.
+				 */
+
+
+#define SSID_H_MASK	0x80
+#define SSID_H_SHIFT	7
+
+#define SSID_RR_MASK	0x60
+#define SSID_RR_SHIFT	5
+
+#define SSID_SSID_MASK	0x1e
+#define SSID_SSID_SHIFT	1
+
+#define SSID_LAST_MASK	0x01
+
+
+	int frame_len;		/* Frame length without CRC. */
+
+	int modulo;		/* I & S frames have sequence numbers of either 3 bits (modulo 8) */
+				/* or 7 bits (modulo 128).  This is conveyed by either 1 or 2 */
+				/* control bytes.  Unfortunately, we can't determine this by looking */
+				/* at an isolated frame.  We need to know about the context.  If we */
+				/* are part of the conversation, we would know.  But if we are */
+				/* just listening to others, this would be more difficult to determine. */
+
+				/* For U frames:   	set to 0 - not applicable */
+				/* For I & S frames:	8 or 128 if known.  0 if unknown. */
+
+	unsigned char frame_data[AX25_MAX_PACKET_LEN + 1];
+	/* Raw frame contents, without the CRC. */
+
+
+	int magic2;		/* Will get stomped on if above overflows. */
+};
+
+
+
+
+
+typedef struct packet_s *packet_t;
+
+typedef enum cmdres_e { cr_00 = 2, cr_cmd = 1, cr_res = 0, cr_11 = 3 } cmdres_t;
+
+
+extern packet_t ax25_new(void);
+
+
+/*
+ * APRS always has one control octet of 0x03 but the more
+ * general AX.25 case is one or two control bytes depending on
+ * whether "modulo 128 operation" is in effect.
+ */
+
+ //#define DEBUGX 1
+
+static inline int ax25_get_control_offset(packet_t this_p)
+{
+	return (this_p->num_addr * 7);
+}
+
+static inline int ax25_get_num_control(packet_t this_p)
+{
+	int c;
+
+	c = this_p->frame_data[ax25_get_control_offset(this_p)];
+
+	if ((c & 0x01) == 0) {			/* I   xxxx xxx0 */
+#if DEBUGX
+		dw_printf("ax25_get_num_control, %02x is I frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1);
+#endif
+		return ((this_p->modulo == 128) ? 2 : 1);
+	}
+
+	if ((c & 0x03) == 1) {			/* S   xxxx xx01 */
+#if DEBUGX
+		dw_printf("ax25_get_num_control, %02x is S frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1);
+#endif
+		return ((this_p->modulo == 128) ? 2 : 1);
+	}
+
+#if DEBUGX
+	dw_printf("ax25_get_num_control, %02x is U frame, always returns 1.\n", c);
+#endif
+
+	return (1);					/* U   xxxx xx11 */
+}
+
+
+
+/*
+ * APRS always has one protocol octet of 0xF0 meaning no level 3
+ * protocol but the more general case is 0, 1 or 2 protocol ID octets.
+ */
+
+static inline int ax25_get_pid_offset(packet_t this_p)
+{
+	return (ax25_get_control_offset(this_p) + ax25_get_num_control(this_p));
+}
+
+static int ax25_get_num_pid(packet_t this_p)
+{
+	int c;
+	int pid;
+
+	c = this_p->frame_data[ax25_get_control_offset(this_p)];
+
+	if ((c & 0x01) == 0 ||				/* I   xxxx xxx0 */
+		c == 0x03 || c == 0x13) {			/* UI  000x 0011 */
+
+		pid = this_p->frame_data[ax25_get_pid_offset(this_p)];
+#if DEBUGX
+		dw_printf("ax25_get_num_pid, %02x is I or UI frame, pid = %02x, returns %d\n", c, pid, (pid == AX25_PID_ESCAPE_CHARACTER) ? 2 : 1);
+#endif
+		if (pid == AX25_PID_ESCAPE_CHARACTER) {
+			return (2);			/* pid 1111 1111 means another follows. */
+		}
+		return (1);
+	}
+#if DEBUGX
+	dw_printf("ax25_get_num_pid, %02x is neither I nor UI frame, returns 0\n", c);
+#endif
+	return (0);
+}
+
+
+/*
+ * AX.25 has info field for 5 frame types depending on the control field.
+ *
+ *	xxxx xxx0	I
+ *	000x 0011	UI		(which includes APRS)
+ *	101x 1111	XID
+ *	111x 0011	TEST
+ *	100x 0111	FRMR
+ *
+ * APRS always has an Information field with at least one octet for the Data Type Indicator.
+ */
+
+static inline int ax25_get_info_offset(packet_t this_p)
+{
+	int offset = ax25_get_control_offset(this_p) + ax25_get_num_control(this_p) + ax25_get_num_pid(this_p);
+#if DEBUGX
+	dw_printf("ax25_get_info_offset, returns %d\n", offset);
+#endif
+	return (offset);
+}
+
+static inline int ax25_get_num_info(packet_t this_p)
+{
+	int len;
+
+	/* assuming AX.25 frame. */
+
+	len = this_p->frame_len - this_p->num_addr * 7 - ax25_get_num_control(this_p) - ax25_get_num_pid(this_p);
+	if (len < 0) {
+		len = 0;		/* print error? */
+	}
+
+	return (len);
+}
+
+
+
+
+typedef enum ax25_modulo_e { modulo_unknown = 0, modulo_8 = 8, modulo_128 = 128 } ax25_modulo_t;
+
+typedef enum ax25_frame_type_e {
+
+	frame_type_I = 0,	// Information
+
+	frame_type_S_RR,	// Receive Ready - System Ready To Receive
+	frame_type_S_RNR,	// Receive Not Ready - TNC Buffer Full
+	frame_type_S_REJ,	// Reject Frame - Out of Sequence or Duplicate
+	frame_type_S_SREJ,	// Selective Reject - Request single frame repeat
+
+	frame_type_U_SABME,	// Set Async Balanced Mode, Extended
+	frame_type_U_SABM,	// Set Async Balanced Mode
+	frame_type_U_DISC,	// Disconnect
+	frame_type_U_DM,	// Disconnect Mode
+	frame_type_U_UA,	// Unnumbered Acknowledge
+	frame_type_U_FRMR,	// Frame Reject
+	frame_type_U_UI,	// Unnumbered Information
+	frame_type_U_XID,	// Exchange Identification
+	frame_type_U_TEST,	// Test
+	frame_type_U,		// other Unnumbered, not used by AX.25.
+
+	frame_not_AX25		// Could not get control byte from frame.
+				// This must be last because value plus 1 is
+				// for the size of an array.
+
+} ax25_frame_type_t;
+
+
+/*
+ * Originally this was a single number.
+ * Let's try something new in version 1.2.
+ * Also collect AGC values from the mark and space filters.
+ */
+
+typedef struct alevel_s {
+
+	int rec;
+	int mark;
+	int space;
+	//float ms_ratio;	// TODO: take out after temporary investigation.
+} alevel_t;
+
+
+#ifndef AXTEST
+// TODO: remove this?
+#define AX25MEMDEBUG 1
+#endif
+
+
+#if AX25MEMDEBUG	// to investigate a memory leak problem
+
+
+extern void ax25memdebug_set(void);
+extern int ax25memdebug_get(void);
+extern int ax25memdebug_seq(packet_t this_p);
+
+
+extern packet_t ax25_from_text_debug(char *monitor, int strict, char *src_file, int src_line);
+#define ax25_from_text(m,s) ax25_from_text_debug(m,s,__FILE__,__LINE__)
+
+extern packet_t ax25_from_frame_debug(unsigned char *data, int len, alevel_t alevel, char *src_file, int src_line);
+#define ax25_from_frame(d,l,a) ax25_from_frame_debug(d,l,a,__FILE__,__LINE__);
+
+extern packet_t ax25_dup_debug(packet_t copy_from, char *src_file, int src_line);
+#define ax25_dup(p) ax25_dup_debug(p,__FILE__,__LINE__);
+
+extern void ax25_delete_debug(packet_t pp, char *src_file, int src_line);
+#define ax25_delete(p) ax25_delete_debug(p,__FILE__,__LINE__);
+
+#else
+
+extern packet_t ax25_from_text(char *monitor, int strict);
+
+extern packet_t ax25_from_frame(unsigned char *data, int len, alevel_t alevel);
+
+extern packet_t ax25_dup(packet_t copy_from);
+
+extern void ax25_delete(packet_t pp);
+
+#endif
+
+
+
+
+extern int ax25_parse_addr(int position, char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard);
+extern int ax25_check_addresses(packet_t pp);
+
+extern packet_t ax25_unwrap_third_party(packet_t from_pp);
+
+extern void ax25_set_addr(packet_t pp, int, char *);
+extern void ax25_insert_addr(packet_t this_p, int n, char *ad);
+extern void ax25_remove_addr(packet_t this_p, int n);
+
+extern int ax25_get_num_addr(packet_t pp);
+extern int ax25_get_num_repeaters(packet_t this_p);
+
+extern void ax25_get_addr_with_ssid(packet_t pp, int n, char *station);
+extern void ax25_get_addr_no_ssid(packet_t pp, int n, char *station);
+
+extern int ax25_get_ssid(packet_t pp, int n);
+extern void ax25_set_ssid(packet_t this_p, int n, int ssid);
+
+extern int ax25_get_h(packet_t pp, int n);
+
+extern void ax25_set_h(packet_t pp, int n);
+
+extern int ax25_get_heard(packet_t this_p);
+
+extern int ax25_get_first_not_repeated(packet_t pp);
+
+extern int ax25_get_rr(packet_t this_p, int n);
+
+extern int ax25_get_info(packet_t pp, unsigned char **paddr);
+extern void ax25_set_info(packet_t pp, unsigned char *info_ptr, int info_len);
+extern int ax25_cut_at_crlf(packet_t this_p);
+
+extern void ax25_set_nextp(packet_t this_p, packet_t next_p);
+
+extern int ax25_get_dti(packet_t this_p);
+
+extern packet_t ax25_get_nextp(packet_t this_p);
+
+extern void ax25_set_release_time(packet_t this_p, double release_time);
+extern double ax25_get_release_time(packet_t this_p);
+
+extern void ax25_set_modulo(packet_t this_p, int modulo);
+extern int ax25_get_modulo(packet_t this_p);
+
+extern void ax25_format_addrs(packet_t pp, char *);
+extern void ax25_format_via_path(packet_t this_p, char *result, size_t result_size);
+
+extern int ax25_pack(packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]);
+
+extern ax25_frame_type_t ax25_frame_type(packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns);
+
+extern void ax25_hex_dump(packet_t this_p);
+
+extern int ax25_is_aprs(packet_t pp);
+extern int ax25_is_null_frame(packet_t this_p);
+
+extern int ax25_get_control(packet_t this_p);
+extern int ax25_get_c2(packet_t this_p);
+
+extern int ax25_get_pid(packet_t this_p);
+
+extern int ax25_get_frame_len(packet_t this_p);
+extern unsigned char *ax25_get_frame_data_ptr(packet_t this_p);
+
+extern unsigned short ax25_dedupe_crc(packet_t pp);
+
+extern unsigned short ax25_m_m_crc(packet_t pp);
+
+extern void ax25_safe_print(char *, int, int ascii_only);
+
+#define AX25_ALEVEL_TO_TEXT_SIZE 40	// overkill but safe.
+extern int ax25_alevel_to_text(alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE]);
+
+
+
+int demod_init(struct audio_s *pa);
+int demod_get_sample(int a);
+void demod_process_sample(int chan, int subchan, int sam);
+void demod_print_agc(int chan, int subchan);
+alevel_t demod_get_audio_level(int chan, int subchan);
+void hdlc_rec_bit(int chan, int subchan, int slice, int raw, int is_scrambled, int descram_state);
+
+/* Provided elsewhere to process a complete frame. */
+
+//void process_rec_frame (int chan, unsigned char *fbuf, int flen, int level);
+
+
+/* Is HLDC decoder is currently gathering bits into a frame? */
+/* Similar to, but not exactly the same as, data carrier detect. */
+/* We use this to influence the PLL inertia. */
+
+int hdlc_rec_gathering(int chan, int subchan, int slice);
+
+/* Transmit needs to know when someone else is transmitting. */
+
+void dcd_change(int chan, int subchan, int slice, int state);
+
+int hdlc_rec_data_detect_any(int chan);
+
+
+
+/* direwolf.h - Common stuff used many places. */
+
+// TODO:   include this file first before anything else in each .c file.
+
+
+#ifdef NDEBUG
+#undef NDEBUG		// Because it would disable assert().
+#endif
+
+#define __restrict__
+
+#define dw_printf Debugprintf
+
+#define M_PI       3.1415926f
+
+#define no_init_all deprecated
+
+#ifndef DIREWOLF_H
+#define DIREWOLF_H 1
+
+/*
+ * Support Windows XP and later.
+ *
+ * We need this before "#include <ws2tcpip.h>".
+ *
+ * Don't know what other impact it might have on others.
+ */
+
+#ifdef WIN32
+#define __WIN32__ 1
+#endif
+
+#if __WIN32__
+
+#ifdef _WIN32_WINNT
+#error	Include "direwolf.h" before any windows system files.
+#endif
+#ifdef WINVER
+#error	Include "direwolf.h" before any windows system files.
+#endif
+
+#define _WIN32_WINNT 0x0501     /* Minimum OS version is XP. */
+#define WINVER       0x0501     /* Minimum OS version is XP. */
+
+#include <winsock2.h>
+#include <windows.h>
+
+#endif
+
+ /*
+  * Maximum number of audio devices.
+  * Three is probably adequate for standard version.
+  * Larger reasonable numbers should also be fine.
+  *
+  * For example, if you wanted to use 4 audio devices at once, change this to 4.
+  */
+
+#define MAX_ADEVS 3			
+
+
+  /*
+   * Maximum number of radio channels.
+   * Note that there could be gaps.
+   * Suppose audio device 0 was in mono mode and audio device 1 was stereo.
+   * The channels available would be:
+   *
+   *	ADevice 0:	channel 0
+   *	ADevice 1:	left = 2, right = 3
+   *
+   * TODO1.2:  Look for any places that have
+   *		for (ch=0; ch<MAX_CHANS; ch++) ...
+   * and make sure they handle undefined channels correctly.
+   */
+
+#define MAX_RADIO_CHANS 4
+
+#define MAX_CHANS 4	// TODO: Replace all former  with latter to avoid confusion with following.
+
+#define MAX_TOTAL_CHANS 16		// v1.7 allows additional virtual channels which are connected
+   // to something other than radio modems.
+   // Total maximum channels is based on the 4 bit KISS field.
+   // Someone with very unusual requirements could increase this and
+   // use only the AGW network protocol.
+
+/*
+ * Maximum number of rigs.
+ */
+
+#ifdef USE_HAMLIB
+#define MAX_RIGS MAX_CHANS
+#endif
+
+ /*
+  * Get audio device number for given channel.
+  * and first channel for given device.
+  */
+
+#define ACHAN2ADEV(n) ((n)>>1)
+#define ADEVFIRSTCHAN(n) ((n) * 2)
+
+  /*
+   * Maximum number of modems per channel.
+   * I called them "subchannels" (in the code) because
+   * it is short and unambiguous.
+   * Nothing magic about the number.  Could be larger
+   * but CPU demands might be overwhelming.
+   */
+
+#define MAX_SUBCHANS 9
+
+   /*
+	* Each one of these can have multiple slicers, at
+	* different levels, to compensate for different
+	* amplitudes of the AFSK tones.
+	* Initially used same number as subchannels but
+	* we could probably trim this down a little
+	* without impacting performance.
+	*/
+
+#define MAX_SLICERS 9
+
+
+#if __WIN32__
+#define SLEEP_SEC(n) Sleep((n)*1000)
+#define SLEEP_MS(n) Sleep(n)
+#else
+#define SLEEP_SEC(n) sleep(n)
+#define SLEEP_MS(n) usleep((n)*1000)
+#endif
+
+#if __WIN32__
+
+#define PTW32_STATIC_LIB
+	//#include "pthreads/pthread.h"
+
+	// This enables definitions of localtime_r and gmtime_r in system time.h.
+	//#define _POSIX_THREAD_SAFE_FUNCTIONS 1
+#define _POSIX_C_SOURCE 1
+
+#else
+#include <pthread.h>
+#endif
+
+
+#ifdef __APPLE__
+
+	// https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2072
+
+	// The original suggestion was to add this to only ptt.c.
+	// I thought it would make sense to put it here, so it will apply to all files,
+	// consistently, rather than only one file ptt.c.
+
+	// The placement of this is critical.  Putting it earlier was a problem.
+	// https://github.com/wb2osz/direwolf/issues/113
+
+	// It needs to be after the include pthread.h because
+	// pthread.h pulls in <sys/cdefs.h>, which redefines __DARWIN_C_LEVEL back to ansi,
+	// which breaks things.
+	// Maybe it should just go in ptt.c as originally suggested.
+
+	// #define __DARWIN_C_LEVEL  __DARWIN_C_FULL
+
+	// There is a more involved patch here:
+	//  https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2458
+
+#ifndef _DARWIN_C_SOURCE
+#define _DARWIN_C_SOURCE
+#endif
+
+// Defining _DARWIN_C_SOURCE ensures that the definition for the cfmakeraw function (or similar)
+// are pulled in through the include file <sys/termios.h>.
+
+#ifdef __DARWIN_C_LEVEL
+#undef __DARWIN_C_LEVEL
+#endif
+
+#define __DARWIN_C_LEVEL  __DARWIN_C_FULL
+
+#endif
+
+
+
+
+#define DW_METERS_TO_FEET(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 3.2808399)
+#define DW_FEET_TO_METERS(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.3048)
+#define DW_KM_TO_MILES(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.621371192)
+#define DW_MILES_TO_KM(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 1.609344)
+
+#define DW_KNOTS_TO_MPH(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 1.15077945)
+#define DW_KNOTS_TO_METERS_PER_SEC(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.51444444444)
+#define DW_MPH_TO_KNOTS(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.868976)
+#define DW_MPH_TO_METERS_PER_SEC(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.44704)
+
+#define DW_MBAR_TO_INHG(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.0295333727)
+
+
+
+
+#if __WIN32__
+
+typedef CRITICAL_SECTION dw_mutex_t;
+
+#define dw_mutex_init(x) \
+	InitializeCriticalSection (x)
+
+/* This one waits for lock. */
+
+#define dw_mutex_lock(x) \
+	EnterCriticalSection (x) 
+
+/* Returns non-zero if lock was obtained. */
+
+#define dw_mutex_try_lock(x) \
+	TryEnterCriticalSection (x)
+
+#define dw_mutex_unlock(x) \
+	LeaveCriticalSection (x)
+
+
+#else
+
+typedef pthread_mutex_t dw_mutex_t;
+
+#define dw_mutex_init(x) pthread_mutex_init (x, NULL)
+
+/* this one will wait. */
+
+#define dw_mutex_lock(x) \
+	{	\
+	  int err; \
+	  err = pthread_mutex_lock (x); \
+	  if (err != 0) { \
+	    text_color_set(DW_COLOR_ERROR); \
+	    dw_printf ("INTERNAL ERROR %s %d pthread_mutex_lock returned %d", __FILE__, __LINE__, err); \
+	    exit (1); \
+	  } \
+	}
+
+/* This one returns true if lock successful, false if not. */
+/* pthread_mutex_trylock returns 0 for success. */
+
+#define dw_mutex_try_lock(x) \
+	({	\
+	  int err; \
+	  err = pthread_mutex_trylock (x); \
+	  if (err != 0 && err != EBUSY) { \
+	    text_color_set(DW_COLOR_ERROR); \
+	    dw_printf ("INTERNAL ERROR %s %d pthread_mutex_trylock returned %d", __FILE__, __LINE__, err); \
+	    exit (1); \
+	  } ; \
+	  ! err; \
+	})
+
+#define dw_mutex_unlock(x) \
+	{	\
+	  int err; \
+	  err = pthread_mutex_unlock (x); \
+	  if (err != 0) { \
+	    text_color_set(DW_COLOR_ERROR); \
+	    dw_printf ("INTERNAL ERROR %s %d pthread_mutex_unlock returned %d", __FILE__, __LINE__, err); \
+	    exit (1); \
+	  } \
+	}
+
+#endif
+
+
+
+// Formerly used write/read on Linux, for some forgotten reason,
+// but always using send/recv makes more sense.
+// Need option to prevent a SIGPIPE signal on Linux.  (added for 1.5 beta 2)
+
+#if __WIN32__ || __APPLE__
+#define SOCK_SEND(s,data,size) send(s,data,size,0)
+#else
+#define SOCK_SEND(s,data,size) send(s,data,size, MSG_NOSIGNAL)
+#endif
+#define SOCK_RECV(s,data,size) recv(s,data,size,0)
+
+
+/* Platform differences for string functions. */
+
+
+
+#if __WIN32__
+char *strsep(char **stringp, const char *delim);
+char *strtok_r(char *str, const char *delim, char **saveptr);
+#endif
+
+// Don't recall why I added this for everyone rather than only for Windows.
+char *strcasestr(const char *S, const char *FIND);
+
+#endif   /* ifndef DIREWOLF_H */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      audio.h
+ *
+ * Purpose:   	Interface to audio device commonly called a "sound card"
+ *		for historical reasons.
+ *
+ *---------------------------------------------------------------*/
+
+
+
+ /*
+  * PTT control.
+  */
+
+enum ptt_method_e {
+	PTT_METHOD_NONE,	/* VOX or no transmit. */
+	PTT_METHOD_SERIAL,	/* Serial port RTS or DTR. */
+	PTT_METHOD_GPIO,	/* General purpose I/O, Linux only. */
+	PTT_METHOD_LPT,	    	/* Parallel printer port, Linux only. */
+	PTT_METHOD_HAMLIB, 	/* HAMLib, Linux only. */
+	PTT_METHOD_CM108
+};	/* GPIO pin of CM108/CM119/etc.  Linux only. */
+
+typedef enum ptt_method_e ptt_method_t;
+
+enum ptt_line_e { PTT_LINE_NONE = 0, PTT_LINE_RTS = 1, PTT_LINE_DTR = 2 };	  //  Important: 0 for neither.	
+typedef enum ptt_line_e ptt_line_t;
+
+enum audio_in_type_e {
+	AUDIO_IN_TYPE_SOUNDCARD,
+	AUDIO_IN_TYPE_SDR_UDP,
+	AUDIO_IN_TYPE_STDIN
+};
+
+/* For option to try fixing frames with bad CRC. */
+
+typedef enum retry_e {
+	RETRY_NONE = 0,
+	RETRY_INVERT_SINGLE = 1,
+	RETRY_INVERT_DOUBLE = 2,
+	RETRY_INVERT_TRIPLE = 3,
+	RETRY_INVERT_TWO_SEP = 4,
+	RETRY_MAX = 5
+}  retry_t;
+
+// Type of communication medium associated with the channel.
+
+enum medium_e {
+	MEDIUM_NONE = 0,	// Channel is not valid for use.
+	MEDIUM_RADIO,		// Internal modem for radio.
+	MEDIUM_IGATE,		// Access IGate as ordinary channel.
+	MEDIUM_NETTNC
+};	// Remote network TNC.  (possible future)
+
+
+typedef enum sanity_e { SANITY_APRS, SANITY_AX25, SANITY_NONE } sanity_t;
+
+
+struct audio_s {
+
+	/* Previously we could handle only a single audio device. */
+	/* In version 1.2, we generalize this to handle multiple devices. */
+	/* This means we can now have more than 2 radio channels. */
+
+	struct adev_param_s {
+
+		/* Properties of the sound device. */
+
+		int defined;		/* Was device defined? */
+					/* First one defaults to yes. */
+
+		char adevice_in[80];	/* Name of the audio input device (or file?). */
+					/* TODO: Can be "-" to read from stdin. */
+
+		char adevice_out[80];	/* Name of the audio output device (or file?). */
+
+		int num_channels;		/* Should be 1 for mono or 2 for stereo. */
+		int samples_per_sec;	/* Audio sampling rate.  Typically 11025, 22050, or 44100. */
+		int bits_per_sample;	/* 8 (unsigned char) or 16 (signed short). */
+
+	} adev[MAX_ADEVS];
+
+
+	/* Common to all channels. */
+
+	char tts_script[80];		/* Script for text to speech. */
+
+	int statistics_interval;	/* Number of seconds between the audio */
+					/* statistics reports.  This is set by */
+					/* the "-a" option.  0 to disable feature. */
+
+	int xmit_error_rate;		/* For testing purposes, we can generate frames with an invalid CRC */
+					/* to simulate corruption while going over the air. */
+					/* This is the probability, in per cent, of randomly corrupting it. */
+					/* Normally this is 0.  25 would mean corrupt it 25% of the time. */
+
+	int recv_error_rate;		/* Similar but the % probability of dropping a received frame. */
+
+	float recv_ber;			/* Receive Bit Error Rate (BER). */
+					/* Probability of inverting a bit coming out of the modem. */
+
+	//int fx25_xmit_enable;		/* Enable transmission of FX.25.  */
+					/* See fx25_init.c for explanation of values. */
+					/* Initially this applies to all channels. */
+					/* This should probably be per channel. One step at a time. */
+					/* v1.7 - replaced by layer2_xmit==LAYER2_FX25 */
+
+	int fx25_auto_enable;		/* Turn on FX.25 for current connected mode session */
+					/* under poor conditions. */
+					/* Set to 0 to disable feature. */
+					/* I put it here, rather than with the rest of the link layer */
+					/* parameters because it is really a part of the HDLC layer */
+					/* and is part of the KISS TNC functionality rather than our data link layer. */
+					/* Future: not used yet. */
+
+
+	char timestamp_format[40];	/* -T option */
+					/* Precede received & transmitted frames with timestamp. */
+					/* Command line option uses "strftime" format string. */
+
+
+
+	/* originally a "channel" was always connected to an internal modem. */
+	/* In version 1.6, this is generalized so that a channel (as seen by client application) */
+	/* can be connected to something else.  Initially, this will allow application */
+	/* access to the IGate.  Later we might have network TNCs or other internal functions. */
+
+	// Properties for all channels.
+
+	enum medium_e chan_medium[MAX_TOTAL_CHANS];
+	// MEDIUM_NONE for invalid.
+	// MEDIUM_RADIO for internal modem.  (only possibility earlier)
+	// MEDIUM_IGATE allows application access to IGate.
+	// MEDIUM_NETTNC for external TNC via TCP.
+
+	int igate_vchannel;		/* Virtual channel mapped to APRS-IS. */
+					/* -1 for none. */
+					/* Redundant but it makes things quicker and simpler */
+					/* than always searching thru above. */
+
+	/* Properties for each radio channel, common to receive and transmit. */
+	/* Can be different for each radio channel. */
+
+	struct achan_param_s {
+
+		// What else should be moved out of structure and enlarged when NETTNC is implemented.  ???
+		char mycall[AX25_MAX_ADDR_LEN];      /* Call associated with this radio channel. */
+									/* Could all be the same or different. */
+
+
+		enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_QPSK, MODEM_8PSK, MODEM_OFF, MODEM_16_QAM, MODEM_64_QAM, MODEM_AIS, MODEM_EAS } modem_type;
+
+		/* Usual AFSK. */
+		/* Baseband signal. Not used yet. */
+		/* Scrambled http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif */
+		/* Might try MFJ-2400 / CCITT v.26 / Bell 201 someday. */
+		/* No modem.  Might want this for DTMF only channel. */
+
+		enum layer2_t { LAYER2_AX25 = 0, LAYER2_FX25, LAYER2_IL2P } layer2_xmit;
+
+		// IL2P - New for version 1.7.
+		// New layer 2 with FEC.  Much less overhead than FX.25 but no longer backward compatible.
+		// Only applies to transmit.
+		// Listening for FEC sync word should add negligible overhead so
+		// we leave reception enabled all the time as we do with FX.25.
+		// TODO:  FX.25 should probably be put here rather than global for all channels.
+
+		int fx25_strength;		// Strength of FX.25 FEC.
+					// 16, 23, 64 for specific number of parity symbols.
+					// 1 for automatic selection based on frame size.
+
+		int il2p_max_fec;		// 1 for max FEC length, 0 for automatic based on size.
+
+		int il2p_invert_polarity;	// 1 means invert on transmit.  Receive handles either automatically.
+
+		enum v26_e { V26_UNSPECIFIED = 0, V26_A, V26_B } v26_alternative;
+
+		// Original implementation used alternative A for 2400 bbps PSK.
+		// Years later, we discover that MFJ-2400 used alternative B.
+		// It's likely the others did too.  it also works a little better.
+		// Default to MFJ compatible and print warning if user did not
+		// pick one explicitly.
+
+#define V26_DEFAULT V26_B
+
+		enum dtmf_decode_t { DTMF_DECODE_OFF, DTMF_DECODE_ON } dtmf_decode;
+
+		/* Originally the DTMF ("Touch Tone") decoder was always */
+		/* enabled because it took a negligible amount of CPU. */
+		/* There were complaints about the false positives when */
+		/* hearing other modulation schemes on HF SSB so now it */
+		/* is enabled only when needed. */
+
+		/* "On" will send special "t" packet to attached applications */
+		/* and process as APRStt.  Someday we might want to separate */
+		/* these but for now, we have a single off/on. */
+
+		int decimate;		/* Reduce AFSK sample rate by this factor to */
+					/* decrease computational requirements. */
+
+		int upsample;		/* Upsample by this factor for G3RUH. */
+
+		int mark_freq;		/* Two tones for AFSK modulation, in Hz. */
+		int space_freq;		/* Standard tones are 1200 and 2200 for 1200 baud. */
+
+		int baud;			/* Data bits per second. */
+					/* Standard rates are 1200 for VHF and 300 for HF. */
+					/* This should really be called bits per second. */
+
+	/* Next 3 come from config file or command line. */
+
+		char profiles[16];		/* zero or more of ABC etc, optional + */
+
+		int num_freq;		/* Number of different frequency pairs for decoders. */
+
+		int offset;			/* Spacing between filter frequencies. */
+
+		int num_slicers;		/* Number of different threshold points to decide */
+					/* between mark or space. */
+
+	/* This is derived from above by demod_init. */
+
+		int num_subchan;		/* Total number of modems for each channel. */
+
+
+	/* These are for dealing with imperfect frames. */
+
+		enum retry_e fix_bits;	/* Level of effort to recover from */
+					/* a bad FCS on the frame. */
+					/* 0 = no effort */
+					/* 1 = try fixing a single bit */
+					/* 2... = more techniques... */
+
+		enum sanity_e sanity_test;	/* Sanity test to apply when finding a good */
+					/* CRC after making a change. */
+					/* Must look like APRS, AX.25, or anything. */
+
+		int passall;		/* Allow thru even with bad CRC. */
+
+
+
+	/* Additional properties for transmit. */
+
+	/* Originally we had control outputs only for PTT. */
+	/* In version 1.2, we generalize this to allow others such as DCD. */
+	/* In version 1.4 we add CON for connected to another station. */
+	/* Index following structure by one of these: */
+
+
+#define OCTYPE_PTT 0
+#define OCTYPE_DCD 1
+#define OCTYPE_CON 2
+
+#define NUM_OCTYPES 3		/* number of values above.   i.e. last value +1. */
+
+		struct {
+
+			ptt_method_t ptt_method; /* none, serial port, GPIO, LPT, HAMLIB, CM108. */
+
+			char ptt_device[128];	/* Serial device name for PTT.  e.g. COM1 or /dev/ttyS0 */
+					/* Also used for HAMLIB.  Could be host:port when model is 1. */
+					/* For years, 20 characters was plenty then we start getting extreme names like this: */
+					/* /dev/serial/by-id/usb-FTDI_Navigator__CAT___2nd_PTT__00000000-if00-port0 */
+					/* /dev/serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller_D-if00-port0 */
+					/* Issue 104, changed to 100 bytes in version 1.5. */
+
+					/* This same field is also used for CM108/CM119 GPIO PTT which will */
+					/* have a name like /dev/hidraw1 for Linux or */
+					/* \\?\hid#vid_0d8c&pid_0008&mi_03#8&39d3555&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} */
+					/* for Windows.  Largest observed was 95 but add some extra to be safe. */
+
+			ptt_line_t ptt_line;	/* Control line when using serial port. PTT_LINE_RTS, PTT_LINE_DTR. */
+			ptt_line_t ptt_line2;	/* Optional second one:  PTT_LINE_NONE when not used. */
+
+			int out_gpio_num;	/* GPIO number.  Originally this was only for PTT. */
+					/* It is now more general. */
+					/* octrl array is indexed by PTT, DCD, or CONnected indicator. */
+					/* For CM108/CM119, this should be in range of 1-8. */
+
+#define MAX_GPIO_NAME_LEN 20	// 12 would cover any case I've seen so this should be safe
+
+			char out_gpio_name[MAX_GPIO_NAME_LEN];
+			/* originally, gpio number NN was assumed to simply */
+			/* have the name gpioNN but this turned out not to be */
+			/* the case for CubieBoard where it was longer. */
+			/* This is filled in by ptt_init so we don't have to */
+			/* recalculate it each time we access it. */
+
+			/* This could probably be collapsed into ptt_device instead of being separate. */
+
+			int ptt_lpt_bit;	/* Bit number for parallel printer port.  */
+					/* Bit 0 = pin 2, ..., bit 7 = pin 9. */
+
+			int ptt_invert;		/* Invert the output. */
+			int ptt_invert2;	/* Invert the secondary output. */
+
+#ifdef USE_HAMLIB
+
+			int ptt_model;		/* HAMLIB model.  -1 for AUTO.  2 for rigctld.  Others are radio model. */
+			int ptt_rate;		/* Serial port speed when using hamlib CAT control for PTT. */
+					/* If zero, hamlib will come up with a default for pariticular rig. */
+#endif
+
+		} octrl[NUM_OCTYPES];
+
+
+		/* Each channel can also have associated input lines. */
+		/* So far, we just have one for transmit inhibit. */
+
+#define ICTYPE_TXINH 0
+
+#define NUM_ICTYPES 1		/* number of values above. i.e. last value +1. */
+
+		struct {
+			ptt_method_t method;	/* none, serial port, GPIO, LPT. */
+
+			int in_gpio_num;	/* GPIO number */
+
+			char in_gpio_name[MAX_GPIO_NAME_LEN];
+			/* originally, gpio number NN was assumed to simply */
+			/* have the name gpioNN but this turned out not to be */
+			/* the case for CubieBoard where it was longer. */
+			/* This is filled in by ptt_init so we don't have to */
+			/* recalculate it each time we access it. */
+
+			int invert;		/* 1 = active low */
+		} ictrl[NUM_ICTYPES];
+
+		/* Transmit timing. */
+
+		int dwait;			/* First wait extra time for receiver squelch. */
+					/* Default 0 units of 10 mS each . */
+
+		int slottime;		/* Slot time in 10 mS units for persistence algorithm. */
+					/* Typical value is 10 meaning 100 milliseconds. */
+
+		int persist;		/* Sets probability for transmitting after each */
+					/* slot time delay.  Transmit if a random number */
+					/* in range of 0 - 255 <= persist value.  */
+					/* Otherwise wait another slot time and try again. */
+					/* Default value is 63 for 25% probability. */
+
+		int txdelay;		/* After turning on the transmitter, */
+					/* send "flags" for txdelay * 10 mS. */
+					/* Default value is 30 meaning 300 milliseconds. */
+
+		int txtail;			/* Amount of time to keep transmitting after we */
+					/* are done sending the data.  This is to avoid */
+					/* dropping PTT too soon and chopping off the end */
+					/* of the frame.  Again 10 mS units. */
+					/* At this point, I'm thinking of 10 (= 100 mS) as the default */
+					/* because we're not quite sure when the soundcard audio stops. */
+
+		int fulldup;		/* Full Duplex. */
+
+	} achan[MAX_CHANS];
+
+#ifdef USE_HAMLIB
+	int rigs;               /* Total number of configured rigs */
+	RIG *rig[MAX_RIGS];     /* HAMLib rig instances */
+#endif
+
+};
+
+
+#if __WIN32__
+#define DEFAULT_ADEVICE	""		/* Windows: Empty string = default audio device. */
+#elif __APPLE__
+#define DEFAULT_ADEVICE	""		/* Mac OSX: Empty string = default audio device. */
+#elif USE_ALSA
+#define DEFAULT_ADEVICE	"default"	/* Use default device for ALSA. */
+#elif USE_SNDIO
+#define DEFAULT_ADEVICE	"default"	/* Use default device for sndio. */
+#else
+#define DEFAULT_ADEVICE	"/dev/dsp"	/* First audio device for OSS.  (FreeBSD) */
+#endif					
+
+
+
+/*
+ * UDP audio receiving port.  Couldn't find any standard or usage precedent.
+ * Got the number from this example:   http://gqrx.dk/doc/streaming-audio-over-udp
+ * Any better suggestions?
+ */
+
+#define DEFAULT_UDP_AUDIO_PORT 7355
+
+
+ // Maximum size of the UDP buffer (for allowing IP routing, udp packets are often limited to 1472 bytes)
+
+#define SDR_UDP_BUF_MAXLEN 2000
+
+
+
+#define DEFAULT_NUM_CHANNELS 	1
+#define DEFAULT_SAMPLES_PER_SEC	44100	/* Very early observations.  Might no longer be valid. */
+					/* 22050 works a lot better than 11025. */
+					/* 44100 works a little better than 22050. */
+					/* If you have a reasonable machine, use the highest rate. */
+#define MIN_SAMPLES_PER_SEC	8000
+//#define MAX_SAMPLES_PER_SEC	48000	/* Originally 44100.  Later increased because */
+					/* Software Defined Radio often uses 48000. */
+
+#define MAX_SAMPLES_PER_SEC	192000	/* The cheap USB-audio adapters (e.g. CM108) can handle 44100 and 48000. */
+					/* The "soundcard" in my desktop PC can do 96kHz or even 192kHz. */
+					/* We will probably need to increase the sample rate to go much above 9600 baud. */
+
+#define DEFAULT_BITS_PER_SAMPLE	16
+
+#define DEFAULT_FIX_BITS RETRY_INVERT_SINGLE
+
+/*
+ * Standard for AFSK on VHF FM.
+ * Reversing mark and space makes no difference because
+ * NRZI encoding only cares about change or lack of change
+ * between the two tones.
+ *
+ * HF SSB uses 300 baud and 200 Hz shift.
+ * 1600 & 1800 Hz is a popular tone pair, sometimes
+ * called the KAM tones.
+ */
+
+#define DEFAULT_MARK_FREQ	1200	
+#define DEFAULT_SPACE_FREQ	2200
+#define DEFAULT_BAUD		1200
+
+ /* Used for sanity checking in config file and command line options. */
+ /* 9600 baud is known to work.  */
+ /* TODO: Is 19200 possible with a soundcard at 44100 samples/sec or do we need a higher sample rate? */
+
+#define MIN_BAUD		100
+//#define MAX_BAUD		10000
+#define MAX_BAUD		40000		// Anyone want to try 38.4 k baud?
+
+/*
+ * Typical transmit timings for VHF.
+ */
+
+#define DEFAULT_DWAIT		0
+#define DEFAULT_SLOTTIME	10
+#define DEFAULT_PERSIST		63
+#define DEFAULT_TXDELAY		30
+#define DEFAULT_TXTAIL		10	
+#define DEFAULT_FULLDUP		0
+
+ /*
+  * Note that we have two versions of these in audio.c and audio_win.c.
+  * Use one or the other depending on the platform.
+  */
+
+int audio_open(struct audio_s *pa);
+
+int audio_get(int a);		/* a = audio device, 0 for first */
+
+int audio_put(int a, int c);
+
+int audio_flush(int a);
+
+void audio_wait(int a);
+
+int audio_close(void);
+
+
+
+/* end audio.h */
+
+
+
+void multi_modem_init(struct audio_s *pmodem);
+
+void multi_modem_process_sample(int c, int audio_sample);
+
+int multi_modem_get_dc_average(int chan);
+
+// Deprecated.  Replace with ...packet
+
+
+void multi_modem_process_rec_packet(int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, int is_fx25);
+
+
+void fsk_gen_filter(int samples_per_sec,
+	int baud,
+	int mark_freq, int space_freq,
+	char profile,
+	struct demodulator_state_s *D);
+
+
+
+/* fsk_demod_state.h */
+
+
+
+/*
+ * Demodulator state.
+ * The name of the file is from we only had FSK.  Now we have other techniques.
+ * Different copy is required for each channel & subchannel being processed concurrently.
+ */
+
+ // TODO1.2:  change prefix from BP_ to DSP_
+
+typedef enum bp_window_e {
+	BP_WINDOW_TRUNCATED,
+	BP_WINDOW_COSINE,
+	BP_WINDOW_HAMMING,
+	BP_WINDOW_BLACKMAN,
+	BP_WINDOW_FLATTOP
+} bp_window_t;
+
+// Experimental low pass filter to detect DC bias or low frequency changes.
+// IIR behaves like an analog R-C filter.
+// Intuitively, it seems like FIR would be better because it is based on a finite history.
+// However, it would require MANY taps and a LOT of computation for a low frequency.
+// We can use a little trick here to keep a running average.
+// This would be equivalent to convolving with an array of all 1 values.
+// That would eliminate the need to multiply.
+// We can also eliminate the need to add them all up each time by keeping a running total.
+// Add a sample to the total when putting it in our array of recent samples.
+// Subtract it from the total when it gets pushed off the end.
+// We can also eliminate the need to shift them all down by using a circular buffer.
+
+#define CIC_LEN_MAX 4000
+
+typedef struct cic_s {
+	int len;		// Number of elements used.
+				// Might want to dynamically allocate.
+	short in[CIC_LEN_MAX];	// Samples coming in.
+	int sum;		// Running sum.
+	int inext;		// Next position to fill.
+} cic_t;
+
+
+#define MAX_FILTER_SIZE 480		/* 401 is needed for profile A, 300 baud & 44100. Revisit someday. */
+// Size comes out to 417 for 1200 bps with 48000 sample rate
+// v1.7 - Was 404.  Bump up to 480.
+
+struct demodulator_state_s
+{
+	/*
+	 * These are set once during initialization.
+	 */
+	enum modem_t modem_type;		// MODEM_AFSK, MODEM_8PSK, etc.
+
+//	enum v26_e v26_alt;			// Which alternative when V.26.
+
+	char profile;			// 'A', 'B', etc.	Upper case.
+					// Only needed to see if we are using 'F' to take fast path.
+
+#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.
+					// Data is sampled when it overflows.
+
+
+/*
+ * Window type for the various filters.
+ */
+
+	bp_window_t lp_window;
+
+	/*
+	 * Alternate Low pass filters.
+	 * First is arbitrary number for quick IIR.
+	 * Second is frequency as ratio to baud rate for FIR.
+	 */
+	int lpf_use_fir;		/* 0 for IIR, 1 for FIR. */
+
+	float lpf_iir;			/* Only if using IIR. */
+
+	float lpf_baud;			/* Cutoff frequency as fraction of baud. */
+					/* Intuitively we'd expect this to be somewhere */
+					/* in the range of 0.5 to 1. */
+					/* In practice, it turned out a little larger */
+					/* for profiles B, C, D. */
+
+	float lp_filter_width_sym;  	/* Length in number of symbol times. */
+
+#define lp_filter_len_bits lp_filter_width_sym	// FIXME: temp hack
+
+	int lp_filter_taps;		/* Size of Low Pass filter, in audio samples. */
+
+#define lp_filter_size lp_filter_taps		// FIXME: temp hack
+
+
+/*
+ * Automatic gain control.  Fast attack and slow decay factors.
+ */
+	float agc_fast_attack;
+	float agc_slow_decay;
+
+	/*
+	 * Use a longer term view for reporting signal levels.
+	 */
+	float quick_attack;
+	float sluggish_decay;
+
+	/*
+	 * Hysteresis before final demodulator 0 / 1 decision.
+	 */
+	float hysteresis;
+	int num_slicers;		/* >1 for multiple slicers. */
+
+/*
+ * Phase Locked Loop (PLL) inertia.
+ * Larger number means less influence by signal transitions.
+ * It is more resistant to change when locked on to a signal.
+ */
+	float pll_locked_inertia;
+	float pll_searching_inertia;
+
+
+	/*
+	 * Optional band pass pre-filter before mark/space detector.
+	 */
+	int use_prefilter;	/* True to enable it. */
+
+	float prefilter_baud;	/* Cutoff frequencies, as fraction of */
+				/* baud rate, beyond tones used.  */
+				/* Example, if we used 1600/1800 tones at */
+				/* 300 baud, and this was 0.5, the cutoff */
+				/* frequencies would be: */
+				/* lower = min(1600,1800) - 0.5 * 300 = 1450 */
+				/* upper = max(1600,1800) + 0.5 * 300 = 1950 */
+
+	float pre_filter_len_sym;  	// Length in number of symbol times.
+#define pre_filter_len_bits pre_filter_len_sym 		// temp until all references changed.
+
+	bp_window_t pre_window;		// Window type for filter shaping.
+
+	int pre_filter_taps;		// Calculated number of filter taps.
+#define pre_filter_size pre_filter_taps		// temp until all references changed.
+
+	float pre_filter[MAX_FILTER_SIZE];
+
+	float raw_cb[MAX_FILTER_SIZE];	// audio in,  need better name.
+
+/*
+ * The rest are continuously updated.
+ */
+
+	unsigned int lo_phase;	/* Local oscillator for PSK. */
+
+
+/*
+ * Use half of the AGC code to get a measure of input audio amplitude.
+ * These use "quick" attack and "sluggish" decay while the
+ * AGC uses "fast" attack and "slow" decay.
+ */
+
+	float alevel_rec_peak;
+	float alevel_rec_valley;
+	float alevel_mark_peak;
+	float alevel_space_peak;
+
+	/*
+	 * Outputs from the mark and space amplitude detection,
+	 * used as inputs to the FIR lowpass filters.
+	 * Kernel for the lowpass filters.
+	 */
+
+	float lp_filter[MAX_FILTER_SIZE];
+
+	float m_peak, s_peak;
+	float m_valley, s_valley;
+	float m_amp_prev, s_amp_prev;
+
+
+	/*
+	 * For the PLL and data bit timing.
+	 * starting in version 1.2 we can have multiple slicers for one demodulator.
+	 * Each slicer has its own PLL and HDLC decoder.
+	 */
+
+	 /*
+	  * Version 1.3: Clean up subchan vs. slicer.
+	  *
+	  * Originally some number of CHANNELS (originally 2, later 6)
+	  * which can have multiple parallel demodulators called SUB-CHANNELS.
+	  * This was originally for staggered frequencies for HF SSB.
+	  * It can also be used for multiple demodulators with the same
+	  * frequency but other differing parameters.
+	  * Each subchannel has its own demodulator and HDLC decoder.
+	  *
+	  * In version 1.2 we added multiple SLICERS.
+	  * The data structure, here, has multiple slicers per
+	  * demodulator (subchannel).  Due to fuzzy thinking or
+	  * expediency, the multiple slicers got mapped into subchannels.
+	  * This means we can't use both multiple decoders and
+	  * multiple slicers at the same time.
+	  *
+	  * Clean this up in 1.3 and keep the concepts separate.
+	  * This means adding a third variable many places
+	  * we are passing around the origin.
+	  *
+	  */
+	struct {
+
+		signed int data_clock_pll;		// PLL for data clock recovery.
+							// It is incremented by pll_step_per_sample
+							// for each audio sample.
+							// Must be 32 bits!!!
+							// So far, this is the case for every compiler used.
+
+		signed int prev_d_c_pll;		// Previous value of above, before
+							// incrementing, to detect overflows.
+
+		int pll_symbol_count;			// Number symbols during time nudge_total is accumulated.
+		int64_t pll_nudge_total;		// Sum of DPLL nudge amounts.
+							// Both of these are cleared at start of frame.
+							// At end of frame, we can see if incoming
+							// baud rate is a little off.
+
+		int prev_demod_data;			// Previous data bit detected.
+							// Used to look for transitions.
+		float prev_demod_out_f;
+
+		/* This is used only for "9600" baud data. */
+
+		int lfsr;				// Descrambler shift register.
+
+		// This is for detecting phase lock to incoming signal.
+
+		int good_flag;				// Set if transition is near where expected,
+							// i.e. at a good time.
+		int bad_flag;				// Set if transition is not where expected,
+							// i.e. at a bad time.
+		unsigned char good_hist;		// History of good transitions for past octet.
+		unsigned char bad_hist;			// History of bad transitions for past octet.
+		unsigned int score;			// History of whether good triumphs over bad
+							// for past 32 symbols.
+		int data_detect;			// True when locked on to signal.
+
+	} slicer[MAX_SLICERS];				// Actual number in use is num_slicers.
+							// Should be in range 1 .. MAX_SLICERS,
+/*
+ * Version 1.6:
+ *
+ *	This has become quite disorganized and messy with different combinations of
+ *	fields used for different demodulator types.  Start to reorganize it into a common
+ *	part (with things like the DPLL for clock recovery), and separate sections
+ *	for each of the demodulator types.
+ *	Still a lot to do here.
+ */
+
+	union {
+
+		//////////////////////////////////////////////////////////////////////////////////
+		//										//
+		//			AFSK only - new method in 1.7				//
+		//										//
+		//////////////////////////////////////////////////////////////////////////////////
+
+
+		struct afsk_only_s {
+
+			unsigned int m_osc_phase;		// Phase for Mark local oscillator.
+			unsigned int m_osc_delta;		// How much to change for each audio sample.
+
+			unsigned int s_osc_phase;		// Phase for Space local oscillator.
+			unsigned int s_osc_delta;		// How much to change for each audio sample.
+
+			unsigned int c_osc_phase;		// Phase for Center frequency local oscillator.
+			unsigned int c_osc_delta;		// How much to change for each audio sample.
+
+			// Need two mixers for profile "A".
+
+			float m_I_raw[MAX_FILTER_SIZE];
+			float m_Q_raw[MAX_FILTER_SIZE];
+
+			float s_I_raw[MAX_FILTER_SIZE];
+			float s_Q_raw[MAX_FILTER_SIZE];
+
+			// Only need one mixer for profile "B".  Reuse the same storage?
+
+	//#define c_I_raw m_I_raw
+	//#define c_Q_raw m_Q_raw
+			float c_I_raw[MAX_FILTER_SIZE];
+			float c_Q_raw[MAX_FILTER_SIZE];
+
+			int use_rrc;		// Use RRC rather than generic low pass.
+
+			float rrc_width_sym;	/* Width of RRC filter in number of symbols.  */
+
+			float rrc_rolloff;		/* Rolloff factor for RRC.  Between 0 and 1. */
+
+			float prev_phase;		// To see phase shift between samples for FM demod.
+
+			float normalize_rpsam;	// Normalize to -1 to +1 for expected tones.
+
+		} afsk;
+
+		//////////////////////////////////////////////////////////////////////////////////
+		//										//
+		//				Baseband only, AKA G3RUH			//
+		//										//
+		//////////////////////////////////////////////////////////////////////////////////
+
+		// TODO: Continue experiments with root raised cosine filter.
+		// Either switch to that or take out all the related stuff.
+
+		struct bb_only_s {
+
+			float rrc_width_sym;		/* Width of RRC filter in number of symbols. */
+
+			float rrc_rolloff;		/* Rolloff factor for RRC.  Between 0 and 1. */
+
+			int rrc_filter_taps;		// Number of elements used in the next two.
+
+	// FIXME: TODO: reevaluate max size needed.
+
+			float audio_in[MAX_FILTER_SIZE];	// Audio samples in.
+
+
+			float lp_filter[MAX_FILTER_SIZE];	// Low pass filter.
+
+			// New in 1.7 - Polyphase filter to reduce CPU requirements.
+
+			float lp_polyphase_1[MAX_FILTER_SIZE];
+			float lp_polyphase_2[MAX_FILTER_SIZE];
+			float lp_polyphase_3[MAX_FILTER_SIZE];
+			float lp_polyphase_4[MAX_FILTER_SIZE];
+
+			float lp_1_iir_param;		// very low pass filters to get DC offset.
+			float lp_1_out;
+
+			float lp_2_iir_param;
+			float lp_2_out;
+
+			float agc_1_fast_attack;	// Signal envelope detection.
+			float agc_1_slow_decay;
+			float agc_1_peak;
+			float agc_1_valley;
+
+			float agc_2_fast_attack;
+			float agc_2_slow_decay;
+			float agc_2_peak;
+			float agc_2_valley;
+
+			float agc_3_fast_attack;
+			float agc_3_slow_decay;
+			float agc_3_peak;
+			float agc_3_valley;
+
+			// CIC low pass filters to detect DC bias or low frequency changes.
+			// IIR behaves like an analog R-C filter.
+			// Intuitively, it seems like FIR would be better because it is based on a finite history.
+			// However, it would require MANY taps and a LOT of computation for a low frequency.
+			// We can use a little trick here to keep a running average.
+			// This would be equivalent to convolving with an array of all 1 values.
+			// That would eliminate the need to multiply.
+			// We can also eliminate the need to add them all up each time by keeping a running total.
+			// Add a sample to the total when putting it in our array of recent samples.
+			// Subtract it from the total when it gets pushed off the end.
+			// We can also eliminate the need to shift them all down by using a circular buffer.
+			// This only works with integers because float would have cumulated round off errors.
+
+			cic_t cic_center1;
+			cic_t cic_above;
+			cic_t cic_below;
+
+		} bb;
+
+		//////////////////////////////////////////////////////////////////////////////////
+		//										//
+		//					PSK only.				//
+		//										//
+		//////////////////////////////////////////////////////////////////////////////////
+
+
+		struct psk_only_s {
+
+			enum v26_e v26_alt;		// Which alternative when V.26.
+
+			float sin_table256[256];	// Precomputed sin table for speed.
+
+
+		// Optional band pass pre-filter before phase detector.
+
+	// TODO? put back into common section?
+	// TODO? Why was I thinking that?
+
+			int use_prefilter;	// True to enable it.
+
+			float prefilter_baud;	// Cutoff frequencies, as fraction of baud rate, beyond tones used.
+						// In the case of PSK, we use only a single tone of 1800 Hz.
+						// If we were using 2400 bps (= 1200 baud), this would be
+						// the fraction of 1200 for the cutoff below and above 1800.
+
+
+			float pre_filter_width_sym;  /* Length in number of symbol times. */
+
+			int pre_filter_taps;	/* Size of pre filter, in audio samples. */
+
+			bp_window_t pre_window;
+
+			float audio_in[MAX_FILTER_SIZE];
+			float pre_filter[MAX_FILTER_SIZE];
+
+			// Use local oscillator or correlate with previous sample.
+
+			int psk_use_lo;		/* Use local oscillator rather than self correlation. */
+
+			unsigned int lo_step;	/* How much to advance the local oscillator */
+						/* phase for each audio sample. */
+
+			unsigned int lo_phase;	/* Local oscillator phase accumulator for PSK. */
+
+			// After mixing with LO before low pass filter.
+
+			float I_raw[MAX_FILTER_SIZE];	// signal * LO cos.
+			float Q_raw[MAX_FILTER_SIZE];	// signal * LO sin.
+
+			// Number of delay line taps into previous symbol.
+			// They are one symbol period and + or - 45 degrees of the carrier frequency.
+
+			int boffs;		/* symbol length based on sample rate and baud. */
+			int coffs;		/* to get cos component of previous symbol. */
+			int soffs;		/* to get sin component of previous symbol. */
+
+			float delay_line_width_sym;
+			int delay_line_taps;	// In audio samples.
+
+			float delay_line[MAX_FILTER_SIZE];
+
+			// Low pass filter Second is frequency as ratio to baud rate for FIR.
+
+		// TODO? put back into common section?
+		// TODO? What are the tradeoffs?
+			float lpf_baud;			/* Cutoff frequency as fraction of baud. */
+							/* Intuitively we'd expect this to be somewhere */
+							/* in the range of 0.5 to 1. */
+
+			float lp_filter_width_sym;  	/* Length in number of symbol times. */
+
+			int lp_filter_taps;		/* Size of Low Pass filter, in audio samples (i.e. filter taps). */
+
+			bp_window_t lp_window;
+
+			float lp_filter[MAX_FILTER_SIZE];
+
+		} psk;
+
+	} u;	// end of union for different demodulator types.
+
+};
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        pll_dcd_signal_transition2
+ *		dcd_each_symbol2
+ *
+ * Purpose:     New DCD strategy for 1.6.
+ *
+ * Inputs:	D		Pointer to demodulator state.
+ *
+ *		chan		Radio channel: 0 to MAX_CHANS - 1
+ *
+ *		subchan		Which of multiple demodulators: 0 to MAX_SUBCHANS - 1
+ *
+ *		slice		Slicer number: 0 to MAX_SLICERS - 1.
+ *
+ *		dpll_phase	Signed 32 bit counter for DPLL phase.
+ *				Wraparound is where data is sampled.
+ *				Ideally transitions would occur close to 0.
+ *
+ * Output:	D->slicer[slice].data_detect - true when PLL is locked to incoming signal.
+ *
+ * Description:	From the beginning, DCD was based on finding several flag octets
+ *		in a row and dropping when eight bits with no transitions.
+ *		It was less than ideal but we limped along with it all these years.
+ *		This fell apart when FX.25 came along and a couple of the
+ *		correlation tags have eight "1" bits in a row.
+ *
+ * 		Our new strategy is to keep a running score of how well demodulator
+ *		output transitions match to where expected.
+ *
+ *--------------------------------------------------------------------*/
+
+
+
+ // These are good for 1200 bps AFSK.
+ // Might want to override for other modems.
+
+#ifndef DCD_THRESH_ON
+#define DCD_THRESH_ON 30		// Hysteresis: Can miss 2 out of 32 for detecting lock.
+					// 31 is best for TNC Test CD.  30 almost as good.
+					// 30 better for 1200 regression test.
+#endif
+
+#ifndef DCD_THRESH_OFF
+#define DCD_THRESH_OFF 6		// Might want a little more fine tuning.
+#endif
+
+#ifndef DCD_GOOD_WIDTH
+#define DCD_GOOD_WIDTH 512		// No more than 1024!!!
+#endif
+
+
+inline static void pll_dcd_signal_transition2(struct demodulator_state_s *D, int slice, int dpll_phase)
+{
+	if (dpll_phase > -DCD_GOOD_WIDTH * 1024 * 1024 && dpll_phase < DCD_GOOD_WIDTH * 1024 * 1024) {
+		D->slicer[slice].good_flag = 1;
+	}
+	else {
+		D->slicer[slice].bad_flag = 1;
+	}
+}
+
+
+inline static void pll_dcd_each_symbol2(struct demodulator_state_s *D, int chan, int subchan, int slice)
+{
+	D->slicer[slice].good_hist <<= 1;
+	D->slicer[slice].good_hist |= D->slicer[slice].good_flag;
+	D->slicer[slice].good_flag = 0;
+
+	D->slicer[slice].bad_hist <<= 1;
+	D->slicer[slice].bad_hist |= D->slicer[slice].bad_flag;
+	D->slicer[slice].bad_flag = 0;
+
+	D->slicer[slice].score <<= 1;
+	// 2 is to detect 'flag' patterns with 2 transitions per octet.
+	D->slicer[slice].score |= (signed)__builtin_popcount(D->slicer[slice].good_hist)
+		- (signed)__builtin_popcount(D->slicer[slice].bad_hist) >= 2;
+
+	int s = __builtin_popcount(D->slicer[slice].score);
+	if (s >= DCD_THRESH_ON) {
+		if (D->slicer[slice].data_detect == 0) {
+			D->slicer[slice].data_detect = 1;
+			dcd_change(chan, subchan, slice, D->slicer[slice].data_detect);
+		}
+	}
+	else if (s <= DCD_THRESH_OFF) {
+		if (D->slicer[slice].data_detect != 0) {
+			D->slicer[slice].data_detect = 0;
+			dcd_change(chan, subchan, slice, D->slicer[slice].data_detect);
+		}
+	}
+}
+
+
+
+/* Provided elsewhere to process a complete frame. */
+
+//void process_rec_frame (int chan, unsigned char *fbuf, int flen, int level);
+
+
+/* Is HLDC decoder is currently gathering bits into a frame? */
+/* Similar to, but not exactly the same as, data carrier detect. */
+/* We use this to influence the PLL inertia. */
+
+int hdlc_rec_gathering(int chan, int subchan, int slice);
+
+/* Transmit needs to know when someone else is transmitting. */
+
+void dcd_change(int chan, int subchan, int slice, int state);
+
+int hdlc_rec_data_detect_any(int chan);
+
+
+#define FASTER13 1		// Don't pack 8 samples per byte.
+
+
+//typedef short slice_t;
+
+
+/*
+ * Maximum size (in bytes) of an AX.25 frame including the 2 octet FCS.
+ */
+
+#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)	
+
+ /*
+  * Maximum number of bits in AX.25 frame excluding the flags.
+  * Adequate for extreme case of bit stuffing after every 5 bits
+  * which could never happen.
+  */
+
+#define MAX_NUM_BITS (MAX_FRAME_LEN * 8 * 6 / 5)
+
+typedef struct rrbb_s {
+	int magic1;
+	struct rrbb_s* nextp;	/* Next pointer to maintain a queue. */
+
+	int chan;		/* Radio channel from which it was received. */
+	int subchan;		/* Which modem when more than one per channel. */
+	int slice;		/* Which slicer. */
+
+	alevel_t alevel;	/* Received audio level at time of frame capture. */
+	unsigned int len;	/* Current number of samples in array. */
+
+	int is_scrambled;	/* Is data scrambled G3RUH / K9NG style? */
+	int descram_state;	/* Descrambler state before first data bit of frame. */
+	int prev_descram;	/* Previous descrambled bit. */
+
+	unsigned char fdata[MAX_NUM_BITS];
+
+	int magic2;
+} *rrbb_t;
+
+
+
+rrbb_t rrbb_new(int chan, int subchan, int slice, int is_scrambled, int descram_state, int prev_descram);
+
+void rrbb_clear(rrbb_t b, int is_scrambled, int descram_state, int prev_descram);
+
+
+static inline /*__attribute__((always_inline))*/ void rrbb_append_bit(rrbb_t b, const unsigned char val)
+{
+	if (b->len >= MAX_NUM_BITS) {
+		return;	/* Silently discard if full. */
+	}
+	b->fdata[b->len] = val;
+	b->len++;
+}
+
+static inline /*__attribute__((always_inline))*/ unsigned char rrbb_get_bit(const rrbb_t b, const int ind)
+{
+	return (b->fdata[ind]);
+}
+
+
+void rrbb_chop8(rrbb_t b);
+
+int rrbb_get_len(rrbb_t b);
+
+//void rrbb_flip_bit (rrbb_t b, unsigned int ind);
+
+void rrbb_delete(rrbb_t b);
+
+void rrbb_set_nextp(rrbb_t b, rrbb_t np);
+rrbb_t rrbb_get_nextp(rrbb_t b);
+
+int rrbb_get_chan(rrbb_t b);
+int rrbb_get_subchan(rrbb_t b);
+int rrbb_get_slice(rrbb_t b);
+
+void rrbb_set_audio_level(rrbb_t b, alevel_t alevel);
+alevel_t rrbb_get_audio_level(rrbb_t b);
+
+int rrbb_get_is_scrambled(rrbb_t b);
+int rrbb_get_descram_state(rrbb_t b);
+int rrbb_get_prev_descram(rrbb_t b);
+
+
+
+
+void hdlc_rec2_init(struct audio_s *audio_config_p);
+
+void hdlc_rec2_block(rrbb_t block);
+
+int hdlc_rec2_try_to_fix_later(rrbb_t block, int chan, int subchan, int slice, alevel_t alevel);
+
+/* Provided by the top level application to process a complete frame. */
+
+void app_process_rec_packet(int chan, int subchan, int slice, packet_t pp, alevel_t level, int is_fx25, retry_t retries, char *spectrum);
+
+
+
+
+
+int gen_tone_init(struct audio_s *pp, int amp, int gen_packets);
+
+
+//int gen_tone_open (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, char *fname);
+
+//int gen_tone_open_fd (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, int fd)  ;
+
+//int gen_tone_close (void);
+
+void tone_gen_put_bit(int chan, int dat);
+
+void gen_tone_put_sample(int chan, int a, int sam); 
+
+enum dw_color_e {
+	DW_COLOR_INFO,		/* black */
+	DW_COLOR_ERROR,		/* red */
+	DW_COLOR_REC,		/* green */
+	DW_COLOR_DECODED,	/* blue */
+	DW_COLOR_XMIT,		/* magenta */
+	DW_COLOR_DEBUG		/* dark_green */
+};
+
+typedef enum dw_color_e dw_color_t;
+
+
+void text_color_init(int enable_color);
+void text_color_set(dw_color_t c);
+void text_color_term(void);
+
+
+/* Degree symbol. */
+
+#if __WIN32__
+
+//#define CH_DEGREE "\xc2\xb0"	/* UTF-8. */
+
+#define CH_DEGREE " "
+
+
+#else
+
+/* Maybe we could change this based on LANG environment variable. */
+
+//#define CH_DEGREE "\xc2\xb0"	/* UTF-8. */
+
+#define CH_DEGREE " "
+
+#endif
+
+/* demod_9600.h */
+
+
+void demod_9600_init(enum modem_t modem_type, int original_sample_rate, int upsample, int baud, struct demodulator_state_s *D);
+
+void demod_9600_process_sample(int chan, int sam, int upsample, struct demodulator_state_s *D);
+
+
+/* Undo data scrambling for 9600 baud. */
+
+static inline int descramble(int in, int *state)
+{
+	int out;
+
+	out = (in ^ (*state >> 16) ^ (*state >> 11)) & 1;
+	*state = (*state << 1) | (in & 1);
+	return (out);
+}
diff --git a/dwgps.h b/dwgps.h
new file mode 100644
index 0000000..78f821f
--- /dev/null
+++ b/dwgps.h
@@ -0,0 +1,61 @@
+
+/* dwgps.h */
+
+#ifndef DWGPS_H
+#define DWGPS_H 1
+
+
+#include <time.h>
+#include "config.h"	/* for struct misc_config_s */
+
+
+/*
+ * Values for fix, equivalent to values from libgps.
+ *	-2 = not initialized.
+ *	-1 = error communicating with GPS receiver.
+ *	0 = nothing heard yet.
+ *	1 = had signal but lost it.
+ *	2 = 2D.
+ *	3 = 3D.
+ *
+ * Undefined float & double values are set to G_UNKNOWN.
+ *
+ */
+
+enum dwfix_e { DWFIX_NOT_INIT= -2, DWFIX_ERROR= -1, DWFIX_NOT_SEEN=0, DWFIX_NO_FIX=1, DWFIX_2D=2, DWFIX_3D=3 };
+
+typedef enum dwfix_e dwfix_t;
+
+typedef struct dwgps_info_s {
+	time_t timestamp;	/* When last updated.  System time. */
+	dwfix_t fix;		/* Quality of position fix. */
+	double dlat;		/* Latitude.  Valid if fix >= 2. */
+	double dlon;		/* Longitude. Valid if fix >= 2. */
+	float speed_knots;	/* libgps uses meters/sec but we use GPS usual knots. */
+	float track;		/* What is difference between track and course? */
+	float altitude;		/* meters above mean sea level. Valid if fix == 3. */
+} dwgps_info_t;
+
+
+
+
+
+void dwgps_init (struct misc_config_s *pconfig, int debug);
+
+void dwgps_clear (dwgps_info_t *gpsinfo);
+
+dwfix_t dwgps_read (dwgps_info_t *gpsinfo);
+
+void dwgps_print (char *msg, dwgps_info_t *gpsinfo);
+
+void dwgps_term (void);
+
+void dwgps_set_data (dwgps_info_t *gpsinfo);
+
+
+#endif /* DWGPS_H 1 */
+
+/* end dwgps.h */
+
+
+
diff --git a/dwgpsd.h b/dwgpsd.h
new file mode 100644
index 0000000..4c0e0fd
--- /dev/null
+++ b/dwgpsd.h
@@ -0,0 +1,22 @@
+
+/* dwgpsd.h   -   For communicating with daemon */
+
+
+
+#ifndef DWGPSD_H
+#define DWGPSD_H 1
+
+#include "config.h"
+
+
+int dwgpsd_init (struct misc_config_s *pconfig, int debug);
+
+void dwgpsd_term (void);
+
+#endif
+
+
+/* end dwgpsd.h */
+
+
+
diff --git a/dwgpsnmea.h b/dwgpsnmea.h
new file mode 100644
index 0000000..ffe5a12
--- /dev/null
+++ b/dwgpsnmea.h
@@ -0,0 +1,32 @@
+
+/* dwgpsnmea.h   -   For reading NMEA sentences over serial port */
+
+
+
+#ifndef DWGPSNMEA_H
+#define DWGPSNMEA_H 1
+
+#include "dwgps.h"		/* for dwfix_t */
+#include "config.h"
+#include "serial_port.h"	/* for MYFDTYPE */
+
+
+int dwgpsnmea_init (struct misc_config_s *pconfig, int debug);
+
+MYFDTYPE dwgpsnmea_get_fd(char *wp_port_name, int speed);
+
+void dwgpsnmea_term (void);
+
+
+dwfix_t dwgpsnmea_gprmc (char *sentence, int quiet, double *odlat, double *odlon, float *oknots, float *ocourse);
+
+dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon, float *oalt, int *onsat);
+
+
+#endif
+
+
+/* end dwgpsnmea.h */
+
+
+
diff --git a/dwsock.h b/dwsock.h
new file mode 100644
index 0000000..986f6a2
--- /dev/null
+++ b/dwsock.h
@@ -0,0 +1,21 @@
+
+/* dwsock.h - Socket helper functions. */
+
+#ifndef DWSOCK_H
+#define DWSOCK_H 1
+
+#define DWSOCK_IPADDR_LEN 48		// Size of string to hold IPv4 or IPv6 address.
+					// I think 40 would be adequate but we'll make
+					// it a little larger just to be safe.
+					// Use INET6_ADDRSTRLEN (from netinet/in.h) instead?
+
+int dwsock_init (void);
+
+int dwsock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char ipaddr_str[DWSOCK_IPADDR_LEN]);
+								/* ipaddr_str needs to be at least SOCK_IPADDR_LEN bytes */
+
+char *dwsock_ia_to_text (int  Family, void * pAddr, char * pStringBuf, size_t StringBufSize);
+
+void dwsock_close (int fd);
+
+#endif
\ No newline at end of file
diff --git a/encode_aprs.h b/encode_aprs.h
new file mode 100644
index 0000000..dc7b8bd
--- /dev/null
+++ b/encode_aprs.h
@@ -0,0 +1,17 @@
+
+int encode_position (int messaging, int compressed, double lat, double lon, int ambiguity, int alt_ft,
+		char symtab, char symbol, 
+		int power, int height, int gain, char *dir,
+		int course, int speed_knots,
+		float freq, float tone, float offset,
+		char *comment,
+		char *presult, size_t result_size);
+
+int encode_object (char *name, int compressed, time_t thyme, double lat, double lon, int ambiguity,
+		char symtab, char symbol, 
+		int power, int height, int gain, char *dir,
+		int course, int speed_knots,
+		float freq, float tone, float offset, char *comment,
+		char *presult, size_t result_size);
+
+int encode_message (char *addressee, char *text, char *id, char *presult, size_t result_size);
diff --git a/fcs_calc.h b/fcs_calc.h
new file mode 100644
index 0000000..2e2b0ef
--- /dev/null
+++ b/fcs_calc.h
@@ -0,0 +1,11 @@
+
+/* fcs_calc.h */
+
+
+unsigned short fcs_calc (unsigned char *data, int len);
+
+unsigned short crc16 (unsigned char *data, int len, unsigned short seed);
+
+/* end fcs_calc.h */
+
+
diff --git a/fsk_filters.h b/fsk_filters.h
new file mode 100644
index 0000000..81c4e9a
--- /dev/null
+++ b/fsk_filters.h
@@ -0,0 +1,7 @@
+/* 1200 bits/sec with Audio sample rate = 11025 */
+/* Mark freq = 1200, Space freq = 2200 */
+
+static const signed short m_sin_table[9] = { 0 , 7347 , 11257 , 9899 , 3909 , -3909 , -9899 , -11257 , -7347  };
+static const signed short m_cos_table[9] = { 11431 , 8756 , 1984 , -5715 , -10741 , -10741 , -5715 , 1984 , 8756  };
+static const signed short s_sin_table[9] = { 0 , 10950 , 6281 , -7347 , -10496 , 1327 , 11257 , 5130 , -8314  };
+static const signed short s_cos_table[9] = { 11431 , 3278 , -9550 , -8756 , 4527 , 11353 , 1984 , -10215 , -7844  };
diff --git a/fsk_gen_filter.h b/fsk_gen_filter.h
new file mode 100644
index 0000000..e7e8fa6
--- /dev/null
+++ b/fsk_gen_filter.h
@@ -0,0 +1,15 @@
+
+
+#ifndef FSK_GEN_FILTER_H
+#define FSK_GEN_FILTER_H 1
+
+#include "audio.h"
+#include "fsk_demod_state.h"
+
+void fsk_gen_filter (int samples_per_sec, 
+			int baud, 
+			int mark_freq, int space_freq, 
+			char profile,
+			struct demodulator_state_s *D);
+
+#endif
\ No newline at end of file
diff --git a/gen_tone.h b/gen_tone.h
new file mode 100644
index 0000000..bbe23b5
--- /dev/null
+++ b/gen_tone.h
@@ -0,0 +1,17 @@
+/*
+ * gen_tone.h
+ */
+
+
+int gen_tone_init (struct audio_s *pp, int amp, int gen_packets);
+
+
+//int gen_tone_open (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, char *fname);
+
+//int gen_tone_open_fd (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, int fd)  ;
+
+//int gen_tone_close (void);
+
+void tone_gen_put_bit (int chan, int dat);
+
+void gen_tone_put_sample (int chan, int a, int sam);
\ No newline at end of file
diff --git a/grm_sym.h b/grm_sym.h
new file mode 100644
index 0000000..7a15041
--- /dev/null
+++ b/grm_sym.h
@@ -0,0 +1,501 @@
+
+/*
+ * grm_sym.h
+ *
+ * Symbol codes for use in $PGRMWPL sentence. 
+ *
+ * Copied from 
+ *	Garmin Device Interface Specification
+ *	May 19, 2006
+ *	Drawing Number: 001-00063-00 Rev. C
+ */
+
+
+typedef unsigned short symbol_type_t;
+
+enum symbol_type_e
+{
+/*---------------------------------------------------------------
+Marine symbols
+---------------------------------------------------------------*/
+sym_anchor = 0, /* white anchor symbol */
+sym_bell = 1, /* white bell symbol */
+sym_diamond_grn = 2, /* green diamond symbol */
+sym_diamond_red = 3, /* red diamond symbol */
+sym_dive1 = 4, /* diver down flag 1 */
+sym_dive2 = 5, /* diver down flag 2 */
+sym_dollar = 6, /* white dollar symbol */
+sym_fish = 7, /* white fish symbol */
+sym_fuel = 8, /* white fuel symbol */
+sym_horn = 9, /* white horn symbol */
+sym_house = 10, /* white house symbol */
+sym_knife = 11, /* white knife & fork symbol */
+sym_light = 12, /* white light symbol */
+sym_mug = 13, /* white mug symbol */
+sym_skull = 14, /* white skull and crossbones symbol*/
+sym_square_grn = 15, /* green square symbol */
+sym_square_red = 16, /* red square symbol */
+sym_wbuoy = 17, /* white buoy waypoint symbol */
+sym_wpt_dot = 18, /* waypoint dot */
+sym_wreck = 19, /* white wreck symbol */
+sym_null = 20, /* null symbol (transparent) */
+sym_mob = 21, /* man overboard symbol */
+sym_buoy_ambr = 22, /* amber map buoy symbol */
+sym_buoy_blck = 23, /* black map buoy symbol */
+sym_buoy_blue = 24, /* blue map buoy symbol */
+sym_buoy_grn = 25, /* green map buoy symbol */
+sym_buoy_grn_red = 26, /* green/red map buoy symbol */
+sym_buoy_grn_wht = 27, /* green/white map buoy symbol */
+sym_buoy_orng = 28, /* orange map buoy symbol */
+sym_buoy_red = 29, /* red map buoy symbol */
+sym_buoy_red_grn = 30, /* red/green map buoy symbol */
+sym_buoy_red_wht = 31, /* red/white map buoy symbol */
+sym_buoy_violet = 32, /* violet map buoy symbol */
+sym_buoy_wht = 33, /* white map buoy symbol */
+sym_buoy_wht_grn = 34, /* white/green map buoy symbol */
+sym_buoy_wht_red = 35, /* white/red map buoy symbol */
+sym_dot = 36, /* white dot symbol */
+sym_rbcn = 37, /* radio beacon symbol */
+sym_boat_ramp = 150, /* boat ramp symbol */
+sym_camp = 151, /* campground symbol */
+sym_restrooms = 152, /* restrooms symbol */
+sym_showers = 153, /* shower symbol */
+sym_drinking_wtr = 154, /* drinking water symbol */
+sym_phone = 155, /* telephone symbol */
+sym_1st_aid = 156, /* first aid symbol */
+sym_info = 157, /* information symbol */
+sym_parking = 158, /* parking symbol */
+sym_park = 159, /* park symbol */
+sym_picnic = 160, /* picnic symbol */
+sym_scenic = 161, /* scenic area symbol */
+sym_skiing = 162, /* skiing symbol */
+sym_swimming = 163, /* swimming symbol */
+sym_dam = 164, /* dam symbol */
+sym_controlled = 165, /* controlled area symbol */
+sym_danger = 166, /* danger symbol */
+sym_restricted = 167, /* restricted area symbol */
+sym_null_2 = 168, /* null symbol */
+sym_ball = 169, /* ball symbol */
+sym_car = 170, /* car symbol */
+sym_deer = 171, /* deer symbol */
+sym_shpng_cart = 172, /* shopping cart symbol */
+sym_lodging = 173, /* lodging symbol */
+sym_mine = 174, /* mine symbol */
+sym_trail_head = 175, /* trail head symbol */
+sym_truck_stop = 176, /* truck stop symbol */
+sym_user_exit = 177, /* user exit symbol */
+sym_flag = 178, /* flag symbol */
+sym_circle_x = 179, /* circle with x in the center */
+sym_open_24hr = 180, /* open 24 hours symbol */
+sym_fhs_facility = 181, /* U Fishing Hot Spots(tm) Facility */
+sym_bot_cond = 182, /* Bottom Conditions */
+sym_tide_pred_stn = 183, /* Tide/Current Prediction Station */
+sym_anchor_prohib = 184, /* U anchor prohibited symbol */
+sym_beacon = 185, /* U beacon symbol */
+sym_coast_guard = 186, /* U coast guard symbol */
+sym_reef = 187, /* U reef symbol */
+sym_weedbed = 188, /* U weedbed symbol */
+sym_dropoff = 189, /* U dropoff symbol */
+sym_dock = 190, /* U dock symbol */
+sym_marina = 191, /* U marina symbol */
+sym_bait_tackle = 192, /* U bait and tackle symbol */
+sym_stump = 193, /* U stump symbol */
+/*---------------------------------------------------------------
+User customizable symbols
+The values from sym_begin_custom to sym_end_custom inclusive are
+reserved for the identification of user customizable symbols.
+---------------------------------------------------------------*/
+sym_begin_custom = 7680, /* first user customizable symbol */
+sym_end_custom = 8191, /* last user customizable symbol */
+/*---------------------------------------------------------------
+Land symbols
+---------------------------------------------------------------*/
+sym_is_hwy = 8192, /* interstate hwy symbol */
+sym_us_hwy = 8193, /* us hwy symbol */
+sym_st_hwy = 8194, /* state hwy symbol */
+sym_mi_mrkr = 8195, /* mile marker symbol */
+sym_trcbck = 8196, /* TracBack (feet) symbol */
+sym_golf = 8197, /* golf symbol */
+sym_sml_cty = 8198, /* small city symbol */
+sym_med_cty = 8199, /* medium city symbol */
+sym_lrg_cty = 8200, /* large city symbol */
+sym_freeway = 8201, /* intl freeway hwy symbol */
+sym_ntl_hwy = 8202, /* intl national hwy symbol */
+sym_cap_cty = 8203, /* capitol city symbol (star) */
+sym_amuse_pk = 8204, /* amusement park symbol */
+sym_bowling = 8205, /* bowling symbol */
+sym_car_rental = 8206, /* car rental symbol */
+sym_car_repair = 8207, /* car repair symbol */
+sym_fastfood = 8208, /* fast food symbol */
+sym_fitness = 8209, /* fitness symbol */
+sym_movie = 8210, /* movie symbol */
+sym_museum = 8211, /* museum symbol */
+sym_pharmacy = 8212, /* pharmacy symbol */
+sym_pizza = 8213, /* pizza symbol */
+sym_post_ofc = 8214, /* post office symbol */
+sym_rv_park = 8215, /* RV park symbol */
+sym_school = 8216, /* school symbol */
+sym_stadium = 8217, /* stadium symbol */
+sym_store = 8218, /* dept. store symbol */
+sym_zoo = 8219, /* zoo symbol */
+sym_gas_plus = 8220, /* convenience store symbol */
+sym_faces = 8221, /* live theater symbol */
+sym_ramp_int = 8222, /* ramp intersection symbol */
+sym_st_int = 8223, /* street intersection symbol */
+sym_weigh_sttn = 8226, /* inspection/weigh station symbol */
+sym_toll_booth = 8227, /* toll booth symbol */
+sym_elev_pt = 8228, /* elevation point symbol */
+sym_ex_no_srvc = 8229, /* exit without services symbol */
+sym_geo_place_mm = 8230, /* Geographic place name, man-made */
+sym_geo_place_wtr = 8231, /* Geographic place name, water */
+sym_geo_place_lnd = 8232, /* Geographic place name, land */
+sym_bridge = 8233, /* bridge symbol */
+sym_building = 8234, /* building symbol */
+sym_cemetery = 8235, /* cemetery symbol */
+sym_church = 8236, /* church symbol */
+sym_civil = 8237, /* civil location symbol */
+sym_crossing = 8238, /* crossing symbol */
+sym_hist_town = 8239, /* historical town symbol */
+sym_levee = 8240, /* levee symbol */
+sym_military = 8241, /* military location symbol */
+sym_oil_field = 8242, /* oil field symbol */
+sym_tunnel = 8243, /* tunnel symbol */
+sym_beach = 8244, /* beach symbol */
+sym_forest = 8245, /* forest symbol */
+sym_summit = 8246, /* summit symbol */
+sym_lrg_ramp_int = 8247, /* large ramp intersection symbol */
+sym_lrg_ex_no_srvc = 8248, /* large exit without services smbl */
+sym_badge = 8249, /* police/official badge symbol */
+sym_cards = 8250, /* gambling/casino symbol */
+sym_snowski = 8251, /* snow skiing symbol */
+sym_iceskate = 8252, /* ice skating symbol */
+sym_wrecker = 8253, /* tow truck (wrecker) symbol */
+sym_border = 8254, /* border crossing (port of entry) */
+sym_geocache = 8255, /* geocache location */
+sym_geocache_fnd = 8256, /* found geocache */
+sym_cntct_smiley = 8257, /* Rino contact symbol, "smiley" */
+sym_cntct_ball_cap = 8258, /* Rino contact symbol, "ball cap" */
+sym_cntct_big_ears = 8259, /* Rino contact symbol, "big ear" */
+sym_cntct_spike = 8260, /* Rino contact symbol, "spike" */
+sym_cntct_goatee = 8261, /* Rino contact symbol, "goatee" */
+sym_cntct_afro = 8262, /* Rino contact symbol, "afro" */
+sym_cntct_dreads = 8263, /* Rino contact symbol, "dreads" */
+sym_cntct_female1 = 8264, /* Rino contact symbol, "female 1" */
+sym_cntct_female2 = 8265, /* Rino contact symbol, "female 2" */
+sym_cntct_female3 = 8266, /* Rino contact symbol, "female 3" */
+sym_cntct_ranger = 8267, /* Rino contact symbol, "ranger" */
+sym_cntct_kung_fu = 8268, /* Rino contact symbol, "kung fu" */
+sym_cntct_sumo = 8269, /* Rino contact symbol, "sumo" */
+sym_cntct_pirate = 8270, /* Rino contact symbol, "pirate" */
+sym_cntct_biker = 8271, /* Rino contact symbol, "biker" */
+sym_cntct_alien = 8272, /* Rino contact symbol, "alien" */
+sym_cntct_bug = 8273, /* Rino contact symbol, "bug" */
+sym_cntct_cat = 8274, /* Rino contact symbol, "cat" */
+sym_cntct_dog = 8275, /* Rino contact symbol, "dog" */
+sym_cntct_pig = 8276, /* Rino contact symbol, "pig" */
+sym_hydrant = 8282, /* water hydrant symbol */
+sym_flag_blue = 8284, /* blue flag symbol */
+sym_flag_green = 8285, /* green flag symbol */
+sym_flag_red = 8286, /* red flag symbol */
+sym_pin_blue = 8287, /* blue pin symbol */
+sym_pin_green = 8288, /* green pin symbol */
+sym_pin_red = 8289, /* red pin symbol */
+sym_block_blue = 8290, /* blue block symbol */
+sym_block_green = 8291, /* green block symbol */
+sym_block_red = 8292, /* red block symbol */
+sym_bike_trail = 8293, /* bike trail symbol */
+sym_circle_red = 8294, /* red circle symbol */
+sym_circle_green = 8295, /* green circle symbol */
+sym_circle_blue = 8296, /* blue circle symbol */
+sym_diamond_blue = 8299, /* blue diamond symbol */
+sym_oval_red = 8300, /* red oval symbol */
+sym_oval_green = 8301, /* green oval symbol */
+sym_oval_blue = 8302, /* blue oval symbol */
+sym_rect_red = 8303, /* red rectangle symbol */
+sym_rect_green = 8304, /* green rectangle symbol */
+sym_rect_blue = 8305, /* blue rectangle symbol */
+sym_square_blue = 8308, /* blue square symbol */
+sym_letter_a_red = 8309, /* red letter 'A' symbol */
+sym_letter_b_red = 8310, /* red letter 'B' symbol */
+sym_letter_c_red = 8311, /* red letter 'C' symbol */
+sym_letter_d_red = 8312, /* red letter 'D' symbol */
+sym_letter_a_green = 8313, /* green letter 'A' symbol */
+sym_letter_c_green = 8314, /* green letter 'C' symbol */
+sym_letter_b_green = 8315, /* green letter 'B' symbol */
+sym_letter_d_green = 8316, /* green letter 'D' symbol */
+sym_letter_a_blue = 8317, /* blue letter 'A' symbol */
+sym_letter_b_blue = 8318, /* blue letter 'B' symbol */
+sym_letter_c_blue = 8319, /* blue letter 'C' symbol */
+sym_letter_d_blue = 8320, /* blue letter 'D' symbol */
+sym_number_0_red = 8321, /* red number '0' symbol */
+sym_number_1_red = 8322, /* red number '1' symbol */
+sym_number_2_red = 8323, /* red number '2' symbol */
+sym_number_3_red = 8324, /* red number '3' symbol */
+sym_number_4_red = 8325, /* red number '4' symbol */
+sym_number_5_red = 8326, /* red number '5' symbol */
+sym_number_6_red = 8327, /* red number '6' symbol */
+sym_number_7_red = 8328, /* red number '7' symbol */
+sym_number_8_red = 8329, /* red number '8' symbol */
+sym_number_9_red = 8330, /* red number '9' symbol */
+sym_number_0_green = 8331, /* green number '0' symbol */
+sym_number_1_green = 8332, /* green number '1' symbol */
+sym_number_2_green = 8333, /* green number '2' symbol */
+sym_number_3_green = 8334, /* green number '3' symbol */
+sym_number_4_green = 8335, /* green number '4' symbol */
+sym_number_5_green = 8336, /* green number '5' symbol */
+sym_number_6_green = 8337, /* green number '6' symbol */
+sym_number_7_green = 8338, /* green number '7' symbol */
+sym_number_8_green = 8339, /* green number '8' symbol */
+sym_number_9_green = 8340, /* green number '9' symbol */
+sym_number_0_blue = 8341, /* blue number '0' symbol */
+sym_number_1_blue = 8342, /* blue number '1' symbol */
+sym_number_2_blue = 8343, /* blue number '2' symbol */
+sym_number_3_blue = 8344, /* blue number '3' symbol */
+sym_number_4_blue = 8345, /* blue number '4' symbol */
+sym_number_5_blue = 8346, /* blue number '5' symbol */
+sym_number_6_blue = 8347, /* blue number '6' symbol */
+sym_number_7_blue = 8348, /* blue number '7' symbol */
+sym_number_8_blue = 8349, /* blue number '8' symbol */
+sym_number_9_blue = 8350, /* blue number '9' symbol */
+sym_triangle_blue = 8351, /* blue triangle symbol */
+sym_triangle_green = 8352, /* green triangle symbol */
+sym_triangle_red = 8353, /* red triangle symbol */
+sym_food_asian = 8359, /* asian food symbol */
+sym_food_deli = 8360, /* deli symbol */
+sym_food_italian = 8361, /* italian food symbol */
+sym_food_seafood = 8362, /* seafood symbol */
+sym_food_steak = 8363, /* steak symbol */
+/*---------------------------------------------------------------
+Aviation symbols
+---------------------------------------------------------------*/
+sym_airport = 16384, /* airport symbol */
+sym_int = 16385, /* intersection symbol */
+sym_ndb = 16386, /* non-directional beacon symbol */
+sym_vor = 16387, /* VHF omni-range symbol */
+sym_heliport = 16388, /* heliport symbol */
+sym_private = 16389, /* private field symbol */
+sym_soft_fld = 16390, /* soft field symbol */
+sym_tall_tower = 16391, /* tall tower symbol */
+sym_short_tower = 16392, /* short tower symbol */
+sym_glider = 16393, /* glider symbol */
+sym_ultralight = 16394, /* ultralight symbol */
+sym_parachute = 16395, /* parachute symbol */
+sym_vortac = 16396, /* VOR/TACAN symbol */
+sym_vordme = 16397, /* VOR-DME symbol */
+sym_faf = 16398, /* first approach fix */
+sym_lom = 16399, /* localizer outer marker */
+sym_map = 16400, /* missed approach point */
+sym_tacan = 16401, /* TACAN symbol */
+sym_seaplane = 16402, /* Seaplane Base */
+};
+
+
+
+/*
+ * Mapping from APRS symbols to Garmin.
+ */
+
+// TODO:  NEEDS MORE WORK!!!
+
+
+#define SYMTAB_SIZE 95
+
+#define sym_default sym_diamond_grn
+
+
+static const symbol_type_t grm_primary_symtab[SYMTAB_SIZE] =  {
+
+	sym_default,		//     00  	 --no-symbol--
+	sym_cntct_ranger,	//  !  01  	 Police, Sheriff
+	sym_default,		//  "  02  	 reserved  (was rain)
+	sym_rbcn,		//  #  03  	 DIGI (white center)
+	sym_phone,		//  $  04  	 PHONE
+	sym_rbcn,		//  %  05  	 DX CLUSTER
+	sym_rbcn,		//  &  06  	 HF GATEway
+	sym_glider,		//  '  07  	 Small AIRCRAFT
+	sym_rbcn,		//  (  08  	 Mobile Satellite Station
+	sym_default,		//  )  09  	 Wheelchair (handicapped)
+	sym_car,		//  *  10  	 SnowMobile
+	sym_1st_aid,		//  +  11  	 Red Cross
+	sym_cntct_ball_cap,	//  ,  12  	 Boy Scouts
+	sym_house,		//  -  13  	 House QTH (VHF)
+	sym_default,		//  .  14  	 X
+	sym_default,		//  /  15  	 Red Dot
+	sym_default,		//  0  16  	 # circle (obsolete)
+	sym_default,		//  1  17  	 TBD
+	sym_default,		//  2  18  	 TBD
+	sym_default,		//  3  19  	 TBD
+	sym_default,		//  4  20  	 TBD
+	sym_default,		//  5  21  	 TBD
+	sym_default,		//  6  22  	 TBD
+	sym_default,		//  7  23  	 TBD
+	sym_default,		//  8  24  	 TBD
+	sym_default,		//  9  25  	 TBD
+	sym_default,		//  :  26  	 FIRE
+	sym_camp,		//  ;  27  	 Campground (Portable ops)
+	sym_cntct_biker,	//  <  28  	 Motorcycle
+	sym_default,		//  =  29  	 RAILROAD ENGINE
+	sym_car,		//  >  30  	 CAR
+	sym_default,		//  ?  31  	 SERVER for Files
+	sym_default,		//  @  32  	 HC FUTURE predict (dot)
+	sym_1st_aid,		//  A  33  	 Aid Station
+	sym_rbcn,		//  B  34  	 BBS or PBBS
+	sym_boat_ramp,		//  C  35  	 Canoe
+	sym_default,		//  D  36  	 
+	sym_default,		//  E  37  	 EYEBALL (Eye catcher!)
+	sym_default,		//  F  38  	 Farm Vehicle (tractor)
+	sym_default,		//  G  39  	 Grid Square (6 digit)
+	sym_lodging,		//  H  40  	 HOTEL (blue bed symbol)
+	sym_rbcn,		//  I  41  	 TcpIp on air network stn
+	sym_default,		//  J  42  	 
+	sym_school,		//  K  43  	 School
+	sym_default,		//  L  44  	 PC user
+	sym_default,		//  M  45  	 MacAPRS
+	sym_default,		//  N  46  	 NTS Station
+	sym_parachute,		//  O  47  	 BALLOON
+	sym_cntct_ranger,	//  P  48  	 Police
+	sym_default,		//  Q  49  	 TBD
+	sym_rv_park,		//  R  50  	 REC. VEHICLE
+	sym_glider,		//  S  51  	 SHUTTLE
+	sym_default,		//  T  52  	 SSTV
+	sym_car,		//  U  53  	 BUS
+	sym_cntct_biker,	//  V  54  	 ATV
+	sym_default,		//  W  55  	 National WX Service Site
+	sym_default,		//  X  56  	 HELO
+	sym_default,		//  Y  57  	 YACHT (sail)
+	sym_default,		//  Z  58  	 WinAPRS
+	sym_cntct_smiley,	//  [  59  	 Human/Person (HT)
+	sym_triangle_green,	//  \  60  	 TRIANGLE(DF station)
+	sym_default,		//  ]  61  	 MAIL/PostOffice(was PBBS)
+	sym_glider,		//  ^  62  	 LARGE AIRCRAFT
+	sym_default,		//  _  63  	 WEATHER Station (blue)
+	sym_rbcn,		//  `  64  	 Dish Antenna
+	sym_1st_aid,		//  a  65  	 AMBULANCE
+	sym_cntct_biker,	//  b  66  	 BIKE
+	sym_default,		//  c  67  	 Incident Command Post
+	sym_hydrant,		//  d  68  	 Fire dept
+	sym_deer,		//  e  69  	 HORSE (equestrian)
+	sym_hydrant,		//  f  70  	 FIRE TRUCK
+	sym_glider,		//  g  71  	 Glider
+	sym_1st_aid,		//  h  72  	 HOSPITAL
+	sym_default,		//  i  73  	 IOTA (islands on the air)
+	sym_car,		//  j  74  	 JEEP
+	sym_car,		//  k  75  	 TRUCK
+	sym_default,		//  l  76  	 Laptop
+	sym_rbcn,		//  m  77  	 Mic-E Repeater
+	sym_default,		//  n  78  	 Node (black bulls-eye)
+	sym_default,		//  o  79  	 EOC
+	sym_cntct_dog,		//  p  80  	 ROVER (puppy, or dog)
+	sym_default,		//  q  81  	 GRID SQ shown above 128 m
+	sym_rbcn,		//  r  82  	 Repeater
+	sym_default,		//  s  83  	 SHIP (pwr boat)
+	sym_truck_stop,		//  t  84  	 TRUCK STOP
+	sym_truck_stop,		//  u  85  	 TRUCK (18 wheeler)
+	sym_car,		//  v  86  	 VAN
+	sym_drinking_wtr,	//  w  87  	 WATER station
+	sym_default,		//  x  88  	 xAPRS (Unix)
+	sym_tall_tower,		//  y  89  	 YAGI @ QTH
+	sym_default,		//  z  90  	 TBD
+	sym_default,		//  {  91  	 
+	sym_default,		//  |  92  	 TNC Stream Switch
+	sym_default,		//  }  93  	 
+	sym_default };		//  ~  94  	 TNC Stream Switch
+
+static const symbol_type_t grm_alternate_symtab[SYMTAB_SIZE] =  {
+
+	sym_default,		//     00  	 --no-symbol--
+	sym_default,		//  !  01  	 EMERGENCY (!)
+	sym_default,		//  "  02  	 reserved
+	sym_default,		//  #  03  	 OVERLAY DIGI (green star)
+	sym_default,		//  $  04  	 Bank or ATM  (green box)
+	sym_default,		//  %  05  	 Power Plant with overlay
+	sym_rbcn,		//  &  06  	 I=Igte IGate R=RX T=1hopTX 2=2hopTX
+	sym_default,		//  '  07  	 Crash (& now Incident sites)
+	sym_default,		//  (  08  	 CLOUDY (other clouds w ovrly)
+	sym_hydrant,		//  )  09  	 Firenet MEO, MODIS Earth Obs.
+	sym_default,		//  *  10  	 SNOW (& future ovrly codes)
+	sym_default,		//  +  11  	 Church
+	sym_cntct_female1,	//  ,  12  	 Girl Scouts
+	sym_house,		//  -  13  	 House (H=HF) (O = Op Present)
+	sym_default,		//  .  14  	 Ambiguous (Big Question mark)
+	sym_default,		//  /  15  	 Waypoint Destination
+	sym_default,		//  0  16  	 CIRCLE (E/I/W=IRLP/Echolink/WIRES)
+	sym_default,		//  1  17  	 
+	sym_default,		//  2  18  	 
+	sym_default,		//  3  19  	
+	sym_default,		//  4  20  
+	sym_default,		//  5  21 
+	sym_default,		//  6  22
+	sym_default,		//  7  23
+	sym_default,		//  8  24  	 802.11 or other network node
+	sym_default,		//  9  25  	 Gas Station (blue pump)
+	sym_default,		//  :  26  	 Hail (& future ovrly codes)
+	sym_park,		//  ;  27  	 Park/Picnic area
+	sym_default,		//  <  28  	 ADVISORY (one WX flag)
+	sym_rbcn,		//  =  29  	 APRStt Touchtone (DTMF users)
+	sym_car,		//  >  30  	 OVERLAID CAR
+	sym_default,		//  ?  31  	 INFO Kiosk  (Blue box with ?)
+	sym_default,		//  @  32  	 HURRICANE/Trop-Storm
+	sym_default,		//  A  33  	 overlayBOX DTMF & RFID & XO
+	sym_default,		//  B  34  	 Blwng Snow (& future codes)
+	sym_coast_guard,	//  C  35  	 Coast Guard
+	sym_default,		//  D  36  	 Drizzle (proposed APRStt)
+	sym_default,		//  E  37  	 Smoke (& other vis codes)
+	sym_default,		//  F  38  	 Freezng rain (&future codes)
+	sym_default,		//  G  39  	 Snow Shwr (& future ovrlys)
+	sym_default,		//  H  40  	 Haze (& Overlay Hazards)
+	sym_default,		//  I  41  	 Rain Shower
+	sym_default,		//  J  42  	 Lightning (& future ovrlys)
+	sym_rbcn,		//  K  43  	 Kenwood HT (W)
+	sym_light,		//  L  44  	 Lighthouse
+	sym_default,		//  M  45  	 MARS (A=Army,N=Navy,F=AF)
+	sym_default,		//  N  46  	 Navigation Buoy
+	sym_default,		//  O  47  	 Rocket
+	sym_default,		//  P  48  	 Parking
+	sym_default,		//  Q  49  	 QUAKE
+	sym_default,		//  R  50  	 Restaurant
+	sym_rbcn,		//  S  51  	 Satellite/Pacsat
+	sym_default,		//  T  52  	 Thunderstorm
+	sym_default,		//  U  53  	 SUNNY
+	sym_default,		//  V  54  	 VORTAC Nav Aid
+	sym_default,		//  W  55  	 # NWS site (NWS options)
+	sym_pharmacy,		//  X  56  	 Pharmacy Rx (Apothicary)
+	sym_rbcn,		//  Y  57  	 Radios and devices
+	sym_default,		//  Z  58  	 
+	sym_default,		//  [  59  	 W.Cloud (& humans w Ovrly)
+	sym_default,		//  \  60  	 New overlayable GPS symbol
+	sym_default,		//  ]  61  	 
+	sym_glider,		//  ^  62  	 # Aircraft (shows heading)
+	sym_default,		//  _  63  	 # WX site (green digi)
+	sym_default,		//  `  64  	 Rain (all types w ovrly)
+	sym_default,		//  a  65  	 ARRL, ARES, WinLINK
+	sym_default,		//  b  66  	 Blwng Dst/Snd (& others)
+	sym_default,		//  c  67  	 CD triangle RACES/SATERN/etc
+	sym_default,		//  d  68  	 DX spot by callsign
+	sym_default,		//  e  69  	 Sleet (& future ovrly codes)
+	sym_default,		//  f  70  	 Funnel Cloud
+	sym_default,		//  g  71  	 Gale Flags
+	sym_default,		//  h  72  	 Store. or HAMFST Hh=HAM store
+	sym_default,		//  i  73  	 BOX or points of Interest
+	sym_default,		//  j  74  	 WorkZone (Steam Shovel)
+	sym_car,		//  k  75  	 Special Vehicle SUV,ATV,4x4
+	sym_default,		//  l  76  	 Areas      (box,circles,etc)
+	sym_default,		//  m  77  	 Value Sign (3 digit display)
+	sym_default,		//  n  78  	 OVERLAY TRIANGLE
+	sym_default,		//  o  79  	 small circle
+	sym_default,		//  p  80  	 Prtly Cldy (& future ovrlys)
+	sym_default,		//  q  81  	 
+	sym_restrooms,		//  r  82  	 Restrooms
+	sym_default,		//  s  83  	 OVERLAY SHIP/boat (top view)
+	sym_default,		//  t  84  	 Tornado
+	sym_car,		//  u  85  	 OVERLAID TRUCK
+	sym_car,		//  v  86  	 OVERLAID Van
+	sym_default,		//  w  87  	 Flooding
+	sym_wreck,		//  x  88  	 Wreck or Obstruction ->X<-
+	sym_default,		//  y  89  	 Skywarn
+	sym_default,		//  z  90  	 OVERLAID Shelter
+	sym_default,		//  {  91  	 Fog (& future ovrly codes)
+	sym_default,		//  |  92  	 TNC Stream Switch
+	sym_default,		//  }  93  	 
+	sym_default };		//  ~  94  	 TNC Stream Switch
+
diff --git a/hdlc_send.h b/hdlc_send.h
new file mode 100644
index 0000000..4f8a105
--- /dev/null
+++ b/hdlc_send.h
@@ -0,0 +1,17 @@
+
+/* hdlc_send.h */
+
+// In version 1.7 an extra layer of abstraction was added here.
+// Rather than calling hdlc_send_frame, we now use another function
+// which sends AX.25, FX.25, or IL2P depending on
+
+#include "ax25_pad.h"
+#include "audio.h"
+
+int layer2_send_frame (int chan, packet_t pp, int bad_fcs, struct audio_s *audio_config_p);
+
+int layer2_preamble_postamble (int chan, int flags, int finish, struct audio_s *audio_config_p);
+
+/* end hdlc_send.h */
+
+
diff --git a/igate.h b/igate.h
new file mode 100644
index 0000000..8203ac7
--- /dev/null
+++ b/igate.h
@@ -0,0 +1,128 @@
+
+/*----------------------------------------------------------------------------
+ * 
+ * Name:	igate.h
+ *
+ * Purpose:	Interface to the Internet Gateway functions.
+ *
+ *-----------------------------------------------------------------------------*/
+
+
+#ifndef IGATE_H
+#define IGATE_H 1
+
+
+#include "ax25_pad.h"
+#include "digipeater.h"
+#include "audio.h"
+
+
+#define DEFAULT_IGATE_PORT 14580
+
+
+
+struct igate_config_s {
+
+/*
+ * For logging into the IGate server.
+ */
+	char t2_server_name[40];	/* Tier 2 IGate server name. */
+
+	int t2_server_port;		/* Typically 14580. */
+
+	char t2_login[AX25_MAX_ADDR_LEN];/* e.g. WA9XYZ-15 */
+					/* Note that the ssid could be any two alphanumeric */
+					/* characters not just 1 thru 15. */
+					/* Could be same or different than the radio call(s). */
+					/* Not sure what the consequences would be. */
+
+	char t2_passcode[8];		/* Max. 5 digits. Could be "-1". */
+
+	char *t2_filter;		/* Optional filter for IS -> RF direction. */
+					/* This is the "server side" filter. */
+					/* A better name would be subscription or something */
+					/* like that because we can only ask for more. */
+
+/*
+ * For transmitting.
+ */
+	int tx_chan;			/* Radio channel for transmitting. */
+					/* 0=first, etc.  -1 for none. */
+					/* Presently IGate can transmit on only a single channel. */
+					/* A future version might generalize this.  */
+					/* Each transmit channel would have its own client side filtering. */
+
+	char tx_via[80];		/* VIA path for transmitting third party packets. */
+					/* Usual text representation.  */
+					/* Must start with "," if not empty so it can */
+					/* simply be inserted after the destination address. */
+
+	int max_digi_hops;		/* Maximum number of digipeater hops possible for via path. */
+					/* Derived from the SSID when last character of address is a digit. */
+					/* e.g.  "WIDE1-1,WIDE5-2" would be 3. */
+					/* This is useful to know so we can determine how many */
+					/* stations we might be able to reach. */
+
+	int tx_limit_1;			/* Max. packets to transmit in 1 minute. */
+
+	int tx_limit_5;			/* Max. packets to transmit in 5 minutes. */
+
+	int igmsp;			/* Number of message sender position reports to allow. */
+					/* Common practice is to default to 1.  */
+					/* We allow additional flexibility of 0 to disable feature */
+					/* or a small number to allow more. */
+
+/*
+ * Receiver to IS data options.
+ */
+	int rx2ig_dedupe_time;		/* seconds.  0 to disable. */
+
+/*
+ * Special SATgate mode to delay packets heard directly.
+ */
+	int satgate_delay;		/* seconds.  0 to disable. */
+};
+
+
+#define IGATE_TX_LIMIT_1_DEFAULT 6
+#define IGATE_TX_LIMIT_1_MAX     20
+
+#define IGATE_TX_LIMIT_5_DEFAULT 20
+#define IGATE_TX_LIMIT_5_MAX     80
+
+#define IGATE_RX2IG_DEDUPE_TIME 0		/* Issue 85.  0 means disable dupe checking in RF>IS direction. */
+						/* See comments in rx_to_ig_remember & rx_to_ig_allow. */
+						/* Currently there is no configuration setting to change this. */
+
+#define DEFAULT_SATGATE_DELAY 10
+#define MIN_SATGATE_DELAY 5
+#define MAX_SATGATE_DELAY 30
+
+
+/* Call this once at startup */
+
+void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config, int debug_level);
+
+/* Call this with each packet received from the radio. */
+
+void igate_send_rec_packet (int chan, packet_t recv_pp);
+
+/* This when digipeater transmits.  Set bydigi to 1 . */
+
+void ig_to_tx_remember (packet_t pp, int chan, int bydigi);
+
+
+
+/* Get statistics for IGATE status beacon. */
+
+int igate_get_msg_cnt (void);
+
+int igate_get_pkt_cnt (void);
+
+int igate_get_upl_cnt (void);
+
+int igate_get_dnl_cnt (void);
+
+
+
+#endif
diff --git a/il2p.c b/il2p.c
index ec38263..6b3ae52 100644
--- a/il2p.c
+++ b/il2p.c
@@ -48,6 +48,7 @@ along with QtSoundModem.  If not, see http://www.gnu.org/licenses
 #include "UZ7HOStuff.h"
 
 void Debugprintf(const char * format, ...);
+int SMUpdatePhaseConstellation(int chan, float * Phases, float * Mags, int intPSKPhase, int Count);
 
 #define MAX_ADEVS 3			
 
@@ -69,6 +70,10 @@ void Debugprintf(const char * format, ...);
 #define max(x, y) ((x) > (y) ? (x) : (y))
 #define min(x, y) ((x) < (y) ? (x) : (y))
 
+extern int nPhases[4][16][nr_emph + 1];
+extern float Phases[4][16][nr_emph + 1][4096];
+extern float Mags[4][16][nr_emph + 1][4096];
+
 /* For option to try fixing frames with bad CRC. */
 
 typedef enum retry_e {
@@ -99,14 +104,14 @@ float MagOut[4096];
 float MaxMagOut = 0;
 int MaxMagIndex = 0;
 
-// FFT Bin Size is 12000 / 2048
+// FFT Bin Size is 12000 / FFTSize
 
-#define BinSize 5.859375f
 #ifndef FX25_H
 #define FX25_H
 
 #include <stdint.h>	// for uint64_t
 
+extern unsigned int pskStates[4];
 
 /* Reed-Solomon codec control block */
 struct rs {
@@ -643,10 +648,15 @@ float GuessCentreFreq(int i)
 	int n;
 	float Max = 0;
 	int Index = 0;
+	float BinSize = 12000.0 / FFTSize;
 
 	Start = (rx_freq[i] - RCVR[i] * rcvr_offset[i]) / BinSize;
 	End = (rx_freq[i] + RCVR[i] * rcvr_offset[i]) / BinSize;
 
+	Start = (active_rx_freq[i] - RCVR[i] * rcvr_offset[i]) / BinSize;
+	End = (active_rx_freq[i] + RCVR[i] * rcvr_offset[i]) / BinSize;
+
+
 	for (n = Start; n <= End; n++)
 	{
 		if (MagOut[n] > Max)
@@ -698,7 +708,7 @@ packet_t ax25_new(void)
 		 //if (new_count > delete_count + 100) {
 	if (new_count > delete_count + 256) {
 
-		Debugprintf("Report to WB2OSZ - Memory leak for packet objects.  new=%d, delete=%d\n", new_count, delete_count);
+		Debugprintf("Memory leak for packet objects.  new=%d, delete=%d\n", new_count, delete_count);
 #if AX25MEMDEBUG
 #endif
 	}
@@ -1000,7 +1010,8 @@ void multi_modem_process_rec_packet(int snd_ch, int subchan, int slice, packet_t
 
 	struct TDetector_t * pDET = &DET[emph][subchan];
 	string *  data = newString();
-	char Mode[16] = "IL2P";
+	char Mode[32] = "IL2P";
+	int Quality = 0;
 
 	sprintf(Mode, "IL2P %d", centreFreq);
 
@@ -1034,6 +1045,12 @@ void multi_modem_process_rec_packet(int snd_ch, int subchan, int slice, packet_t
 	string * xx = newString();
 	memset(xx->Data, 0, 16);
 
+	if (pskStates[snd_ch])
+	{
+		Quality = SMUpdatePhaseConstellation(snd_ch, &Phases[snd_ch][subchan][slice][0], &Mags[snd_ch][subchan][slice][0], pskStates[snd_ch], nPhases[snd_ch][subchan][slice]);
+		sprintf(Mode, "%s][Q%d", Mode, Quality);
+	}
+
 	Add(&detect_list_c[snd_ch], xx);
 	Add(&detect_list[snd_ch], data);
 
@@ -1041,6 +1058,8 @@ void multi_modem_process_rec_packet(int snd_ch, int subchan, int slice, packet_t
 //		sprintf(Mode, "IP2P-%d", retries);
 
 	stringAdd(xx, Mode, strlen(Mode));
+
+
 	return;
 
 }
@@ -2252,6 +2271,7 @@ int il2p_decode_rs(unsigned char *rec_block, int data_size, int num_parity, unsi
 	int derrlocs[FX25_MAX_CHECK];	// Half would probably be OK.
 
 	int derrors = DECODE_RS(il2p_find_rs(num_parity), rs_block, derrlocs, 0);
+
 	memcpy(out, rs_block + sizeof(rs_block) - n, data_size);
 
 	if (il2p_get_debug() >= 3) {
@@ -2749,7 +2769,6 @@ void fx_hex_dump(unsigned char *p, int len)
 
 int il2p_encode_frame(packet_t pp, int max_fec, unsigned char *iout)
 {
-
 	// Can a type 1 header be used?
 
 	unsigned char hdr[IL2P_HEADER_SIZE + IL2P_HEADER_PARITY];
@@ -2768,6 +2787,7 @@ int il2p_encode_frame(packet_t pp, int max_fec, unsigned char *iout)
 		}
 
 		// Payload is AX.25 info part.
+
 		unsigned char *pinfo;
 		int info_len;
 		info_len = ax25_get_info(pp, &pinfo);
@@ -3579,6 +3599,12 @@ int il2p_clarify_header(unsigned char *rec_hdr, unsigned char *corrected_descram
 
 	int e = il2p_decode_rs(rec_hdr, IL2P_HEADER_SIZE, IL2P_HEADER_PARITY, corrected);
 
+	if (e > 1)		// only have 2 rs bytes so can only detect 1 error
+	{
+		Debugprintf("Header correction seems ok but errors > 1");
+		return -1;
+	}
+
 	il2p_descramble_block(corrected, corrected_descrambled_hdr, IL2P_HEADER_SIZE);
 
 	return (e);
@@ -3853,38 +3879,7 @@ int il2p_decode_payload(unsigned char *received, int payload_size, int max_fec,
 
 // end il2p_payload.c
 
-
-
-struct il2p_context_s {
-
-	enum { IL2P_SEARCHING = 0, IL2P_HEADER, IL2P_PAYLOAD, IL2P_DECODE } state;
-
-	unsigned int acc;	// Accumulate most recent 24 bits for sync word matching.
-				// Lower 8 bits are also used for accumulating bytes for
-				// the header and payload.
-
-	int bc;			// Bit counter so we know when a complete byte has been accumulated.
-
-	int polarity;		// 1 if opposite of expected polarity.
-
-	unsigned char shdr[IL2P_HEADER_SIZE + IL2P_HEADER_PARITY];
-	// Scrambled header as received over the radio.  Includes parity.
-	int hc;			// Number if bytes placed in above.
-
-	unsigned char uhdr[IL2P_HEADER_SIZE];  // Header after FEC and unscrambling.
-
-	int eplen;		// Encoded payload length.  This is not the nuumber from
-				// from the header but rather the number of encoded bytes to gather.
-
-	unsigned char spayload[IL2P_MAX_ENCODED_PAYLOAD_SIZE];
-	// Scrambled and encoded payload as received over the radio.
-	int pc;			// Number of bytes placed in above.
-
-	int corrected;		// Number of symbols corrected by RS FEC.
-};
-
-static struct il2p_context_s *il2p_context[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS];
-
+struct il2p_context_s *il2p_context[4][16][3];
 
 
 /***********************************************************************************
@@ -3944,7 +3939,8 @@ void il2p_rec_bit(int chan, int subchan, int slice, int dbit)
 			F->state = IL2P_HEADER;
 			F->bc = 0;
 			F->hc = 0;
-		
+			nPhases[chan][subchan][slice] = 0;
+
 			// Determine Centre Freq
 			
 			centreFreq[chan] = GuessCentreFreq(chan);
@@ -3958,6 +3954,7 @@ void il2p_rec_bit(int chan, int subchan, int slice, int dbit)
 			F->bc = 0;
 			F->hc = 0;
 			centreFreq[chan] = GuessCentreFreq(chan);
+			nPhases[chan][subchan][slice] = 0;
 		}
 			
 		break;
@@ -4001,6 +3998,12 @@ void il2p_rec_bit(int chan, int subchan, int slice, int dbit)
 							plprop.large_block_count, plprop.large_block_size, plprop.parity_symbols_per_block);
 					}
 
+					if (len > 340)
+					{
+						Debugprintf("Packet too big for QtSM");
+		//				F->state = IL2P_SEARCHING;
+		//				return;
+					}
 					if (F->eplen >= 1) {		// Need to gather payload.
 						F->pc = 0;
 						F->state = IL2P_PAYLOAD;
@@ -4273,12 +4276,12 @@ string * il2p_send_frame(int chan, packet_t pp, int max_fec, int polarity)
 	int preamblecount;
 	unsigned char preamble[1024];
 
-
 	encoded[0] = (IL2P_SYNC_WORD >> 16) & 0xff;
 	encoded[1] = (IL2P_SYNC_WORD >> 8) & 0xff;
 	encoded[2] = (IL2P_SYNC_WORD) & 0xff;
 
 	int elen = il2p_encode_frame(pp, max_fec, encoded + IL2P_SYNC_WORD_SIZE);
+
 	if (elen <= 0) {
 		Debugprintf("IL2P: Unable to encode frame into IL2P.\n");
 		return (packet);
@@ -4297,14 +4300,27 @@ string * il2p_send_frame(int chan, packet_t pp, int max_fec, int polarity)
 
 	// Try using preaamble for txdelay
 
-	preamblecount = (txdelay[chan] * tx_baudrate[chan]) / 8000;		// 8 for bits, 1000 for mS
+	// Nino now uses 00 as preamble for QPSK
 
-	if (preamblecount > 1024)
-		preamblecount = 1024;
+	// We don't need txdelay between frames in one transmission
 
- 	memset(preamble, IL2P_PREAMBLE, preamblecount);
 
-	stringAdd(packet, preamble, preamblecount);
+	if (Continuation[chan] == 0)
+	{
+		preamblecount = (txdelay[chan] * tx_bitrate[chan]) / 8000;		// 8 for bits, 1000 for mS
+
+		if (preamblecount > 1024)
+			preamblecount = 1024;
+
+		if (pskStates[chan])		// PSK Modes
+			memset(preamble, 01, preamblecount);
+		else
+			memset(preamble, IL2P_PREAMBLE, preamblecount);
+
+		stringAdd(packet, preamble, preamblecount);
+		Continuation[chan] = 1;
+	}
+
 	stringAdd(packet, encoded, elen);
 
 	tx_fx25_size[chan] = packet->Length * 8;
@@ -4347,7 +4363,6 @@ string * fill_il2p_data(int snd_ch, string * data)
 	string * result;
 	packet_t pp = ax25_new();
 	
-
 	// Call il2p_send_frame to build the bit stream
 
 	pp->frame_len = data->Length - 2;					// Included CRC
diff --git a/il2p.c.bak b/il2p.c.bak
new file mode 100644
index 0000000..2c81201
--- /dev/null
+++ b/il2p.c.bak
@@ -0,0 +1,4502 @@
+/*
+Copyright (C) 2019-2020 Andrei Kopanchuk UZ7HO
+
+This file is part of QtSoundModem
+
+QtSoundModem is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+QtSoundModem is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with QtSoundModem.  If not, see http://www.gnu.org/licenses
+
+*/
+
+// UZ7HO Soundmodem Port by John Wiseman G8BPQ
+
+// IL2P code. Based on Direwolf code, under the following copyright
+
+//
+//    Copyright (C) 2021  John Langner, WB2OSZ
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU General Public License as published by
+//    the Free Software Foundation, either version 2 of the License, or
+//    (at your option) any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+// IP2P receive code (il2p_rec_bit) is called from the bit receiving code in ax25_demod.c, so includes parallel decoders
+
+
+
+
+#include "UZ7HOStuff.h"
+
+void Debugprintf(const char * format, ...);
+
+#define MAX_ADEVS 3			
+
+#define MAX_RADIO_CHANS ((MAX_ADEVS) * 2)
+
+#define MAX_CHANS MAX_RADIO_CHANS	// TODO: Replace all former  with latter to avoid confusion with following.
+
+#define MAX_TOTAL_CHANS 16		// v1.7 allows additional virtual channels which are connected
+ // to something other than radio modems.
+ // Total maximum channels is based on the 4 bit KISS field.
+ // Someone with very unusual requirements could increase this and
+ // use only the AGW network protocol.
+
+
+#define MAX_SUBCHANS 9
+
+#define MAX_SLICERS 9
+
+#define max(x, y) ((x) > (y) ? (x) : (y))
+#define min(x, y) ((x) < (y) ? (x) : (y))
+
+/* For option to try fixing frames with bad CRC. */
+
+typedef enum retry_e {
+	RETRY_NONE = 0,
+	RETRY_INVERT_SINGLE = 1,
+	RETRY_INVERT_DOUBLE = 2,
+	RETRY_INVERT_TRIPLE = 3,
+	RETRY_INVERT_TWO_SEP = 4,
+	RETRY_MAX = 5
+}  retry_t;
+
+typedef struct alevel_s {
+	int rec;
+	int mark;
+	int space;
+	//float ms_ratio;	// TODO: take out after temporary investigation.
+} alevel_t;
+
+
+alevel_t demod_get_audio_level(int chan, int subchan);
+void tone_gen_put_bit(int chan, int dat);
+
+int ax25memdebug = 1;
+
+// Code to try to determine centre freq
+
+float MagOut[4096];
+float MaxMagOut = 0;
+int MaxMagIndex = 0;
+
+// FFT Bin Size is 12000 / FFTSize
+
+#ifndef FX25_H
+#define FX25_H
+
+#include <stdint.h>	// for uint64_t
+
+
+/* Reed-Solomon codec control block */
+struct rs {
+	unsigned int mm;              /* Bits per symbol */
+	unsigned int nn;              /* Symbols per block (= (1<<mm)-1) */
+	unsigned char *alpha_to;      /* log lookup table */
+	unsigned char *index_of;      /* Antilog lookup table */
+	unsigned char *genpoly;       /* Generator polynomial */
+	unsigned int nroots;     /* Number of generator roots = number of parity symbols */
+	unsigned char fcr;        /* First consecutive root, index form */
+	unsigned char prim;       /* Primitive element, index form */
+	unsigned char iprim;      /* prim-th root of 1, index form */
+};
+
+#define MM (rs->mm)
+#define NN (rs->nn)
+#define ALPHA_TO (rs->alpha_to) 
+#define INDEX_OF (rs->index_of)
+#define GENPOLY (rs->genpoly)
+#define NROOTS (rs->nroots)
+#define FCR (rs->fcr)
+#define PRIM (rs->prim)
+#define IPRIM (rs->iprim)
+#define A0 (NN)
+
+int __builtin_popcountll(unsigned long long int i)
+{
+	return 0;
+}
+
+int __builtin_popcount(unsigned int n)
+{
+	unsigned int count = 0;
+	while (n)
+	{
+		count += n & 1;
+		n >>= 1;
+	}
+	return count;
+}
+
+static inline int modnn(struct rs *rs, int x) {
+	while (x >= rs->nn) {
+		x -= rs->nn;
+		x = (x >> rs->mm) + (x & rs->nn);
+	}
+	return x;
+}
+
+#define MODNN(x) modnn(rs,x)
+
+
+#define ENCODE_RS encode_rs_char
+#define DECODE_RS decode_rs_char
+#define INIT_RS init_rs_char
+#define FREE_RS free_rs_char
+
+#define DTYPE unsigned char
+
+void ENCODE_RS(struct rs *rs, DTYPE *data, DTYPE *bb);
+
+int DECODE_RS(struct rs *rs, DTYPE *data, int *eras_pos, int no_eras);
+
+struct rs *INIT_RS(unsigned int symsize, unsigned int gfpoly,
+	unsigned int fcr, unsigned int prim, unsigned int nroots);
+
+void FREE_RS(struct rs *rs);
+
+
+
+// These 3 are the external interface.
+// Maybe these should be in a different file, separated from the internal stuff.
+
+void fx25_init(int debug_level);
+int fx25_send_frame(int chan, unsigned char *fbuf, int flen, int fx_mode);
+void fx25_rec_bit(int chan, int subchan, int slice, int dbit);
+int fx25_rec_busy(int chan);
+
+
+// Other functions in fx25_init.c.
+
+struct rs *fx25_get_rs(int ctag_num);
+uint64_t fx25_get_ctag_value(int ctag_num);
+int fx25_get_k_data_radio(int ctag_num);
+int fx25_get_k_data_rs(int ctag_num);
+int fx25_get_nroots(int ctag_num);
+int fx25_get_debug(void);
+int fx25_tag_find_match(uint64_t t);
+int fx25_pick_mode(int fx_mode, int dlen);
+
+void fx_hex_dump(unsigned char *x, int len);
+
+/*-------------------------------------------------------------------
+ *
+ * Name:	ax25_pad.h
+ *
+ * Purpose:	Header file for using ax25_pad.c
+ *
+ *------------------------------------------------------------------*/
+
+#ifndef AX25_PAD_H
+#define AX25_PAD_H 1
+
+
+#define AX25_MAX_REPEATERS 8
+#define AX25_MIN_ADDRS 2	/* Destination & Source. */
+#define AX25_MAX_ADDRS 10	/* Destination, Source, 8 digipeaters. */	
+
+#define AX25_DESTINATION  0	/* Address positions in frame. */
+#define AX25_SOURCE       1	
+#define AX25_REPEATER_1   2
+#define AX25_REPEATER_2   3
+#define AX25_REPEATER_3   4
+#define AX25_REPEATER_4   5
+#define AX25_REPEATER_5   6
+#define AX25_REPEATER_6   7
+#define AX25_REPEATER_7   8
+#define AX25_REPEATER_8   9
+
+#define AX25_MAX_ADDR_LEN 12	/* In theory, you would expect the maximum length */
+ /* to be 6 letters, dash, 2 digits, and nul for a */
+ /* total of 10.  However, object labels can be 10 */
+ /* characters so throw in a couple extra bytes */
+ /* to be safe. */
+
+#define AX25_MIN_INFO_LEN 0	/* Previously 1 when considering only APRS. */
+
+#define AX25_MAX_INFO_LEN 2048	/* Maximum size for APRS. */
+				/* AX.25 starts out with 256 as the default max */
+				/* length but the end stations can negotiate */
+				/* something different. */
+				/* version 0.8:  Change from 256 to 2028 to */
+				/* handle the larger paclen for Linux AX25. */
+
+				/* These don't include the 2 bytes for the */
+				/* HDLC frame FCS. */
+
+/*
+ * Previously, for APRS only.
+ * #define AX25_MIN_PACKET_LEN ( 2 * 7 + 2 + AX25_MIN_INFO_LEN)
+ * #define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + AX25_MAX_INFO_LEN)
+ */
+
+ /* The more general case. */
+ /* An AX.25 frame can have a control byte and no protocol. */
+
+#define AX25_MIN_PACKET_LEN ( 2 * 7 + 1 )
+
+#define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + 3 + AX25_MAX_INFO_LEN)
+
+
+/*
+ * packet_t is a pointer to a packet object.
+ *
+ * The actual implementation is not visible outside ax25_pad.c.
+ */
+
+#define AX25_UI_FRAME 3		/* Control field value. */
+
+#define AX25_PID_NO_LAYER_3 0xf0		/* protocol ID used for APRS */
+#define AX25_PID_SEGMENTATION_FRAGMENT 0x08
+#define AX25_PID_ESCAPE_CHARACTER 0xff
+
+struct packet_s {
+
+	int magic1;		/* for error checking. */
+
+	int seq;		/* unique sequence number for debugging. */
+
+	double release_time;	/* Time stamp in format returned by dtime_now(). */
+				/* When to release from the SATgate mode delay queue. */
+
+#define MAGIC 0x41583235
+
+	struct packet_s *nextp;	/* Pointer to next in queue. */
+
+	int num_addr;		/* Number of addresses in frame. */
+				/* Range of AX25_MIN_ADDRS .. AX25_MAX_ADDRS for AX.25. */
+				/* It will be 0 if it doesn't look like AX.25. */
+				/* -1 is used temporarily at allocation to mean */
+				/* not determined yet. */
+
+
+
+				/*
+				 * The 7th octet of each address contains:
+					 *
+				 * Bits:   H  R  R  SSID  0
+				 *
+				 *   H 		for digipeaters set to 0 initially.
+				 *		Changed to 1 when position has been used.
+				 *
+				 *		for source & destination it is called
+				 *		command/response.  Normally both 1 for APRS.
+				 *		They should be opposites for connected mode.
+				 *
+				 *   R	R	Reserved.  Normally set to 1 1.
+				 *
+				 *   SSID	Substation ID.  Range of 0 - 15.
+				 *
+				 *   0		Usually 0 but 1 for last address.
+				 */
+
+
+#define SSID_H_MASK	0x80
+#define SSID_H_SHIFT	7
+
+#define SSID_RR_MASK	0x60
+#define SSID_RR_SHIFT	5
+
+#define SSID_SSID_MASK	0x1e
+#define SSID_SSID_SHIFT	1
+
+#define SSID_LAST_MASK	0x01
+
+
+	int frame_len;		/* Frame length without CRC. */
+
+	int modulo;		/* I & S frames have sequence numbers of either 3 bits (modulo 8) */
+				/* or 7 bits (modulo 128).  This is conveyed by either 1 or 2 */
+				/* control bytes.  Unfortunately, we can't determine this by looking */
+				/* at an isolated frame.  We need to know about the context.  If we */
+				/* are part of the conversation, we would know.  But if we are */
+				/* just listening to others, this would be more difficult to determine. */
+
+				/* For U frames:   	set to 0 - not applicable */
+				/* For I & S frames:	8 or 128 if known.  0 if unknown. */
+
+	unsigned char frame_data[AX25_MAX_PACKET_LEN + 1];
+	/* Raw frame contents, without the CRC. */
+
+
+	int magic2;		/* Will get stomped on if above overflows. */
+};
+
+
+
+typedef struct packet_s *packet_t;
+
+typedef enum cmdres_e { cr_00 = 2, cr_cmd = 1, cr_res = 0, cr_11 = 3 } cmdres_t;
+
+
+extern packet_t ax25_new(void);
+
+
+#ifdef AX25_PAD_C	/* Keep this hidden - implementation could change. */
+
+
+/*
+ * APRS always has one control octet of 0x03 but the more
+ * general AX.25 case is one or two control bytes depending on
+ * whether "modulo 128 operation" is in effect.
+ */
+
+ //#define DEBUGX 1
+
+static inline int ax25_get_control_offset(packet_t this_p)
+{
+	return (this_p->num_addr * 7);
+}
+
+static inline int ax25_get_num_control(packet_t this_p)
+{
+	int c;
+
+	c = this_p->frame_data[ax25_get_control_offset(this_p)];
+
+	if ((c & 0x01) == 0) {			/* I   xxxx xxx0 */
+#if DEBUGX
+		Debugprintf("ax25_get_num_control, %02x is I frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1);
+#endif
+		return ((this_p->modulo == 128) ? 2 : 1);
+	}
+
+	if ((c & 0x03) == 1) {			/* S   xxxx xx01 */
+#if DEBUGX
+		Debugprintf("ax25_get_num_control, %02x is S frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1);
+#endif
+		return ((this_p->modulo == 128) ? 2 : 1);
+	}
+
+#if DEBUGX
+	Debugprintf("ax25_get_num_control, %02x is U frame, always returns 1.\n", c);
+#endif
+
+	return (1);					/* U   xxxx xx11 */
+}
+
+
+
+/*
+ * APRS always has one protocol octet of 0xF0 meaning no level 3
+ * protocol but the more general case is 0, 1 or 2 protocol ID octets.
+ */
+
+static inline int ax25_get_pid_offset(packet_t this_p)
+{
+	return (ax25_get_control_offset(this_p) + ax25_get_num_control(this_p));
+}
+
+static int ax25_get_num_pid(packet_t this_p)
+{
+	int c;
+	int pid;
+
+	c = this_p->frame_data[ax25_get_control_offset(this_p)];
+
+	if ((c & 0x01) == 0 ||				/* I   xxxx xxx0 */
+		c == 0x03 || c == 0x13) {			/* UI  000x 0011 */
+
+		pid = this_p->frame_data[ax25_get_pid_offset(this_p)];
+#if DEBUGX
+		Debugprintf("ax25_get_num_pid, %02x is I or UI frame, pid = %02x, returns %d\n", c, pid, (pid == AX25_PID_ESCAPE_CHARACTER) ? 2 : 1);
+#endif
+		if (pid == AX25_PID_ESCAPE_CHARACTER) {
+			return (2);			/* pid 1111 1111 means another follows. */
+		}
+		return (1);
+	}
+#if DEBUGX
+	Debugprintf("ax25_get_num_pid, %02x is neither I nor UI frame, returns 0\n", c);
+#endif
+	return (0);
+}
+
+
+/*
+ * AX.25 has info field for 5 frame types depending on the control field.
+ *
+ *	xxxx xxx0	I
+ *	000x 0011	UI		(which includes APRS)
+ *	101x 1111	XID
+ *	111x 0011	TEST
+ *	100x 0111	FRMR
+ *
+ * APRS always has an Information field with at least one octet for the Data Type Indicator.
+ */
+
+static inline int ax25_get_info_offset(packet_t this_p)
+{
+	int offset = ax25_get_control_offset(this_p) + ax25_get_num_control(this_p) + ax25_get_num_pid(this_p);
+#if DEBUGX
+	Debugprintf("ax25_get_info_offset, returns %d\n", offset);
+#endif
+	return (offset);
+}
+
+static inline int ax25_get_num_info(packet_t this_p)
+{
+	int len;
+
+	/* assuming AX.25 frame. */
+
+	len = this_p->frame_len - this_p->num_addr * 7 - ax25_get_num_control(this_p) - ax25_get_num_pid(this_p);
+	if (len < 0) {
+		len = 0;		/* print error? */
+	}
+
+	return (len);
+}
+
+#endif
+
+
+typedef enum ax25_modulo_e { modulo_unknown = 0, modulo_8 = 8, modulo_128 = 128 } ax25_modulo_t;
+
+typedef enum ax25_frame_type_e {
+
+	frame_type_I = 0,	// Information
+
+	frame_type_S_RR,	// Receive Ready - System Ready To Receive
+	frame_type_S_RNR,	// Receive Not Ready - TNC Buffer Full
+	frame_type_S_REJ,	// Reject Frame - Out of Sequence or Duplicate
+	frame_type_S_SREJ,	// Selective Reject - Request single frame repeat
+
+	frame_type_U_SABME,	// Set Async Balanced Mode, Extended
+	frame_type_U_SABM,	// Set Async Balanced Mode
+	frame_type_U_DISC,	// Disconnect
+	frame_type_U_DM,	// Disconnect Mode
+	frame_type_U_UA,	// Unnumbered Acknowledge
+	frame_type_U_FRMR,	// Frame Reject
+	frame_type_U_UI,	// Unnumbered Information
+	frame_type_U_XID,	// Exchange Identification
+	frame_type_U_TEST,	// Test
+	frame_type_U,		// other Unnumbered, not used by AX.25.
+
+	frame_not_AX25		// Could not get control byte from frame.
+				// This must be last because value plus 1 is
+				// for the size of an array.
+
+} ax25_frame_type_t;
+
+
+/*
+ * Originally this was a single number.
+ * Let's try something new in version 1.2.
+ * Also collect AGC values from the mark and space filters.
+ */
+
+#ifndef AXTEST
+// TODO: remove this?
+#define AX25MEMDEBUG 1
+#endif
+
+
+
+extern packet_t ax25_from_text(char *monitor, int strict);
+
+extern packet_t ax25_from_frame(unsigned char *data, int len, alevel_t alevel);
+
+extern packet_t ax25_dup(packet_t copy_from);
+
+extern void ax25_delete(packet_t pp);
+
+
+
+extern int ax25_parse_addr(int position, char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard);
+extern int ax25_check_addresses(packet_t pp);
+
+extern packet_t ax25_unwrap_third_party(packet_t from_pp);
+
+extern void ax25_set_addr(packet_t pp, int, char *);
+extern void ax25_insert_addr(packet_t this_p, int n, char *ad);
+extern void ax25_remove_addr(packet_t this_p, int n);
+
+extern int ax25_get_num_addr(packet_t pp);
+extern int ax25_get_num_repeaters(packet_t this_p);
+
+extern void ax25_get_addr_with_ssid(packet_t pp, int n, char *station);
+extern void ax25_get_addr_no_ssid(packet_t pp, int n, char *station);
+
+extern int ax25_get_ssid(packet_t pp, int n);
+extern void ax25_set_ssid(packet_t this_p, int n, int ssid);
+
+extern int ax25_get_h(packet_t pp, int n);
+
+extern void ax25_set_h(packet_t pp, int n);
+
+extern int ax25_get_heard(packet_t this_p);
+
+extern int ax25_get_first_not_repeated(packet_t pp);
+
+extern int ax25_get_rr(packet_t this_p, int n);
+
+extern int ax25_get_info(packet_t pp, unsigned char **paddr);
+extern void ax25_set_info(packet_t pp, unsigned char *info_ptr, int info_len);
+extern int ax25_cut_at_crlf(packet_t this_p);
+
+extern void ax25_set_nextp(packet_t this_p, packet_t next_p);
+
+extern int ax25_get_dti(packet_t this_p);
+
+extern packet_t ax25_get_nextp(packet_t this_p);
+
+extern void ax25_set_release_time(packet_t this_p, double release_time);
+extern double ax25_get_release_time(packet_t this_p);
+
+extern void ax25_set_modulo(packet_t this_p, int modulo);
+extern int ax25_get_modulo(packet_t this_p);
+
+extern void ax25_format_addrs(packet_t pp, char *);
+extern void ax25_format_via_path(packet_t this_p, char *result, size_t result_size);
+
+extern int ax25_pack(packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]);
+
+extern ax25_frame_type_t ax25_frame_type(packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns);
+
+extern void ax25_hex_dump(packet_t this_p);
+
+extern int ax25_is_aprs(packet_t pp);
+extern int ax25_is_null_frame(packet_t this_p);
+
+extern int ax25_get_control(packet_t this_p);
+extern int ax25_get_c2(packet_t this_p);
+
+extern int ax25_get_pid(packet_t this_p);
+
+extern int ax25_get_frame_len(packet_t this_p);
+extern unsigned char *ax25_get_frame_data_ptr(packet_t this_p);
+
+extern unsigned short ax25_dedupe_crc(packet_t pp);
+
+extern unsigned short ax25_m_m_crc(packet_t pp);
+
+extern void ax25_safe_print(char *, int, int ascii_only);
+
+#define AX25_ALEVEL_TO_TEXT_SIZE 40	// overkill but safe.
+extern int ax25_alevel_to_text(alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE]);
+
+
+#endif /* AX25_PAD_H */
+
+/* end ax25_pad.h */
+
+
+
+
+#define CTAG_MIN 0x01
+#define CTAG_MAX 0x0B
+
+// Maximum sizes of "data" and "check" parts.
+
+#define FX25_MAX_DATA 239	// i.e. RS(255,239)
+#define FX25_MAX_CHECK 64	// e.g. RS(255, 191)
+#define FX25_BLOCK_SIZE 255	// Block size always 255 for 8 bit symbols.
+
+#endif // FX25_H
+
+#ifndef IL2P_H
+#define IL2P_H 1
+
+
+#define IL2P_PREAMBLE 0x55
+
+#define IL2P_SYNC_WORD 0xF15E48
+
+#define IL2P_SYNC_WORD_SIZE 3
+#define IL2P_HEADER_SIZE 13	// Does not include 2 parity.
+#define IL2P_HEADER_PARITY 2
+
+#define IL2P_MAX_PAYLOAD_SIZE 1023
+#define IL2P_MAX_PAYLOAD_BLOCKS 5
+#define IL2P_MAX_PARITY_SYMBOLS 16		// For payload only.
+#define IL2P_MAX_ENCODED_PAYLOAD_SIZE (IL2P_MAX_PAYLOAD_SIZE + IL2P_MAX_PAYLOAD_BLOCKS * IL2P_MAX_PARITY_SYMBOLS)
+
+#define IL2P_MAX_PACKET_SIZE (IL2P_SYNC_WORD_SIZE + IL2P_HEADER_SIZE + IL2P_HEADER_PARITY + IL2P_MAX_ENCODED_PAYLOAD_SIZE)
+
+
+float GuessCentreFreq(int i)
+{
+	float Freq = 0;
+	float Start;
+	float End;
+	int n;
+	float Max = 0;
+	int Index = 0;
+
+	Start = (rx_freq[i] - RCVR[i] * rcvr_offset[i]) / BinSize;
+	End = (rx_freq[i] + RCVR[i] * rcvr_offset[i]) / BinSize;
+
+	for (n = Start; n <= End; n++)
+	{
+		if (MagOut[n] > Max)
+		{
+			Max = MagOut[n];
+			Index = n;
+		}
+	}
+
+	Freq = Index * BinSize;
+
+	return Freq;
+}
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_new
+ *
+ * Purpose:	Allocate memory for a new packet object.
+ *
+ * Returns:	Identifier for a new packet object.
+ *		In the current implementation this happens to be a pointer.
+ *
+ *------------------------------------------------------------------------------*/
+
+int last_seq_num = 0;
+int new_count = 0;
+int delete_count = 0;
+
+packet_t ax25_new(void)
+{
+	struct packet_s *this_p;
+
+
+#if DEBUG 
+	text_color_set(DW_COLOR_DEBUG);
+	Debugprintf("ax25_new(): before alloc, new=%d, delete=%d\n", new_count, delete_count);
+#endif
+
+	last_seq_num++;
+	new_count++;
+
+	/*
+	 * check for memory leak.
+	 */
+
+	 // version 1.4 push up the threshold.   We could have considerably more with connected mode.
+
+		 //if (new_count > delete_count + 100) {
+	if (new_count > delete_count + 256) {
+
+		Debugprintf("Report to WB2OSZ - Memory leak for packet objects.  new=%d, delete=%d\n", new_count, delete_count);
+#if AX25MEMDEBUG
+#endif
+	}
+
+	this_p = calloc(sizeof(struct packet_s), (size_t)1);
+
+	if (this_p == NULL) {
+		Debugprintf("ERROR - can't allocate memory in ax25_new.\n");
+	}
+
+//	assert(this_p != NULL);
+
+	this_p->magic1 = MAGIC;
+	this_p->seq = last_seq_num;
+	this_p->magic2 = MAGIC;
+	this_p->num_addr = (-1);
+
+	return (this_p);
+}
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_delete
+ *
+ * Purpose:	Destroy a packet object, freeing up memory it was using.
+ *
+ *------------------------------------------------------------------------------*/
+
+void ax25_delete(packet_t this_p)
+{
+	if (this_p == NULL) {
+		Debugprintf("ERROR - NULL pointer passed to ax25_delete.\n");
+		return;
+	}
+
+	delete_count++;
+
+//	assert(this_p->magic1 == MAGIC);
+//	assert(this_p->magic2 == MAGIC);
+
+	this_p->magic1 = 0;
+	this_p->magic1 = 0;
+
+	free(this_p);
+}
+
+
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_s_frame
+ *
+ * Purpose:	Construct an S frame.
+ *
+ * Input:	addrs		- Array of addresses.
+ *
+ *		num_addr	- Number of addresses, range 2 .. 10.
+ *
+ *		cr		- cr_cmd command frame, cr_res for a response frame.
+ *
+ *		ftype		- One of:
+ *				        frame_type_S_RR,        // Receive Ready - System Ready To Receive
+ *				        frame_type_S_RNR,       // Receive Not Ready - TNC Buffer Full
+ *				        frame_type_S_REJ,       // Reject Frame - Out of Sequence or Duplicate
+ *				        frame_type_S_SREJ,      // Selective Reject - Request single frame repeat
+ *
+ *		modulo		- 8 or 128.  Determines if we have 1 or 2 control bytes.
+ *
+ *		nr		- N(R) field --- describe.
+ *
+ *		pf		- Poll/Final flag.
+ *
+ *		pinfo		- Pointer to data for Info field.  Allowed only for SREJ.
+ *
+ *		info_len	- Length for Info field.
+ *
+ *
+ * Returns:	Pointer to new packet object.
+ *
+ *------------------------------------------------------------------------------*/
+
+
+packet_t ax25_s_frame(char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, unsigned char *pinfo, int info_len)
+{
+	packet_t this_p;
+	unsigned char *p;
+	int ctrl = 0;
+
+	this_p = ax25_new();
+
+	if (this_p == NULL) return (NULL);
+
+	if (!set_addrs(this_p, addrs, num_addr, cr)) {
+		Debugprintf("Internal error in %s: Could not set addresses for S frame.\n", __func__);
+		ax25_delete(this_p);
+		return (NULL);
+	}
+
+	if (modulo != 8 && modulo != 128) {
+		Debugprintf("Internal error in %s: Invalid modulo %d for S frame.\n", __func__, modulo);
+		modulo = 8;
+	}
+	this_p->modulo = modulo;
+
+	if (nr < 0 || nr >= modulo) {
+		Debugprintf("Internal error in %s: Invalid N(R) %d for S frame.\n", __func__, nr);
+		nr &= (modulo - 1);
+	}
+
+	// Erratum: The AX.25 spec is not clear about whether SREJ should be command, response, or both.
+	// The underlying X.25 spec clearly says it is response only.  Let's go with that.
+
+	if (ftype == frame_type_S_SREJ && cr != cr_res) {
+		Debugprintf("Internal error in %s: SREJ must be response.\n", __func__);
+	}
+
+	switch (ftype) {
+
+	case frame_type_S_RR:		ctrl = 0x01;	break;
+	case frame_type_S_RNR:	ctrl = 0x05;	break;
+	case frame_type_S_REJ:	ctrl = 0x09;	break;
+	case frame_type_S_SREJ:	ctrl = 0x0d;	break;
+
+	default:
+		Debugprintf("Internal error in %s: Invalid ftype %d for S frame.\n", __func__, ftype);
+		ax25_delete(this_p);
+		return (NULL);
+		break;
+	}
+
+	p = this_p->frame_data + this_p->frame_len;
+
+	if (modulo == 8) {
+		if (pf) ctrl |= 0x10;
+		ctrl |= nr << 5;
+		*p++ = ctrl;
+		this_p->frame_len++;
+	}
+	else {
+		*p++ = ctrl;
+		this_p->frame_len++;
+
+		ctrl = pf & 1;
+		ctrl |= nr << 1;
+		*p++ = ctrl;
+		this_p->frame_len++;
+	}
+
+	if (ftype == frame_type_S_SREJ) {
+		if (pinfo != NULL && info_len > 0) {
+			if (info_len > AX25_MAX_INFO_LEN) {
+				Debugprintf("Internal error in %s: SREJ frame, Invalid information field length %d.\n", __func__, info_len);
+				info_len = AX25_MAX_INFO_LEN;
+			}
+			memcpy(p, pinfo, info_len);
+			p += info_len;
+			this_p->frame_len += info_len;
+		}
+	}
+	else {
+		if (pinfo != NULL || info_len != 0) {
+			Debugprintf("Internal error in %s: Info part not allowed for RR, RNR, REJ frame.\n", __func__);
+		}
+	}
+	*p = '\0';
+
+
+	return (this_p);
+
+} /* end ax25_s_frame */
+
+
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_i_frame
+ *
+ * Purpose:	Construct an I frame.
+ *
+ * Input:	addrs		- Array of addresses.
+ *
+ *		num_addr	- Number of addresses, range 2 .. 10.
+ *
+ *		cr		- cr_cmd command frame, cr_res for a response frame.
+ *
+ *		modulo		- 8 or 128.
+ *
+ *		nr		- N(R) field --- describe.
+ *
+ *		ns		- N(S) field --- describe.
+ *
+ *		pf		- Poll/Final flag.
+ *
+ *		pid		- Protocol ID.
+ *				  Normally 0xf0 meaning no level 3.
+ *				  Could be other values for NET/ROM, etc.
+ *
+ *		pinfo		- Pointer to data for Info field.
+ *
+ *		info_len	- Length for Info field.
+ *
+ *
+ * Returns:	Pointer to new packet object.
+ *
+ *------------------------------------------------------------------------------*/
+
+packet_t ax25_i_frame(char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len)
+{
+	packet_t this_p;
+	unsigned char *p;
+	int ctrl = 0;
+
+	this_p = ax25_new();
+
+	if (this_p == NULL) return (NULL);
+
+	if (!set_addrs(this_p, addrs, num_addr, cr)) {
+		Debugprintf("Internal error in %s: Could not set addresses for I frame.\n", __func__);
+		ax25_delete(this_p);
+		return (NULL);
+	}
+
+	if (modulo != 8 && modulo != 128) {
+		Debugprintf("Internal error in %s: Invalid modulo %d for I frame.\n", __func__, modulo);
+		modulo = 8;
+	}
+	this_p->modulo = modulo;
+
+	if (nr < 0 || nr >= modulo) {
+		Debugprintf("Internal error in %s: Invalid N(R) %d for I frame.\n", __func__, nr);
+		nr &= (modulo - 1);
+	}
+
+	if (ns < 0 || ns >= modulo) {
+		Debugprintf("Internal error in %s: Invalid N(S) %d for I frame.\n", __func__, ns);
+		ns &= (modulo - 1);
+	}
+
+	p = this_p->frame_data + this_p->frame_len;
+
+	if (modulo == 8) {
+		ctrl = (nr << 5) | (ns << 1);
+		if (pf) ctrl |= 0x10;
+		*p++ = ctrl;
+		this_p->frame_len++;
+	}
+	else {
+		ctrl = ns << 1;
+		*p++ = ctrl;
+		this_p->frame_len++;
+
+		ctrl = nr << 1;
+		if (pf) ctrl |= 0x01;
+		*p++ = ctrl;
+		this_p->frame_len++;
+	}
+
+	// Definitely don't want pid value of 0 (not in valid list)
+	// or 0xff (which means more bytes follow).
+
+	if (pid < 0 || pid == 0 || pid == 0xff) {
+		Debugprintf("Warning: Client application provided invalid PID value, 0x%02x, for I frame.\n", pid);
+		pid = AX25_PID_NO_LAYER_3;
+	}
+	*p++ = pid;
+	this_p->frame_len++;
+
+	if (pinfo != NULL && info_len > 0) {
+		if (info_len > AX25_MAX_INFO_LEN) {
+			Debugprintf("Internal error in %s: I frame, Invalid information field length %d.\n", __func__, info_len);
+			info_len = AX25_MAX_INFO_LEN;
+		}
+		memcpy(p, pinfo, info_len);
+		p += info_len;
+		this_p->frame_len += info_len;
+	}
+
+	*p = '\0';
+
+
+	return (this_p);
+
+} /* end ax25_i_frame */
+
+
+
+
+
+extern TStringList detect_list[5];
+extern TStringList detect_list_c[5];
+
+void multi_modem_process_rec_packet(int snd_ch, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, int is_fx25, int emph, int centreFreq)
+{
+	// Convert to QtSM internal format
+
+	struct TDetector_t * pDET = &DET[emph][subchan];
+	string *  data = newString();
+	char Mode[16] = "IL2P";
+
+	sprintf(Mode, "IL2P %d", centreFreq);
+
+	stringAdd(data, pp->frame_data, pp->frame_len + 2);  // QTSM assumes a CRC
+
+	ax25_delete(pp);
+
+	if (retries)
+	{
+		pDET->rx_decoded = decodedFEC;
+		pDET->emph_decoded = decodedFEC;
+		pDET->errors = retries;
+	}
+	else
+	{
+		pDET->rx_decoded = decodedNormal;
+		pDET->emph_decoded = decodedNormal;
+		pDET->errors = 0;
+	}
+
+	if (detect_list[snd_ch].Count > 0 &&
+		my_indexof(&detect_list[snd_ch], data) >= 0)
+	{
+		// Already have a copy of this frame
+
+		freeString(data);
+		Debugprintf("Discarding copy rcvr %d emph %d", subchan, 0);
+		return;
+	}
+
+	string * xx = newString();
+	memset(xx->Data, 0, 16);
+
+	Add(&detect_list_c[snd_ch], xx);
+	Add(&detect_list[snd_ch], data);
+
+//	if (retries)
+//		sprintf(Mode, "IP2P-%d", retries);
+
+	stringAdd(xx, Mode, strlen(Mode));
+	return;
+
+}
+
+
+
+
+alevel_t demod_get_audio_level(int chan, int subchan)
+{
+	alevel_t alevel;
+	alevel.rec = 0;
+	alevel.mark = 0;
+	alevel.space = 0;
+	return (alevel);
+}
+
+void ax25_hex_dump(packet_t this_p)
+{}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_from_frame
+ *
+ * Purpose:	Split apart an HDLC frame to components.
+ *
+ * Inputs:	fbuf	- Pointer to beginning of frame.
+ *
+ *		flen	- Length excluding the two FCS bytes.
+ *
+ *		alevel	- Audio level of received signal.
+ *			  Maximum range 0 - 100.
+ *			  -1 might be used when not applicable.
+ *
+ * Returns:	Pointer to new packet object or NULL if error.
+ *
+ * Outputs:	Use the "get" functions to retrieve information in different ways.
+ *
+ *------------------------------------------------------------------------------*/
+
+
+packet_t ax25_from_frame(unsigned char *fbuf, int flen, alevel_t alevel)
+{
+	packet_t this_p;
+
+
+	/*
+	 * First make sure we have an acceptable length:
+	 *
+	 *	We are not concerned with the FCS (CRC) because someone else checked it.
+	 *
+	 * Is is possible to have zero length for info?
+	 *
+	 * In the original version, assuming APRS, the answer was no.
+	 * We always had at least 3 octets after the address part:
+	 * control, protocol, and first byte of info part for data type.
+	 *
+	 * In later versions, this restriction was relaxed so other
+	 * variations of AX.25 could be used.  Now the minimum length
+	 * is 7+7 for addresses plus 1 for control.
+	 *
+	 */
+
+
+	if (flen < AX25_MIN_PACKET_LEN || flen > AX25_MAX_PACKET_LEN)
+	{
+		Debugprintf("Frame length %d not in allowable range of %d to %d.", flen, AX25_MIN_PACKET_LEN, AX25_MAX_PACKET_LEN);
+		return (NULL);
+	}
+
+	this_p = ax25_new();
+
+	/* Copy the whole thing intact. */
+
+	memcpy(this_p->frame_data, fbuf, flen);
+	this_p->frame_data[flen] = 0;
+	this_p->frame_len = flen;
+
+	/* Find number of addresses. */
+
+	this_p->num_addr = (-1);
+	(void)ax25_get_num_addr(this_p);
+
+	return (this_p);
+}
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_num_addr
+ *
+ * Purpose:	Return number of addresses in current packet.
+ *
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	Number of addresses in the current packet.
+ *		Should be in the range of 2 .. AX25_MAX_ADDRS.
+ *
+ * Version 0.9:	Could be zero for a non AX.25 frame in KISS mode.
+ *
+ *------------------------------------------------------------------------------*/
+
+int ax25_get_num_addr(packet_t this_p)
+{
+	//unsigned char *pf;
+	int a;
+	int addr_bytes;
+
+
+//	assert(this_p->magic1 == MAGIC);
+//	assert(this_p->magic2 == MAGIC);
+
+	/* Use cached value if already set. */
+
+	if (this_p->num_addr >= 0) {
+		return (this_p->num_addr);
+	}
+
+	/* Otherwise, determine the number ofaddresses. */
+
+	this_p->num_addr = 0;		/* Number of addresses extracted. */
+
+	addr_bytes = 0;
+	for (a = 0; a < this_p->frame_len && addr_bytes == 0; a++) {
+		if (this_p->frame_data[a] & SSID_LAST_MASK) {
+			addr_bytes = a + 1;
+		}
+	}
+
+	if (addr_bytes % 7 == 0) {
+		int addrs = addr_bytes / 7;
+		if (addrs >= AX25_MIN_ADDRS && addrs <= AX25_MAX_ADDRS) {
+			this_p->num_addr = addrs;
+		}
+	}
+
+	return (this_p->num_addr);
+}
+
+
+
+void ax25_get_addr_with_ssid(packet_t pp, int n, char *station)
+{}
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_addr_no_ssid
+ *
+ * Purpose:	Return specified address WITHOUT any SSID.
+ *
+ * Inputs:	n	- Index of address.   Use the symbols
+ *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
+ *
+ * Outputs:	station - String representation of the station, WITHOUT the SSID.
+ *			e.g.  "WB2OSZ"
+ *			  Usually variables will be AX25_MAX_ADDR_LEN bytes
+ *			  but 7 would be adequate.
+ *
+ * Bugs:	No bounds checking is performed.  Be careful.
+ *
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	Character string in usual human readable format,
+ *
+ *
+ *------------------------------------------------------------------------------*/
+
+void ax25_get_addr_no_ssid(packet_t this_p, int n, char *station)
+{
+	int i;
+
+	//assert(this_p->magic1 == MAGIC);
+	//assert(this_p->magic2 == MAGIC);
+
+
+	if (n < 0) {
+		Debugprintf("Internal error detected in ax25_get_addr_no_ssid, %s, line %d.\n", __FILE__, __LINE__);
+		Debugprintf("Address index, %d, is less than zero.\n", n);
+		strcpy(station, "??????");
+		return;
+	}
+
+	if (n >= this_p->num_addr) {
+		Debugprintf("Internal error detected in ax25_get_no_with_ssid, %s, line %d.\n", __FILE__, __LINE__);
+		Debugprintf("Address index, %d, is too large for number of addresses, %d.\n", n, this_p->num_addr);
+		strcpy(station, "??????");
+		return;
+	}
+
+	// At one time this would stop at the first space, on the assumption we would have only trailing spaces.
+	// Then there was a forum discussion where someone encountered the address " WIDE2" with a leading space.
+	// In that case, we would have returned a zero length string here.
+	// Now we return exactly what is in the address field and trim trailing spaces.
+	// This will provide better information for troubleshooting.
+
+	for (i = 0; i < 6; i++) {
+		station[i] = (this_p->frame_data[n * 7 + i] >> 1) & 0x7f;
+	}
+	station[6] = '\0';
+
+	for (i = 5; i >= 0; i--) {
+		if (station[i] == ' ')
+			station[i] = '\0';
+		else
+			break;
+	}
+
+	if (strlen(station) == 0) {
+		Debugprintf("Station address, in position %d, is empty!  This is not a valid AX.25 frame.\n", n);
+	}
+
+} /* end ax25_get_addr_no_ssid */
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_ssid
+ *
+ * Purpose:	Return SSID of specified address in current packet.
+ *
+ * Inputs:	n	- Index of address.   Use the symbols
+ *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
+ *
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	Substation id, as integer 0 .. 15.
+ *
+ *------------------------------------------------------------------------------*/
+
+int ax25_get_ssid(packet_t this_p, int n)
+{
+
+//	assert(this_p->magic1 == MAGIC);
+//	assert(this_p->magic2 == MAGIC);
+
+	if (n >= 0 && n < this_p->num_addr) {
+		return ((this_p->frame_data[n * 7 + 6] & SSID_SSID_MASK) >> SSID_SSID_SHIFT);
+	}
+	else {
+		Debugprintf("Internal error: ax25_get_ssid(%d), num_addr=%d\n", n, this_p->num_addr);
+		return (0);
+	}
+}
+
+
+
+static inline int ax25_get_pid_offset(packet_t this_p)
+{
+	return (ax25_get_control_offset(this_p) + ax25_get_num_control(this_p));
+}
+
+static int ax25_get_num_pid(packet_t this_p)
+{
+	int c;
+	int pid;
+
+	c = this_p->frame_data[ax25_get_control_offset(this_p)];
+
+	if ((c & 0x01) == 0 ||				/* I   xxxx xxx0 */
+		c == 0x03 || c == 0x13) {			/* UI  000x 0011 */
+
+		pid = this_p->frame_data[ax25_get_pid_offset(this_p)];
+		if (pid == AX25_PID_ESCAPE_CHARACTER) {
+			return (2);			/* pid 1111 1111 means another follows. */
+		}
+		return (1);
+	}
+	return (0);
+}
+
+
+inline int ax25_get_control_offset(packet_t this_p)
+{
+	return (this_p->num_addr * 7);
+}
+
+inline int ax25_get_num_control(packet_t this_p)
+{
+	int c;
+
+	c = this_p->frame_data[ax25_get_control_offset(this_p)];
+
+	if ((c & 0x01) == 0) {			/* I   xxxx xxx0 */
+		return ((this_p->modulo == 128) ? 2 : 1);
+	}
+
+	if ((c & 0x03) == 1) {			/* S   xxxx xx01 */
+		return ((this_p->modulo == 128) ? 2 : 1);
+	}
+
+	return (1);					/* U   xxxx xx11 */
+}
+
+
+
+
+int ax25_get_info_offset(packet_t this_p)
+{
+	int offset = ax25_get_control_offset(this_p) + ax25_get_num_control(this_p) + ax25_get_num_pid(this_p);
+	return (offset);
+}
+
+int ax25_get_num_info(packet_t this_p)
+{
+	int len;
+
+	/* assuming AX.25 frame. */
+
+	len = this_p->frame_len - this_p->num_addr * 7 - ax25_get_num_control(this_p) - ax25_get_num_pid(this_p);
+	if (len < 0) {
+		len = 0;		/* print error? */
+	}
+
+	return (len);
+}
+
+
+
+
+
+	/*------------------------------------------------------------------------------
+	 *
+	 * Name:	ax25_get_info
+	 *
+	 * Purpose:	Obtain Information part of current packet.
+	 *
+	 * Inputs:	this_p	- Packet object pointer.
+	 *
+	 * Outputs:	paddr	- Starting address of information part is returned here.
+	 *
+	 * Assumption:	ax25_from_text or ax25_from_frame was called first.
+	 *
+	 * Returns:	Number of octets in the Information part.
+	 *		Should be in the range of AX25_MIN_INFO_LEN .. AX25_MAX_INFO_LEN.
+	 *
+	 *------------------------------------------------------------------------------*/
+
+int ax25_get_info(packet_t this_p, unsigned char **paddr)
+{
+	unsigned char *info_ptr;
+	int info_len;
+
+
+	//assert(this_p->magic1 == MAGIC);
+	//assert(this_p->magic2 == MAGIC);
+
+	if (this_p->num_addr >= 2) {
+
+		/* AX.25 */
+
+		info_ptr = this_p->frame_data + ax25_get_info_offset(this_p);
+		info_len = ax25_get_num_info(this_p);
+	}
+	else {
+
+		/* Not AX.25.  Treat Whole packet as info. */
+
+		info_ptr = this_p->frame_data;
+		info_len = this_p->frame_len;
+	}
+
+	/* Add nul character in case caller treats as printable string. */
+
+//		assert(info_len >= 0);
+
+	info_ptr[info_len] = '\0';
+
+	*paddr = info_ptr;
+	return (info_len);
+
+} /* end ax25_get_info */
+
+
+
+
+void ax25_set_info(packet_t this_p, unsigned char *new_info_ptr, int new_info_len)
+{
+	unsigned char *old_info_ptr;
+	int old_info_len = ax25_get_info(this_p, &old_info_ptr);
+	this_p->frame_len -= old_info_len;
+
+	if (new_info_len < 0) new_info_len = 0;
+	if (new_info_len > AX25_MAX_INFO_LEN) new_info_len = AX25_MAX_INFO_LEN;
+	memcpy(old_info_ptr, new_info_ptr, new_info_len);
+	this_p->frame_len += new_info_len;
+}
+
+int ax25_get_pid(packet_t this_p)
+{
+//	assert(this_p->magic1 == MAGIC);
+//	assert(this_p->magic2 == MAGIC);
+
+	// TODO: handle 2 control byte case.
+	// TODO: sanity check: is it I or UI frame?
+
+	if (this_p->frame_len == 0) return(-1);
+
+	if (this_p->num_addr >= 2) {
+		return (this_p->frame_data[ax25_get_pid_offset(this_p)]);
+	}
+	return (-1);
+}
+
+
+int ax25_get_frame_len(packet_t this_p)
+{
+//	assert(this_p->magic1 == MAGIC);
+//	assert(this_p->magic2 == MAGIC);
+
+//	assert(this_p->frame_len >= 0 && this_p->frame_len <= AX25_MAX_PACKET_LEN);
+
+	return (this_p->frame_len);
+
+} /* end ax25_get_frame_len */
+
+
+unsigned char *ax25_get_frame_data_ptr(packet_t this_p)
+{
+//	assert(this_p->magic1 == MAGIC);
+//	assert(this_p->magic2 == MAGIC);
+
+	return (this_p->frame_data);
+
+} /* end ax25_get_frame_data_ptr */
+
+
+int ax25_get_modulo(packet_t this_p)
+{
+	return 7;
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	ax25_get_control
+		ax25_get_c2
+ *
+ * Purpose:	Get Control field from packet.
+ *
+ * Inputs:	this_p	- pointer to packet object.
+ *
+ * Returns:	APRS uses AX25_UI_FRAME.
+ *		This could also be used in other situations.
+ *
+ *------------------------------------------------------------------*/
+
+
+int ax25_get_control(packet_t this_p)
+{
+//	assert(this_p->magic1 == MAGIC);
+//	assert(this_p->magic2 == MAGIC);
+
+	if (this_p->frame_len == 0) return(-1);
+
+	if (this_p->num_addr >= 2) {
+		return (this_p->frame_data[ax25_get_control_offset(this_p)]);
+	}
+	return (-1);
+}
+
+
+/*------------------------------------------------------------------
+*
+* Function:	ax25_frame_type
+*
+* Purpose : Extract the type of frame.
+*		This is derived from the control byte(s) but
+*		is an enumerated type for easier handling.
+*
+* Inputs : this_p - pointer to packet object.
+*
+* Outputs : desc - Text description such as "I frame" or
+*"U frame SABME".
+*			  Supply 56 bytes to be safe.
+*
+*		cr - Command or response ?
+*
+*		pf - P / F - Poll / Final or -1 if not applicable
+*
+*		nr - N(R) - receive sequence or -1 if not applicable.
+*
+*		ns - N(S) - send sequence or -1 if not applicable.
+*
+* Returns:	Frame type from  enum ax25_frame_type_e.
+*
+*------------------------------------------------------------------*/
+
+// TODO: need someway to ensure caller allocated enough space.
+// Should pass in as parameter.
+
+#define DESC_SIZ 56
+
+
+ax25_frame_type_t ax25_frame_type(packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns)
+{
+	int c;		// U frames are always one control byte.
+	int c2 = 0;	// I & S frames can have second Control byte.
+
+//	assert(this_p->magic1 == MAGIC);
+//	assert(this_p->magic2 == MAGIC);
+
+
+	strcpy(desc, "????");
+	*cr = cr_11;
+	*pf = -1;
+	*nr = -1;
+	*ns = -1;
+
+	c = ax25_get_control(this_p);
+	if (c < 0) {
+		strcpy(desc, "Not AX.25");
+		return (frame_not_AX25);
+	}
+
+	/*
+	 * TERRIBLE HACK :-(  for display purposes.
+	 *
+	 * I and S frames can have 1 or 2 control bytes but there is
+	 * no good way to determine this without dipping into the data
+	 * link state machine.  Can we guess?
+	 *
+	 * S frames have no protocol id or information so if there is one
+	 * more byte beyond the control field, we could assume there are
+	 * two control bytes.
+	 *
+	 * For I frames, the protocol id will usually be 0xf0.  If we find
+	 * that as the first byte of the information field, it is probably
+	 * the pid and not part of the information.  Ditto for segments 0x08.
+	 * Not fool proof but good enough for troubleshooting text out.
+	 *
+	 * If we have a link to the peer station, this will be set properly
+	 * before it needs to be used for other reasons.
+	 *
+	 * Setting one of the RR bits (find reference!) is sounding better and better.
+	 * It's in common usage so I should lobby to get that in the official protocol spec.
+	 */
+
+	// Dont support mod 128
+/*
+	if (this_p->modulo == 0 && (c & 3) == 1 && ax25_get_c2(this_p) != -1) {
+		this_p->modulo = modulo_128;
+	}
+	else if (this_p->modulo == 0 && (c & 1) == 0 && this_p->frame_data[ax25_get_info_offset(this_p)] == 0xF0) {
+		this_p->modulo = modulo_128;
+	}
+	else if (this_p->modulo == 0 && (c & 1) == 0 && this_p->frame_data[ax25_get_info_offset(this_p)] == 0x08) {	// same for segments
+		this_p->modulo = modulo_128;
+	}
+
+
+	if (this_p->modulo == modulo_128) {
+		c2 = ax25_get_c2(this_p);
+	}
+*/
+
+		int dst_c = this_p->frame_data[AX25_DESTINATION * 7 + 6] & SSID_H_MASK;
+		int src_c = this_p->frame_data[AX25_SOURCE * 7 + 6] & SSID_H_MASK;
+
+		char cr_text[8];
+		char pf_text[8];
+
+		if (dst_c) {
+			if (src_c) { *cr = cr_11;  strcpy(cr_text, "cc=11"); strcpy(pf_text, "p/f"); }
+			else { *cr = cr_cmd; strcpy(cr_text, "cmd");   strcpy(pf_text, "p"); }
+		}
+		else {
+			if (src_c) { *cr = cr_res; strcpy(cr_text, "res");   strcpy(pf_text, "f"); }
+			else { *cr = cr_00;  strcpy(cr_text, "cc=00"); strcpy(pf_text, "p/f"); }
+		}
+
+		if ((c & 1) == 0) {
+
+			// Information 			rrr p sss 0		or	sssssss 0  rrrrrrr p
+
+			if (this_p->modulo == modulo_128) {
+				*ns = (c >> 1) & 0x7f;
+				*pf = c2 & 1;
+				*nr = (c2 >> 1) & 0x7f;
+			}
+			else {
+				*ns = (c >> 1) & 7;
+				*pf = (c >> 4) & 1;
+				*nr = (c >> 5) & 7;
+			}
+
+			//snprintf (desc, DESC_SIZ, "I %s, n(s)=%d, n(r)=%d, %s=%d", cr_text, *ns, *nr, pf_text, *pf);
+			sprintf(desc, "I %s, n(s)=%d, n(r)=%d, %s=%d, pid=0x%02x", cr_text, *ns, *nr, pf_text, *pf, ax25_get_pid(this_p));
+			return (frame_type_I);
+		}
+		else if ((c & 2) == 0) {
+
+			// Supervisory			rrr p/f ss 0 1		or	0000 ss 0 1  rrrrrrr p/f
+
+			if (this_p->modulo == modulo_128) {
+				*pf = c2 & 1;
+				*nr = (c2 >> 1) & 0x7f;
+			}
+			else {
+				*pf = (c >> 4) & 1;
+				*nr = (c >> 5) & 7;
+			}
+
+
+			switch ((c >> 2) & 3) {
+			case 0: sprintf(desc, "RR %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf);   return (frame_type_S_RR);   break;
+			case 1: sprintf(desc, "RNR %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf);  return (frame_type_S_RNR);  break;
+			case 2: sprintf(desc, "REJ %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf);  return (frame_type_S_REJ);  break;
+			case 3: sprintf(desc, "SREJ %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_SREJ); break;
+			}
+		}
+		else {
+
+			// Unnumbered			mmm p/f mm 1 1
+
+			*pf = (c >> 4) & 1;
+
+			switch (c & 0xef) {
+
+			case 0x6f: sprintf(desc, "SABME %s, %s=%d", cr_text, pf_text, *pf);  return (frame_type_U_SABME); break;
+			case 0x2f: sprintf(desc, "SABM %s, %s=%d", cr_text, pf_text, *pf);  return (frame_type_U_SABM);  break;
+			case 0x43: sprintf(desc, "DISC %s, %s=%d", cr_text, pf_text, *pf);  return (frame_type_U_DISC);  break;
+			case 0x0f: sprintf(desc, "DM %s, %s=%d", cr_text, pf_text, *pf);  return (frame_type_U_DM);    break;
+			case 0x63: sprintf(desc, "UA %s, %s=%d", cr_text, pf_text, *pf);  return (frame_type_U_UA);    break;
+			case 0x87: sprintf(desc, "FRMR %s, %s=%d", cr_text, pf_text, *pf);  return (frame_type_U_FRMR);  break;
+			case 0x03: sprintf(desc, "UI %s, %s=%d", cr_text, pf_text, *pf);  return (frame_type_U_UI);    break;
+			case 0xaf: sprintf(desc, "XID %s, %s=%d", cr_text, pf_text, *pf);  return (frame_type_U_XID);   break;
+			case 0xe3: sprintf(desc, "TEST %s, %s=%d", cr_text, pf_text, *pf);  return (frame_type_U_TEST);  break;
+			default:   sprintf(desc, "U other???");        				 return (frame_type_U);       break;
+			}
+		}
+
+		// Should be unreachable but compiler doesn't realize that.
+		// Here only to suppress "warning: control reaches end of non-void function"
+
+	return (frame_not_AX25);
+
+} /* end ax25_frame_type */
+
+
+
+packet_t ax25_u_frame(char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len)
+{
+	packet_t this_p;
+	unsigned char *p;
+	int ctrl = 0;
+	unsigned int t = 999;	// 1 = must be cmd, 0 = must be response, 2 = can be either.
+	int i = 0;		// Is Info part allowed?
+
+	this_p = ax25_new();
+
+	if (this_p == NULL) return (NULL);
+
+	this_p->modulo = 0;
+
+	if (!set_addrs(this_p, addrs, num_addr, cr)) {
+		Debugprintf("Internal error in %s: Could not set addresses for U frame.\n", __func__);
+		ax25_delete(this_p);
+		return (NULL);
+	}
+
+	switch (ftype) {
+		// 1 = cmd only, 0 = res only, 2 = either
+	case frame_type_U_SABME:	ctrl = 0x6f;	t = 1;		break;
+	case frame_type_U_SABM:	ctrl = 0x2f;	t = 1;		break;
+	case frame_type_U_DISC:	ctrl = 0x43;	t = 1;		break;
+	case frame_type_U_DM:		ctrl = 0x0f;	t = 0;		break;
+	case frame_type_U_UA:		ctrl = 0x63;	t = 0;		break;
+	case frame_type_U_FRMR:	ctrl = 0x87;	t = 0;	i = 1;	break;
+	case frame_type_U_UI:		ctrl = 0x03;	t = 2;	i = 1;	break;
+	case frame_type_U_XID:	ctrl = 0xaf;	t = 2;	i = 1;	break;
+	case frame_type_U_TEST:	ctrl = 0xe3;	t = 2;	i = 1;	break;
+
+	default:
+		Debugprintf("Internal error in %s: Invalid ftype %d for U frame.\n", __func__, ftype);
+		ax25_delete(this_p);
+		return (NULL);
+		break;
+	}
+	if (pf) ctrl |= 0x10;
+
+	if (t != 2) {
+		if (cr != t) {
+			Debugprintf("Internal error in %s: U frame, cr is %d but must be %d. ftype=%d\n", __func__, cr, t, ftype);
+		}
+	}
+
+	p = this_p->frame_data + this_p->frame_len;
+	*p++ = ctrl;
+	this_p->frame_len++;
+
+	if (ftype == frame_type_U_UI) {
+
+		// Definitely don't want pid value of 0 (not in valid list)
+		// or 0xff (which means more bytes follow).
+
+		if (pid < 0 || pid == 0 || pid == 0xff) {
+			Debugprintf("Internal error in %s: U frame, Invalid pid value 0x%02x.\n", __func__, pid);
+			pid = AX25_PID_NO_LAYER_3;
+		}
+		*p++ = pid;
+		this_p->frame_len++;
+	}
+
+	if (i) {
+		if (pinfo != NULL && info_len > 0) {
+			if (info_len > AX25_MAX_INFO_LEN) {
+
+				Debugprintf("Internal error in %s: U frame, Invalid information field length %d.\n", __func__, info_len);
+				info_len = AX25_MAX_INFO_LEN;
+			}
+			memcpy(p, pinfo, info_len);
+			p += info_len;
+			this_p->frame_len += info_len;
+		}
+	}
+	else {
+		if (pinfo != NULL && info_len > 0) {
+			Debugprintf("Internal error in %s: Info part not allowed for U frame type.\n", __func__);
+		}
+	}
+	*p = '\0';
+
+	//assert(p == this_p->frame_data + this_p->frame_len);
+	//assert(this_p->magic1 == MAGIC);
+	//assert(this_p->magic2 == MAGIC);
+
+#if PAD2TEST
+	ax25_frame_type_t check_ftype;
+	cmdres_t check_cr;
+	char check_desc[80];
+	int check_pf;
+	int check_nr;
+	int check_ns;
+
+	check_ftype = ax25_frame_type(this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns);
+
+	text_color_set(DW_COLOR_DEBUG);
+	Debugprintf("check: ftype=%d, desc=\"%s\", pf=%d\n", check_ftype, check_desc, check_pf);
+
+	assert(check_cr == cr);
+	assert(check_ftype == ftype);
+	assert(check_pf == pf);
+	assert(check_nr == -1);
+	assert(check_ns == -1);
+
+#endif
+
+	return (this_p);
+
+} /* end ax25_u_frame */
+
+
+
+
+
+
+static const char *position_name[1 + AX25_MAX_ADDRS] = {
+	"", "Destination ", "Source ",
+	"Digi1 ", "Digi2 ", "Digi3 ", "Digi4 ",
+	"Digi5 ", "Digi6 ", "Digi7 ", "Digi8 " };
+
+int ax25_parse_addr(int position, char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard)
+{
+	char *p;
+	char sstr[8];		/* Should be 1 or 2 digits for SSID. */
+	int i, j, k;
+	int maxlen;
+
+	*out_addr = '\0';
+	*out_ssid = 0;
+	*out_heard = 0;
+
+	// Debugprintf ("ax25_parse_addr in: position=%d, '%s', strict=%d\n", position, in_addr, strict);
+
+	if (position < -1) position = -1;
+	if (position > AX25_REPEATER_8) position = AX25_REPEATER_8;
+	position++;	/* Adjust for position_name above. */
+
+	if (strlen(in_addr) == 0) {
+		Debugprintf("%sAddress \"%s\" is empty.\n", position_name[position], in_addr);
+		return 0;
+	}
+
+	if (strict && strlen(in_addr) >= 2 && strncmp(in_addr, "qA", 2) == 0) {
+
+		Debugprintf("%sAddress \"%s\" is a \"q-construct\" used for communicating with\n", position_name[position], in_addr);
+		Debugprintf("APRS Internet Servers.  It should never appear when going over the radio.\n");
+	}
+
+	// Debugprintf ("ax25_parse_addr in: %s\n", in_addr);
+
+	maxlen = strict ? 6 : (AX25_MAX_ADDR_LEN - 1);
+	p = in_addr;
+	i = 0;
+	for (p = in_addr; *p != '\0' && *p != '-' && *p != '*'; p++) {
+		if (i >= maxlen) {
+			Debugprintf("%sAddress is too long. \"%s\" has more than %d characters.\n", position_name[position], in_addr, maxlen);
+			return 0;
+		}
+		if (!isalnum(*p)) {
+			Debugprintf("%sAddress, \"%s\" contains character other than letter or digit in character position %d.\n", position_name[position], in_addr, (int)(long)(p - in_addr) + 1);
+			return 0;
+		}
+
+		out_addr[i++] = *p;
+		out_addr[i] = '\0';
+
+#if DECAMAIN	// Hack when running in decode_aprs utility.
+		// Exempt the "qA..." case because it was already mentioned.
+
+		if (strict && islower(*p) && strncmp(in_addr, "qA", 2) != 0) {
+			text_color_set(DW_COLOR_ERROR);
+			Debugprintf("%sAddress has lower case letters. \"%s\" must be all upper case.\n", position_name[position], in_addr);
+		}
+#else
+		if (strict && islower(*p)) {
+			Debugprintf("%sAddress has lower case letters. \"%s\" must be all upper case.\n", position_name[position], in_addr);
+			return 0;
+		}
+#endif
+	}
+
+	j = 0;
+	sstr[j] = '\0';
+	if (*p == '-') {
+		for (p++; isalnum(*p); p++) {
+			if (j >= 2) {
+				Debugprintf("%sSSID is too long. SSID part of \"%s\" has more than 2 characters.\n", position_name[position], in_addr);
+				return 0;
+			}
+			sstr[j++] = *p;
+			sstr[j] = '\0';
+			if (strict && !isdigit(*p)) {
+				Debugprintf("%sSSID must be digits. \"%s\" has letters in SSID.\n", position_name[position], in_addr);
+				return 0;
+			}
+		}
+		k = atoi(sstr);
+		if (k < 0 || k > 15) {
+			Debugprintf("%sSSID out of range. SSID of \"%s\" not in range of 0 to 15.\n", position_name[position], in_addr);
+			return 0;
+		}
+		*out_ssid = k;
+	}
+
+	if (*p == '*') {
+		*out_heard = 1;
+		p++;
+		if (strict == 2) {
+			Debugprintf("\"*\" is not allowed at end of address \"%s\" here.\n", in_addr);
+			return 0;
+		}
+	}
+
+	if (*p != '\0') {
+		Debugprintf("Invalid character \"%c\" found in %saddress \"%s\".\n", *p, position_name[position], in_addr);
+		return 0;
+	}
+
+	// Debugprintf ("ax25_parse_addr out: '%s' %d %d\n", out_addr, *out_ssid, *out_heard);
+
+	return (1);
+
+} /* end ax25_parse_addr */
+
+
+
+int set_addrs(packet_t pp, char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr)
+{
+	int n;
+
+	//assert(pp->frame_len == 0);
+	//assert(cr == cr_cmd || cr == cr_res);
+
+	if (num_addr < AX25_MIN_ADDRS || num_addr > AX25_MAX_ADDRS) {
+		Debugprintf("INTERNAL ERROR: %s %s %d, num_addr = %d\n", __FILE__, __func__, __LINE__, num_addr);
+		return (0);
+	}
+
+	for (n = 0; n < num_addr; n++) {
+
+		unsigned char *pa = pp->frame_data + n * 7;
+		int ok;
+		int strict = 1;
+		char oaddr[AX25_MAX_ADDR_LEN];
+		int ssid;
+		int heard;
+		int j;
+
+		ok = ax25_parse_addr(n, addrs[n], strict, oaddr, &ssid, &heard);
+
+		if (!ok) return (0);
+
+		// Fill in address.
+
+		memset(pa, ' ' << 1, 6);
+		for (j = 0; oaddr[j]; j++) {
+			pa[j] = oaddr[j] << 1;
+		}
+		pa += 6;
+
+		// Fill in SSID.
+
+		*pa = 0x60 | ((ssid & 0xf) << 1);
+
+		// Command / response flag.
+
+		switch (n) {
+		case AX25_DESTINATION:
+			if (cr == cr_cmd) *pa |= 0x80;
+			break;
+		case AX25_SOURCE:
+			if (cr == cr_res) *pa |= 0x80;
+			break;
+		default:
+			break;
+		}
+
+		// Is this the end of address field?
+
+		if (n == num_addr - 1) {
+			*pa |= 1;
+		}
+
+		pp->frame_len += 7;
+	}
+
+	pp->num_addr = num_addr;
+	return (1);
+
+} /* end set_addrs */
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 	il2p_init.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+
+// Init must be called at start of application.
+
+extern void il2p_init(int debug);
+
+extern struct rs *il2p_find_rs(int nparity);	// Internal later?
+
+extern void il2p_encode_rs(unsigned char *tx_data, int data_size, int num_parity, unsigned char *parity_out);
+
+extern int il2p_decode_rs(unsigned char *rec_block, int data_size, int num_parity, unsigned char *out);
+
+extern int il2p_get_debug(void);
+extern void il2p_set_debug(int debug);
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 	il2p_rec.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Receives a bit stream from demodulator.
+
+extern void il2p_rec_bit(int chan, int subchan, int slice, int dbit);
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 	il2p_send.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+
+// Send bit stream to modulator.
+
+string * il2p_send_frame(int chan, packet_t pp, int max_fec, int polarity);
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 	il2p_codec.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+
+extern int il2p_encode_frame(packet_t pp, int max_fec, unsigned char *iout);
+
+packet_t il2p_decode_frame(unsigned char *irec);
+
+packet_t il2p_decode_header_payload(unsigned char* uhdr, unsigned char *epayload, int *symbols_corrected);
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 	il2p_header.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+
+extern int il2p_type_1_header(packet_t pp, int max_fec, unsigned char *hdr);
+
+extern packet_t il2p_decode_header_type_1(unsigned char *hdr, int num_sym_changed);
+
+
+extern int il2p_type_0_header(packet_t pp, int max_fec, unsigned char *hdr);
+
+extern int il2p_clarify_header(unsigned char *rec_hdr, unsigned char *corrected_descrambled_hdr);
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 	il2p_scramble.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+extern void il2p_scramble_block(unsigned char *in, unsigned char *out, int len);
+
+extern void il2p_descramble_block(unsigned char *in, unsigned char *out, int len);
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 	il2p_payload.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+
+typedef struct {
+	int payload_byte_count;		// Total size, 0 thru 1023
+	int payload_block_count;
+	int small_block_size;
+	int large_block_size;
+	int large_block_count;
+	int small_block_count;
+	int parity_symbols_per_block;	// 2, 4, 6, 8, 16
+} il2p_payload_properties_t;
+
+extern int il2p_payload_compute(il2p_payload_properties_t *p, int payload_size, int max_fec);
+
+extern int il2p_encode_payload(unsigned char *payload, int payload_size, int max_fec, unsigned char *enc);
+
+extern int il2p_decode_payload(unsigned char *received, int payload_size, int max_fec, unsigned char *payload_out, int *symbols_corrected);
+
+extern int il2p_get_header_attributes(unsigned char *hdr, int *hdr_type, int *max_fec);
+
+#endif
+
+
+
+// Interesting related stuff:
+// https://www.kernel.org/doc/html/v4.15/core-api/librs.html
+// https://berthub.eu/articles/posts/reed-solomon-for-programmers/ 
+
+
+#define MAX_NROOTS 16
+
+#define NTAB 5
+
+static struct {
+	int symsize;          // Symbol size, bits (1-8).  Always 8 for this application.
+	int genpoly;          // Field generator polynomial coefficients.
+	int fcs;              // First root of RS code generator polynomial, index form.
+			  // FX.25 uses 1 but IL2P uses 0.
+	int prim;             // Primitive element to generate polynomial roots.
+	int nroots;           // RS code generator polynomial degree (number of roots).
+						  // Same as number of check bytes added.
+	struct rs *rs;        // Pointer to RS codec control block.  Filled in at init time.
+} Tab[NTAB] = {
+  {8, 0x11d,   0,   1, 2, NULL },  // 2 parity
+  {8, 0x11d,   0,   1, 4, NULL },  // 4 parity
+  {8, 0x11d,   0,   1, 6, NULL },  // 6 parity
+  {8, 0x11d,   0,   1, 8, NULL },  // 8 parity
+  {8, 0x11d,   0,   1, 16, NULL },  // 16 parity
+};
+
+
+
+static int g_il2p_debug = 0;
+
+
+/*-------------------------------------------------------------
+ *
+ * Name:	il2p_init
+ *
+ * Purpose:	This must be called at application start up time.
+ *		It sets up tables for the Reed-Solomon functions.
+ *
+ * Inputs:	debug	- Enable debug output.
+ *
+ *--------------------------------------------------------------*/
+
+void il2p_init(int il2p_debug)
+{
+	g_il2p_debug = il2p_debug;
+
+	for (int i = 0; i < NTAB; i++) {
+		//assert(Tab[i].nroots <= MAX_NROOTS);
+		Tab[i].rs = INIT_RS(Tab[i].symsize, Tab[i].genpoly, Tab[i].fcs, Tab[i].prim, Tab[i].nroots);
+		if (Tab[i].rs == NULL) {
+			Debugprintf("IL2P internal error: init_rs_char failed!\n");
+			exit(0);
+		}
+	}
+
+} // end il2p_init
+
+
+int il2p_get_debug(void)
+{
+	return (g_il2p_debug);
+}
+void il2p_set_debug(int debug)
+{
+	g_il2p_debug = debug;
+}
+
+
+// Find RS codec control block for specified number of parity symbols.
+
+struct rs *il2p_find_rs(int nparity)
+{
+	for (int n = 0; n < NTAB; n++) {
+		if (Tab[n].nroots == nparity) {
+			return (Tab[n].rs);
+		}
+	}
+	Debugprintf("IL2P INTERNAL ERROR: il2p_find_rs: control block not found for nparity = %d.\n", nparity);
+	return (Tab[0].rs);
+}
+
+
+/*-------------------------------------------------------------
+ *
+ * Name:	void il2p_encode_rs
+ *
+ * Purpose:	Add parity symbols to a block of data.
+ *
+ * Inputs:	tx_data		Header or other data to transmit.
+ *		data_size	Number of data bytes in above.
+ *		num_parity	Number of parity symbols to add.
+ *				Maximum of IL2P_MAX_PARITY_SYMBOLS.
+ *
+ * Outputs:	parity_out	Specified number of parity symbols
+ *
+ * Restriction:	data_size + num_parity <= 255 which is the RS block size.
+ *		The caller must ensure this.
+ *
+ *--------------------------------------------------------------*/
+
+void il2p_encode_rs(unsigned char *tx_data, int data_size, int num_parity, unsigned char *parity_out)
+{
+	//assert(data_size >= 1);
+	//assert(num_parity == 2 || num_parity == 4 || num_parity == 6 || num_parity == 8 || num_parity == 16);
+	//assert(data_size + num_parity <= 255);
+
+	unsigned char rs_block[FX25_BLOCK_SIZE];
+	memset(rs_block, 0, sizeof(rs_block));
+	memcpy(rs_block + sizeof(rs_block) - data_size - num_parity, tx_data, data_size);
+	ENCODE_RS(il2p_find_rs(num_parity), rs_block, parity_out);
+}
+
+/*-------------------------------------------------------------
+ *
+ * Name:	void il2p_decode_rs
+ *
+ * Purpose:	Check and attempt to fix block with FEC.
+ *
+ * Inputs:	rec_block	Received block composed of data and parity.
+ *				Total size is sum of following two parameters.
+ *		data_size	Number of data bytes in above.
+ *		num_parity	Number of parity symbols (bytes) in above.
+ *
+ * Outputs:	out		Original with possible corrections applied.
+ *				data_size bytes.
+ *
+ * Returns:	-1 for unrecoverable.
+ *		>= 0 for success.  Number of symbols corrected.
+ *
+ *--------------------------------------------------------------*/
+
+int il2p_decode_rs(unsigned char *rec_block, int data_size, int num_parity, unsigned char *out)
+{
+
+	//  Use zero padding in front if data size is too small.
+
+	int n = data_size + num_parity;		// total size in.
+
+	unsigned char rs_block[FX25_BLOCK_SIZE];
+
+	// We could probably do this more efficiently by skipping the
+	// processing of the bytes known to be zero.  Good enough for now.
+
+	memset(rs_block, 0, sizeof(rs_block) - n);
+	memcpy(rs_block + sizeof(rs_block) - n, rec_block, n);
+
+	if (il2p_get_debug() >= 3) {
+		Debugprintf("==============================  il2p_decode_rs  ==============================\n");
+		Debugprintf("%d filler zeros, %d data, %d parity\n", (int)(sizeof(rs_block) - n), data_size, num_parity);
+		fx_hex_dump(rs_block, sizeof(rs_block));
+	}
+
+	int derrlocs[FX25_MAX_CHECK];	// Half would probably be OK.
+
+	int derrors = DECODE_RS(il2p_find_rs(num_parity), rs_block, derrlocs, 0);
+	memcpy(out, rs_block + sizeof(rs_block) - n, data_size);
+
+	if (il2p_get_debug() >= 3) {
+		if (derrors == 0) {
+			Debugprintf("No errors reported for RS block.\n");
+		}
+		else if (derrors > 0) {
+			Debugprintf("%d errors fixed in positions:\n", derrors);
+			for (int j = 0; j < derrors; j++) {
+				Debugprintf("        %3d  (0x%02x)\n", derrlocs[j], derrlocs[j]);
+			}
+			fx_hex_dump(rs_block, sizeof(rs_block));
+		}
+	}
+
+	// It is possible to have a situation where too many errors are
+	// present but the algorithm could get a good code block by "fixing"
+	// one of the padding bytes that should be 0.
+
+	for (int i = 0; i < derrors; i++) {
+		if (derrlocs[i] < sizeof(rs_block) - n) {
+			if (il2p_get_debug() >= 3) {
+				Debugprintf("RS DECODE ERROR!  Padding position %d should be 0 but it was set to %02x.\n", derrlocs[i], rs_block[derrlocs[i]]);
+			}
+			derrors = -1;
+			break;
+		}
+	}
+
+	if (il2p_get_debug() >= 3) {
+		Debugprintf("==============================  il2p_decode_rs  returns %d  ==============================\n", derrors);
+	}
+	return (derrors);
+}
+
+// end il2p_init.c
+
+
+
+
+
+
+
+
+
+
+
+
+
+void ENCODE_RS(struct rs * rs, DTYPE * data, DTYPE * bb)
+{
+
+	int i, j;
+	DTYPE feedback;
+
+	memset(bb, 0, NROOTS * sizeof(DTYPE)); // clear out the FEC data area
+
+	for (i = 0; i < NN - NROOTS; i++) {
+		feedback = INDEX_OF[data[i] ^ bb[0]];
+		if (feedback != A0) {      /* feedback term is non-zero */
+			for (j = 1; j < NROOTS; j++)
+				bb[j] ^= ALPHA_TO[MODNN(feedback + GENPOLY[NROOTS - j])];
+		}
+		/* Shift */
+		memmove(&bb[0], &bb[1], sizeof(DTYPE)*(NROOTS - 1));
+		if (feedback != A0)
+			bb[NROOTS - 1] = ALPHA_TO[MODNN(feedback + GENPOLY[0])];
+		else
+			bb[NROOTS - 1] = 0;
+	}
+}
+
+
+
+
+int DECODE_RS(struct rs * rs, DTYPE * data, int *eras_pos, int no_eras) {
+
+	int deg_lambda, el, deg_omega;
+	int i, j, r, k;
+	DTYPE u, q, tmp, num1, num2, den, discr_r;
+	//  DTYPE lambda[NROOTS+1], s[NROOTS];	/* Err+Eras Locator poly and syndrome poly */
+	//  DTYPE b[NROOTS+1], t[NROOTS+1], omega[NROOTS+1];
+	//  DTYPE root[NROOTS], reg[NROOTS+1], loc[NROOTS];
+	DTYPE lambda[FX25_MAX_CHECK + 1], s[FX25_MAX_CHECK];	/* Err+Eras Locator poly and syndrome poly */
+	DTYPE b[FX25_MAX_CHECK + 1], t[FX25_MAX_CHECK + 1], omega[FX25_MAX_CHECK + 1];
+	DTYPE root[FX25_MAX_CHECK], reg[FX25_MAX_CHECK + 1], loc[FX25_MAX_CHECK];
+	int syn_error, count;
+
+	/* form the syndromes; i.e., evaluate data(x) at roots of g(x) */
+	for (i = 0; i < NROOTS; i++)
+		s[i] = data[0];
+
+	for (j = 1; j < NN; j++) {
+		for (i = 0; i < NROOTS; i++) {
+			if (s[i] == 0) {
+				s[i] = data[j];
+			}
+			else {
+				s[i] = data[j] ^ ALPHA_TO[MODNN(INDEX_OF[s[i]] + (FCR + i)*PRIM)];
+			}
+		}
+	}
+
+	/* Convert syndromes to index form, checking for nonzero condition */
+	syn_error = 0;
+	for (i = 0; i < NROOTS; i++) {
+		syn_error |= s[i];
+		s[i] = INDEX_OF[s[i]];
+	}
+
+	// fprintf(stderr,"syn_error = %4x\n",syn_error);
+	if (!syn_error) {
+		/* if syndrome is zero, data[] is a codeword and there are no
+		 * errors to correct. So return data[] unmodified
+		 */
+		count = 0;
+		goto finish;
+	}
+	memset(&lambda[1], 0, NROOTS * sizeof(lambda[0]));
+	lambda[0] = 1;
+
+	if (no_eras > 0) {
+		/* Init lambda to be the erasure locator polynomial */
+		lambda[1] = ALPHA_TO[MODNN(PRIM*(NN - 1 - eras_pos[0]))];
+		for (i = 1; i < no_eras; i++) {
+			u = MODNN(PRIM*(NN - 1 - eras_pos[i]));
+			for (j = i + 1; j > 0; j--) {
+				tmp = INDEX_OF[lambda[j - 1]];
+				if (tmp != A0)
+					lambda[j] ^= ALPHA_TO[MODNN(u + tmp)];
+			}
+		}
+
+#if DEBUG >= 1
+		/* Test code that verifies the erasure locator polynomial just constructed
+		   Needed only for decoder debugging. */
+
+		   /* find roots of the erasure location polynomial */
+		for (i = 1; i <= no_eras; i++)
+			reg[i] = INDEX_OF[lambda[i]];
+
+		count = 0;
+		for (i = 1, k = IPRIM - 1; i <= NN; i++, k = MODNN(k + IPRIM)) {
+			q = 1;
+			for (j = 1; j <= no_eras; j++)
+				if (reg[j] != A0) {
+					reg[j] = MODNN(reg[j] + j);
+					q ^= ALPHA_TO[reg[j]];
+				}
+			if (q != 0)
+				continue;
+			/* store root and error location number indices */
+			root[count] = i;
+			loc[count] = k;
+			count++;
+		}
+		if (count != no_eras) {
+			fprintf(stderr, "count = %d no_eras = %d\n lambda(x) is WRONG\n", count, no_eras);
+			count = -1;
+			goto finish;
+		}
+#if DEBUG >= 2
+		fprintf(stderr, "\n Erasure positions as determined by roots of Eras Loc Poly:\n");
+		for (i = 0; i < count; i++)
+			fprintf(stderr, "%d ", loc[i]);
+		fprintf(stderr, "\n");
+#endif
+#endif
+	}
+	for (i = 0; i < NROOTS + 1; i++)
+		b[i] = INDEX_OF[lambda[i]];
+
+	/*
+	 * Begin Berlekamp-Massey algorithm to determine error+erasure
+	 * locator polynomial
+	 */
+	r = no_eras;
+	el = no_eras;
+	while (++r <= NROOTS) {	/* r is the step number */
+	  /* Compute discrepancy at the r-th step in poly-form */
+		discr_r = 0;
+		for (i = 0; i < r; i++) {
+			if ((lambda[i] != 0) && (s[r - i - 1] != A0)) {
+				discr_r ^= ALPHA_TO[MODNN(INDEX_OF[lambda[i]] + s[r - i - 1])];
+			}
+		}
+		discr_r = INDEX_OF[discr_r];	/* Index form */
+		if (discr_r == A0) {
+			/* 2 lines below: B(x) <-- x*B(x) */
+			memmove(&b[1], b, NROOTS * sizeof(b[0]));
+			b[0] = A0;
+		}
+		else {
+			/* 7 lines below: T(x) <-- lambda(x) - discr_r*x*b(x) */
+			t[0] = lambda[0];
+			for (i = 0; i < NROOTS; i++) {
+				if (b[i] != A0)
+					t[i + 1] = lambda[i + 1] ^ ALPHA_TO[MODNN(discr_r + b[i])];
+				else
+					t[i + 1] = lambda[i + 1];
+			}
+			if (2 * el <= r + no_eras - 1) {
+				el = r + no_eras - el;
+				/*
+				 * 2 lines below: B(x) <-- inv(discr_r) *
+				 * lambda(x)
+				 */
+				for (i = 0; i <= NROOTS; i++)
+					b[i] = (lambda[i] == 0) ? A0 : MODNN(INDEX_OF[lambda[i]] - discr_r + NN);
+			}
+			else {
+				/* 2 lines below: B(x) <-- x*B(x) */
+				memmove(&b[1], b, NROOTS * sizeof(b[0]));
+				b[0] = A0;
+			}
+			memcpy(lambda, t, (NROOTS + 1) * sizeof(t[0]));
+		}
+	}
+
+	/* Convert lambda to index form and compute deg(lambda(x)) */
+	deg_lambda = 0;
+	for (i = 0; i < NROOTS + 1; i++) {
+		lambda[i] = INDEX_OF[lambda[i]];
+		if (lambda[i] != A0)
+			deg_lambda = i;
+	}
+	/* Find roots of the error+erasure locator polynomial by Chien search */
+	memcpy(&reg[1], &lambda[1], NROOTS * sizeof(reg[0]));
+	count = 0;		/* Number of roots of lambda(x) */
+	for (i = 1, k = IPRIM - 1; i <= NN; i++, k = MODNN(k + IPRIM)) {
+		q = 1; /* lambda[0] is always 0 */
+		for (j = deg_lambda; j > 0; j--) {
+			if (reg[j] != A0) {
+				reg[j] = MODNN(reg[j] + j);
+				q ^= ALPHA_TO[reg[j]];
+			}
+		}
+		if (q != 0)
+			continue; /* Not a root */
+		  /* store root (index-form) and error location number */
+#if DEBUG>=2
+		fprintf(stderr, "count %d root %d loc %d\n", count, i, k);
+#endif
+		root[count] = i;
+		loc[count] = k;
+		/* If we've already found max possible roots,
+		 * abort the search to save time
+		 */
+		if (++count == deg_lambda)
+			break;
+	}
+	if (deg_lambda != count) {
+		/*
+		 * deg(lambda) unequal to number of roots => uncorrectable
+		 * error detected
+		 */
+		count = -1;
+		goto finish;
+	}
+	/*
+	 * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo
+	 * x**NROOTS). in index form. Also find deg(omega).
+	 */
+	deg_omega = 0;
+	for (i = 0; i < NROOTS; i++) {
+		tmp = 0;
+		j = (deg_lambda < i) ? deg_lambda : i;
+		for (; j >= 0; j--) {
+			if ((s[i - j] != A0) && (lambda[j] != A0))
+				tmp ^= ALPHA_TO[MODNN(s[i - j] + lambda[j])];
+		}
+		if (tmp != 0)
+			deg_omega = i;
+		omega[i] = INDEX_OF[tmp];
+	}
+	omega[NROOTS] = A0;
+
+	/*
+	 * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 =
+	 * inv(X(l))**(FCR-1) and den = lambda_pr(inv(X(l))) all in poly-form
+	 */
+	for (j = count - 1; j >= 0; j--) {
+		num1 = 0;
+		for (i = deg_omega; i >= 0; i--) {
+			if (omega[i] != A0)
+				num1 ^= ALPHA_TO[MODNN(omega[i] + i * root[j])];
+		}
+		num2 = ALPHA_TO[MODNN(root[j] * (FCR - 1) + NN)];
+		den = 0;
+
+		/* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */
+		for (i = min(deg_lambda, NROOTS - 1) & ~1; i >= 0; i -= 2) {
+			if (lambda[i + 1] != A0)
+				den ^= ALPHA_TO[MODNN(lambda[i + 1] + i * root[j])];
+		}
+		if (den == 0) {
+#if DEBUG >= 1
+			fprintf(stderr, "\n ERROR: denominator = 0\n");
+#endif
+			count = -1;
+			goto finish;
+		}
+		/* Apply error to data */
+		if (num1 != 0) {
+			data[loc[j]] ^= ALPHA_TO[MODNN(INDEX_OF[num1] + INDEX_OF[num2] + NN - INDEX_OF[den])];
+		}
+	}
+finish:
+	if (eras_pos != NULL) {
+		for (i = 0; i < count; i++)
+			eras_pos[i] = loc[i];
+	}
+	return count;
+}
+
+
+
+
+struct rs *INIT_RS(unsigned int symsize, unsigned int gfpoly, unsigned fcr, unsigned prim,
+	unsigned int nroots) {
+	struct rs *rs;
+	int i, j, sr, root, iprim;
+
+	if (symsize > 8 * sizeof(DTYPE))
+		return NULL; /* Need version with ints rather than chars */
+
+	if (fcr >= (1 << symsize))
+		return NULL;
+	if (prim == 0 || prim >= (1 << symsize))
+		return NULL;
+	if (nroots >= (1 << symsize))
+		return NULL; /* Can't have more roots than symbol values! */
+
+	rs = (struct rs *)calloc(1, sizeof(struct rs));
+	if (rs == NULL) {
+		Debugprintf("FATAL ERROR: Out of memory.\n");
+		exit(0);
+	}
+	rs->mm = symsize;
+	rs->nn = (1 << symsize) - 1;
+
+	rs->alpha_to = (DTYPE *)calloc((rs->nn + 1), sizeof(DTYPE));
+	if (rs->alpha_to == NULL) {
+		Debugprintf("FATAL ERROR: Out of memory.\n");
+		exit(0);
+	}
+	rs->index_of = (DTYPE *)calloc((rs->nn + 1), sizeof(DTYPE));
+	if (rs->index_of == NULL) {
+		Debugprintf("FATAL ERROR: Out of memory.\n");
+		exit(0);
+	}
+
+	/* Generate Galois field lookup tables */
+	rs->index_of[0] = A0; /* log(zero) = -inf */
+	rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */
+	sr = 1;
+	for (i = 0; i < rs->nn; i++) {
+		rs->index_of[sr] = i;
+		rs->alpha_to[i] = sr;
+		sr <<= 1;
+		if (sr & (1 << symsize))
+			sr ^= gfpoly;
+		sr &= rs->nn;
+	}
+	if (sr != 1) {
+		/* field generator polynomial is not primitive! */
+		free(rs->alpha_to);
+		free(rs->index_of);
+		free(rs);
+		return NULL;
+	}
+
+	/* Form RS code generator polynomial from its roots */
+	rs->genpoly = (DTYPE *)calloc((nroots + 1), sizeof(DTYPE));
+	if (rs->genpoly == NULL) {
+		Debugprintf("FATAL ERROR: Out of memory.\n");
+		exit(0);
+	}
+	rs->fcr = fcr;
+	rs->prim = prim;
+	rs->nroots = nroots;
+
+	/* Find prim-th root of 1, used in decoding */
+	for (iprim = 1; (iprim % prim) != 0; iprim += rs->nn)
+		;
+	rs->iprim = iprim / prim;
+
+	rs->genpoly[0] = 1;
+	for (i = 0, root = fcr * prim; i < nroots; i++, root += prim) {
+		rs->genpoly[i + 1] = 1;
+
+		/* Multiply rs->genpoly[] by  @**(root + x) */
+		for (j = i; j > 0; j--) {
+			if (rs->genpoly[j] != 0)
+				rs->genpoly[j] = rs->genpoly[j - 1] ^ rs->alpha_to[modnn(rs, rs->index_of[rs->genpoly[j]] + root)];
+			else
+				rs->genpoly[j] = rs->genpoly[j - 1];
+		}
+		/* rs->genpoly[0] can never be zero */
+		rs->genpoly[0] = rs->alpha_to[modnn(rs, rs->index_of[rs->genpoly[0]] + root)];
+	}
+	/* convert rs->genpoly[] to index form for quicker encoding */
+	for (i = 0; i <= nroots; i++) {
+		rs->genpoly[i] = rs->index_of[rs->genpoly[i]];
+	}
+
+	// diagnostic prints
+#if 0
+	printf("Alpha To:\n\r");
+	for (i = 0; i < sizeof(DTYPE)*(rs->nn + 1); i++)
+		printf("0x%2x,", rs->alpha_to[i]);
+	printf("\n\r");
+
+	printf("Index Of:\n\r");
+	for (i = 0; i < sizeof(DTYPE)*(rs->nn + 1); i++)
+		printf("0x%2x,", rs->index_of[i]);
+	printf("\n\r");
+
+	printf("GenPoly:\n\r");
+	for (i = 0; i <= nroots; i++)
+		printf("0x%2x,", rs->genpoly[i]);
+	printf("\n\r");
+#endif
+	return rs;
+}
+
+
+// TEMPORARY!!!
+// FIXME: We already have multiple copies of this.
+// Consolidate them into one somewhere.
+
+void fx_hex_dump(unsigned char *p, int len)
+{
+	int n, i, offset;
+
+	offset = 0;
+	while (len > 0) {
+		n = len < 16 ? len : 16;
+		Debugprintf("  %03x: ", offset);
+		for (i = 0; i < n; i++) {
+			Debugprintf(" %02x", p[i]);
+		}
+		for (i = n; i < 16; i++) {
+			Debugprintf("   ");
+		}
+		Debugprintf("  ");
+		for (i = 0; i < n; i++) {
+			Debugprintf("%c", isprint(p[i]) ? p[i] : '.');
+		}
+		Debugprintf("\n");
+		p += 16;
+		offset += 16;
+		len -= 16;
+	}
+}
+
+
+/*-------------------------------------------------------------
+ *
+ * File:	il2p_codec.c
+ *
+ * Purpose:	Convert IL2P encoded format from and to direwolf internal packet format.
+ *
+ *--------------------------------------------------------------*/
+
+
+ /*-------------------------------------------------------------
+  *
+  * Name:	il2p_encode_frame
+  *
+  * Purpose:	Convert AX.25 frame to IL2P encoding.
+  *
+  * Inputs:	chan	- Audio channel number, 0 = first.
+  *
+  *		pp	- Packet object pointer.
+  *
+  *		max_fec	- 1 to send maximum FEC size rather than automatic.
+  *
+  * Outputs:	iout	- Encoded result, excluding the 3 byte sync word.
+  *			  Caller should provide  IL2P_MAX_PACKET_SIZE  bytes.
+  *
+  * Returns:	Number of bytes for transmission.
+  *		-1 is returned for failure.
+  *
+  * Description:	Encode into IL2P format.
+  *
+  * Errors:	If something goes wrong, return -1.
+  *
+  *		Most likely reason is that the frame is too large.
+  *		IL2P has a max payload size of 1023 bytes.
+  *		For a type 1 header, this is the maximum AX.25 Information part size.
+  *		For a type 0 header, this is the entire AX.25 frame.
+  *
+  *--------------------------------------------------------------*/
+
+int il2p_encode_frame(packet_t pp, int max_fec, unsigned char *iout)
+{
+
+	// Can a type 1 header be used?
+
+	unsigned char hdr[IL2P_HEADER_SIZE + IL2P_HEADER_PARITY];
+	int e;
+	int out_len = 0;
+
+	e = il2p_type_1_header(pp, max_fec, hdr);
+	if (e >= 0) {
+		il2p_scramble_block(hdr, iout, IL2P_HEADER_SIZE);
+		il2p_encode_rs(iout, IL2P_HEADER_SIZE, IL2P_HEADER_PARITY, iout + IL2P_HEADER_SIZE);
+		out_len = IL2P_HEADER_SIZE + IL2P_HEADER_PARITY;
+
+		if (e == 0) {
+			// Success. No info part.
+			return (out_len);
+		}
+
+		// Payload is AX.25 info part.
+		unsigned char *pinfo;
+		int info_len;
+		info_len = ax25_get_info(pp, &pinfo);
+
+		int k = il2p_encode_payload(pinfo, info_len, max_fec, iout + out_len);
+		if (k > 0) {
+			out_len += k;
+			// Success. Info part was <= 1023 bytes.
+			return (out_len);
+		}
+
+		// Something went wrong with the payload encoding.
+		return (-1);
+	}
+	else if (e == -1) {
+
+		// Could not use type 1 header for some reason.
+		// e.g. More than 2 addresses, extended (mod 128) sequence numbers, etc.
+
+		e = il2p_type_0_header(pp, max_fec, hdr);
+		if (e > 0) {
+
+			il2p_scramble_block(hdr, iout, IL2P_HEADER_SIZE);
+			il2p_encode_rs(iout, IL2P_HEADER_SIZE, IL2P_HEADER_PARITY, iout + IL2P_HEADER_SIZE);
+			out_len = IL2P_HEADER_SIZE + IL2P_HEADER_PARITY;
+
+			// Payload is entire AX.25 frame.
+
+			unsigned char *frame_data_ptr = ax25_get_frame_data_ptr(pp);
+			int frame_len = ax25_get_frame_len(pp);
+			int k = il2p_encode_payload(frame_data_ptr, frame_len, max_fec, iout + out_len);
+			if (k > 0) {
+				out_len += k;
+				// Success. Entire AX.25 frame <= 1023 bytes.
+				return (out_len);
+			}
+			// Something went wrong with the payload encoding.
+			return (-1);
+		}
+		else if (e == 0) {
+			// Impossible condition.  Type 0 header must have payload.
+			return (-1);
+		}
+		else {
+			// AX.25 frame is too large.
+			return (-1);
+		}
+	}
+
+	// AX.25 Information part is too large.
+	return (-1);
+}
+
+
+
+/*-------------------------------------------------------------
+ *
+ * Name:	il2p_decode_frame
+ *
+ * Purpose:	Convert IL2P encoding to AX.25 frame.
+ *		This is only used during testing, with a whole encoded frame.
+ *		During reception, the header would have FEC and descrambling
+ *		applied first so we would know how much to collect for the payload.
+ *
+ * Inputs:	irec	- Received IL2P frame excluding the 3 byte sync word.
+ *
+ * Future Out:	Number of symbols corrected.
+ *
+ * Returns:	Packet pointer or NULL for error.
+ *
+ *--------------------------------------------------------------*/
+
+packet_t il2p_decode_frame(unsigned char *irec)
+{
+	unsigned char uhdr[IL2P_HEADER_SIZE];		// After FEC and descrambling.
+	int e = il2p_clarify_header(irec, uhdr);
+
+	// TODO?: for symmetry we might want to clarify the payload before combining.
+
+	return (il2p_decode_header_payload(uhdr, irec + IL2P_HEADER_SIZE + IL2P_HEADER_PARITY, &e));
+}
+
+
+/*-------------------------------------------------------------
+ *
+ * Name:	il2p_decode_header_payload
+ *
+ * Purpose:	Convert IL2P encoding to AX.25 frame
+ *
+ * Inputs:	uhdr 		- Received header after FEC and descrambling.
+ *		epayload	- Encoded payload.
+ *
+ * In/Out:	symbols_corrected - Symbols (bytes) corrected in the header.
+ *				  Should be 0 or 1 because it has 2 parity symbols.
+ *				  Here we add number of corrections for the payload.
+ *
+ * Returns:	Packet pointer or NULL for error.
+ *
+ *--------------------------------------------------------------*/
+
+packet_t il2p_decode_header_payload(unsigned char* uhdr, unsigned char *epayload, int *symbols_corrected)
+{
+	int hdr_type;
+	int max_fec;
+	int payload_len = il2p_get_header_attributes(uhdr, &hdr_type, &max_fec);
+
+	packet_t pp = NULL;
+
+	if (hdr_type == 1) {
+
+		// Header type 1.  Any payload is the AX.25 Information part.
+
+		pp = il2p_decode_header_type_1(uhdr, *symbols_corrected);
+		if (pp == NULL) {
+			// Failed for some reason.
+			return (NULL);
+		}
+
+		if (payload_len > 0) {
+			// This is the AX.25 Information part.
+
+			unsigned char extracted[IL2P_MAX_PAYLOAD_SIZE];
+			int e = il2p_decode_payload(epayload, payload_len, max_fec, extracted, symbols_corrected);
+
+			// It would be possible to have a good header but too many errors in the payload.
+
+			if (e <= 0) {
+				ax25_delete(pp);
+				pp = NULL;
+				return (pp);
+			}
+
+			if (e != payload_len) {
+				Debugprintf("IL2P Internal Error: %s(): hdr_type=%d, max_fec=%d, payload_len=%d, e=%d.\n", __func__, hdr_type, max_fec, payload_len, e);
+			}
+
+			ax25_set_info(pp, extracted, payload_len);
+		}
+		return (pp);
+	}
+	else {
+
+		// Header type 0.  The payload is the entire AX.25 frame.
+
+		unsigned char extracted[IL2P_MAX_PAYLOAD_SIZE];
+		int e = il2p_decode_payload(epayload, payload_len, max_fec, extracted, symbols_corrected);
+
+		if (e <= 0) {	// Payload was not received correctly.
+			return (NULL);
+		}
+
+		if (e != payload_len) {
+			Debugprintf("IL2P Internal Error: %s(): hdr_type=%d, e=%d, payload_len=%d\n", __func__, hdr_type, e, payload_len);
+			return (NULL);
+		}
+
+		alevel_t alevel;
+		memset(&alevel, 0, sizeof(alevel));
+		//alevel = demod_get_audio_level (chan, subchan); 	// What TODO? We don't know channel here.
+						// I think alevel gets filled in somewhere later making
+						// this redundant.
+
+		pp = ax25_from_frame(extracted, payload_len, alevel);
+		return (pp);
+	}
+
+} // end il2p_decode_header_payload
+
+// end il2p_codec.c
+
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * File:	il2p_header.c
+ *
+ * Purpose:	Functions to deal with the IL2P header.
+ *
+ * Reference:	http://tarpn.net/t/il2p/il2p-specification0-4.pdf
+ *
+ *--------------------------------------------------------------------------------*/
+
+
+
+ // Convert ASCII to/from DEC SIXBIT as defined here:
+ // https://en.wikipedia.org/wiki/Six-bit_character_code#DEC_six-bit_code
+
+static inline int ascii_to_sixbit(int a)
+{
+	if (a >= ' ' && a <= '_') return (a - ' ');
+	return (31);	// '?' for any invalid.
+}
+
+static inline int sixbit_to_ascii(int s)
+{
+	return (s + ' ');
+}
+
+// Functions for setting the various header fields.
+// It is assumed that it was zeroed first so only the '1' bits are set.
+
+static void set_field(unsigned char *hdr, int bit_num, int lsb_index, int width, int value)
+{
+	while (width > 0 && value != 0) {
+		//assert(lsb_index >= 0 && lsb_index <= 11);
+		if (value & 1) {
+			hdr[lsb_index] |= 1 << bit_num;
+		}
+		value >>= 1;
+		lsb_index--;
+		width--;
+	}
+	//assert(value == 0);
+}
+
+#define SET_UI(hdr,val) set_field(hdr, 6, 0, 1, val)
+
+#define SET_PID(hdr,val) set_field(hdr, 6, 4, 4, val)
+
+#define SET_CONTROL(hdr,val) set_field(hdr, 6, 11, 7, val)
+
+
+#define SET_FEC_LEVEL(hdr,val) set_field(hdr, 7, 0, 1, val)
+
+#define SET_HDR_TYPE(hdr,val) set_field(hdr, 7, 1, 1, val)
+
+#define SET_PAYLOAD_BYTE_COUNT(hdr,val) set_field(hdr, 7, 11, 10, val)
+
+
+// Extracting the fields.
+
+static int get_field(unsigned char *hdr, int bit_num, int lsb_index, int width)
+{
+	int result = 0;
+	lsb_index -= width - 1;
+	while (width > 0) {
+		result <<= 1;
+		//assert(lsb_index >= 0 && lsb_index <= 11);
+		if (hdr[lsb_index] & (1 << bit_num)) {
+			result |= 1;
+		}
+		lsb_index++;
+		width--;
+	}
+	return (result);
+}
+
+#define GET_UI(hdr) get_field(hdr, 6, 0, 1)
+
+#define GET_PID(hdr) get_field(hdr, 6, 4, 4)
+
+#define GET_CONTROL(hdr) get_field(hdr, 6, 11, 7)
+
+
+#define GET_FEC_LEVEL(hdr) get_field(hdr, 7, 0, 1)
+
+#define GET_HDR_TYPE(hdr) get_field(hdr, 7, 1, 1)
+
+#define GET_PAYLOAD_BYTE_COUNT(hdr) get_field(hdr, 7, 11, 10)
+
+
+
+// AX.25 'I' and 'UI' frames have a protocol ID which determines how the
+// information part should be interpreted.
+// Here we squeeze the most common cases down to 4 bits.
+// Return -1 if translation is not possible.  Fall back to type 0 header in this case.
+
+static int encode_pid(packet_t pp)
+{
+	int pid = ax25_get_pid(pp);
+
+	if ((pid & 0x30) == 0x20) return (0x2);		// AX.25 Layer 3
+	if ((pid & 0x30) == 0x10) return (0x2);		// AX.25 Layer 3
+	if (pid == 0x01) return (0x3);			// ISO 8208 / CCIT X.25 PLP
+	if (pid == 0x06) return (0x4);			// Compressed TCP/IP
+	if (pid == 0x07) return (0x5);			// Uncompressed TCP/IP
+	if (pid == 0x08) return (0x6);			// Segmentation fragmen
+	if (pid == 0xcc) return (0xb);			// ARPA Internet Protocol
+	if (pid == 0xcd) return (0xc);			// ARPA Address Resolution
+	if (pid == 0xce) return (0xd);			// FlexNet
+	if (pid == 0xcf) return (0xe);			// TheNET
+	if (pid == 0xf0) return (0xf);			// No L3
+	return (-1);
+}
+
+// Convert IL2P 4 bit PID to AX.25 8 bit PID.
+
+
+static int decode_pid(int pid)
+{
+	static const unsigned char axpid[16] = {
+	0xf0,			// Should not happen. 0 is for 'S' frames.
+	0xf0,			// Should not happen. 1 is for 'U' frames (but not UI).
+	0x20,			// AX.25 Layer 3
+	0x01,			// ISO 8208 / CCIT X.25 PLP
+	0x06,			// Compressed TCP/IP
+	0x07,			// Uncompressed TCP/IP
+	0x08,			// Segmentation fragment
+	0xf0,			// Future
+	0xf0,			// Future
+	0xf0,			// Future
+	0xf0,			// Future
+	0xcc,			// ARPA Internet Protocol
+	0xcd,			// ARPA Address Resolution
+	0xce,			// FlexNet
+	0xcf,			// TheNET
+	0xf0 };			// No L3
+
+	//assert(pid >= 0 && pid <= 15);
+	return (axpid[pid]);
+}
+
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function:	il2p_type_1_header
+ *
+ * Purpose:	Attempt to create type 1 header from packet object.
+ *
+ * Inputs:	pp	- Packet object.
+ *
+ *		max_fec	- 1 to use maximum FEC symbols , 0 for automatic.
+ *
+ * Outputs:	hdr	- IL2P header with no scrambling or parity symbols.
+ *			  Must be large enough to hold IL2P_HEADER_SIZE unsigned bytes.
+ *
+ * Returns:	Number of bytes for information part or -1 for failure.
+ *		In case of failure, fall back to type 0 transparent encapsulation.
+ *
+ * Description:	Type 1 Headers do not support AX.25 repeater callsign addressing,
+ *		Modulo-128 extended mode window sequence numbers, nor any callsign
+ *		characters that cannot translate to DEC SIXBIT.
+ *		If these cases are encountered during IL2P packet encoding,
+ *		the encoder switches to Type 0 Transparent Encapsulation.
+ *		SABME can't be handled by type 1.
+ *
+ *--------------------------------------------------------------------------------*/
+
+int il2p_type_1_header(packet_t pp, int max_fec, unsigned char *hdr)
+{
+	memset(hdr, 0, IL2P_HEADER_SIZE);
+
+	if (ax25_get_num_addr(pp) != 2) {
+		// Only two addresses are allowed for type 1 header.
+		return (-1);
+	}
+
+	// Check does not apply for 'U' frames but put in one place rather than two.
+
+	if (ax25_get_modulo(pp) == 128) return(-1);
+
+	// Destination and source addresses go into low bits 0-5 for bytes 0-11.
+
+	char dst_addr[AX25_MAX_ADDR_LEN];
+	char src_addr[AX25_MAX_ADDR_LEN];
+
+	ax25_get_addr_no_ssid(pp, AX25_DESTINATION, dst_addr);
+	int dst_ssid = ax25_get_ssid(pp, AX25_DESTINATION);
+
+	ax25_get_addr_no_ssid(pp, AX25_SOURCE, src_addr);
+	int src_ssid = ax25_get_ssid(pp, AX25_SOURCE);
+
+	unsigned char *a = (unsigned char *)dst_addr;
+	for (int i = 0; *a != '\0'; i++, a++) {
+		if (*a < ' ' || *a > '_') {
+			// Shouldn't happen but follow the rule.
+			return (-1);
+		}
+		hdr[i] = ascii_to_sixbit(*a);
+	}
+
+	a = (unsigned char *)src_addr;
+	for (int i = 6; *a != '\0'; i++, a++) {
+		if (*a < ' ' || *a > '_') {
+			// Shouldn't happen but follow the rule.
+			return (-1);
+		}
+		hdr[i] = ascii_to_sixbit(*a);
+	}
+
+	// Byte 12 has DEST SSID in upper nybble and SRC SSID in lower nybble and 
+	hdr[12] = (dst_ssid << 4) | src_ssid;
+
+	ax25_frame_type_t frame_type;
+	cmdres_t cr;			// command or response.
+	char description[64];
+	int pf;				// Poll/Final.
+	int nr, ns;			// Sequence numbers.
+
+	frame_type = ax25_frame_type(pp, &cr, description, &pf, &nr, &ns);
+
+	//Debugprintf ("%s(): %s-%d>%s-%d: %s\n", __func__, src_addr, src_ssid, dst_addr, dst_ssid, description);
+
+	switch (frame_type) {
+
+	case frame_type_S_RR:		// Receive Ready - System Ready To Receive
+	case frame_type_S_RNR:		// Receive Not Ready - TNC Buffer Full
+	case frame_type_S_REJ:		// Reject Frame - Out of Sequence or Duplicate
+	case frame_type_S_SREJ:		// Selective Reject - Request single frame repeat
+
+		// S frames (RR, RNR, REJ, SREJ), mod 8, have control N(R) P/F S S 0 1
+		// These are mapped into    P/F N(R) C S S
+		// Bit 6 is not mentioned in documentation but it is used for P/F for the other frame types.
+		// C is copied from the C bit in the destination addr.
+		// C from source is not used here.  Reception assumes it is the opposite.
+		// PID is set to 0, meaning none, for S frames.
+
+		SET_UI(hdr, 0);
+		SET_PID(hdr, 0);
+		SET_CONTROL(hdr, (pf << 6) | (nr << 3) | (((cr == cr_cmd) | (cr == cr_11)) << 2));
+
+		// This gets OR'ed into the above.
+		switch (frame_type) {
+		case frame_type_S_RR:	SET_CONTROL(hdr, 0);	break;
+		case frame_type_S_RNR:	SET_CONTROL(hdr, 1);	break;
+		case frame_type_S_REJ:	SET_CONTROL(hdr, 2);	break;
+		case frame_type_S_SREJ:	SET_CONTROL(hdr, 3);	break;
+		default:	break;
+		}
+
+		break;
+
+	case frame_type_U_SABM:		// Set Async Balanced Mode
+	case frame_type_U_DISC:		// Disconnect
+	case frame_type_U_DM:		// Disconnect Mode
+	case frame_type_U_UA:		// Unnumbered Acknowledge
+	case frame_type_U_FRMR:		// Frame Reject
+	case frame_type_U_UI:		// Unnumbered Information
+	case frame_type_U_XID:		// Exchange Identification
+	case frame_type_U_TEST:		// Test
+
+		// The encoding allows only 3 bits for frame type and SABME got left out.
+		// Control format:  P/F opcode[3] C n/a n/a
+		// The grayed out n/a bits are observed as 00 in the example.
+		// The header UI field must also be set for UI frames.
+		// PID is set to 1 for all U frames other than UI.
+
+		if (frame_type == frame_type_U_UI) {
+			SET_UI(hdr, 1);	// I guess this is how we distinguish 'I' and 'UI'
+				// on the receiving end.
+			int pid = encode_pid(pp);
+			if (pid < 0) return (-1);
+			SET_PID(hdr, pid);
+		}
+		else {
+			SET_PID(hdr, 1);	// 1 for 'U' other than 'UI'.
+		}
+
+		// Each of the destination and source addresses has a "C" bit.
+		// They should normally have the opposite setting.
+		// IL2P has only a single bit to represent 4 possbilities.
+		//
+		//	dst	src	il2p	meaning
+		//	---	---	----	-------
+		//	0	0	0	Not valid (earlier protocol version)
+		//	1	0	1	Command (v2)
+		//	0	1	0	Response (v2)
+		//	1	1	1	Not valid (earlier protocol version)
+		//
+		// APRS does not mention how to set these bits and all 4 combinations
+		// are seen in the wild.  Apparently these are ignored on receive and no
+		// one cares.  Here we copy from the C bit in the destination address.
+		// It should be noted that the case of both C bits being the same can't
+		// be represented so the il2p encode/decode bit not produce exactly the
+		// same bits.  We see this in the second example in the protocol spec.
+		// The original UI frame has both C bits of 0 so it is received as a response.
+
+		SET_CONTROL(hdr, (pf << 6) | (((cr == cr_cmd) | (cr == cr_11)) << 2));
+
+		// This gets OR'ed into the above.
+		switch (frame_type) {
+		case frame_type_U_SABM:	SET_CONTROL(hdr, 0 << 3);	break;
+		case frame_type_U_DISC:	SET_CONTROL(hdr, 1 << 3);	break;
+		case frame_type_U_DM:	SET_CONTROL(hdr, 2 << 3);	break;
+		case frame_type_U_UA:	SET_CONTROL(hdr, 3 << 3);	break;
+		case frame_type_U_FRMR:	SET_CONTROL(hdr, 4 << 3);	break;
+		case frame_type_U_UI:	SET_CONTROL(hdr, 5 << 3);	break;
+		case frame_type_U_XID:	SET_CONTROL(hdr, 6 << 3);	break;
+		case frame_type_U_TEST:	SET_CONTROL(hdr, 7 << 3);	break;
+		default:	break;
+		}
+		break;
+
+	case frame_type_I:		// Information
+
+		// I frames (mod 8 only)
+		// encoded control: P/F N(R) N(S)
+
+		SET_UI(hdr, 0);
+
+		int pid2 = encode_pid(pp);
+		if (pid2 < 0) return (-1);
+		SET_PID(hdr, pid2);
+
+		SET_CONTROL(hdr, (pf << 6) | (nr << 3) | ns);
+		break;
+
+	case frame_type_U_SABME:		// Set Async Balanced Mode, Extended
+	case frame_type_U:			// other Unnumbered, not used by AX.25.
+	case frame_not_AX25:		// Could not get control byte from frame.
+	default:
+
+		// Fall back to the header type 0 for these.
+		return (-1);
+	}
+
+	// Common for all header type 1.
+
+		// Bit 7 has [FEC Level:1], [HDR Type:1], [Payload byte Count:10]
+
+	SET_FEC_LEVEL(hdr, max_fec);
+	SET_HDR_TYPE(hdr, 1);
+
+	unsigned char *pinfo;
+	int info_len;
+
+	info_len = ax25_get_info(pp, &pinfo);
+	if (info_len < 0 || info_len > IL2P_MAX_PAYLOAD_SIZE) {
+		return (-2);
+	}
+
+	SET_PAYLOAD_BYTE_COUNT(hdr, info_len);
+	return (info_len);
+}
+
+
+// This should create a packet from the IL2P header.
+// The information part will not be filled in.
+
+static void trim(char *stuff)
+{
+	char *p = stuff + strlen(stuff) - 1;
+	while (strlen(stuff) > 0 && (*p == ' ')) {
+		*p = '\0';
+		p--;
+	}
+}
+
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function:	il2p_decode_header_type_1
+ *
+ * Purpose:	Attempt to convert type 1 header to a packet object.
+ *
+ * Inputs:	hdr - IL2P header with no scrambling or parity symbols.
+ *
+ *		num_sym_changed - Number of symbols changed by FEC in the header.
+ *				Should be 0 or 1.
+ *
+ * Returns:	Packet Object or NULL for failure.
+ *
+ * Description:	A later step will process the payload for the information part.
+ *
+ *--------------------------------------------------------------------------------*/
+
+packet_t il2p_decode_header_type_1(unsigned char *hdr, int num_sym_changed)
+{
+
+	if (GET_HDR_TYPE(hdr) != 1) {
+		Debugprintf("IL2P Internal error.  Should not be here: %s, when header type is 0.\n", __func__);
+		return (NULL);
+	}
+
+	// First get the addresses including SSID.
+
+	char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
+	int num_addr = 2;
+	memset(addrs, 0, 2 * AX25_MAX_ADDR_LEN);
+
+	// The IL2P header uses 2 parity symbols which means a single corrupted symbol (byte)
+	// can always be corrected.
+	// However, I have seen cases, where the error rate is very high, where the RS decoder
+	// thinks it found a valid code block by changing one symbol but it was the wrong one.
+	// The result is trash.  This shows up as address fields like 'R&G4"A' and 'TEW\ !'.
+	// I added a sanity check here to catch characters other than upper case letters and digits.
+	// The frame should be rejected in this case.  The question is whether to discard it
+	// silently or print a message so the user can see that something strange is happening?
+	// My current thinking is that it should be silently ignored if the header has been
+	// modified (correctee or more likely, made worse in this cases).
+	// If no changes were made, something weird is happening.  We should mention it for
+	// troubleshooting rather than sweeping it under the rug.
+
+	// The same thing has been observed with the payload, under very high error conditions,
+	// and max_fec==0.  Here I don't see a good solution.  AX.25 information can contain
+	// "binary" data so I'm not sure what sort of sanity check could be added.
+	// This was not observed with max_fec==1.  If we make that the default, same as Nino TNC,
+	// it would be extremely extremely unlikely unless someone explicitly selects weaker FEC.
+
+	// TODO: We could do something similar for header type 0.
+	// The address fields should be all binary zero values.
+	// Someone overly ambitious might check the addresses found in the first payload block.
+
+	for (int i = 0; i <= 5; i++) {
+		addrs[AX25_DESTINATION][i] = sixbit_to_ascii(hdr[i] & 0x3f);
+	}
+	trim(addrs[AX25_DESTINATION]);
+	for (int i = 0; i < strlen(addrs[AX25_DESTINATION]); i++) {
+		if (!isupper(addrs[AX25_DESTINATION][i]) && !isdigit(addrs[AX25_DESTINATION][i])) {
+			if (num_sym_changed == 0) {
+				// This can pop up sporadically when receiving random noise.
+				// Would be better to show only when debug is enabled but variable not available here.
+				// TODO: For now we will just suppress it.
+					//text_color_set(DW_COLOR_ERROR);
+					//Debugprintf ("IL2P: Invalid character '%c' in destination address '%s'\n", addrs[AX25_DESTINATION][i], addrs[AX25_DESTINATION]);
+			}
+			return (NULL);
+		}
+	}
+	sprintf(addrs[AX25_DESTINATION] + strlen(addrs[AX25_DESTINATION]), "-%d", (hdr[12] >> 4) & 0xf);
+
+	for (int i = 0; i <= 5; i++) {
+		addrs[AX25_SOURCE][i] = sixbit_to_ascii(hdr[i + 6] & 0x3f);
+	}
+	trim(addrs[AX25_SOURCE]);
+	for (int i = 0; i < strlen(addrs[AX25_SOURCE]); i++) {
+		if (!isupper(addrs[AX25_SOURCE][i]) && !isdigit(addrs[AX25_SOURCE][i])) {
+			if (num_sym_changed == 0) {
+				// This can pop up sporadically when receiving random noise.
+				// Would be better to show only when debug is enabled but variable not available here.
+				// TODO: For now we will just suppress it.
+					//text_color_set(DW_COLOR_ERROR);
+					//Debugprintf ("IL2P: Invalid character '%c' in source address '%s'\n", addrs[AX25_SOURCE][i], addrs[AX25_SOURCE]);
+			}
+			return (NULL);
+		}
+	}
+	sprintf(addrs[AX25_SOURCE] + strlen(addrs[AX25_SOURCE]), "-%d", hdr[12] & 0xf);
+
+	// The PID field gives us the general type.
+	// 0 = 'S' frame.
+	// 1 = 'U' frame other than UI.
+	// others are either 'UI' or 'I' depending on the UI field.
+
+	int pid = GET_PID(hdr);
+	int ui = GET_UI(hdr);
+
+	if (pid == 0) {
+
+		// 'S' frame.
+		// The control field contains: P/F N(R) C S S
+
+		int control = GET_CONTROL(hdr);
+		cmdres_t cr = (control & 0x04) ? cr_cmd : cr_res;
+		ax25_frame_type_t ftype;
+		switch (control & 0x03) {
+		case 0: ftype = frame_type_S_RR; break;
+		case 1: ftype = frame_type_S_RNR; break;
+		case 2: ftype = frame_type_S_REJ; break;
+		default: ftype = frame_type_S_SREJ; break;
+		}
+		int modulo = 8;
+		int nr = (control >> 3) & 0x07;
+		int pf = (control >> 6) & 0x01;
+		unsigned char *pinfo = NULL;	// Any info for SREJ will be added later.
+		int info_len = 0;
+		return (ax25_s_frame(addrs, num_addr, cr, ftype, modulo, nr, pf, pinfo, info_len));
+	}
+	else if (pid == 1) {
+
+		// 'U' frame other than 'UI'.
+		// The control field contains: P/F OPCODE{3) C x x
+
+		int control = GET_CONTROL(hdr);
+		cmdres_t cr = (control & 0x04) ? cr_cmd : cr_res;
+		int axpid = 0;	// unused for U other than UI.
+		ax25_frame_type_t ftype;
+		switch ((control >> 3) & 0x7) {
+		case 0: ftype = frame_type_U_SABM; break;
+		case 1: ftype = frame_type_U_DISC; break;
+		case 2: ftype = frame_type_U_DM; break;
+		case 3: ftype = frame_type_U_UA; break;
+		case 4: ftype = frame_type_U_FRMR; break;
+		case 5: ftype = frame_type_U_UI; axpid = 0xf0; break;		// Should not happen with IL2P pid == 1.
+		case 6: ftype = frame_type_U_XID; break;
+		default: ftype = frame_type_U_TEST; break;
+		}
+		int pf = (control >> 6) & 0x01;
+		unsigned char *pinfo = NULL;	// Any info for UI, XID, TEST will be added later.
+		int info_len = 0;
+		return (ax25_u_frame(addrs, num_addr, cr, ftype, pf, axpid, pinfo, info_len));
+	}
+	else if (ui) {
+
+		// 'UI' frame.
+		// The control field contains: P/F OPCODE{3) C x x
+
+		int control = GET_CONTROL(hdr);
+		cmdres_t cr = (control & 0x04) ? cr_cmd : cr_res;
+		ax25_frame_type_t ftype = frame_type_U_UI;
+		int pf = (control >> 6) & 0x01;
+		int axpid = decode_pid(GET_PID(hdr));
+		unsigned char *pinfo = NULL;	// Any info for UI, XID, TEST will be added later.
+		int info_len = 0;
+		return (ax25_u_frame(addrs, num_addr, cr, ftype, pf, axpid, pinfo, info_len));
+	}
+	else {
+
+		// 'I' frame.
+		// The control field contains: P/F N(R) N(S)
+
+		int control = GET_CONTROL(hdr);
+		cmdres_t cr = cr_cmd;		// Always command.
+		int pf = (control >> 6) & 0x01;
+		int nr = (control >> 3) & 0x7;
+		int ns = control & 0x7;
+		int modulo = 8;
+		int axpid = decode_pid(GET_PID(hdr));
+		unsigned char *pinfo = NULL;	// Any info for UI, XID, TEST will be added later.
+		int info_len = 0;
+		return (ax25_i_frame(addrs, num_addr, cr, modulo, nr, ns, pf, axpid, pinfo, info_len));
+	}
+	return (NULL);	// unreachable but avoid warning.
+
+} // end
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function:	il2p_type_0_header
+ *
+ * Purpose:	Attempt to create type 0 header from packet object.
+ *
+ * Inputs:	pp	- Packet object.
+ *
+ *		max_fec	- 1 to use maximum FEC symbols, 0 for automatic.
+ *
+ * Outputs:	hdr	- IL2P header with no scrambling or parity symbols.
+ *			  Must be large enough to hold IL2P_HEADER_SIZE unsigned bytes.
+ *
+ * Returns:	Number of bytes for information part or -1 for failure.
+ *		In case of failure, fall back to type 0 transparent encapsulation.
+ *
+ * Description:	The type 0 header is used when it is not one of the restricted cases
+ *		covered by the type 1 header.
+ *		The AX.25 frame is put in the payload.
+ *		This will cover: more than one address, mod 128 sequences, etc.
+ *
+ *--------------------------------------------------------------------------------*/
+
+int il2p_type_0_header(packet_t pp, int max_fec, unsigned char *hdr)
+{
+	memset(hdr, 0, IL2P_HEADER_SIZE);
+
+	// Bit 7 has [FEC Level:1], [HDR Type:1], [Payload byte Count:10]
+
+	SET_FEC_LEVEL(hdr, max_fec);
+	SET_HDR_TYPE(hdr, 0);
+
+	int frame_len = ax25_get_frame_len(pp);
+
+	if (frame_len < 14 || frame_len > IL2P_MAX_PAYLOAD_SIZE) {
+		return (-2);
+	}
+
+	SET_PAYLOAD_BYTE_COUNT(hdr, frame_len);
+	return (frame_len);
+}
+
+
+/***********************************************************************************
+ *
+ * Name:        il2p_get_header_attributes
+ *
+ * Purpose:     Extract a few attributes from an IL2p header.
+ *
+ * Inputs:      hdr	- IL2P header structure.
+ *
+ * Outputs:     hdr_type - 0 or 1.
+ *
+ *		max_fec	- 0 for automatic or 1 for fixed maximum size.
+ *
+ * Returns:	Payload byte count.   (actual payload size, not the larger encoded format)
+ *
+ ***********************************************************************************/
+
+
+int il2p_get_header_attributes(unsigned char *hdr, int *hdr_type, int *max_fec)
+{
+	*hdr_type = GET_HDR_TYPE(hdr);
+	*max_fec = GET_FEC_LEVEL(hdr);
+	return(GET_PAYLOAD_BYTE_COUNT(hdr));
+}
+
+
+/***********************************************************************************
+ *
+ * Name:        il2p_clarify_header
+ *
+ * Purpose:     Convert received header to usable form.
+ *		This involves RS FEC then descrambling.
+ *
+ * Inputs:      rec_hdr	- Header as received over the radio.
+ *
+ * Outputs:     corrected_descrambled_hdr - After RS FEC and unscrambling.
+ *
+ * Returns:	Number of symbols that were corrected:
+ *		 0 = No errors
+ *		 1 = Single symbol corrected.
+ *		 <0 = Unable to obtain good header.
+ *
+ ***********************************************************************************/
+
+int il2p_clarify_header(unsigned char *rec_hdr, unsigned char *corrected_descrambled_hdr)
+{
+	unsigned char corrected[IL2P_HEADER_SIZE + IL2P_HEADER_PARITY];
+
+	int e = il2p_decode_rs(rec_hdr, IL2P_HEADER_SIZE, IL2P_HEADER_PARITY, corrected);
+
+	il2p_descramble_block(corrected, corrected_descrambled_hdr, IL2P_HEADER_SIZE);
+
+	return (e);
+}
+
+// end il2p_header.c 
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * File:	il2p_payload.c
+ *
+ * Purpose:	Functions dealing with the payload.
+ *
+ *--------------------------------------------------------------------------------*/
+
+
+ /*--------------------------------------------------------------------------------
+  *
+  * Function:	il2p_payload_compute
+  *
+  * Purpose:	Compute number and sizes of data blocks based on total size.
+  *
+  * Inputs:	payload_size	0 to 1023.  (IL2P_MAX_PAYLOAD_SIZE)
+  *		max_fec		true for 16 parity symbols, false for automatic.
+  *
+  * Outputs:	*p		Payload block sizes and counts.
+  *				Number of parity symbols per block.
+  *
+  * Returns:	Number of bytes in the encoded format.
+  *		Could be 0 for no payload blocks.
+  *		-1 for error (i.e. invalid unencoded size: <0 or >1023)
+  *
+  *--------------------------------------------------------------------------------*/
+
+int il2p_payload_compute(il2p_payload_properties_t *p, int payload_size, int max_fec)
+{
+	memset(p, 0, sizeof(il2p_payload_properties_t));
+
+	if (payload_size < 0 || payload_size > IL2P_MAX_PAYLOAD_SIZE) {
+		return (-1);
+	}
+	if (payload_size == 0) {
+		return (0);
+	}
+
+	if (max_fec) {
+		p->payload_byte_count = payload_size;
+		p->payload_block_count = (p->payload_byte_count + 238) / 239;
+		p->small_block_size = p->payload_byte_count / p->payload_block_count;
+		p->large_block_size = p->small_block_size + 1;
+		p->large_block_count = p->payload_byte_count - (p->payload_block_count * p->small_block_size);
+		p->small_block_count = p->payload_block_count - p->large_block_count;
+		p->parity_symbols_per_block = 16;
+	}
+	else {
+		p->payload_byte_count = payload_size;
+		p->payload_block_count = (p->payload_byte_count + 246) / 247;
+		p->small_block_size = p->payload_byte_count / p->payload_block_count;
+		p->large_block_size = p->small_block_size + 1;
+		p->large_block_count = p->payload_byte_count - (p->payload_block_count * p->small_block_size);
+		p->small_block_count = p->payload_block_count - p->large_block_count;
+		//p->parity_symbols_per_block = (p->small_block_size / 32) + 2;  // Looks like error in documentation
+
+		// It would work if the number of parity symbols was based on large block size.
+
+		if (p->small_block_size <= 61) p->parity_symbols_per_block = 2;
+		else if (p->small_block_size <= 123) p->parity_symbols_per_block = 4;
+		else if (p->small_block_size <= 185) p->parity_symbols_per_block = 6;
+		else if (p->small_block_size <= 247) p->parity_symbols_per_block = 8;
+		else {
+			// Should not happen.  But just in case...
+			Debugprintf("IL2P parity symbol per payload block error.  small_block_size = %d\n", p->small_block_size);
+			return (-1);
+		}
+	}
+
+	// Return the total size for the encoded format.
+
+	return (p->small_block_count * (p->small_block_size + p->parity_symbols_per_block) +
+		p->large_block_count * (p->large_block_size + p->parity_symbols_per_block));
+
+} // end il2p_payload_compute
+
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function:	il2p_encode_payload
+ *
+ * Purpose:	Split payload into multiple blocks such that each set
+ *		of data and parity symbols fit into a 255 byte RS block.
+ *
+ * Inputs:	*payload	Array of bytes.
+ *		payload_size	0 to 1023.  (IL2P_MAX_PAYLOAD_SIZE)
+ *		max_fec		true for 16 parity symbols, false for automatic.
+ *
+ * Outputs:	*enc		Encoded payload for transmission.
+ *				Up to IL2P_MAX_ENCODED_SIZE bytes.
+ *
+ * Returns:	-1 for error (i.e. invalid size)
+ *		0 for no blocks.  (i.e. size zero)
+ *		Number of bytes generated.  Maximum IL2P_MAX_ENCODED_SIZE.
+ *
+ * Note:	I interpreted the protocol spec as saying the LFSR state is retained
+ *		between data blocks.  During interoperability testing, I found that
+ *		was not the case.  It is reset for each data block.
+ *
+ *--------------------------------------------------------------------------------*/
+
+
+int il2p_encode_payload(unsigned char *payload, int payload_size, int max_fec, unsigned char *enc)
+{
+	if (payload_size > IL2P_MAX_PAYLOAD_SIZE) return (-1);
+	if (payload_size == 0) return (0);
+
+	// Determine number of blocks and sizes.
+
+	il2p_payload_properties_t ipp;
+	int e;
+	e = il2p_payload_compute(&ipp, payload_size, max_fec);
+	if (e <= 0) {
+		return (e);
+	}
+
+	unsigned char *pin = payload;
+	unsigned char *pout = enc;
+	int encoded_length = 0;
+	unsigned char scram[256];
+	unsigned char parity[IL2P_MAX_PARITY_SYMBOLS];
+
+	// First the large blocks.
+
+	for (int b = 0; b < ipp.large_block_count; b++) {
+
+		il2p_scramble_block(pin, scram, ipp.large_block_size);
+		memcpy(pout, scram, ipp.large_block_size);
+		pin += ipp.large_block_size;
+		pout += ipp.large_block_size;
+		encoded_length += ipp.large_block_size;
+		il2p_encode_rs(scram, ipp.large_block_size, ipp.parity_symbols_per_block, parity);
+		memcpy(pout, parity, ipp.parity_symbols_per_block);
+		pout += ipp.parity_symbols_per_block;
+		encoded_length += ipp.parity_symbols_per_block;
+	}
+
+	// Then the small blocks.
+
+	for (int b = 0; b < ipp.small_block_count; b++) {
+
+		il2p_scramble_block(pin, scram, ipp.small_block_size);
+		memcpy(pout, scram, ipp.small_block_size);
+		pin += ipp.small_block_size;
+		pout += ipp.small_block_size;
+		encoded_length += ipp.small_block_size;
+		il2p_encode_rs(scram, ipp.small_block_size, ipp.parity_symbols_per_block, parity);
+		memcpy(pout, parity, ipp.parity_symbols_per_block);
+		pout += ipp.parity_symbols_per_block;
+		encoded_length += ipp.parity_symbols_per_block;
+	}
+
+	return (encoded_length);
+
+} // end il2p_encode_payload
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function:	il2p_decode_payload
+ *
+ * Purpose:	Extract original data from encoded payload.
+ *
+ * Inputs:	received	Array of bytes.  Size is unknown but in practice it
+ *				must not exceed IL2P_MAX_ENCODED_SIZE.
+ *		payload_size	0 to 1023.  (IL2P_MAX_PAYLOAD_SIZE)
+ *				Expected result size based on header.
+ *		max_fec		true for 16 parity symbols, false for automatic.
+ *
+ * Outputs:	payload_out	Recovered payload.
+ *
+ * In/Out:	symbols_corrected	Number of symbols corrected.
+ *
+ *
+ * Returns:	Number of bytes extracted.  Should be same as payload_size going in.
+ *		-3 for unexpected internal inconsistency.
+ *		-2 for unable to recover from signal corruption.
+ *		-1 for invalid size.
+ *		0 for no blocks.  (i.e. size zero)
+ *
+ * Description:	Each block is scrambled separately but the LSFR state is carried
+ *		from the first payload block to the next.
+ *
+ *--------------------------------------------------------------------------------*/
+
+int il2p_decode_payload(unsigned char *received, int payload_size, int max_fec, unsigned char *payload_out, int *symbols_corrected)
+{
+	// Determine number of blocks and sizes.
+
+	il2p_payload_properties_t ipp;
+	int e;
+	e = il2p_payload_compute(&ipp, payload_size, max_fec);
+	if (e <= 0) {
+		return (e);
+	}
+
+	unsigned char *pin = received;
+	unsigned char *pout = payload_out;
+	int decoded_length = 0;
+	int failed = 0;
+
+	// First the large blocks.
+
+	for (int b = 0; b < ipp.large_block_count; b++) {
+		unsigned char corrected_block[255];
+		int e = il2p_decode_rs(pin, ipp.large_block_size, ipp.parity_symbols_per_block, corrected_block);
+
+		// Debugprintf ("%s:%d: large block decode_rs returned status = %d\n", __FILE__, __LINE__, e);
+
+		if (e < 0) failed = 1;
+		*symbols_corrected += e;
+
+		il2p_descramble_block(corrected_block, pout, ipp.large_block_size);
+
+		if (il2p_get_debug() >= 2) {
+	
+			Debugprintf("Descrambled large payload block, %d bytes:\n", ipp.large_block_size);
+			fx_hex_dump(pout, ipp.large_block_size);
+		}
+
+		pin += ipp.large_block_size + ipp.parity_symbols_per_block;
+		pout += ipp.large_block_size;
+		decoded_length += ipp.large_block_size;
+	}
+
+	// Then the small blocks.
+
+	for (int b = 0; b < ipp.small_block_count; b++) {
+		unsigned char corrected_block[255];
+		int e = il2p_decode_rs(pin, ipp.small_block_size, ipp.parity_symbols_per_block, corrected_block);
+
+		// Debugprintf ("%s:%d: small block decode_rs returned status = %d\n", __FILE__, __LINE__, e);
+
+		if (e < 0) failed = 1;
+		*symbols_corrected += e;
+
+		il2p_descramble_block(corrected_block, pout, ipp.small_block_size);
+
+		if (il2p_get_debug() >= 2) {
+	
+			Debugprintf("Descrambled small payload block, %d bytes:\n", ipp.small_block_size);
+			fx_hex_dump(pout, ipp.small_block_size);
+		}
+
+		pin += ipp.small_block_size + ipp.parity_symbols_per_block;
+		pout += ipp.small_block_size;
+		decoded_length += ipp.small_block_size;
+	}
+
+	if (failed) {
+		//Debugprintf ("%s:%d: failed = %0x\n", __FILE__, __LINE__, failed);
+		return (-2);
+	}
+
+	if (decoded_length != payload_size) {
+		Debugprintf("IL2P Internal error: decoded_length = %d, payload_size = %d\n", decoded_length, payload_size);
+		return (-3);
+	}
+
+	return (decoded_length);
+
+} // end il2p_decode_payload
+
+// end il2p_payload.c
+
+
+
+struct il2p_context_s {
+
+	enum { IL2P_SEARCHING = 0, IL2P_HEADER, IL2P_PAYLOAD, IL2P_DECODE } state;
+
+	unsigned int acc;	// Accumulate most recent 24 bits for sync word matching.
+				// Lower 8 bits are also used for accumulating bytes for
+				// the header and payload.
+
+	int bc;			// Bit counter so we know when a complete byte has been accumulated.
+
+	int polarity;		// 1 if opposite of expected polarity.
+
+	unsigned char shdr[IL2P_HEADER_SIZE + IL2P_HEADER_PARITY];
+	// Scrambled header as received over the radio.  Includes parity.
+	int hc;			// Number if bytes placed in above.
+
+	unsigned char uhdr[IL2P_HEADER_SIZE];  // Header after FEC and unscrambling.
+
+	int eplen;		// Encoded payload length.  This is not the nuumber from
+				// from the header but rather the number of encoded bytes to gather.
+
+	unsigned char spayload[IL2P_MAX_ENCODED_PAYLOAD_SIZE];
+	// Scrambled and encoded payload as received over the radio.
+	int pc;			// Number of bytes placed in above.
+
+	int corrected;		// Number of symbols corrected by RS FEC.
+};
+
+static struct il2p_context_s *il2p_context[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS];
+
+
+
+/***********************************************************************************
+ *
+ * Name:        il2p_rec_bit
+ *
+ * Purpose:     Extract FX.25 packets from a stream of bits.
+ *
+ * Inputs:      chan    - Channel number.
+ *
+ *              subchan - This allows multiple demodulators per channel.
+ *
+ *              slice   - Allows multiple slicers per demodulator (subchannel).
+ *
+ *              dbit	- One bit from the received data stream.
+ *
+ * Description: This is called once for each received bit.
+ *              For each valid packet, process_rec_frame() is called for further processing.
+ *		It can gather multiple candidates from different parallel demodulators
+ *		("subchannels") and slicers, then decide which one is the best.
+ *
+ ***********************************************************************************/
+
+int centreFreq[4] = { 0, 0, 0, 0 };
+
+void il2p_rec_bit(int chan, int subchan, int slice, int dbit)
+{
+	// Allocate context blocks only as needed.
+
+	if (dbit)
+		dbit = 1;
+
+	struct il2p_context_s *F = il2p_context[chan][subchan][slice];
+	if (F == NULL) {
+		//assert(chan >= 0 && chan < MAX_CHANS);
+		//assert(subchan >= 0 && subchan < MAX_SUBCHANS);
+		//assert(slice >= 0 && slice < MAX_SLICERS);
+		F = il2p_context[chan][subchan][slice] = (struct il2p_context_s *)malloc(sizeof(struct il2p_context_s));
+		//assert(F != NULL);
+		memset(F, 0, sizeof(struct il2p_context_s));
+	}
+
+	// Accumulate most recent 24 bits received.  Most recent is LSB.
+
+	F->acc = ((F->acc << 1) | (dbit & 1)) & 0x00ffffff;
+
+	// State machine to look for sync word then gather appropriate number of header and payload bytes.
+
+	switch (F->state) {
+
+	case IL2P_SEARCHING:		// Searching for the sync word.
+
+		if (__builtin_popcount(F->acc ^ IL2P_SYNC_WORD) <= 1) {	// allow single bit mismatch
+		  //text_color_set (DW_COLOR_INFO);
+		  //Debugprintf ("IL2P header has normal polarity\n");
+			F->polarity = 0;
+			F->state = IL2P_HEADER;
+			F->bc = 0;
+			F->hc = 0;
+		
+			// Determine Centre Freq
+			
+			centreFreq[chan] = GuessCentreFreq(chan);
+		}
+		else if (__builtin_popcount((~F->acc & 0x00ffffff) ^ IL2P_SYNC_WORD) <= 1) {
+			// FIXME - this pops up occasionally with random noise.  Find better way to convey information.
+			// This also happens for each slicer - to noisy.
+			//Debugprintf ("IL2P header has reverse polarity\n");
+			F->polarity = 1;
+			F->state = IL2P_HEADER;
+			F->bc = 0;
+			F->hc = 0;
+			centreFreq[chan] = GuessCentreFreq(chan);
+		}
+			
+		break;
+
+	case IL2P_HEADER:		// Gathering the header.
+
+		F->bc++;
+		if (F->bc == 8) {	// full byte has been collected.
+			F->bc = 0;
+			if (!F->polarity) {
+				F->shdr[F->hc++] = F->acc & 0xff;
+			}
+			else {
+				F->shdr[F->hc++] = (~F->acc) & 0xff;
+			}
+			if (F->hc == IL2P_HEADER_SIZE + IL2P_HEADER_PARITY) {		// Have all of header
+
+				//if (il2p_get_debug() >= 1)
+				//{
+				//	Debugprintf("IL2P header as received [%d.%d.%d]:\n", chan, subchan, slice);
+				//	fx_hex_dump(F->shdr, IL2P_HEADER_SIZE + IL2P_HEADER_PARITY);
+				//}
+
+				// Fix any errors and descramble.
+				F->corrected = il2p_clarify_header(F->shdr, F->uhdr);
+
+				if (F->corrected >= 0) {	// Good header.
+							// How much payload is expected?
+					il2p_payload_properties_t plprop;
+					int hdr_type, max_fec;
+					int len = il2p_get_header_attributes(F->uhdr, &hdr_type, &max_fec);
+
+					F->eplen = il2p_payload_compute(&plprop, len, max_fec);
+
+					if (il2p_get_debug() >= 1)
+					{	
+						Debugprintf("Header type %d, max fec = %d", hdr_type, max_fec);
+						Debugprintf("Need to collect %d encoded bytes for %d byte payload.", F->eplen, len);
+						Debugprintf("%d small blocks of %d and %d large blocks of %d.  %d parity symbols per block",
+							plprop.small_block_count, plprop.small_block_size,
+							plprop.large_block_count, plprop.large_block_size, plprop.parity_symbols_per_block);
+					}
+
+					if (F->eplen >= 1) {		// Need to gather payload.
+						F->pc = 0;
+						F->state = IL2P_PAYLOAD;
+					}
+					else if (F->eplen == 0) {	// No payload.
+						F->pc = 0;
+						F->state = IL2P_DECODE;
+					}
+					else {			// Error.
+
+						if (il2p_get_debug() >= 1) {
+							Debugprintf("IL2P header INVALID.\n");
+						}
+
+						F->state = IL2P_SEARCHING;
+					}
+				}  // good header after FEC.
+				else {
+					F->state = IL2P_SEARCHING;	// Header failed FEC check.
+				}
+			}  // entire header has been collected.    
+		}  // full byte collected.
+		break;
+
+	case IL2P_PAYLOAD:		// Gathering the payload, if any.
+
+		F->bc++;
+		if (F->bc == 8) {	// full byte has been collected.
+			F->bc = 0;
+			if (!F->polarity) {
+				F->spayload[F->pc++] = F->acc & 0xff;
+			}
+			else {
+				F->spayload[F->pc++] = (~F->acc) & 0xff;
+			}
+			if (F->pc == F->eplen) {
+
+				// TODO?: for symmetry it seems like we should clarify the payload before combining.
+
+				F->state = IL2P_DECODE;
+			}
+		}
+		break;
+
+	case IL2P_DECODE:
+		// We get here after a good header and any payload has been collected.
+		// Processing is delayed by one bit but I think it makes the logic cleaner.
+		// During unit testing be sure to send an extra bit to flush it out at the end.
+
+		// in uhdr[IL2P_HEADER_SIZE];  // Header after FEC and descrambling.
+
+		// TODO?:  for symmetry, we might decode the payload here and later build the frame.
+
+	{
+		packet_t pp = il2p_decode_header_payload(F->uhdr, F->spayload, &(F->corrected));
+
+		if (il2p_get_debug() >= 1)
+		{
+			if (pp == NULL)
+			{
+				// Most likely too many FEC errors.
+				Debugprintf("FAILED to construct frame in %s.\n", __func__);
+			}
+		}
+
+		if (pp != NULL) {
+			alevel_t alevel = demod_get_audio_level(chan, subchan);
+			retry_t retries = F->corrected;
+			int is_fx25 = 1;		// FIXME: distinguish fx.25 and IL2P.
+					  // Currently this just means that a FEC mode was used.
+
+			// TODO: Could we put last 3 arguments in packet object rather than passing around separately?
+
+			multi_modem_process_rec_packet(chan, subchan, slice, pp, alevel, retries, is_fx25, slice, centreFreq[chan]);
+		}
+	}   // end block for local variables.
+
+	if (il2p_get_debug() >= 1) {
+
+		Debugprintf("-----");
+	}
+
+	F->state = IL2P_SEARCHING;
+	break;
+
+	} // end of switch
+
+} // end il2p_rec_bit
+
+
+
+
+
+
+
+// Scramble bits for il2p transmit.
+
+// Note that there is a delay of 5 until the first bit comes out.
+// So we need to need to ignore the first 5 out and stick in
+// an extra 5 filler bits to flush at the end.
+
+#define INIT_TX_LSFR 0x00f
+
+static inline int scramble_bit(int in, int *state)
+{
+	int out = ((*state >> 4) ^ *state) & 1;
+	*state = ((((in ^ *state) & 1) << 9) | (*state ^ ((*state & 1) << 4))) >> 1;
+	return (out);
+}
+
+
+// Undo data scrambling for il2p receive.
+
+#define INIT_RX_LSFR 0x1f0
+
+static inline int descramble_bit(int in, int *state)
+{
+	int out = (in ^ *state) & 1;
+	*state = ((*state >> 1) | ((in & 1) << 8)) ^ ((in & 1) << 3);
+	return (out);
+}
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function:	il2p_scramble_block
+ *
+ * Purpose:	Scramble a block before adding RS parity.
+ *
+ * Inputs:	in		Array of bytes.
+ *		len		Number of bytes both in and out.
+ *
+ * Outputs:	out		Array of bytes.
+ *
+ *--------------------------------------------------------------------------------*/
+
+void il2p_scramble_block(unsigned char *in, unsigned char *out, int len)
+{
+	int tx_lfsr_state = INIT_TX_LSFR;
+
+	memset(out, 0, len);
+
+	int skipping = 1;	// Discard the first 5 out.
+	int ob = 0;		// Index to output byte.
+	int om = 0x80;		// Output bit mask;
+	for (int ib = 0; ib < len; ib++) {
+		for (int im = 0x80; im != 0; im >>= 1) {
+			int s = scramble_bit((in[ib] & im) != 0, &tx_lfsr_state);
+			if (ib == 0 && im == 0x04) skipping = 0;
+			if (!skipping) {
+				if (s) {
+					out[ob] |= om;
+				}
+				om >>= 1;
+				if (om == 0) {
+					om = 0x80;
+					ob++;
+				}
+			}
+		}
+	}
+	// Flush it.
+
+	// This is a relic from when I thought the state would need to
+	// be passed along for the next block.
+	// Preserve the LSFR state from before flushing.
+	// This might be needed as the initial state for later payload blocks.
+	int x = tx_lfsr_state;
+	for (int n = 0; n < 5; n++) {
+		int s = scramble_bit(0, &x);
+		if (s) {
+			out[ob] |= om;
+		}
+		om >>= 1;
+		if (om == 0) {
+			om = 0x80;
+			ob++;
+		}
+	}
+
+}  // end il2p_scramble_block
+
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function:	il2p_descramble_block
+ *
+ * Purpose:	Descramble a block after removing RS parity.
+ *
+ * Inputs:	in		Array of bytes.
+ *		len		Number of bytes both in and out.
+ *
+ * Outputs:	out		Array of bytes.
+ *
+ *--------------------------------------------------------------------------------*/
+
+void il2p_descramble_block(unsigned char *in, unsigned char *out, int len)
+{
+	int rx_lfsr_state = INIT_RX_LSFR;
+
+	memset(out, 0, len);
+
+	for (int b = 0; b < len; b++) {
+		for (int m = 0x80; m != 0; m >>= 1) {
+			int d = descramble_bit((in[b] & m) != 0, &rx_lfsr_state);
+			if (d) {
+				out[b] |= m;
+			}
+		}
+	}
+}
+
+// end il2p_scramble.c
+
+
+
+
+static int number_of_bits_sent[MAX_CHANS];		// Count number of bits sent by "il2p_send_frame"
+
+static void send_bytes(int chan, unsigned char *b, int count, int polarity);
+static void send_bit(int chan, int b, int polarity);
+
+
+
+/*-------------------------------------------------------------
+ *
+ * Name:	il2p_send_frame
+ *
+ * Purpose:	Convert frames to a stream of bits in IL2P format.
+ *
+ * Inputs:	chan	- Audio channel number, 0 = first.
+ *
+ *		pp	- Pointer to packet object.
+ *
+ *		max_fec	- 1 to force 16 parity symbols for each payload block.
+ *			  0 for automatic depending on block size.
+ *
+ *		polarity - 0 for normal.  1 to invert signal.
+ *			   2 special case for testing - introduce some errors to test FEC.
+ *
+ * Outputs:	Bits are shipped out by calling tone_gen_put_bit().
+ *
+ * Returns:	Number of bits sent including
+ *		- Preamble   (01010101...)
+ *		- 3 byte Sync Word.
+ *		- 15 bytes for Header.
+ *		- Optional payload.
+ *		The required time can be calculated by dividing this
+ *		number by the transmit rate of bits/sec.
+ *		-1 is returned for failure.
+ *
+ * Description:	Generate an IL2P encoded frame.
+ *
+ * Assumptions:	It is assumed that the tone_gen module has been
+ *		properly initialized so that bits sent with
+ *		tone_gen_put_bit() are processed correctly.
+ *
+ * Errors:	Return -1 for error.  Probably frame too large.
+ *
+ * Note:	Inconsistency here. ax25 version has just a byte array
+ *		and length going in.  Here we need the full packet object.
+ *
+ *--------------------------------------------------------------*/
+
+string * il2p_send_frame(int chan, packet_t pp, int max_fec, int polarity)
+{
+	unsigned char encoded[IL2P_MAX_PACKET_SIZE];
+	string * packet = newString();
+	int preamblecount;
+	unsigned char preamble[1024];
+
+
+	encoded[0] = (IL2P_SYNC_WORD >> 16) & 0xff;
+	encoded[1] = (IL2P_SYNC_WORD >> 8) & 0xff;
+	encoded[2] = (IL2P_SYNC_WORD) & 0xff;
+
+	int elen = il2p_encode_frame(pp, max_fec, encoded + IL2P_SYNC_WORD_SIZE);
+	if (elen <= 0) {
+		Debugprintf("IL2P: Unable to encode frame into IL2P.\n");
+		return (packet);
+	}
+
+	elen += IL2P_SYNC_WORD_SIZE;
+
+	number_of_bits_sent[chan] = 0;
+
+	if (il2p_get_debug() >= 1) {
+		Debugprintf("IL2P frame, max_fec = %d, %d encoded bytes total", max_fec, elen);
+//		fx_hex_dump(encoded, elen);
+	}
+
+	// Send bits to modulator.
+
+	// Try using preaamble for txdelay
+
+	preamblecount = (txdelay[chan] * tx_baudrate[chan]) / 8000;		// 8 for bits, 1000 for mS
+
+	if (preamblecount > 1024)
+		preamblecount = 1024;
+
+ 	memset(preamble, IL2P_PREAMBLE, preamblecount);
+
+	stringAdd(packet, preamble, preamblecount);
+	stringAdd(packet, encoded, elen);
+
+	tx_fx25_size[chan] = packet->Length * 8;
+
+	return packet;
+}
+
+
+
+// TX Code. Builds whole packet then sends a bit at a time
+
+#define TX_SILENCE 0
+#define TX_DELAY 1
+#define TX_TAIL 2
+#define TX_NO_DATA 3
+#define TX_FRAME 4
+#define TX_WAIT_BPF 5
+
+
+#define TX_BIT0 0
+#define TX_BIT1 1
+#define FRAME_EMPTY 0
+#define FRAME_FULL 1
+#define FRAME_NO_FRAME 2
+#define FRAME_NEW_FRAME 3
+#define BYTE_EMPTY 0
+#define BYTE_FULL 1
+
+extern UCHAR tx_frame_status[5];
+extern UCHAR tx_byte_status[5];
+extern string * tx_data[5];
+extern int tx_data_len[5];
+extern UCHAR tx_bit_stream[5];
+extern UCHAR tx_bit_cnt[5];
+extern long tx_tail_cnt[5];
+extern BOOL tx_bs_bit[5];
+
+string * fill_il2p_data(int snd_ch, string * data)
+{
+	string * result;
+	packet_t pp = ax25_new();
+	
+
+	// Call il2p_send_frame to build the bit stream
+
+	pp->frame_len = data->Length - 2;					// Included CRC
+	memcpy(pp->frame_data, data->Data, data->Length);
+
+	result = il2p_send_frame(snd_ch, pp, 1, 0);
+
+	return result;
+}
+
+
+
+void il2p_get_new_frame(int snd_ch, TStringList * frame_stream)
+{
+	string * myTemp;
+
+	tx_bs_bit[snd_ch] = 0;
+	tx_bit_cnt[snd_ch] = 0;
+	tx_fx25_size_cnt[snd_ch] = 0;
+	tx_fx25_size[snd_ch] = 1;
+	tx_frame_status[snd_ch] = FRAME_NEW_FRAME;
+	tx_byte_status[snd_ch] = BYTE_EMPTY;
+
+	if (frame_stream->Count == 0)
+		tx_frame_status[snd_ch] = FRAME_NO_FRAME;
+	else
+	{
+		// We now pass control byte and ack bytes on front and pointer to socket on end if ackmode
+
+		myTemp = Strings(frame_stream, 0);			// get message
+
+		if ((myTemp->Data[0] & 0x0f) == 12)			// ACKMODE
+		{
+			// Save copy then copy data up 3 bytes
+
+			Add(&KISS_acked[snd_ch], duplicateString(myTemp));
+
+			mydelete(myTemp, 0, 3);
+			myTemp->Length -= sizeof(void *);
+		}
+		else
+		{
+			// Just remove control 
+
+			mydelete(myTemp, 0, 1);
+		}
+
+		AGW_AX25_frame_analiz(snd_ch, FALSE, myTemp);
+		put_frame(snd_ch, myTemp, "", TRUE, FALSE);
+
+		tx_data[snd_ch] = fill_il2p_data(snd_ch, myTemp);
+
+		Delete(frame_stream, 0);			// This will invalidate temp
+	}
+}
+
+
+
+// Original code
+
+/*
+static void send_bytes(int chan, unsigned char *b, int count, int polarity)
+{
+	for (int j = 0; j < count; j++) {
+		unsigned int x = b[j];
+		for (int k = 0; k < 8; k++) {
+			send_bit(chan, (x & 0x80) != 0, polarity);
+			x <<= 1;
+		}
+	}
+}
+
+// NRZI would be applied for AX.25 but IL2P does not use it.
+// However we do have an option to invert the signal.
+// The direwolf receive implementation will automatically compensate
+// for either polarity but other implementations might not.
+
+static void send_bit(int chan, int b, int polarity)
+{
+	tone_gen_put_bit(chan, (b ^ polarity) & 1);
+	number_of_bits_sent[chan]++;
+}
+*/
+
+
+
+
+int il2p_get_new_bit(int snd_ch, Byte bit)
+{
+	string *s;
+
+	if (tx_frame_status[snd_ch] == FRAME_EMPTY)
+	{
+		il2p_get_new_frame(snd_ch, &all_frame_buf[snd_ch]);
+		if (tx_frame_status[snd_ch] == FRAME_NEW_FRAME)
+			tx_frame_status[snd_ch] = FRAME_FULL;
+	}
+
+	if (tx_frame_status[snd_ch] == FRAME_FULL)
+	{
+		if (tx_byte_status[snd_ch] == BYTE_EMPTY)
+		{
+			if (tx_data[snd_ch]->Length)
+			{
+				s = tx_data[snd_ch];
+
+				tx_bit_stream[snd_ch] = s->Data[0];
+				tx_frame_status[snd_ch] = FRAME_FULL;
+				tx_byte_status[snd_ch] = BYTE_FULL;
+				tx_bit_cnt[snd_ch] = 0;
+				mydelete(tx_data[snd_ch], 0, 1);
+			}
+			else
+				tx_frame_status[snd_ch] = FRAME_EMPTY;
+		}
+		if (tx_byte_status[snd_ch] == BYTE_FULL)
+		{
+			// il2p sends high order bit first
+
+			bit = tx_bit_stream[snd_ch] >> 7;			// top bit to bottom
+
+			tx_bit_stream[snd_ch] = tx_bit_stream[snd_ch] << 1;
+			tx_bit_cnt[snd_ch]++;
+			tx_fx25_size_cnt[snd_ch]++;
+			if (tx_bit_cnt[snd_ch] >= 8)
+				tx_byte_status[snd_ch] = BYTE_EMPTY;
+			if (tx_fx25_size_cnt[snd_ch] == tx_fx25_size[snd_ch])
+				tx_frame_status[snd_ch] = FRAME_EMPTY;
+		}
+	}
+
+	if (tx_frame_status[snd_ch] == FRAME_EMPTY)
+	{
+		il2p_get_new_frame(snd_ch, &all_frame_buf[snd_ch]);
+
+		switch (tx_frame_status[snd_ch])
+		{
+		case FRAME_NEW_FRAME:
+			tx_frame_status[snd_ch] = FRAME_FULL;
+			break;
+
+		case FRAME_NO_FRAME:
+			tx_tail_cnt[snd_ch] = 0;
+			tx_frame_status[snd_ch] = FRAME_EMPTY;
+			tx_status[snd_ch] = TX_TAIL;
+			break;
+		}
+	}
+	return bit;
+}
+
+
+
diff --git a/kiss.h b/kiss.h
new file mode 100644
index 0000000..1dc40da
--- /dev/null
+++ b/kiss.h
@@ -0,0 +1,24 @@
+
+/* 
+ * Name:	kiss.h
+ *
+ * This is for the pseudo terminal KISS interface.
+ */
+
+
+#include "ax25_pad.h"		/* for packet_t */
+
+#include "config.h"
+
+#include "kiss_frame.h"		// for struct kissport_status_s
+
+
+void kisspt_init (struct misc_config_s *misc_config);
+
+void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf,  int flen,
+		struct kissport_status_s *notused1, int notused2);
+
+void kisspt_set_debug (int n);
+
+
+/* end kiss.h */
diff --git a/kiss_frame.h b/kiss_frame.h
new file mode 100644
index 0000000..941f5c0
--- /dev/null
+++ b/kiss_frame.h
@@ -0,0 +1,126 @@
+
+/* kiss_frame.h */
+
+#ifndef KISS_FRAME_H
+#define KISS_FRAME_H
+
+
+#include "audio.h"		/* for struct audio_s */
+
+
+/*
+ * The first byte of a KISS frame has:
+ *	channel in upper nybble.
+ *	command in lower nybble.
+ */
+
+#define KISS_CMD_DATA_FRAME	0
+#define KISS_CMD_TXDELAY	1
+#define KISS_CMD_PERSISTENCE	2
+#define KISS_CMD_SLOTTIME	3
+#define KISS_CMD_TXTAIL		4
+#define KISS_CMD_FULLDUPLEX	5
+#define KISS_CMD_SET_HARDWARE	6
+#define XKISS_CMD_DATA		12	// Not supported. http://he.fi/pub/oh7lzb/bpq/multi-kiss.pdf
+#define XKISS_CMD_POLL		14	// Not supported.
+#define KISS_CMD_END_KISS	15
+
+
+
+/*
+ * Special characters used by SLIP protocol.
+ */
+
+#define FEND 0xC0
+#define FESC 0xDB
+#define TFEND 0xDC
+#define TFESC 0xDD
+
+
+
+enum kiss_state_e {
+	KS_SEARCHING = 0,	/* Looking for FEND to start KISS frame. */
+				/* Must be 0 so we can simply zero whole structure to initialize. */
+	KS_COLLECTING};		/* In process of collecting KISS frame. */
+
+
+#define MAX_KISS_LEN 2048	/* Spec calls for at least 1024. */
+				/* Might want to make it longer to accommodate */
+				/* maximum packet length. */
+
+#define MAX_NOISE_LEN 100
+
+typedef struct kiss_frame_s {
+	
+	enum kiss_state_e state;
+
+	unsigned char kiss_msg[MAX_KISS_LEN];
+				/* Leading FEND is optional. */
+				/* Contains escapes and ending FEND. */
+	int kiss_len;
+
+	unsigned char noise[MAX_NOISE_LEN];
+	int noise_len;
+
+} kiss_frame_t;
+
+
+// This is used only for TCPKISS but it put in kissnet.h,
+// there would be a circular dependency between the two header files.
+// Each KISS TCP port has its own status block.
+
+struct kissport_status_s {
+
+	struct kissport_status_s *pnext;	// To next in list.
+
+	volatile int arg2;			// temp for passing second arg into
+						// kissnet_listen_thread
+
+	int tcp_port;				// default 8001
+
+	int chan;				// Radio channel for this tcp port.
+						// -1 for all.
+
+	// The default is a limit of 3 client applications at the same time.
+	// You can increase the limit by changing the line below.
+	// A larger number consumes more resources so don't go crazy by making it larger than needed.
+	// TODO:  Should this be moved to direwolf.h so max number of audio devices
+	// client apps are in the same place?
+
+#define MAX_NET_CLIENTS 3
+
+	int client_sock[MAX_NET_CLIENTS];
+				/* File descriptor for socket for */
+				/* communication with client application. */
+				/* Set to -1 if not connected. */
+				/* (Don't use SOCKET type because it is unsigned.) */
+
+	kiss_frame_t kf[MAX_NET_CLIENTS];
+				/* Accumulated KISS frame and state of decoder. */
+};
+
+
+
+#ifndef KISSUTIL
+void kiss_frame_init (struct audio_s *pa);
+#endif
+
+int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out);
+
+int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out);
+
+void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, struct kissport_status_s *kps, int client,
+			void (*sendfun)(int chan, int kiss_cmd, unsigned char *fbuf, int flen, struct kissport_status_s *onlykps, int onlyclient));
+
+typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
+
+void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, struct kissport_status_s *kps, int client,
+			void (*sendfun)(int chan, int kiss_cmd, unsigned char *fbuf, int flen, struct kissport_status_s *onlykps, int onlyclient));
+
+void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len);
+
+
+#endif  // KISS_FRAME_H
+
+
+/* end kiss_frame.h */
diff --git a/kiss_mode.c b/kiss_mode.c
index eb21953..5adba5d 100644
--- a/kiss_mode.c
+++ b/kiss_mode.c
@@ -473,7 +473,7 @@ void sendAckModeAcks(int snd_ch)
 
 		// Socket to reply to is on end
 
-		Msg += (temp->Length - 4);
+		Msg += (temp->Length - sizeof(void *));
 
 		memcpy(&socket, Msg, sizeof(void *));
 
diff --git a/kissnet.h b/kissnet.h
new file mode 100644
index 0000000..469e4e6
--- /dev/null
+++ b/kissnet.h
@@ -0,0 +1,29 @@
+
+/* 
+ * Name:	kissnet.h
+ */
+
+#ifndef KISSNET_H
+#define KISSNET_H
+
+#include "ax25_pad.h"		/* for packet_t */
+
+#include "config.h"
+
+#include "kiss_frame.h"
+
+
+
+void kissnet_init (struct misc_config_s *misc_config);
+
+void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf,  int flen,
+			struct kissport_status_s *onlykps, int onlyclient);
+
+void kiss_net_set_debug (int n);
+
+void kissnet_copy (unsigned char *kiss_msg, int kiss_len, int chan, int cmd, struct kissport_status_s *from_kps, int from_client);
+
+
+#endif  // KISSNET_H
+
+/* end kissnet.h */
diff --git a/kissserial.h b/kissserial.h
new file mode 100644
index 0000000..44fb3c3
--- /dev/null
+++ b/kissserial.h
@@ -0,0 +1,23 @@
+
+/* 
+ * Name:	kissserial.h
+ */
+
+
+#include "ax25_pad.h"		/* for packet_t */
+
+#include "config.h"
+
+#include "kiss_frame.h"
+
+
+void kissserial_init (struct misc_config_s *misc_config);
+
+void kissserial_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf,  int flen,
+		struct kissport_status_s *notused1, int notused2);
+
+
+void kissserial_set_debug (int n);
+
+
+/* end kissserial.h */
diff --git a/latlong.h b/latlong.h
new file mode 100644
index 0000000..ae98fe6
--- /dev/null
+++ b/latlong.h
@@ -0,0 +1,24 @@
+
+/* latlong.h */
+
+
+/* Use this value for unknown latitude/longitude or other values. */
+
+#define G_UNKNOWN (-999999)
+
+
+void latitude_to_str (double dlat, int ambiguity, char *slat);
+void longitude_to_str (double dlong, int ambiguity, char *slong);
+
+void latitude_to_comp_str (double dlat, char *clat);
+void longitude_to_comp_str (double dlon, char *clon);
+
+void latitude_to_nmea (double dlat, char *slat, char *hemi);
+void longitude_to_nmea (double dlong, char *slong, char *hemi);
+
+double latitude_from_nmea (char *pstr, char *phemi);
+double longitude_from_nmea (char *pstr, char *phemi);
+
+double ll_distance_km (double lat1, double lon1, double lat2, double lon2);
+
+int ll_from_grid_square (char *maidenhead, double *dlat, double *dlon);
\ No newline at end of file
diff --git a/libfftw3f-64-3.lib b/libfftw3f-64-3.lib
new file mode 100644
index 0000000000000000000000000000000000000000..e605463135599f1275e44c396d134c591d209ebf
GIT binary patch
literal 247600
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
Z$}G8u*46tWEU3a<(cj7OH-SpA{|A^d&&U7(

literal 0
HcmV?d00001

diff --git a/log.h b/log.h
new file mode 100644
index 0000000..3afb6b1
--- /dev/null
+++ b/log.h
@@ -0,0 +1,19 @@
+
+/* log.h */
+
+
+#include "hdlc_rec2.h"		// for retry_t
+
+#include "decode_aprs.h"	// for decode_aprs_t
+
+#include "ax25_pad.h"
+
+
+
+void log_init (int daily_names, char *path);
+
+void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries);
+
+void log_rr_bits (decode_aprs_t *A, packet_t pp);
+
+void log_term (void); 	
\ No newline at end of file
diff --git a/main.cpp b/main.cpp
index 34973b9..402e849 100644
--- a/main.cpp
+++ b/main.cpp
@@ -67,7 +67,7 @@ int main(int argc, char *argv[])
 		w = new QtSoundModem();
 
 		char Title[128];
-		sprintf(Title, "QtSoundModem Version %s Ports %d/%d", VersionString, AGWPort, KISSPort);
+		sprintf(Title, "QtSoundModem Version %s Ports %d%s/%d%s", VersionString, AGWPort, AGWServ ? "*" : "", KISSPort, KISSServ ? "*" : "");
 		w->setWindowTitle(Title);
 
 		w->show();
diff --git a/mgn_icon.h b/mgn_icon.h
new file mode 100644
index 0000000..c870bc0
--- /dev/null
+++ b/mgn_icon.h
@@ -0,0 +1,276 @@
+
+
+/* 
+ * MGN_icon.h 
+ *
+ * Waypoint icon codes for use in the $PMGNWPL sentence.
+ *
+ * Derived from Data Transmission Protocol For Magellan Products - version 2.11, March 2003
+ *
+ * http://www.gpsinformation.org/mag-proto-2-11.pdf
+ *
+ *
+ * That's 13 years ago.  There should be something newer available but I can't find it.
+ *
+ * The is based on the newer models at the time.  Earlier models had shorter incompatible icon lists.
+ */
+
+
+
+#define MGN_crossed_square "a"
+#define MGN_box "b"
+#define MGN_house "c"
+#define MGN_aerial "d"
+#define MGN_airport "e"
+#define MGN_amusement_park "f"
+#define MGN_ATM "g"
+#define MGN_auto_repair "h"
+#define MGN_boating "I"
+#define MGN_camping "j"
+#define MGN_exit_ramp "k"
+#define MGN_first_aid "l"
+#define MGN_nav_aid "m"
+#define MGN_buoy "n"
+#define MGN_fuel "o"
+#define MGN_garden "p"
+#define MGN_golf "q"
+#define MGN_hotel "r"
+#define MGN_hunting_fishing "s"
+#define MGN_large_city "t"
+#define MGN_lighthouse "u"
+#define MGN_major_city "v"
+#define MGN_marina "w"
+#define MGN_medium_city "x"
+#define MGN_museum "y"
+#define MGN_obstruction "z"
+#define MGN_park "aa"
+#define MGN_resort "ab"
+#define MGN_restaurant "ac"
+#define MGN_rock "ad"
+#define MGN_scuba "ae"
+#define MGN_RV_service "af"
+#define MGN_shooting "ag"
+#define MGN_sight_seeing "ah"
+#define MGN_small_city "ai"
+#define MGN_sounding "aj"
+#define MGN_sports_arena "ak"
+#define MGN_tourist_info "al"
+#define MGN_truck_service "am"
+#define MGN_winery "an"
+#define MGN_wreck "ao"
+#define MGN_zoo "ap"
+
+
+/*
+ * Mapping from APRS symbols to Magellan.
+ *
+ * This is a bit of a challenge because there 
+ * are no icons for moving objects.
+ * We can use airport for flying things but 
+ * what about wheeled transportation devices?
+ */
+
+// TODO:  NEEDS MORE WORK!!!
+
+
+#define MGN_default MGN_crossed_square
+
+#define SYMTAB_SIZE 95
+
+static const char mgn_primary_symtab[SYMTAB_SIZE][3] =  {
+
+	MGN_default,		//     00  	 --no-symbol--
+	MGN_default,		//  !  01  	 Police, Sheriff
+	MGN_default,		//  "  02  	 reserved  (was rain)
+	MGN_aerial,		//  #  03  	 DIGI (white center)
+	MGN_default,		//  $  04  	 PHONE
+	MGN_aerial,		//  %  05  	 DX CLUSTER
+	MGN_aerial,		//  &  06  	 HF GATEway
+	MGN_airport,		//  '  07  	 Small AIRCRAFT
+	MGN_aerial,		//  (  08  	 Mobile Satellite Station
+	MGN_default,		//  )  09  	 Wheelchair (handicapped)
+	MGN_default,		//  *  10  	 SnowMobile
+	MGN_default,		//  +  11  	 Red Cross
+	MGN_default,		//  ,  12  	 Boy Scouts
+	MGN_house,		//  -  13  	 House QTH (VHF)
+	MGN_default,		//  .  14  	 X
+	MGN_default,		//  /  15  	 Red Dot
+	MGN_default,		//  0  16  	 # circle (obsolete)
+	MGN_default,		//  1  17  	 TBD
+	MGN_default,		//  2  18  	 TBD
+	MGN_default,		//  3  19  	 TBD
+	MGN_default,		//  4  20  	 TBD
+	MGN_default,		//  5  21  	 TBD
+	MGN_default,		//  6  22  	 TBD
+	MGN_default,		//  7  23  	 TBD
+	MGN_default,		//  8  24  	 TBD
+	MGN_default,		//  9  25  	 TBD
+	MGN_default,		//  :  26  	 FIRE
+	MGN_camping,		//  ;  27  	 Campground (Portable ops)
+	MGN_default,		//  <  28  	 Motorcycle
+	MGN_default,		//  =  29  	 RAILROAD ENGINE
+	MGN_default,		//  >  30  	 CAR
+	MGN_default,		//  ?  31  	 SERVER for Files
+	MGN_default,		//  @  32  	 HC FUTURE predict (dot)
+	MGN_first_aid,		//  A  33  	 Aid Station
+	MGN_aerial,		//  B  34  	 BBS or PBBS
+	MGN_boating,		//  C  35  	 Canoe
+	MGN_default,		//  D  36  	 
+	MGN_default,		//  E  37  	 EYEBALL (Eye catcher!)
+	MGN_default,		//  F  38  	 Farm Vehicle (tractor)
+	MGN_default,		//  G  39  	 Grid Square (6 digit)
+	MGN_hotel,		//  H  40  	 HOTEL (blue bed symbol)
+	MGN_aerial,		//  I  41  	 TcpIp on air network stn
+	MGN_default,		//  J  42  	 
+	MGN_default,		//  K  43  	 School
+	MGN_default,		//  L  44  	 PC user
+	MGN_default,		//  M  45  	 MacAPRS
+	MGN_aerial,		//  N  46  	 NTS Station
+	MGN_airport,		//  O  47  	 BALLOON
+	MGN_default,		//  P  48  	 Police
+	MGN_default,		//  Q  49  	 TBD
+	MGN_RV_service,		//  R  50  	 REC. VEHICLE
+	MGN_airport,		//  S  51  	 SHUTTLE
+	MGN_default,		//  T  52  	 SSTV
+	MGN_default,		//  U  53  	 BUS
+	MGN_default,		//  V  54  	 ATV
+	MGN_default,		//  W  55  	 National WX Service Site
+	MGN_default,		//  X  56  	 HELO
+	MGN_boating,		//  Y  57  	 YACHT (sail)
+	MGN_default,		//  Z  58  	 WinAPRS
+	MGN_default,		//  [  59  	 Human/Person (HT)
+	MGN_default,		//  \  60  	 TRIANGLE(DF station)
+	MGN_default,		//  ]  61  	 MAIL/PostOffice(was PBBS)
+	MGN_airport,		//  ^  62  	 LARGE AIRCRAFT
+	MGN_default,		//  _  63  	 WEATHER Station (blue)
+	MGN_aerial,		//  `  64  	 Dish Antenna
+	MGN_default,		//  a  65  	 AMBULANCE
+	MGN_default,		//  b  66  	 BIKE
+	MGN_default,		//  c  67  	 Incident Command Post
+	MGN_default,		//  d  68  	 Fire dept
+	MGN_zoo,		//  e  69  	 HORSE (equestrian)
+	MGN_default,		//  f  70  	 FIRE TRUCK
+	MGN_airport,		//  g  71  	 Glider
+	MGN_default,		//  h  72  	 HOSPITAL
+	MGN_default,		//  i  73  	 IOTA (islands on the air)
+	MGN_default,		//  j  74  	 JEEP
+	MGN_default,		//  k  75  	 TRUCK
+	MGN_default,		//  l  76  	 Laptop
+	MGN_aerial,		//  m  77  	 Mic-E Repeater
+	MGN_default,		//  n  78  	 Node (black bulls-eye)
+	MGN_default,		//  o  79  	 EOC
+	MGN_zoo,		//  p  80  	 ROVER (puppy, or dog)
+	MGN_default,		//  q  81  	 GRID SQ shown above 128 m
+	MGN_aerial,		//  r  82  	 Repeater
+	MGN_default,		//  s  83  	 SHIP (pwr boat)
+	MGN_default,		//  t  84  	 TRUCK STOP
+	MGN_default,		//  u  85  	 TRUCK (18 wheeler)
+	MGN_default,		//  v  86  	 VAN
+	MGN_default,		//  w  87  	 WATER station
+	MGN_aerial,		//  x  88  	 xAPRS (Unix)
+	MGN_aerial,		//  y  89  	 YAGI @ QTH
+	MGN_default,		//  z  90  	 TBD
+	MGN_default,		//  {  91  	 
+	MGN_default,		//  |  92  	 TNC Stream Switch
+	MGN_default,		//  }  93  	 
+	MGN_default };		//  ~  94  	 TNC Stream Switch
+
+
+static const char mgn_alternate_symtab[SYMTAB_SIZE][3] =  {
+
+	MGN_default,		//     00  	 --no-symbol--
+	MGN_default,		//  !  01  	 EMERGENCY (!)
+	MGN_default,		//  "  02  	 reserved
+	MGN_aerial,		//  #  03  	 OVERLAY DIGI (green star)
+	MGN_ATM,		//  $  04  	 Bank or ATM  (green box)
+	MGN_default,		//  %  05  	 Power Plant with overlay
+	MGN_aerial,		//  &  06  	 I=Igte IGate R=RX T=1hopTX 2=2hopTX
+	MGN_default,		//  '  07  	 Crash (& now Incident sites)
+	MGN_default,		//  (  08  	 CLOUDY (other clouds w ovrly)
+	MGN_aerial,		//  )  09  	 Firenet MEO, MODIS Earth Obs.
+	MGN_default,		//  *  10  	 SNOW (& future ovrly codes)
+	MGN_default,		//  +  11  	 Church
+	MGN_default,		//  ,  12  	 Girl Scouts
+	MGN_house,		//  -  13  	 House (H=HF) (O = Op Present)
+	MGN_default,		//  .  14  	 Ambiguous (Big Question mark)
+	MGN_default,		//  /  15  	 Waypoint Destination
+	MGN_default,		//  0  16  	 CIRCLE (E/I/W=IRLP/Echolink/WIRES)
+	MGN_default,		//  1  17  	 
+	MGN_default,		//  2  18  	 
+	MGN_default,		//  3  19  	
+	MGN_default,		//  4  20  
+	MGN_default,		//  5  21 
+	MGN_default,		//  6  22
+	MGN_default,		//  7  23
+	MGN_aerial,		//  8  24  	 802.11 or other network node
+	MGN_fuel,		//  9  25  	 Gas Station (blue pump)
+	MGN_default,		//  :  26  	 Hail (& future ovrly codes)
+	MGN_park,		//  ;  27  	 Park/Picnic area
+	MGN_default,		//  <  28  	 ADVISORY (one WX flag)
+	MGN_default,		//  =  29  	 APRStt Touchtone (DTMF users)
+	MGN_default,		//  >  30  	 OVERLAID CAR
+	MGN_tourist_info,	//  ?  31  	 INFO Kiosk  (Blue box with ?)
+	MGN_default,		//  @  32  	 HURRICANE/Trop-Storm
+	MGN_box,		//  A  33  	 overlayBOX DTMF & RFID & XO
+	MGN_default,		//  B  34  	 Blwng Snow (& future codes)
+	MGN_boating,		//  C  35  	 Coast Guard
+	MGN_default,		//  D  36  	 Drizzle (proposed APRStt)
+	MGN_default,		//  E  37  	 Smoke (& other vis codes)
+	MGN_default,		//  F  38  	 Freezng rain (&future codes)
+	MGN_default,		//  G  39  	 Snow Shwr (& future ovrlys)
+	MGN_default,		//  H  40  	 Haze (& Overlay Hazards)
+	MGN_default,		//  I  41  	 Rain Shower
+	MGN_default,		//  J  42  	 Lightning (& future ovrlys)
+	MGN_default,		//  K  43  	 Kenwood HT (W)
+	MGN_lighthouse,		//  L  44  	 Lighthouse
+	MGN_default,		//  M  45  	 MARS (A=Army,N=Navy,F=AF)
+	MGN_buoy,		//  N  46  	 Navigation Buoy
+	MGN_airport,		//  O  47  	 Rocket
+	MGN_default,		//  P  48  	 Parking
+	MGN_default,		//  Q  49  	 QUAKE
+	MGN_restaurant,		//  R  50  	 Restaurant
+	MGN_aerial,		//  S  51  	 Satellite/Pacsat
+	MGN_default,		//  T  52  	 Thunderstorm
+	MGN_default,		//  U  53  	 SUNNY
+	MGN_nav_aid,		//  V  54  	 VORTAC Nav Aid
+	MGN_default,		//  W  55  	 # NWS site (NWS options)
+	MGN_default,		//  X  56  	 Pharmacy Rx (Apothicary)
+	MGN_aerial,		//  Y  57  	 Radios and devices
+	MGN_default,		//  Z  58  	 
+	MGN_default,		//  [  59  	 W.Cloud (& humans w Ovrly)
+	MGN_default,		//  \  60  	 New overlayable GPS symbol
+	MGN_default,		//  ]  61  	 
+	MGN_airport,		//  ^  62  	 # Aircraft (shows heading)
+	MGN_default,		//  _  63  	 # WX site (green digi)
+	MGN_default,		//  `  64  	 Rain (all types w ovrly)
+	MGN_aerial,		//  a  65  	 ARRL, ARES, WinLINK
+	MGN_default,		//  b  66  	 Blwng Dst/Snd (& others)
+	MGN_default,		//  c  67  	 CD triangle RACES/SATERN/etc
+	MGN_default,		//  d  68  	 DX spot by callsign
+	MGN_default,		//  e  69  	 Sleet (& future ovrly codes)
+	MGN_default,		//  f  70  	 Funnel Cloud
+	MGN_default,		//  g  71  	 Gale Flags
+	MGN_default,		//  h  72  	 Store. or HAMFST Hh=HAM store
+	MGN_box,		//  i  73  	 BOX or points of Interest
+	MGN_default,		//  j  74  	 WorkZone (Steam Shovel)
+	MGN_default,		//  k  75  	 Special Vehicle SUV,ATV,4x4
+	MGN_default,		//  l  76  	 Areas      (box,circles,etc)
+	MGN_default,		//  m  77  	 Value Sign (3 digit display)
+	MGN_default,		//  n  78  	 OVERLAY TRIANGLE
+	MGN_default,		//  o  79  	 small circle
+	MGN_default,		//  p  80  	 Prtly Cldy (& future ovrlys)
+	MGN_default,		//  q  81  	 
+	MGN_default,		//  r  82  	 Restrooms
+	MGN_default,		//  s  83  	 OVERLAY SHIP/boat (top view)
+	MGN_default,		//  t  84  	 Tornado
+	MGN_default,		//  u  85  	 OVERLAID TRUCK
+	MGN_default,		//  v  86  	 OVERLAID Van
+	MGN_default,		//  w  87  	 Flooding
+	MGN_wreck,		//  x  88  	 Wreck or Obstruction ->X<-
+	MGN_default,		//  y  89  	 Skywarn
+	MGN_default,		//  z  90  	 OVERLAID Shelter
+	MGN_default,		//  {  91  	 Fog (& future ovrly codes)
+	MGN_default,		//  |  92  	 TNC Stream Switch
+	MGN_default,		//  }  93  	 
+	MGN_default };		//  ~  94  	 TNC Stream Switch
+
diff --git a/mheard.h b/mheard.h
new file mode 100644
index 0000000..f8466ba
--- /dev/null
+++ b/mheard.h
@@ -0,0 +1,20 @@
+
+
+/* mheard.h */
+
+#include "decode_aprs.h"	// for decode_aprs_t
+
+
+void mheard_init (int debug);
+
+void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries);
+
+void mheard_save_is (char *ptext);
+
+int mheard_count (int max_hops, int time_limit);
+
+int mheard_was_recently_nearby (char *role, char *callsign, int time_limit, int max_hops, double dlat, double dlon, double km);
+
+void mheard_set_msp (char *callsign, int num);
+
+int mheard_get_msp (char *callsign);
\ No newline at end of file
diff --git a/morse.h b/morse.h
new file mode 100644
index 0000000..e34dd7b
--- /dev/null
+++ b/morse.h
@@ -0,0 +1,8 @@
+/* morse.h */
+
+int morse_init (struct audio_s *audio_config_p, int amp) ;
+
+int morse_send (int chan, char *str, int wpm, int txdelay, int txtail);
+
+#define MORSE_DEFAULT_WPM 10
+
diff --git a/multi_modem.h b/multi_modem.h
new file mode 100644
index 0000000..de3061e
--- /dev/null
+++ b/multi_modem.h
@@ -0,0 +1,24 @@
+/* multi_modem.h */
+
+#ifndef MULTI_MODEM_H
+#define MULTI_MODEM 1
+
+/* Needed for typedef retry_t. */
+#include "hdlc_rec2.h"
+
+/* Needed for struct audio_s */
+#include "audio.h"
+
+
+void multi_modem_init (struct audio_s *pmodem); 
+
+void multi_modem_process_sample (int c, int audio_sample);
+
+int multi_modem_get_dc_average (int chan);
+
+// Deprecated.  Replace with ...packet
+void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries, int is_fx25);
+
+void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, int is_fx25);
+
+#endif
diff --git a/pfilter.h b/pfilter.h
new file mode 100644
index 0000000..d54e056
--- /dev/null
+++ b/pfilter.h
@@ -0,0 +1,13 @@
+
+/* pfilter.h */
+
+
+#include "igate.h"		// for igate_config_s
+
+
+
+void pfilter_init (struct igate_config_s *p_igate_config, int debug_level);
+
+int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs);
+
+int is_telem_metadata (char *infop);
\ No newline at end of file
diff --git a/pktARDOP.c b/pktARDOP.c
index 2f38059..a2e7473 100644
--- a/pktARDOP.c
+++ b/pktARDOP.c
@@ -48,7 +48,7 @@ extern int PacketMonLength;
 
 #define ARDOPBufferSize 12000 * 100
 
-short ARDOPTXBuffer[4][12000 * 100];		// Enough to hold whole frame of samples
+short ARDOPTXBuffer[4][ARDOPBufferSize];		// Enough to hold whole frame of samples
 
 int ARDOPTXLen[4] = { 0,0,0,0 };			// Length of frame
 int ARDOPTXPtr[4] = { 0,0,0,0 };			// Tx Pointer
diff --git a/ptt.h b/ptt.h
new file mode 100644
index 0000000..6e75253
--- /dev/null
+++ b/ptt.h
@@ -0,0 +1,26 @@
+
+
+#ifndef PTT_H
+#define PTT_H 1
+
+
+#include "audio.h"	/* for struct audio_s and definitions for octype values */
+
+
+void ptt_set_debug(int debug);
+
+void ptt_init (struct audio_s *p_modem);
+
+void ptt_set (int octype, int chan, int ptt); 
+
+void ptt_term (void);
+
+int get_input (int it, int chan);
+
+#endif
+
+
+/* end ptt.h */
+
+
+
diff --git a/recv.h b/recv.h
new file mode 100644
index 0000000..3201991
--- /dev/null
+++ b/recv.h
@@ -0,0 +1,6 @@
+
+/* recv.h */
+
+void recv_init (struct audio_s *pa);
+
+void recv_process (void);
\ No newline at end of file
diff --git a/redecode.h b/redecode.h
new file mode 100644
index 0000000..ffd9a95
--- /dev/null
+++ b/redecode.h
@@ -0,0 +1,15 @@
+
+
+#ifndef REDECODE_H
+#define REDECODE_H 1
+
+#include "rrbb.h"	
+
+
+extern void redecode_init (struct audio_s *p_audio_config);
+
+
+#endif
+
+/* end redecode.h */
+
diff --git a/release/moc_predefs-DESKTOP-MHE5LO8.h b/release/moc_predefs-DESKTOP-MHE5LO8.h
new file mode 100644
index 0000000..4c9c0c7
--- /dev/null
+++ b/release/moc_predefs-DESKTOP-MHE5LO8.h
@@ -0,0 +1,11 @@
+#define _MSC_EXTENSIONS 
+#define _INTEGRAL_MAX_BITS 64
+#define _MSC_VER 1916
+#define _MSC_FULL_VER 191627043
+#define _MSC_BUILD 0
+#define _WIN32 
+#define _M_IX86 600
+#define _M_IX86_FP 2
+#define _CPPRTTI 
+#define _MT 
+#define _DLL 
diff --git a/rpack.h b/rpack.h
new file mode 100644
index 0000000..d972a54
--- /dev/null
+++ b/rpack.h
@@ -0,0 +1,94 @@
+
+/*------------------------------------------------------------------
+ *
+ * File:        rpack.h
+ *
+ * Purpose:   	Definition of Garmin Rino message format.
+ *		
+ * References:	http://www.radio-active.net.au/web3/APRS/Resources/RINO
+ *
+ *		http://www.radio-active.net.au/web3/APRS/Resources/RINO/OnAir
+ *
+ *---------------------------------------------------------------*/
+
+
+#ifndef RPACK_H
+#define RPACK_H 1
+
+
+#define RPACK_FRAME_LEN 168
+
+
+#ifdef RPACK_C		/* Expose private details */
+
+
+ 
+// Transmission order is LSB first. 
+
+struct __attribute__((__packed__)) rpack_s {
+
+	int lat;		// Latitude.
+				// Signed integer.  Scaled by 2**30/90.
+
+	int lon;		// Longitude.  Same encoding.
+
+	char unknown1;		// Unproven theory: altitude.	
+	char unknown2;		
+
+	unsigned name0:6;	// 10 character name.
+	unsigned name1:6;	// Bit packing is implementation dependent.
+	unsigned name2:6;	// Should rewrite to be more portable.
+	unsigned name3:6;
+	unsigned name4:6;
+	unsigned name5:6;
+	unsigned name6:6;
+	unsigned name7:6;
+	unsigned name8:6;
+	unsigned name9:6;
+
+	unsigned symbol:5;	
+
+	unsigned unknown3:7;		
+				
+	
+//	unsigned crc:16;	// Safe bet this is CRC for error checking.
+
+	unsigned char crc1;
+	unsigned char crc2;
+
+	char dummy[3];		// Total size should be 24 bytes if no gaps.
+
+};
+
+#else			/* Show only public interface.  */
+
+
+struct rpack_s {
+	char stuff[24];
+};
+
+
+#endif
+
+
+
+void rpack_set_bit (struct rpack_s *rp, int position, int value);
+
+int rpack_is_valid (struct rpack_s *rp);
+
+int rpack_get_bit (struct rpack_s *rp, int position);
+
+double rpack_get_lat (struct rpack_s *rp);
+
+double rpack_get_lon (struct rpack_s *rp);
+
+int rpack_get_symbol (struct rpack_s *rp);
+
+void rpack_get_name (struct rpack_s *rp, char *str);
+
+
+
+#endif
+
+/* end rpack.h */
+	
diff --git a/rrbb.h b/rrbb.h
new file mode 100644
index 0000000..4b28372
--- /dev/null
+++ b/rrbb.h
@@ -0,0 +1,92 @@
+
+#ifndef RRBB_H
+
+#define RRBB_H
+
+
+#define FASTER13 1		// Don't pack 8 samples per byte.
+
+
+//typedef short slice_t;
+
+
+/* 
+ * Maximum size (in bytes) of an AX.25 frame including the 2 octet FCS. 
+ */
+
+#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)	
+
+/*
+ * Maximum number of bits in AX.25 frame excluding the flags.
+ * Adequate for extreme case of bit stuffing after every 5 bits
+ * which could never happen.
+ */
+
+#define MAX_NUM_BITS (MAX_FRAME_LEN * 8 * 6 / 5)
+
+typedef struct rrbb_s {
+	int magic1;
+	struct rrbb_s* nextp;	/* Next pointer to maintain a queue. */
+
+	int chan;		/* Radio channel from which it was received. */
+	int subchan;		/* Which modem when more than one per channel. */
+	int slice;		/* Which slicer. */
+
+	alevel_t alevel;	/* Received audio level at time of frame capture. */
+	unsigned int len;	/* Current number of samples in array. */
+
+	int is_scrambled;	/* Is data scrambled G3RUH / K9NG style? */
+	int descram_state;	/* Descrambler state before first data bit of frame. */
+	int prev_descram;	/* Previous descrambled bit. */
+
+	unsigned char fdata[MAX_NUM_BITS];
+
+	int magic2;
+} *rrbb_t;
+
+
+
+rrbb_t rrbb_new (int chan, int subchan, int slice, int is_scrambled, int descram_state, int prev_descram);
+
+void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state, int prev_descram);
+
+
+static inline /*__attribute__((always_inline))*/ void rrbb_append_bit (rrbb_t b, const unsigned char val)
+{
+	if (b->len >= MAX_NUM_BITS) {
+	  return;	/* Silently discard if full. */
+	}
+	b->fdata[b->len] = val;
+	b->len++;
+}
+
+static inline /*__attribute__((always_inline))*/ unsigned char rrbb_get_bit (const rrbb_t b, const int ind)
+{
+	return (b->fdata[ind]);
+}
+
+
+void rrbb_chop8 (rrbb_t b);
+
+int rrbb_get_len (rrbb_t b);
+
+//void rrbb_flip_bit (rrbb_t b, unsigned int ind);
+
+void rrbb_delete (rrbb_t b);
+
+void rrbb_set_nextp (rrbb_t b, rrbb_t np);
+rrbb_t rrbb_get_nextp (rrbb_t b);
+
+int rrbb_get_chan (rrbb_t b);
+int rrbb_get_subchan (rrbb_t b);
+int rrbb_get_slice (rrbb_t b);
+
+void rrbb_set_audio_level (rrbb_t b, alevel_t alevel);
+alevel_t rrbb_get_audio_level (rrbb_t b);
+
+int rrbb_get_is_scrambled (rrbb_t b);
+int rrbb_get_descram_state (rrbb_t b);
+int rrbb_get_prev_descram (rrbb_t b);
+
+
+#endif
diff --git a/serial_port.h b/serial_port.h
new file mode 100644
index 0000000..8a65a0b
--- /dev/null
+++ b/serial_port.h
@@ -0,0 +1,32 @@
+/* serial_port.h */
+
+
+#ifndef SERIAL_PORT_H
+#define SERIAL_PORT_H 1
+
+
+#if __WIN32__
+
+#include <stdlib.h>
+
+typedef HANDLE MYFDTYPE;
+#define MYFDERROR INVALID_HANDLE_VALUE
+
+#else
+
+typedef int MYFDTYPE;
+#define MYFDERROR (-1)
+
+#endif
+
+
+extern MYFDTYPE serial_port_open (char *devicename, int baud);
+
+extern int serial_port_write (MYFDTYPE fd, char *str, int len);
+
+extern int serial_port_get1 (MYFDTYPE fd);
+
+extern void serial_port_close (MYFDTYPE fd);
+
+
+#endif
\ No newline at end of file
diff --git a/server.h b/server.h
new file mode 100644
index 0000000..4cc2ea0
--- /dev/null
+++ b/server.h
@@ -0,0 +1,32 @@
+
+/* 
+ * Name:	server.h
+ */
+
+
+#include "ax25_pad.h"		/* for packet_t */
+
+#include "config.h"
+
+
+void server_set_debug (int n);
+
+void server_init (struct audio_s *audio_config_p, struct misc_config_s *misc_config);
+
+void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf,  int flen);
+
+void server_send_monitored (int chan, packet_t pp, int own_xmit);
+
+int server_callsign_registered_by_client (char *callsign);
+
+
+void server_link_established (int chan, int client, char *remote_call, char *own_call, int incoming);
+
+void server_link_terminated (int chan, int client, char *remote_call, char *own_call, int timeout);
+
+void server_rec_conn_data (int chan, int client, char *remote_call, char *own_call, int pid, char *data_ptr, int data_len);
+
+void server_outstanding_frames_reply (int chan, int client, char *own_call, char *remote_call, int count);
+
+
+/* end server.h */
diff --git a/sm_main.c b/sm_main.c
index 2874011..a75ea5f 100644
--- a/sm_main.c
+++ b/sm_main.c
@@ -28,13 +28,17 @@ void make_core_TXBPF(UCHAR snd_ch, float freq, float width);
 void make_core_INTR(UCHAR snd_ch);
 void make_core_LPF(UCHAR snd_ch, short width);
 void wf_pointer(int snd_ch);
+void dw9600ProcessSample(int snd_ch, short Sample);
+void init_RUH48(int snd_ch);
+void init_RUH96(int snd_ch);
 
-char modes_name[modes_count][20] = 
+char modes_name[modes_count][21] = 
 {
 	"AFSK AX.25 300bd","AFSK AX.25 1200bd","AFSK AX.25 600bd","AFSK AX.25 2400bd",
 	"BPSK AX.25 1200bd","BPSK AX.25 600bd","BPSK AX.25 300bd","BPSK AX.25 2400bd",
 	"QPSK AX.25 4800bd","QPSK AX.25 3600bd","QPSK AX.25 2400bd","BPSK FEC 4x100bd",
-	"DW QPSK V26A 2400bd","DW 8PSK V27 4800bd","DW QPSK V26B 2400bd", "ARDOP Packet"
+	"QPSK V26A 2400bps","8PSK V27 4800bps","QPSK V26B 2400bps", "Not Available",
+	"QPSK V26A 600bps", "8PSK 900bps", "RUH 4800(DW)", "RUH 9600(DW)"
 };
 
 typedef struct wavehdr_tag {
@@ -62,200 +66,9 @@ int TXPort = 8884;
 BOOL Firstwaterfall = 1;
 BOOL Secondwaterfall = 1;
 
-BOOL multiCore = FALSE;
+int multiCore = FALSE;
 
-/*
-type
-  TComboBox  =  class(StdCtrls.TComboBox)
-  private
-    procedure CMMouseWheel(var msg TCMMouseWheel); message CM_MOUSEWHEEL;
-  end;
-  TData16  =  array [0..4095] of smallint;
-  PData16  =  ^TData16;
-  TWaveHeader  =  record
-    RIFF        dword;
-    ChunkLen    integer;
-    WAVE        dword;
-    fmt         dword;
-    FormatLen   integer;
-    Format      word;
-    Channels    word;
-    Frequency   integer;
-    BytesPS     integer;
-    BlockAlign  word;
-    BitsPS      word;
-    data        dword;
-    DataLen     integer
-  end;
-  TForm1  =  class(TForm)
-    Panel5 TPanel;
-    ServerSocket1 TServerSocket;
-    MainMenu1 TMainMenu;
-    Settings1 TMenuItem;
-    OutputVolume1 TMenuItem;
-    InputVolume1 TMenuItem;
-    CoolTrayIcon1 TCoolTrayIcon;
-    ImageList1 TImageList;
-    ABout1 TMenuItem;
-    Panel1 TPanel;
-    Panel2 TPanel;
-    View1 TMenuItem;
-    Firstwaterfall1 TMenuItem;
-    Secondwaterfall1 TMenuItem;
-    Panel3 TPanel;
-    StringGrid1 TStringGrid;
-    Devices1 TMenuItem;
-    Statustable1 TMenuItem;
-    Monitor1 TMenuItem;
-    Panel4 TPanel;
-    PaintBox2 TPaintBox;
-    Filters1 TMenuItem;
-    Clearmonitor1 TMenuItem;
-    RxRichEdit1 TRxRichEdit;
-    MemoPopupMenu1 TPopupMenu;
-    Copytext1 TMenuItem;
-    Label1 TLabel;
-    Label5 TLabel;
-    ApplicationEvents1 TApplicationEvents;
-    PaintBox1 TPaintBox;
-    PaintBox3 TPaintBox;
-    ServerSocket2 TServerSocket;
-    Font1 TMenuItem;
-    FontDialog1 TFontDialog;
-    N1 TMenuItem;
-    Calibration1 TMenuItem;
-    Panel9 TPanel;
-    Panel6 TPanel;
-    Label4 TLabel;
-    Shape2 TShape;
-    ComboBox2 TComboBox;
-    SpinEdit2 TSpinEdit;
-    Panel7 TPanel;
-    Label3 TLabel;
-    Shape1 TShape;
-    ComboBox1 TComboBox;
-    SpinEdit1 TSpinEdit;
-    Panel8 TPanel;
-    Label2 TLabel;
-    TrackBar1 TTrackBar;
-    CheckBox1 TCheckBox;
-    OpenDialog1 TOpenDialog;
-    procedure FormCreate(Sender TObject);
-    procedure TrackBar1Change(Sender TObject);
-    procedure PaintBox1MouseMove(Sender TObject; Shift TShiftState; X,
-      Y Integer);
-    procedure PaintBox1MouseDown(Sender TObject; Button TMouseButton;
-      Shift TShiftState; X, Y Integer);
-    procedure ServerSocket1ClientRead(Sender TObject;
-      Socket TCustomWinSocket);
-    procedure ServerSocket1ClientError(Sender TObject;
-      Socket TCustomWinSocket; ErrorEvent TErrorEvent;
-      var ErrorCode Integer);
-    procedure OutputVolume1Click(Sender TObject);
-    procedure InputVolume1Click(Sender TObject);
-    procedure ServerSocket1ClientConnect(Sender TObject;
-      Socket TCustomWinSocket);
-    procedure ServerSocket1ClientDisconnect(Sender TObject;
-      Socket TCustomWinSocket);
-    procedure CoolTrayIcon1Click(Sender TObject);
-    procedure CoolTrayIcon1Cycle(Sender TObject; NextIndex Integer);
-    procedure ABout1Click(Sender TObject);
-    procedure PaintBox3MouseDown(Sender TObject; Button TMouseButton;
-      Shift TShiftState; X, Y Integer);
-    procedure PaintBox3MouseMove(Sender TObject; Shift TShiftState; X,
-      Y Integer);
-    procedure Firstwaterfall1Click(Sender TObject);
-    procedure Secondwaterfall1Click(Sender TObject);
-    procedure Devices1Click(Sender TObject);
-    procedure Statustable1Click(Sender TObject);
-    procedure Monitor1Click(Sender TObject);
-    procedure FormPaint(Sender TObject);
-    procedure Filters1Click(Sender TObject);
-    procedure SpinEdit1Change(Sender TObject);
-    procedure SpinEdit2Change(Sender TObject);
-    procedure Clearmonitor1Click(Sender TObject);
-    procedure Copytext1Click(Sender TObject);
-    procedure PaintBox3MouseUp(Sender TObject; Button TMouseButton;
-      Shift TShiftState; X, Y Integer);
-    procedure PaintBox1MouseUp(Sender TObject; Button TMouseButton;
-      Shift TShiftState; X, Y Integer);
-    procedure ApplicationEvents1Minimize(Sender TObject);
-    procedure ApplicationEvents1Restore(Sender TObject);
-    procedure ServerSocket2ClientConnect(Sender TObject;
-      Socket TCustomWinSocket);
-    procedure ServerSocket2ClientDisconnect(Sender TObject;
-      Socket TCustomWinSocket);
-    procedure ServerSocket2ClientError(Sender TObject;
-      Socket TCustomWinSocket; ErrorEvent TErrorEvent;
-      var ErrorCode Integer);
-    procedure ServerSocket2ClientRead(Sender TObject;
-      Socket TCustomWinSocket);
-    procedure Font1Click(Sender TObject);
-    procedure Calibration1Click(Sender TObject);
-    procedure ComboBox1Change(Sender TObject);
-    procedure ComboBox1KeyDown(Sender TObject; var Key Word;
-      Shift TShiftState);
-    procedure ComboBox1KeyPress(Sender TObject; var Key Char);
-    procedure ComboBox2Change(Sender TObject);
-    procedure FormDestroy(Sender TObject);
-  private
-    { Private declarations }
-    procedure BufferFull(var Msg TMessage); Message MM_WIM_DATA;
-    procedure BufferFull1(var Msg TMessage); Message MM_WOM_DONE;
-    procedure make_wave_buf(snd_ch byte; buf PChar);
-    procedure disp2(snd_ch byte);
-    procedure create_timer1;
-    procedure free_timer1;
-    procedure show_panels;
-    procedure show_combobox;
-    procedure Timer_Event2;
-    procedure waterfall_init;
-    procedure waterfall_free;
-  public
-    { Public declarations }
-    function get_idx_by_name(name string) word;
-    function frame_monitor(s,code string; tx_stat boolean) string;
-    procedure ChangePriority;
-    procedure put_frame(snd_ch byte; frame,code string; tx_stat,excluded boolean);
-    procedure show_grid;
-    procedure RX2TX(snd_ch byte);
-    procedure TX2RX(snd_ch byte);
-    procedure WriteIni;
-    procedure ReadIni;
-    procedure init_8P4800(snd_ch byte);
-    procedure init_DW2400(snd_ch byte);
-    procedure init_AE2400(snd_ch byte);
-    procedure init_MP400(snd_ch byte);
-    procedure init_Q4800(snd_ch byte);
-    procedure init_Q3600(snd_ch byte);
-    procedure init_Q2400(snd_ch byte);
-    procedure init_P2400(snd_ch byte);
-    procedure init_P1200(snd_ch byte);
-    procedure init_P600(snd_ch byte);
-    procedure init_P300(snd_ch byte);
-    procedure init_300(snd_ch byte);
-    procedure init_600(snd_ch byte);
-    procedure init_1200(snd_ch byte);
-    procedure init_2400(snd_ch byte);
-    procedure init_speed(snd_ch byte);
-    procedure get_filter_values(idx byte; var dbpf,dtxbpf,dbpftap,dlpf,dlpftap word);
-    procedure show_mode_panels;
-    procedure show_modes;
-    procedure show_freq_a;
-    procedure show_freq_b;
-    procedure ChkSndDevName;
-    procedure StartRx;
-    procedure StartTx(snd_ch byte);
-    procedure StopRx;
-    procedure StopTx(snd_ch byte);
-    procedure StartAGW;
-    procedure StartKISS;
-    procedure wf_scale;
-    procedure wf_pointer(snd_ch byte);
-  end;
 
-var
-/*/
 
 BOOL MinOnStart  =  0;
 //RS TReedSolomon;
@@ -273,7 +86,6 @@ int tx_bufsize = 512;
 int rx_bufsize = 512;
 int tx_bufcount = 16;
 int rx_bufcount = 16;
-int fft_size = 2048;
 int  mouse_down[2] = {0, 0};
 //UCHAR * RX_pBuf array[257];
 //  RX_header array[1..256] of TWaveHdr;
@@ -287,16 +99,15 @@ UCHAR tx_buf_num [5] = {0,0,0,0};
 
 extern short active_rx_freq[5];
 
-
+unsigned int pskStates[4] = {0, 0, 0, 0};	
 
 int speed[5] = {0,0,0,0};
 int panels[6] = {1,1,1,1,1};
 
-float fft_window_arr[2048];
-float  fft_s[2048], fft_d[2048];
+short fft_buf[2][8192];
+UCHAR fft_disp[2][1024];
+int fftCount = 0;			// FTF samples collected
 
-short fft_buf[5][2048];
-UCHAR fft_disp[5][2048];
 //  bm array[1..4] of TBitMap;
 //  bm1,bm2,bm3 TBitMap;
 
@@ -304,8 +115,6 @@ UCHAR fft_disp[5][2048];
 //  WaveOutHandle array[1..4] of hWaveOut;
 int RXBufferLength;
 
-short * data1;
-
 int grid_time = 0;
 int fft_mult = 0;
 int fft_spd = 3;
@@ -345,50 +154,7 @@ int MainPriority = 0;
 //  MainThreadHandle THandle;
 UCHAR w_state = WIN_MAXIMIZED;
  
-  /*
-implementation
-
-{$R *.DFM}
-
-uses ax25_mod, ax25_demod, ax25, ax25_l2, ax25_ptt, ax25_agw, ax25_about, rgb_rad,
-  AX25_set, ax25_filter, AX25_modem_set, kiss_mode, ax25_calibration;
-
-procedure TComboBox.CMMouseWheel(var msg TCMMouseWheel);
-begin
-  if SendMessage(GetFocus, CB_GETDROPPEDSTATE, 0, 0)  =  0 then msg.Result  =  1;
-end;
-
-procedure TForm1.ChangePriority;
-begin
-  case MainPriority of
-    0  SetThreadPriority(MainThreadHandle,THREAD_PRIORITY_NORMAL);
-    1  SetThreadPriority(MainThreadHandle,THREAD_PRIORITY_ABOVE_NORMAL);
-    2  SetThreadPriority(MainThreadHandle,THREAD_PRIORITY_HIGHEST);
-    3  SetThreadPriority(MainThreadHandle,THREAD_PRIORITY_TIME_CRITICAL);
-  end;
-end;
-
-procedure TForm1.show_modes;
-var
-  s string;
-begin
-  s = MODEM_CAPTION+" - Ver "+MODEM_VERSION+" - ["+modes_name[Speed[1]];
-  if dualchan then s = s+" - "+modes_name[Speed[2]];
-  form1.Caption = s+"]";
-end;
-
-procedure TForm1.show_freq_a;
-begin
-  SpinEdit1.Value = round(rx_freq[1]);
-  SpinEdit1.Refresh;
-end;
-
-procedure TForm1.show_freq_b;
-begin
-  SpinEdit2.Value = round(rx_freq[2]);
-  SpinEdit2.Refresh;
-end;
-*/
+ 
 void get_filter_values(UCHAR snd_ch)
 {
 	//, unsigned short dbpf,
@@ -450,7 +216,7 @@ void get_filter_values(UCHAR snd_ch)
 		break;
 
 	case SPEED_DW2400:
-	case SPEED_AE2400:
+	case SPEED_2400V26B:
 
 
 		lpf[snd_ch] = MODEM_DW2400_LPF;
@@ -533,8 +299,31 @@ void get_filter_values(UCHAR snd_ch)
 		BPF_tap[snd_ch] = MODEM_2400_BPF_TAP;
 		LPF_tap[snd_ch] = MODEM_2400_LPF_TAP;
 		break;
-	}
 
+
+	case SPEED_Q300:
+	case SPEED_8PSK300:
+
+		lpf[snd_ch] = MODEM_P300_LPF;
+		bpf[snd_ch] = MODEM_P300_BPF;
+		txbpf[snd_ch] = MODEM_P300_TXBPF;
+		BPF_tap[snd_ch] = MODEM_P300_BPF_TAP;
+		LPF_tap[snd_ch] = MODEM_P300_LPF_TAP;
+
+		break;
+
+/*
+
+	case SPEED_Q1200:
+
+		lpf[snd_ch] = MODEM_P1200_LPF;
+		bpf[snd_ch] = MODEM_P1200_BPF;
+		txbpf[snd_ch] = MODEM_P1200_TXBPF;
+		BPF_tap[snd_ch] = MODEM_P1200_BPF_TAP;
+		LPF_tap[snd_ch] = MODEM_P1200_LPF_TAP;
+		break;
+*/
+	}
 }
 
 
@@ -543,6 +332,7 @@ void init_2400(int snd_ch)
 	modem_mode[snd_ch] = MODE_FSK;
 	rx_shift[snd_ch] = 1805;
 	rx_baudrate[snd_ch] = 2400;
+	tx_bitrate[snd_ch] = 2400;
 
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
@@ -557,6 +347,7 @@ void init_1200(int snd_ch)
 		rx_freq[snd_ch] = 1700;
 
 	rx_baudrate[snd_ch] = 1200;
+	tx_bitrate[snd_ch] = 1200;
 
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
@@ -568,6 +359,7 @@ void init_600(int snd_ch)
 	rx_shift[snd_ch] = 450;
 
 	rx_baudrate[snd_ch] = 600;
+	tx_bitrate[snd_ch] = 600;
 
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
@@ -578,6 +370,7 @@ void init_300(int snd_ch)
 	modem_mode[snd_ch] = MODE_FSK;
 	rx_shift[snd_ch] = 200;
 	rx_baudrate[snd_ch] = 300;
+	tx_bitrate[snd_ch] = 300;
 
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
@@ -588,6 +381,7 @@ void init_MP400(int snd_ch)
 	modem_mode[snd_ch] = MODE_MPSK;
 	rx_shift[snd_ch] = 175 /*sbc*/ * 3;
 	rx_baudrate[snd_ch] = 100;
+	tx_bitrate[snd_ch] = 400;
 
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
@@ -602,12 +396,14 @@ void init_8P4800(int snd_ch)
 
 	rx_shift[snd_ch] = 1600;
 	rx_baudrate[snd_ch] = 1600;
+	tx_bitrate[snd_ch] = 4800;
+	pskStates[snd_ch] = 8;
 
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
 }
 
-void init_AE2400(int snd_ch)
+void init_V26B2400(int snd_ch)
 {
 	qpsk_set[snd_ch].mode = QPSK_V26;
 	modem_mode[snd_ch] = MODE_PI4QPSK;
@@ -617,6 +413,8 @@ void init_AE2400(int snd_ch)
 
 	rx_shift[snd_ch] = 1200;
 	rx_baudrate[snd_ch] = 1200;
+	tx_bitrate[snd_ch] = 2400;
+	pskStates[snd_ch] = 8;			// Pretend 8 so quality calc works
 
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
@@ -632,6 +430,8 @@ void init_DW2400(int snd_ch)
 
 	rx_shift[snd_ch] = 1200;
 	rx_baudrate[snd_ch] = 1200;
+	tx_bitrate[snd_ch] = 2400;
+	pskStates[snd_ch] = 4;
 
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
@@ -643,6 +443,9 @@ void init_Q4800(int snd_ch)
 	modem_mode[snd_ch] = MODE_QPSK;
 	rx_shift[snd_ch] = 2400;
 	rx_baudrate[snd_ch] = 2400;
+	tx_bitrate[snd_ch] = 4800;
+	pskStates[snd_ch] = 4;
+
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
 }
@@ -653,6 +456,7 @@ void init_Q3600(int snd_ch)
 	modem_mode[snd_ch] = MODE_QPSK;
 	rx_shift[snd_ch] = 1800;
 	rx_baudrate[snd_ch] = 1800;
+	tx_bitrate[snd_ch] = 3600;
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
 }
@@ -663,6 +467,9 @@ void init_Q2400(int snd_ch)
   modem_mode[snd_ch] = MODE_QPSK;
   rx_shift[snd_ch] = 1200;
   rx_baudrate[snd_ch] = 1200;
+  tx_bitrate[snd_ch] = 2400;
+  pskStates[snd_ch] = 4;
+
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
 }
@@ -672,7 +479,10 @@ void init_P2400(int snd_ch)
   modem_mode[snd_ch] = MODE_BPSK;
   rx_shift[snd_ch] = 2400;
   rx_baudrate[snd_ch] = 2400;
- 	if (modem_def[snd_ch])
+  tx_bitrate[snd_ch] = 2400;
+  pskStates[snd_ch] = 2;
+  
+  if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
 }
 
@@ -681,6 +491,9 @@ void init_P1200(int snd_ch)
   modem_mode[snd_ch] = MODE_BPSK;
   rx_shift[snd_ch] = 1200;
   rx_baudrate[snd_ch] = 1200;
+  tx_bitrate[snd_ch] = 1200;
+  pskStates[snd_ch] = 2;
+
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
 }
@@ -690,6 +503,9 @@ void init_P600(int snd_ch)
   modem_mode[snd_ch] = MODE_BPSK;
   rx_shift[snd_ch] = 600;
   rx_baudrate[snd_ch] = 600;
+  tx_bitrate[snd_ch] = 600;
+  pskStates[snd_ch] = 2;
+
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
 }
@@ -699,6 +515,36 @@ void init_P300(int snd_ch)
   modem_mode[snd_ch] = MODE_BPSK;
   rx_shift[snd_ch] = 300;
   rx_baudrate[snd_ch] = 300;
+  pskStates[snd_ch] = 2;
+  tx_bitrate[snd_ch] = 300;
+
+  if (modem_def[snd_ch])
+		get_filter_values(snd_ch);
+}
+
+void init_Q300(int snd_ch)
+{
+	qpsk_set[snd_ch].mode = QPSK_V26;
+	modem_mode[snd_ch] = MODE_QPSK;
+
+	rx_shift[snd_ch] = 300;
+	rx_baudrate[snd_ch] = 300;
+	tx_bitrate[snd_ch] = 600;
+	pskStates[snd_ch] = 4;
+
+	if (modem_def[snd_ch])
+		get_filter_values(snd_ch);
+}
+
+void init_8PSK300(int snd_ch)
+{
+	modem_mode[snd_ch] = MODE_8PSK;
+
+	rx_shift[snd_ch] = 300;
+	rx_baudrate[snd_ch] = 300;
+	tx_bitrate[snd_ch] = 900;
+	pskStates[snd_ch] = 8;
+
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
 }
@@ -709,6 +555,7 @@ void init_ARDOP(int snd_ch)
 	rx_shift[snd_ch] = 500;
 	rx_freq[snd_ch] = 1500;
 	rx_baudrate[snd_ch] = 500;
+	tx_bitrate[snd_ch] = 500;
 
 	if (modem_def[snd_ch])
 		get_filter_values(snd_ch);
@@ -725,36 +572,23 @@ void set_speed(int snd_ch, int Modem)
 
 }
 
+int needPSKRefresh = 0;
+
 void init_speed(int snd_ch)
 {
 	int low, high;
 
-	/*
+	pskStates[snd_ch] = 0;		// Not PSK
 
-  if (BPF[snd_ch]>round(rx_samplerate/2) then BPF[snd_ch] = round(rx_samplerate/2);
-  if TXBPF[snd_ch]>round(rx_samplerate/2) then TXBPF[snd_ch] = round(rx_samplerate/2);
-  if LPF[snd_ch]>round(rx_samplerate/2) then LPF[snd_ch] = round(rx_samplerate/2);
-  if BPF[snd_ch]<1 then BPF[snd_ch] = 1;
-  if TXBPF[snd_ch]<1 then TXBPF[snd_ch] = 1;
-  if LPF[snd_ch]<1 then LPF[snd_ch] = 1;
-  if TXDelay[snd_ch]<1 then TXDelay[snd_ch] = 1;
-  if TXTail[snd_ch]<1 then TXTail[snd_ch] = 1;
-  if BPF_tap[snd_ch]>1024 then BPF_tap[snd_ch] = 1024;
-  if LPF_tap[snd_ch]>512 then LPF_tap[snd_ch] = 512;
-  if BPF_tap[snd_ch]<8 then BPF_tap[snd_ch] = 8;
-  if LPF_tap[snd_ch]<8 then LPF_tap[snd_ch] = 8;
-  if not (RCVR[snd_ch] in [0..8]) then RCVR[snd_ch] = 0;
-  if not (rcvr_offset[snd_ch] in [0..100]) then rcvr_offset[snd_ch] = 30;
-  if not (speed[snd_ch] in [0..modes_count]) then speed[snd_ch] = SPEED_300;
-*/
 	switch (speed[snd_ch])
 	{
 	case SPEED_8P4800:
 		init_8P4800(snd_ch);
 		break;
 
-	case SPEED_AE2400:
-		init_AE2400(snd_ch);
+	case SPEED_2400V26B:
+		init_V26B2400(snd_ch);
+
 		break;
 
 	case SPEED_DW2400:
@@ -784,6 +618,10 @@ void init_speed(int snd_ch)
 		init_P1200(snd_ch);
 		break;
 
+//	case SPEED_Q1200:
+//		init_Q1200(snd_ch);
+//		break;
+
 	case SPEED_P600:
 		init_P600(snd_ch);
 		break;
@@ -792,6 +630,14 @@ void init_speed(int snd_ch)
 		init_P300(snd_ch);
 		break;
 
+	case SPEED_Q300:
+		init_Q300(snd_ch);
+		break;
+
+	case SPEED_8PSK300:
+		init_8PSK300(snd_ch);
+		break;
+
 	case SPEED_300:
 
 		init_300(snd_ch);
@@ -816,12 +662,22 @@ void init_speed(int snd_ch)
 
 		init_ARDOP(snd_ch);
 		break;
+
+	case SPEED_RUH48:
+
+		init_RUH48(snd_ch);
+		break;
+
+	case SPEED_RUH96:
+
+		init_RUH96(snd_ch);
+		break;
+
 	}
 
 	//QPSK_SM: begin move(#0#1#2#3, tx[0], 4); move(#0#32#64#96, rx[0], 4); end;
 	//QPSK_V26: begin move(#2#3#1#0, tx[0], 4); move(#96#64#0#32, rx[0], 4); end;
 
-
 	if (modem_mode[snd_ch] == MODE_QPSK || modem_mode[snd_ch] == MODE_PI4QPSK)
 	{
 		switch (qpsk_set[snd_ch].mode)
@@ -874,752 +730,9 @@ void init_speed(int snd_ch)
 	  */
 	wf_pointer(soundChannel[snd_ch]);
 
-
+	CheckPSKWindows();
 }
 
-/*
-procedure TForm1.show_combobox;
-var
-  i word;
-begin
-  for i = 0 to length(modes_name)-1 do
-  begin
-    ComboBox1.Items.Add(modes_name[i]);
-    ComboBox2.Items.Add(modes_name[i]);
-  end;
-  ComboBox1.ItemIndex = ComboBox1.Items.IndexOf(modes_name[Speed[1]]);
-  ComboBox2.ItemIndex = ComboBox2.Items.IndexOf(modes_name[Speed[2]]);
-end;
-
-function TForm1.get_idx_by_name(name string) word;
-var
-  i word;
-  found boolean;
-begin
-  i = 0;
-  found = FALSE;
-  result = 0;
-  repeat
-    if name = modes_name[i] then
-    begin
-      found = TRUE;
-      result = i;
-    end
-    else inc(i);
-  until found or (i = length(modes_name));
-end;
-
-procedure TForm1.ReadIni;
-var
-  snd_ch byte;
-begin
-  TelIni = TIniFile.Create(cur_dir+"soundmodem.ini");
-  with TelIni do
-  begin
-    UTC_Time = ReadBool("Init","UTCTime",FALSE);
-    MainPriority = ReadInteger("Init","Priority",2);
-    nr_monitor_lines = ReadInteger("Init","NRMonitorLines",500);
-    ptt = ReadString("Init","PTT","NONE");
-    stop_wf = ReadBool("Init","StopWF",FALSE);
-    raduga = ReadBool("Init","DispMode",DISP_MONO);
-    stat_log = ReadBool("Init","StatLog",FALSE);
-    SND_RX_DEVICE = ReadInteger("Init","SndRXDevice",0);
-    SND_TX_DEVICE = ReadInteger("Init","SndTXDevice",0);
-    snd_rx_device_name = ReadString("Init","SndRXDeviceName","");
-    snd_tx_device_name = ReadString("Init","SndTXDeviceName","");
-    RX_SR = ReadInteger("Init","RXSampleRate",11025);
-    TX_SR = ReadInteger("Init","TXSampleRate",11025);
-    RX_PPM = ReadInteger("Init","RX_corr_PPM",0);
-    TX_PPM = ReadInteger("Init","TX_corr_PPM",0);
-    tx_bufcount = ReadInteger("Init","TXBufNumber",32);
-    rx_bufcount = ReadInteger("Init","RXBufNumber",32);
-    DebugMode = ReadInteger("Init","DisableUnit",0);
-    TX_rotate = ReadBool("Init","TXRotate",FALSE);
-    DualChan = ReadBool("Init","DualChan",FALSE);
-    DualPTT = ReadBool("Init","DualPTT",TRUE);
-    SCO = ReadBool("Init","SCO",FALSE);
-    stdtones = ReadBool("Init","UseStandardTones",TRUE);
-    // Channel A settings
-    maxframe[1] = ReadInteger("AX25_A","Maxframe",3);
-    fracks[1] = ReadInteger("AX25_A","Retries",15);
-    frack_time[1] = ReadInteger("AX25_A","FrackTime",5);
-    idletime[1] = ReadInteger("AX25_A","IdleTime",180);
-    slottime[1] = ReadInteger("AX25_A","SlotTime",100);
-    persist[1] = ReadInteger("AX25_A","Persist",128);
-    resptime[1] = ReadInteger("AX25_A","RespTime",1500);
-    TXFrmMode[1] = ReadInteger("AX25_A","TXFrmMode",1);
-    max_frame_collector[1] = ReadInteger("AX25_A","FrameCollector",6);
-    exclude_callsigns[1] = ReadString("AX25_A","ExcludeCallsigns","");
-    exclude_APRS_frm[1] = ReadString("AX25_A","ExcludeAPRSFrmType","");
-    KISS_opt[1] = ReadBool("AX25_A","KISSOptimization",FALSE);
-    dyn_frack[1] = ReadBool("AX25_A","DynamicFrack",FALSE);
-    recovery[1] = ReadInteger("AX25_A","BitRecovery",0);
-    NonAX25[1] = ReadBool("AX25_A","NonAX25Frm",FALSE);
-    MEMRecovery[1] = ReadInteger("AX25_A","MEMRecovery",200);
-    IPOLL[1] = ReadInteger("AX25_A","IPOLL",80);
-    MyDigiCall[1] = ReadString("AX25_A","MyDigiCall","");
-    tx_hitoneraisedb[1] = ReadInteger("AX25_A","HiToneRaise",0);
-    // Channel B settings
-    maxframe[2] = ReadInteger("AX25_B","Maxframe",3);
-    fracks[2] = ReadInteger("AX25_B","Retries",15);
-    frack_time[2] = ReadInteger("AX25_B","FrackTime",5);
-    idletime[2] = ReadInteger("AX25_B","IdleTime",180);
-    slottime[2] = ReadInteger("AX25_B","SlotTime",100);
-    persist[2] = ReadInteger("AX25_B","Persist",128);
-    resptime[2] = ReadInteger("AX25_B","RespTime",1500);
-    TXFrmMode[2] = ReadInteger("AX25_B","TXFrmMode",1);
-    max_frame_collector[2] = ReadInteger("AX25_B","FrameCollector",6);
-    exclude_callsigns[2] = ReadString("AX25_B","ExcludeCallsigns","");
-    exclude_APRS_frm[2] = ReadString("AX25_B","ExcludeAPRSFrmType","");
-    KISS_opt[2] = ReadBool("AX25_B","KISSOptimization",FALSE);
-    dyn_frack[2] = ReadBool("AX25_B","DynamicFrack",FALSE);
-    recovery[2] = ReadInteger("AX25_B","BitRecovery",0);
-    NonAX25[2] = ReadBool("AX25_B","NonAX25Frm",FALSE);
-    MEMRecovery[2] = ReadInteger("AX25_B","MEMRecovery",200);
-    IPOLL[2] = ReadInteger("AX25_B","IPOLL",80);
-    MyDigiCall[2] = ReadString("AX25_B","MyDigiCall","");
-    tx_hitoneraisedb[2] = ReadInteger("AX25_B","HiToneRaise",0);
-    // Modem settings
-    pkt_raw_min_len = ReadInteger("Modem","RawPktMinLen",17);
-    swap_ptt = ReadBool("Modem","SwapPTTPins",FALSE);
-    inv_ptt = ReadBool("Modem","InvPTTPins",FALSE);
-    Emph_all[1] = ReadBool("Modem","PreEmphasisAll1",TRUE);
-    Emph_all[2] = ReadBool("Modem","PreEmphasisAll2",TRUE);
-    emph_db[1] = ReadInteger("Modem","PreEmphasisDB1",0);
-    emph_db[2] = ReadInteger("Modem","PreEmphasisDB2",0);
-    txbpf[1] = ReadInteger("Modem","TXBPF1",500);
-    txbpf[2] = ReadInteger("Modem","TXBPF2",500);
-    bpf[1] = ReadInteger("Modem","BPF1",500);
-    bpf[2] = ReadInteger("Modem","BPF2",500);
-    lpf[1] = ReadInteger("Modem","LPF1",150);
-    lpf[2] = ReadInteger("Modem","LPF2",150);
-    BPF_tap[1] = ReadInteger("Modem","BPFTap1",256);
-    BPF_tap[2] = ReadInteger("Modem","BPFTap2",256);
-    LPF_tap[1] = ReadInteger("Modem","LPFTap1",128);
-    LPF_tap[2] = ReadInteger("Modem","LPFTap2",128);
-    DCD_threshold = ReadInteger("Modem","DCDThreshold",32);
-    rx_freq[1] = ReadFloat("Modem","RXFreq1",1700);
-    rx_freq[2] = ReadFloat("Modem","RXFreq2",1700);
-    CheckBox1.Checked = ReadBool("Modem","HoldPnt",FALSE);
-    BIT_AFC = ReadInteger("Modem","AFC",32);
-    txdelay[1] = ReadInteger("Modem","TxDelay1",250);
-    txdelay[2] = ReadInteger("Modem","TxDelay2",250);
-    txtail[1] = ReadInteger("Modem","TxTail1",50);
-    txtail[2] = ReadInteger("Modem","TxTail2",50);
-    diddles = ReadInteger("Modem","Diddles",0);
-    RCVR[1] = ReadInteger("Modem","NRRcvrPairs1",0);
-    RCVR[2] = ReadInteger("Modem","NRRcvrPairs2",0);
-    rcvr_offset[1] = ReadInteger("Modem","RcvrShift1",30);
-    rcvr_offset[2] = ReadInteger("Modem","RcvrShift2",30);
-    speed[1] = ReadInteger("Modem","ModemType1",SPEED_1200);
-    speed[2] = ReadInteger("Modem","ModemType2",SPEED_1200);
-    modem_def[1] = ReadBool("Modem","Default1",TRUE);
-    modem_def[2] = ReadBool("Modem","Default2",TRUE);
-    AGWServ = ReadBool("AGWHost","Server",TRUE);
-    AGWPort = ReadInteger("AGWHost","Port",8000);
-    KISSServ = ReadBool("KISS","Server",FALSE);
-    KISSPort = ReadInteger("KISS","Port",8100);
-    Form1.Top = ReadInteger("Window","Top",0);
-    Form1.Left = ReadInteger("Window","Left",0);
-    Form1.Height = ReadInteger("Window","Height",656);
-    Form1.Width = ReadInteger("Window","Width",764);
-    MinOnStart = ReadBool("Window","MinimizedOnStartup",FALSE);
-    Firstwaterfall1.checked = ReadBool("Window","Waterfall1",TRUE);
-    Secondwaterfall1.checked = ReadBool("Window","Waterfall2",FALSE);
-    Statustable1.checked = ReadBool("Window","StatTable",TRUE);
-    Monitor1.checked = ReadBool("Window","Monitor",TRUE);
-    RXRichEdit1.Font.Size = ReadInteger("Font","Size",RXRichEdit1.Font.Size);
-    RXRichEdit1.Font.Name = ReadString("Font","Name",RXRichEdit1.Font.Name);
-  end;
-  TelIni.Free;
-  newAGWPort = AGWPort;
-  newAGWServ = AGWServ;
-  newKISSPort = KISSPort;
-  newKISSServ = KISSServ;
-
-  RX_SampleRate = RX_SR+RX_SR*0.000001*RX_PPM;
-  TX_SampleRate = TX_SR+TX_SR*0.000001*TX_PPM;
-
-  panels[4] = Monitor1.Checked;
-  panels[3] = Statustable1.Checked;
-  panels[2] = Firstwaterfall1.Checked;
-  panels[1] = Secondwaterfall1.Checked;
-
-  if tx_bufcount>255 then tx_bufcount = 255;
-  if tx_bufcount<2 then tx_bufcount = 2;
-  if rx_bufcount>255 then rx_bufcount = 255;
-  if rx_bufcount<2 then rx_bufcount = 2;
-
-  if not (diddles in [0..2]) then diddles = 0;
-
-  if nr_monitor_lines>65535 then nr_monitor_lines = 65535;
-  if nr_monitor_lines<10    then nr_monitor_lines = 10;
-
-  if not (MainPriority in [0..3]) then MainPriority = 2;
-
-  for snd_ch = 1 to 2 do
-  begin
-
-    tx_hitoneraise[snd_ch] = power(10,-abs(tx_hitoneraisedb[snd_ch])/20);
-
-    if IPOLL[snd_ch]<0 then IPOLL[snd_ch] = 0;
-    if IPOLL[snd_ch]>65535 then IPOLL[snd_ch] = 65535;
-
-    if MEMRecovery[snd_ch]<1 then MEMRecovery[snd_ch] = 1;
-    if MEMRecovery[snd_ch]>65535 then MEMRecovery[snd_ch] = 65535;
-
-    get_exclude_list(AnsiUpperCase(MyDigiCall[snd_ch]),list_digi_callsigns[snd_ch]);
-    get_exclude_list(AnsiUpperCase(exclude_callsigns[snd_ch]),list_exclude_callsigns[snd_ch]);
-    get_exclude_frm(exclude_APRS_frm[snd_ch],list_exclude_APRS_frm[snd_ch]);
-
-    if resptime[snd_ch]<0 then resptime[snd_ch] = 0;
-    if resptime[snd_ch]>65535 then resptime[snd_ch] = 65535;
-    if persist[snd_ch]>255 then persist[snd_ch] = 255;
-    if persist[snd_ch]<32 then persist[snd_ch] = 32;
-    if fracks[snd_ch]<1 then fracks[snd_ch] = 1;
-    if frack_time[snd_ch]<1 then frack_time[snd_ch] = 1;
-    if idletime[snd_ch]<frack_time[snd_ch] then idletime[snd_ch] = 180;
-
-    if not (Emph_db[snd_ch] in [0..nr_emph]) then Emph_db[snd_ch] = 0;
-    if not (Recovery[snd_ch] in [0..1]) then Recovery[snd_ch] = 0;
-    if not (TXFrmMode[snd_ch] in [0..1]) then TXFrmMode[snd_ch] = 0;
-    if not (max_frame_collector[snd_ch] in [0..6]) then max_frame_collector[snd_ch] = 6;
-    if not (maxframe[snd_ch] in [1..7]) then maxframe[snd_ch] = 3;
-
-    if not (qpsk_set[snd_ch].mode in [0..1]) then qpsk_set[snd_ch].mode = 0;
-    init_speed(snd_ch);
-  end;
-  TrackBar1.Position = DCD_threshold;
-
-  // Check device ID
-  ChkSndDevName;
-end;
-
-procedure TForm1.WriteIni;
-begin
-  TelIni = TIniFile.Create(cur_dir+"soundmodem.ini");
-  with TelIni do
-  begin
-    WriteInteger("Init","Priority",MainPriority);
-    WriteBool("Init","UTCTime",UTC_Time);
-    WriteInteger("Init","NRMonitorLines",nr_monitor_lines);
-    WriteString("Init","PTT",ptt);
-    WriteBool("Init","DispMode",raduga);
-    WriteBool("Init","StopWF",stop_wf);
-    WriteBool("Init","StatLog",stat_log);
-    WriteInteger("Init","SndRXDevice",SND_RX_DEVICE);
-    WriteInteger("Init","SndTXDevice",SND_TX_DEVICE);
-    WriteString("Init","SndRXDeviceName",snd_rx_device_name);
-    WriteString("Init","SndTXDeviceName",snd_tx_device_name);
-    WriteInteger("Init","RXSampleRate",RX_SR);
-    WriteInteger("Init","TXSampleRate",TX_SR);
-    WriteInteger("Init","RX_corr_PPM",RX_PPM);
-    WriteInteger("Init","TX_corr_PPM",TX_PPM);
-    WriteInteger("Init","DisableUnit",DebugMode);
-    WriteBool("Init","TXRotate",TX_rotate);
-    WriteBool("Init","DualChan",DualChan);
-    WriteBool("Init","DualPTT",DualPTT);
-    WriteBool("Init","SCO",SCO);
-    WriteInteger("Init","TXBufNumber",tx_bufcount);
-    WriteInteger("Init","RXBufNumber",rx_bufcount);
-    WriteBool("Init","UseStandardTones",stdtones);
-    // Channel A settings
-    WriteInteger("AX25_A","Maxframe",maxframe[1]);
-    WriteInteger("AX25_A","Retries",fracks[1]);
-    WriteInteger("AX25_A","FrackTime",frack_time[1]);
-    WriteInteger("AX25_A","IdleTime",idletime[1]);
-    WriteInteger("AX25_A","SlotTime",slottime[1]);
-    WriteInteger("AX25_A","Persist",persist[1]);
-    WriteInteger("AX25_A","RespTime",resptime[1]);
-    WriteInteger("AX25_A","TXFrmMode",TXFrmMode[1]);
-    WriteInteger("AX25_A","FrameCollector",max_frame_collector[1]);
-    WriteString("AX25_A","ExcludeCallsigns",exclude_callsigns[1]);
-    WriteString("AX25_A","ExcludeAPRSFrmType",exclude_APRS_frm[1]);
-    WriteBool("AX25_A","KISSOptimization",KISS_opt[1]);
-    WriteBool("AX25_A","DynamicFrack",dyn_frack[1]);
-    WriteInteger("AX25_A","BitRecovery",recovery[1]);
-    WriteBool("AX25_A","NonAX25Frm",NonAX25[1]);
-    WriteInteger("AX25_A","MEMRecovery",MEMRecovery[1]);
-    WriteInteger("AX25_A","IPOLL",IPOLL[1]);
-    WriteString("AX25_A","MyDigiCall",MyDigiCall[1]);
-    WriteInteger("AX25_A","HiToneRaise",tx_hitoneraisedb[1]);
-    // Channel B settings
-    WriteInteger("AX25_B","Maxframe",maxframe[2]);
-    WriteInteger("AX25_B","Retries",fracks[2]);
-    WriteInteger("AX25_B","FrackTime",frack_time[2]);
-    WriteInteger("AX25_B","IdleTime",idletime[2]);
-    WriteInteger("AX25_B","SlotTime",slottime[2]);
-    WriteInteger("AX25_B","Persist",persist[2]);
-    WriteInteger("AX25_B","RespTime",resptime[2]);
-    WriteInteger("AX25_B","TXFrmMode",TXFrmMode[2]);
-    WriteInteger("AX25_B","FrameCollector",max_frame_collector[2]);
-    WriteString("AX25_B","ExcludeCallsigns",exclude_callsigns[2]);
-    WriteString("AX25_B","ExcludeAPRSFrmType",exclude_APRS_frm[2]);
-    WriteBool("AX25_B","KISSOptimization",KISS_opt[2]);
-    WriteBool("AX25_B","DynamicFrack",dyn_frack[2]);
-    WriteInteger("AX25_B","BitRecovery",recovery[2]);
-    WriteBool("AX25_B","NonAX25Frm",NonAX25[2]);
-    WriteInteger("AX25_B","MEMRecovery",MEMRecovery[2]);
-    WriteInteger("AX25_B","IPOLL",IPOLL[2]);
-    WriteString("AX25_B","MyDigiCall",MyDigiCall[2]);
-    WriteInteger("AX25_B","HiToneRaise",tx_hitoneraisedb[2]);
-    // Modem settings
-    if not modem_def[1] then
-    begin
-      WriteInteger("Modem","BPF1",bpf[1]);
-      WriteInteger("Modem","TXBPF1",txbpf[1]);
-      WriteInteger("Modem","LPF1",lpf[1]);
-      WriteInteger("Modem","BPFTap1",BPF_tap[1]);
-      WriteInteger("Modem","LPFTap1",LPF_tap[1]);
-    end;
-    if not modem_def[2] then
-    begin
-      WriteInteger("Modem","BPF2",bpf[2]);
-      WriteInteger("Modem","TXBPF2",txbpf[2]);
-      WriteInteger("Modem","LPF2",lpf[2]);
-      WriteInteger("Modem","BPFTap2",BPF_tap[2]);
-      WriteInteger("Modem","LPFTap2",LPF_tap[2]);
-    end;
-    WriteInteger("Modem","RawPktMinLen",pkt_raw_min_len);
-    WriteBool("Modem","SwapPTTPins",swap_ptt);
-    WriteBool("Modem","InvPTTPins",inv_ptt);
-    WriteInteger("Modem","PreEmphasisDB1",emph_db[1]);
-    WriteInteger("Modem","PreEmphasisDB2",emph_db[2]);
-    WriteBool("Modem","PreEmphasisAll1",emph_all[1]);
-    WriteBool("Modem","PreEmphasisAll2",emph_all[2]);
-    WriteBool("Modem","Default1",modem_def[1]);
-    WriteBool("Modem","Default2",modem_def[2]);
-    WriteInteger("Modem","DCDThreshold",DCD_threshold);
-    WriteBool("Modem","HoldPnt",CheckBox1.Checked);
-    WriteFloat("Modem","RXFreq1",rx_freq[1]);
-    WriteFloat("Modem","RXFreq2",rx_freq[2]);
-    WriteFloat("Modem","AFC",BIT_AFC);
-    WriteInteger("Modem","TxDelay1",txdelay[1]);
-    WriteInteger("Modem","TxDelay2",txdelay[2]);
-    WriteInteger("Modem","TxTail1",txtail[1]);
-    WriteInteger("Modem","TxTail2",txtail[2]);
-    WriteInteger("Modem","Diddles",diddles);
-    WriteInteger("Modem","NRRcvrPairs1",RCVR[1]);
-    WriteInteger("Modem","NRRcvrPairs2",RCVR[2]);
-    WriteInteger("Modem","RcvrShift1",rcvr_offset[1]);
-    WriteInteger("Modem","RcvrShift2",rcvr_offset[2]);
-    WriteInteger("Modem","ModemType1",speed[1]);
-    WriteInteger("Modem","ModemType2",speed[2]);
-    WriteBool("AGWHost","Server",newAGWServ);
-    WriteInteger("AGWHost","Port",newAGWPort);
-    WriteBool("KISS","Server",newKISSServ);
-    WriteInteger("KISS","Port",newKISSPort);
-    WriteInteger("Window","Top",Form1.Top);
-    WriteInteger("Window","Left",Form1.Left);
-    WriteInteger("Window","Height",Form1.Height);
-    WriteInteger("Window","Width",Form1.Width);
-    WriteBool("Window","Waterfall1",Firstwaterfall1.checked);
-    WriteBool("Window","Waterfall2",Secondwaterfall1.checked);
-    WriteBool("Window","StatTable",Statustable1.checked);
-    WriteBool("Window","Monitor",Monitor1.checked);
-    WriteBool("Window","MinimizedOnStartup",MinOnStart);
-    WriteInteger("Font","Size",RXRichEdit1.Font.Size);
-    WriteString("Font","Name",RXRichEdit1.Font.Name);
-  end;
-  TelIni.Free;
-end;
-
-procedure TForm1.ChkSndDevName;
-var
-  DevInCaps TWaveInCapsA;
-  DevOutCaps TWaveOutCapsA;
-  i,k,numdevs integer;
-  RXDevList,TXDevList TStringList;
-begin
-  RXDevList = TStringList.Create;
-  TXDevList = TStringList.Create;
-  numdevs = WaveOutGetNumDevs;
-  if numdevs>0 then
-    for k = 0 to numdevs-1 do
-    begin
-      waveOutGetDevCaps(k,@DevOutCaps,sizeof(DevOutCaps));
-      TXDevList.Add(DevOutCaps.szpname);
-    end;
-  numdevs = WaveInGetNumDevs;
-  if numdevs>0 then
-    for k = 0 to numdevs-1 do
-    begin
-      waveInGetDevCaps(k,@DevInCaps,sizeof(DevInCaps));
-      RXDevList.Add(DevInCaps.szpname);
-    end;
-  // TX Dev
-  if (snd_tx_device<0) or (snd_tx_device> = TXDevList.Count) then snd_tx_device = 0;
-  if TXDevList.Count>0 then
-    if TXDevList.Strings[snd_tx_device]<>snd_tx_device_name then
-    begin
-      i = TXDevList.IndexOf(snd_tx_device_name);
-      if i> = 0 then snd_tx_device = i else snd_tx_device_name = TXDevList.Strings[snd_tx_device];
-    end;
-  // RX Dev
-  if (snd_rx_device<0) or (snd_rx_device> = RXDevList.Count) then snd_rx_device = 0;
-  if RXDevList.Count>0 then
-    if RXDevList.Strings[snd_rx_device]<>snd_rx_device_name then
-    begin
-      i = RXDevList.IndexOf(snd_rx_device_name);
-      if i> = 0 then snd_rx_device = i else snd_rx_device_name = RXDevList.Strings[snd_rx_device];
-    end;
-  RXDevList.Free;
-  TXDevList.Free;
-end;
-
-procedure TForm1.startrx;
-var
-  OpenResult MMRESULT;
-  Loop       integer;
-  ErrorText  string;
-Begin
-  RX_device = TRUE;
-  RXBufferLength  =  rx_bufsize * Channels * (BitsPerSample div 8);
-  with WaveFormat do
-  begin
-    wFormatTag       =  WAVE_FORMAT_PCM;
-    nChannels        =  Channels;
-    nSamplesPerSec   =  RX_SR;
-    nAvgBytesPerSec  =  RX_SR * Channels * (BitsPerSample div 8);
-    nBlockAlign      =  Channels * (BitsPerSample div 8);
-    wBitsPerSample   =  BitsPerSample;
-    cbSize           =  0;
-  end;
-  OpenResult  =  waveInOpen (@WaveInHandle,SND_RX_DEVICE,@WaveFormat,integer(Self.Handle),0,CALLBACK_WINDOW);
-  if OpenResult = MMSYSERR_NOERROR then
-  begin
-    for Loop  =  1 to rx_bufcount do
-    begin
-      GetMem(RX_pbuf[Loop], RXBufferLength);
-      RX_header[Loop].lpData          =  RX_pbuf[Loop];
-      RX_header[Loop].dwBufferLength  =  RXBufferLength;
-      RX_header[Loop].dwUser          =  Loop;
-      RX_header[Loop].dwFlags         =  0;
-      RX_header[Loop].dwLoops         =  0;
-      OpenResult  =  WaveInPrepareHeader(WaveInhandle, @RX_header[Loop], sizeof(TWaveHdr));
-      if OpenResult = MMSYSERR_NOERROR then WaveInAddBuffer(WaveInHandle, @RX_header[Loop], sizeof(TWaveHdr))
-      else
-        begin
-          case OpenResult of
-            MMSYSERR_INVALHANDLE   ErrorText  =  "device handle is invalid";
-            MMSYSERR_NODRIVER      ErrorText  =  "no device driver present";
-            MMSYSERR_NOMEM         ErrorText  =  "memory allocation error, could be incorrect samplerate";
-            else                    ErrorText  =  "unknown error";
-          end;
-          MessageDlg(format("Error adding buffer %d device (%s)",[Loop, ErrorText]), mtError, [mbOk], 0);
-        end;
-    end;
-    WaveInStart(WaveInHandle);
-  end
-  else
-  begin
-    case OpenResult of
-      MMSYSERR_ERROR         ErrorText  =  "unspecified error";
-      MMSYSERR_BADDEVICEID   ErrorText  =  "device ID out of range";
-      MMSYSERR_NOTENABLED    ErrorText  =  "driver failed enable";
-      MMSYSERR_ALLOCATED     ErrorText  =  "device already allocated";
-      MMSYSERR_INVALHANDLE   ErrorText  =  "device handle is invalid";
-      MMSYSERR_NODRIVER      ErrorText  =  "no device driver present";
-      MMSYSERR_NOMEM         ErrorText  =  "memory allocation error, could be incorrect samplerate";
-      MMSYSERR_NOTSUPPORTED  ErrorText  =  "function isn""t supported";
-      MMSYSERR_BADERRNUM     ErrorText  =  "error value out of range";
-      MMSYSERR_INVALFLAG     ErrorText  =  "invalid flag passed";
-      MMSYSERR_INVALPARAM    ErrorText  =  "invalid parameter passed";
-      MMSYSERR_HANDLEBUSY    ErrorText  =  "handle being used simultaneously on another thread (eg callback)";
-      MMSYSERR_INVALIDALIAS  ErrorText  =  "specified alias not found";
-      MMSYSERR_BADDB         ErrorText  =  "bad registry database";
-      MMSYSERR_KEYNOTFOUND   ErrorText  =  "registry key not found";
-      MMSYSERR_READERROR     ErrorText  =  "registry read error";
-      MMSYSERR_WRITEERROR    ErrorText  =  "registry write error";
-      MMSYSERR_DELETEERROR   ErrorText  =  "registry delete error";
-      MMSYSERR_VALNOTFOUND   ErrorText  =  "registry value not found";
-      MMSYSERR_NODRIVERCB    ErrorText  =  "driver does not call DriverCallback";
-      else                    ErrorText  =  "unknown error";
-    end;
-    MessageDlg(format("Error opening wave input device (%s)",[ErrorText]), mtError, [mbOk], 0);
-    RX_device = FALSE;
-  end;
-end;
-
-procedure TForm1.stoprx;
-var
-  Loop integer;
-begin
-  if not RX_device then exit;
-  WaveInStop(WaveInHandle);
-  WaveInReset(WaveInHandle);
-  for Loop  =  1 to rx_bufcount do
-    WaveInUnPrepareHeader(WaveInHandle, @RX_header[Loop], sizeof(TWaveHdr));
-  WaveInClose(WaveInHandle);
-  for Loop  =  1 to rx_bufcount do
-  begin
-    if RX_pbuf[Loop]<>nil then
-    begin
-      FreeMem(RX_pbuf[Loop]);
-      RX_pbuf[Loop]  =  nil;
-    end;
-  end;
-  RX_device = FALSE;
-end;
-
-procedure TForm1.make_wave_buf(snd_ch byte; buf PChar);
-const
-  amplitude = 22000;
-var
-  i word;
-begin
-  modulator(snd_ch,audio_buf[snd_ch],tx_bufsize);
-  if tx_status[snd_ch] = TX_NO_DATA then buf_status[snd_ch] = BUF_EMPTY;
-  for i = 0 to tx_bufsize-1 do
-  begin
-    case snd_ch of
-    1
-      begin
-        // left channel
-        PSmallInt(buf)^ = round(amplitude*audio_buf[snd_ch][i]);
-        Inc(PSmallInt(Buf));
-        // right channel
-        if SCO then PSmallInt(buf)^ = round(amplitude*audio_buf[snd_ch][i]) else PSmallInt(buf)^ = 0;
-        Inc(PSmallInt(Buf));
-      end;
-    2
-      begin
-        // left channel
-        if SCO then PSmallInt(buf)^ = round(amplitude*audio_buf[snd_ch][i]) else PSmallInt(buf)^ = 0;
-        Inc(PSmallInt(Buf));
-        // right channel
-        PSmallInt(buf)^ = round(amplitude*audio_buf[snd_ch][i]);
-        Inc(PSmallInt(Buf));
-      end;
-    end;
-  end;
-end;
-
-procedure TForm1.starttx(snd_ch byte);
-var
-  OpenResult MMRESULT;
-  Loop       integer;
-  ErrorText  string;
-  BufferLength longint;
-Begin
-  if snd_status[snd_ch]<>SND_IDLE then exit;
-  BufferLength  =  tx_bufsize * Channels * (BitsPerSample div 8);
-  with WaveFormat do
-  begin
-    wFormatTag       =  WAVE_FORMAT_PCM;
-    nChannels        =  Channels;
-    nSamplesPerSec   =  TX_SR;
-    nAvgBytesPerSec  =  TX_SR * Channels * (BitsPerSample div 8);
-    nBlockAlign      =  Channels * (BitsPerSample div 8);
-    wBitsPerSample   =  BitsPerSample;
-    cbSize           =  0;
-  end;
-  OpenResult  =  WaveOutOpen (@WaveOutHandle[snd_ch],SND_TX_DEVICE,@WaveFormat,integer(Self.Handle),0,CALLBACK_WINDOW);
-  if OpenResult = MMSYSERR_NOERROR then
-  begin
-    snd_status[snd_ch] = SND_TX;
-    buf_status[snd_ch] = BUF_FULL;
-    tx_status[snd_ch] = TX_SILENCE;
-    tx_buf_num[snd_ch] = 0;
-    tx_buf_num1[snd_ch] = 0;
-    for Loop  =  1 to tx_bufcount do
-    begin
-      GetMem(TX_pbuf[snd_ch][Loop], BufferLength);
-      TX_header[snd_ch][Loop].lpData          =  TX_pbuf[snd_ch][Loop];
-      TX_header[snd_ch][Loop].dwBufferLength  =  BufferLength;
-      TX_header[snd_ch][Loop].dwUser          =  0;
-      TX_header[snd_ch][Loop].dwFlags         =  0;
-      TX_header[snd_ch][Loop].dwLoops         =  0;
-      OpenResult  =  WaveOutPrepareHeader(WaveOuthandle[snd_ch], @TX_header[snd_ch][Loop], sizeof(TWaveHdr));
-      if OpenResult = MMSYSERR_NOERROR then
-      begin
-        // ��������� ����� �� ��������
-        if buf_status[snd_ch] = BUF_FULL then
-        begin
-          make_wave_buf(snd_ch,TX_pbuf[snd_ch][Loop]);
-          WaveOutWrite(WaveOutHandle[snd_ch],@TX_header[snd_ch][Loop],sizeof(TWaveHdr));
-          inc(tx_buf_num1[snd_ch]);
-        end;
-      end
-      else
-      begin
-        case OpenResult of
-          MMSYSERR_INVALHANDLE   ErrorText  =  "device handle is invalid";
-          MMSYSERR_NODRIVER      ErrorText  =  "no device driver present";
-          MMSYSERR_NOMEM         ErrorText  =  "memory allocation error, could be incorrect samplerate";
-          else                    ErrorText  =  "unknown error";
-        end;
-        MessageDlg(format("Error adding buffer %d device (%s)",[Loop, ErrorText]), mtError, [mbOk], 0);
-      end;
-    end;
-  end
-  else
-  begin
-    case OpenResult of
-      MMSYSERR_ERROR         ErrorText  =  "unspecified error";
-      MMSYSERR_BADDEVICEID   ErrorText  =  "device ID out of range";
-      MMSYSERR_NOTENABLED    ErrorText  =  "driver failed enable";
-      MMSYSERR_ALLOCATED     ErrorText  =  "device already allocated";
-      MMSYSERR_INVALHANDLE   ErrorText  =  "device handle is invalid";
-      MMSYSERR_NODRIVER      ErrorText  =  "no device driver present";
-      MMSYSERR_NOMEM         ErrorText  =  "memory allocation error, could be incorrect samplerate";
-      MMSYSERR_NOTSUPPORTED  ErrorText  =  "function isn""t supported";
-      MMSYSERR_BADERRNUM     ErrorText  =  "error value out of range";
-      MMSYSERR_INVALFLAG     ErrorText  =  "invalid flag passed";
-      MMSYSERR_INVALPARAM    ErrorText  =  "invalid parameter passed";
-      MMSYSERR_HANDLEBUSY    ErrorText  =  "handle being used simultaneously on another thread (eg callback)";
-      MMSYSERR_INVALIDALIAS  ErrorText  =  "specified alias not found";
-      MMSYSERR_BADDB         ErrorText  =  "bad registry database";
-      MMSYSERR_KEYNOTFOUND   ErrorText  =  "registry key not found";
-      MMSYSERR_READERROR     ErrorText  =  "registry read error";
-      MMSYSERR_WRITEERROR    ErrorText  =  "registry write error";
-      MMSYSERR_DELETEERROR   ErrorText  =  "registry delete error";
-      MMSYSERR_VALNOTFOUND   ErrorText  =  "registry value not found";
-      MMSYSERR_NODRIVERCB    ErrorText  =  "driver does not call DriverCallback";
-      else                    ErrorText  =  "unknown error";
-    end;
-    MessageDlg(format("Error opening wave output device (%s)",[ErrorText]), mtError, [mbOk], 0);
-  end;
-end;
-
-procedure TForm1.stoptx(snd_ch byte);
-var
-  Loop integer;
-begin
-  if snd_status[snd_ch]<>SND_TX then exit;
-  WaveOutReset(WaveOutHandle[snd_ch]);
-  for Loop  =  1 to tx_bufcount do
-    WaveOutUnPrepareHeader(WaveOutHandle[snd_ch], @TX_header[snd_ch][Loop], sizeof(TWaveHdr));
-  WaveOutClose(WaveOutHandle[snd_ch]);
-  for Loop  =  1 to tx_bufcount do
-  begin
-    if TX_pbuf[snd_ch][Loop]<>nil then
-    begin
-      FreeMem(TX_pbuf[snd_ch][Loop]);
-      TX_pbuf[snd_ch][Loop]  =  nil;
-    end;
-  end;
-  WaveOutHandle[snd_ch] = 0;
-  snd_status[snd_ch] = SND_IDLE;
-end;
-
-procedure show_grid_title;
-const
-  title array [0..10] of string  =  ("MyCall","DestCall","Status","Sent pkts","Sent bytes","Rcvd pkts","Rcvd bytes","Rcvd FC","CPS TX","CPS RX","Direction");
-var
-  i byte;
-begin
-  for i = 0 to 10 do Form1.StringGrid1.Cells[i,0] = title[i];
-end;
-*/
-/*
-
-procedure disp1(src1,src2 array of single);
-var
-  i,n word;
-  k,k1,amp1,amp2,amp3,amp4 single;
-  bm TBitMap;
-begin
-  bm = TBitMap.Create;
-  bm.pixelformat = pf32bit;
-  //bm.pixelformat = pf24bit;
-  bm.Width = Form1.PaintBox2.Width;
-  bm.Height = Form1.PaintBox2.Height;
-  amp1 = 0;
-  amp3 = 0;
-  //k = 0.20;
-  k = 50000;
-  k1 = 0;
-  //k = 1000;
-  //k = 0.00001;
-  bm.Canvas.MoveTo(0,50);
-  bm.Canvas.LineTo(512,50);
-  n = 0;
-  for i = 0 to RX_Bufsize-1 do
-  begin
-    begin
-      amp2 = src1[i];
-      amp4 = src2[i];
-      bm.Canvas.Pen.Color = clRed;
-      bm.Canvas.MoveTo(n,50-round(amp1*k1));
-      bm.Canvas.LineTo(n+1,50-round(amp2*k1));
-      bm.Canvas.Pen.Color = clBlue;
-      bm.Canvas.MoveTo(n,50-round(amp3*k));
-      bm.Canvas.LineTo(n+1,50-round(amp4*k));
-      bm.Canvas.Pen.Color = clBlack;
-      inc(n);
-      amp1 = amp2;
-      amp3 = amp4;
-    end;
-  end;
-  Form1.PaintBox2.Canvas.Draw(0,0,bm);
-  bm.Free;
-end;
-*/
-
-/*
-
-procedure TForm1.wf_pointer(snd_ch byte);
-var
-  x single;
-  x1,x2,y,k,pos1,pos2,pos3 word;
-begin
-  k = 24;
-  x = fft_size/RX_SampleRate;
-  pos1 = round((rx_freq[snd_ch]-0.5*rx_shift[snd_ch])*x)-5;
-  pos2 = round((rx_freq[snd_ch]+0.5*rx_shift[snd_ch])*x)-5;
-  pos3 = round(rx_freq[snd_ch]*x);
-  x1 = pos1+5;
-  x2 = pos2+5;
-  y = k+5;
-  with bm3.Canvas do
-  begin
-    Draw(0,20,bm[snd_ch]);
-    Pen.Color = clWhite;
-    Brush.Color = clRed;
-    Polygon([Point(x1+3,y),Point(x1,y-7),Point(x1-3,y),Point(x2+3,y),Point(x2,y-7),Point(x2-3,y)]);
-    Brush.Color = clBlue;
-    Polygon([Point(x1+3,y),Point(x1,y+7),Point(x1-3,y),Point(x2+3,y),Point(x2,y+7),Point(x2-3,y)]);
-    Polyline([Point(pos3,k+1),Point(pos3,k+9)]);
-    Pen.Color = clBlack;
-  end;
-  case snd_ch of
-    1  PaintBox1.Canvas.Draw(0,0,bm3);
-    2  PaintBox3.Canvas.Draw(0,0,bm3);
-  end;
-end;
-
-procedure TForm1.wf_Scale;
-var
-  k single;
-  max_freq,x,i word;
-begin
-  max_freq = round(RX_SampleRate*0.005);
-  k = 100*fft_size/RX_SampleRate;
-  with bm1.Canvas do
-  begin
-    Brush.Color = clBlack;
-    FillRect(ClipRect);
-    Pen.Color = clWhite;
-    Font.Color = clWhite;
-    Font.Size = 8;
-    for i = 0 to max_freq do
-    begin
-      x = round(k*i);
-      if x<1025 then
-      begin
-        if (i mod 5) = 0 then
-          PolyLine([Point(x,20),Point(x,13)])
-        else
-          PolyLine([Point(x,20),Point(x,16)]);
-        if (i mod 10) = 0 then TextOut(x-12,1,inttostr(i*100));
-      end;
-    end;
-    Pen.Color = clBlack;
-  end;
-  bm3.Canvas.Draw(0,0,bm1);
-end;
-*/
 
 void  chk_snd_buf(float * buf, int len)
 {
@@ -1697,8 +810,11 @@ void runModems()
 		if (modem_mode[snd_ch] == MODE_ARDOP)
 			continue;			// Processed above
 
-	// do we need to do this again ??
-//			make_core_BPF(snd_ch, rx_freq[snd_ch], bpf[snd_ch]);
+		if (modem_mode[snd_ch] == MODE_RUH)
+			continue;			// Processed above
+
+		// do we need to do this again ??
+		// make_core_BPF(snd_ch, rx_freq[snd_ch], bpf[snd_ch]);
 
 		if (multiCore)			// Run modems in separate threads
 			thread[snd_ch] = _beginthread(runModemthread, 0, (void *)(size_t)snd_ch);
@@ -1749,10 +865,12 @@ void runModemthread(void * param)
 
 void BufferFull(short * Samples, int nSamples)			// These are Stereo Samples
 {
-	word i, i1;
+	word i, i1, j;
 	Byte snd_ch, rcvr_idx;
-	boolean add_fft_line;
 	int buf_offset;
+	int Needed;
+	short * data1;
+	short * data2 = 0;
 
 	// if UDP server active send as UDP Datagram
 
@@ -1777,15 +895,6 @@ void BufferFull(short * Samples, int nSamples)			// These are Stereo Samples
 
 	// Do FFT on every 4th buffer (2048 samples)
 
-	add_fft_line = FALSE;
-	fft_mult++;
-
-	if (fft_mult == fft_spd)
-	{
-		add_fft_line = TRUE;
-		fft_mult = 0;
-	}
-
 	// if in Satellite Mode look for a Tuning signal
 
 //	if (SatelliteMode)
@@ -1821,21 +930,72 @@ void BufferFull(short * Samples, int nSamples)			// These are Stereo Samples
 				short ardopbuff[1200];
 				i1 = 0;
 
-				for (i = 0; i < rx_bufsize; i++)
+				if (using48000)
 				{
-					ardopbuff[i] = Samples[i1];
+					i1 = 0;
+					j = 0;
+
+					//	Need to downsample 48K to 12K
+					//	Try just skipping 3 samples	
+
+					nSamples /= 4;
+
+					for (i = 0; i < nSamples; i++)
+					{
+						ardopbuff[i] = Samples[i1];		
+						i1 += 8;
+					}
+				}
+				else
+				{
+					for (i = 0; i < nSamples; i++)
+					{
+						ardopbuff[i] = Samples[i1];
+						i1++;
+						i1++;
+					}
+				}
+				ARDOPProcessNewSamples(snd_ch, ardopbuff, nSamples);
+			}
+
+			else if (modem_mode[snd_ch] == MODE_RUH)
+			{
+				i1 = 0;
+				if (modemtoSoundLR[snd_ch] == 1)		// Using Right Chan
+					i1++;			
+
+				for (i = 0; i < nSamples; i++)
+				{
+					dw9600ProcessSample(snd_ch, Samples[i1]);
 					i1++;
 					i1++;
 				}
-
-				ARDOPProcessNewSamples(ardopbuff, nSamples);
 			}
+			ProcessRXFrames(snd_ch);
 		}
 
 		// extract mono samples from data. 
 
 		data1 = Samples;
 
+		if (using48000)
+		{
+			i1 = 0;
+			j = 0;
+
+			//	Need to downsample 48K to 12K
+			//	Try just skipping 3 samples	
+
+			nSamples /= 4;
+
+			for (i = 0; i < nSamples; i++)
+			{
+				Samples[j++] = Samples[i1];
+				Samples[j++] = Samples[i1 + 1];
+				i1 += 8;
+			}
+		}
+
 		i1 = 0;
 
 		// src_buf[0] is left data,. src_buf[1] right
@@ -1844,7 +1004,7 @@ void BufferFull(short * Samples, int nSamples)			// These are Stereo Samples
 
 		if (UsingBothChannels)
 		{
-			for (i = 0; i < rx_bufsize; i++)
+			for (i = 0; i < nSamples; i++)
 			{
 				src_buf[0][i] = data1[i1];
 				i1++;
@@ -1858,7 +1018,7 @@ void BufferFull(short * Samples, int nSamples)			// These are Stereo Samples
 
 			i1 = 1;
 
-			for (i = 0; i < rx_bufsize; i++)
+			for (i = 0; i < nSamples; i++)
 			{
 				src_buf[1][i] = data1[i1];
 				i1 += 2;
@@ -1868,7 +1028,7 @@ void BufferFull(short * Samples, int nSamples)			// These are Stereo Samples
 		{
 			// Extract just left
 
-			for (i = 0; i < rx_bufsize; i++)
+			for (i = 0; i < nSamples; i++)
 			{
 				src_buf[0][i] = data1[i1];
 				i1 += 2;
@@ -1881,60 +1041,79 @@ void BufferFull(short * Samples, int nSamples)			// These are Stereo Samples
 
 		// Do whichever waterfall is needed
 
+		// We need to run the waterfall FFT for the frequency guessing to work
+
 		int FirstWaterfallChan = 0;
+		short * ptr1 = &fft_buf[0][fftCount];
+		short * ptr2 = &fft_buf[1][fftCount];
 
-		// not sure why this is needed
+		int remainingSamples = rx_bufsize;
 
-//	if (UsingLeft)
-//		chk_snd_buf(src_buf[0], rx_bufsize);
-//	if (UsingRight)
-//		chk_snd_buf(src_buf[1], rx_bufsize);
-
-
-		if (Firstwaterfall)
+		if (UsingLeft == 0)
 		{
-			if (UsingLeft == 0)
-			{
-				FirstWaterfallChan = 1;
-				data1++;					// to Right Samples
-			}
+			FirstWaterfallChan = 1;
+			data1++;					// to Right Samples
+		}
 
-			buf_offset = fft_size - rx_bufsize;
-			move((UCHAR *)&fft_buf[FirstWaterfallChan][rx_bufsize], (UCHAR *)&fft_buf[FirstWaterfallChan][0], buf_offset * 2);
+		if (UsingBothChannels)			// Second is always Right
+			data2 = &Samples[1];		// to Right Samples
 
-			for (i = 0; i < rx_bufsize; i++)
+
+		// FFT size isn't necessarily a multiple of rx_bufsize, so this is a bit more complicated
+		// Save FFTSize samples then process. Put the unused bits back in the fft buffer
+
+		// Collect samples for both channels if needed
+
+		Needed = FFTSize - fftCount;
+
+		if (Needed <= rx_bufsize)
+		{
+			// add Needed samples to fft_buf and process. Copy rest to start of fft_buf
+
+			for (i = 0; i < Needed; i++)
 			{
-				fft_buf[FirstWaterfallChan][i + buf_offset] = *data1;
+				*ptr1++ = *data1;
 				data1 += 2;
 			}
 
-			if (add_fft_line)
-				if (Firstwaterfall)
-					doWaterfall(FirstWaterfallChan);
-		}
+			doWaterfall(FirstWaterfallChan);
 
-		if (UsingBothChannels && Secondwaterfall)
-		{
-			// Second is always Right
-
-			data1 = &Samples[1];			// to Right Samples
-
-			buf_offset = fft_size - rx_bufsize;
-			move((UCHAR *)&fft_buf[1][rx_bufsize], (UCHAR *)&fft_buf[1][0], buf_offset * 2);
-
-			for (i = 0; i < rx_bufsize; i++)
+			if (data2)
 			{
-				fft_buf[1][i + buf_offset] = *data1;
-				data1 += 2;
+				for (i = 0; i < Needed; i++)
+				{
+					*ptr2++ = *data2;
+					data2 += 2;
+				}
+				doWaterfall(1);
 			}
 
-			if (add_fft_line)
-				if (Secondwaterfall)
-					doWaterfall(1);
+			remainingSamples = rx_bufsize - Needed;
+			fftCount = 0;
+
+			ptr1 = &fft_buf[0][0];
+			ptr2 = &fft_buf[1][0];
 		}
 
+		for (i = 0; i < remainingSamples; i++)
+		{
+			*ptr1++ = *data1;
+			data1 += 2;
+		}
+
+		if (data2)
+		{
+			for (i = 0; i < remainingSamples; i++)
+			{
+				*ptr2++ = *data2;
+				data2 += 2;
+			}
+		}
+		fftCount += remainingSamples;
+
 	}
 
+
 	if (TimerEvent == TIMER_EVENT_ON)
 	{
 		timer_event();
@@ -2177,8 +1356,18 @@ char * frame_monitor(string * frame, char * code, int tx_stat)
 	case U_UI:
 
 		frm = "UI";
+		break;
+
+	case U_XID:
+
+		frm = "XID";
+		break;
+
+	case U_TEST:
+
+		frm = "TEST";
 	}
-	
+
 	if (Digi[0])
 		sprintf(AGW_path, "Fm %s To %s Via %s <%s %c%s",CallFrom, CallTo, Digi, frm, c, p);
 	else
@@ -2221,624 +1410,3 @@ char * frame_monitor(string * frame, char * code, int tx_stat)
 	return FrameData;
 }
 
-
-/*
-procedure TForm1.RX2TX(snd_ch byte);
-begin
-  if snd_status[snd_ch] = SND_IDLE then begin ptton(snd_ch); starttx(snd_ch); end;
-end;
-
-function TForm1.frame_monitor(s,code string; tx_stat boolean) string;
-var
-  len word;
-  s_tx_stat string;
-  time_now,s1,c,p string;
-  callfrom,callto,digi,path,data,frm string;
-  frm_body string;
-  pid,nr,ns,f_type,f_id byte;
-  rpt,cr,pf boolean;
-  i word;
-begin
-  decode_frame(s,path,data,pid,nr,ns,f_type,f_id,rpt,pf,cr);
-  len = length(data);
-  // NETROM parsing
-  if pid = $CF then data = parse_NETROM(data,f_id);
-  // IP parsing
-  if pid = $CC then data = parse_IP(data);
-  // ARP parsing
-  if pid = $CD then data = parse_ARP(data);
-  //
-  get_monitor_path(path,CallTo,CallFrom,Digi);
-  if cr then
-  begin
-    c = "C";
-    if pf then p = " P" else p = "";
-  end
-  else
-  begin
-    c = "R";
-    if pf then p = " F" else p = "";
-  end;
-  frm = "UNKN";
-  case f_id of
-    I_I     frm = "I";
-    S_RR    frm = "RR";
-    S_RNR   frm = "RNR";
-    S_REJ   frm = "REJ";
-    U_SABM  frm = "SABM";
-    U_DISC  frm = "DISC";
-    U_DM    frm = "DM";
-    U_UA    frm = "UA";
-    U_FRMR  frm = "FRMR";
-    U_UI    frm = "UI";
-  end;
-  case tx_stat of
-    TRUE    s_tx_stat = "T";
-    FALSE   s_tx_stat = "R";
-  end;
-  s1 = "";
-
-  if code<>"" then code = " ["+code+"]";
-  if UTC_Time then time_now = " [UTC"+get_UTC_time+s_tx_stat+"]"
-  else time_now = " ["+FormatDateTime("hhmmss",now)+s_tx_stat+"]";
-
-  if digi = "" then frm_body = "Fm "+CallFrom+" To "+CallTo+" <"+frm+" "+c+p
-  else frm_body = "Fm "+CallFrom+" To "+CallTo+" Via "+Digi+" <"+frm+" "+c+p;
-  case f_type of
-    I_FRM   frm_body = frm_body+" R"+inttostr(nr)+" S"+inttostr(ns)+" Pid = "+dec2hex(pid)+" Len = "+inttostr(len)+">"+time_now+code+#13+data;
-    U_FRM   if f_id = U_UI then frm_body = frm_body+" Pid = "+dec2hex(pid)+" Len = "+inttostr(len)+">"+time_now+code+#13+data
-               else if f_id = U_FRMR then begin data = copy(data+#0#0#0,1,3); frm_body = frm_body+">"+time_now+code+#13+inttohex((byte(data[1]) shl 16) or (byte(data[2]) shl 8) or byte(data[3]),6) end
-                 else frm_body = frm_body+">"+time_now+code;
-    S_FRM   frm_body = frm_body+" R"+inttostr(nr)+">"+time_now+code;
-  end;
-  for i = 1 to length(frm_body) do
-  begin
-    if frm_body[i]>#31 then s1 = s1+frm_body[i];
-    if frm_body[i] = #13 then s1 = s1+#13#10;
-    if frm_body[i] = #10 then s1 = s1+"";
-    if frm_body[i] = #9 then s1 = s1+#9;
-  end;
-  result = s1;
-end;
-
-procedure TForm1.waterfall_init;
-begin
-  bm[1] = TBitMap.Create;
-  bm[2] = TBitMap.Create;
-  bm1 = TBitMap.Create;
-  bm2 = TBitMap.Create;
-  bm3 = TBitMap.Create;
-  bm[1].pixelformat = pf32bit;
-  bm[2].pixelformat = pf32bit;
-  bm1.pixelformat = pf32bit;
-  bm2.pixelformat = pf32bit;
-  bm3.pixelformat = pf32bit;
-  bm[1].Height = PaintBox1.Height-20;
-  bm[1].Width = PaintBox1.width;
-  bm[2].Height = PaintBox1.Height-20;
-  bm[2].Width = PaintBox1.width;
-  bm1.Height = 20;
-  bm1.Width = PaintBox1.width;
-  bm3.Height = PaintBox1.Height;
-  bm3.Width = PaintBox1.width;
-end;
-
-procedure TForm1.waterfall_free;
-begin
-  bm[1].Free;
-  bm[2].Free;
-  bm1.Free;
-  bm2.Free;
-  bm3.Free;
-end;
-
-procedure TForm1.StartAGW;
-begin
-  try
-    ServerSocket1.Port = AGWPort;
-    ServerSocket1.Active = AGWServ;
-  except
-    ServerSocket1.Active = FALSE;
-    MessageDlg("AGW host port is busy!", mtWarning,[mbOk],0);
-  end;
-end;
-
-procedure TForm1.StartKISS;
-begin
-  try
-    ServerSocket2.Port = KISSPort;
-    ServerSocket2.Active = KISSServ;
-  except
-    ServerSocket2.Active = FALSE;
-    MessageDlg("KISS port is busy!", mtWarning,[mbOk],0);
-  end;
-end;
-
-procedure fft_window_init;
-var
-  mag single;
-  i word;
-begin
-  mag = 2*pi/(fft_size-1);
-  for i = 0 to fft_size-1 do fft_window_arr[i] = 0.5-0.5*cos(i*mag); //hann
-end;
-
-procedure TForm1.FormCreate(Sender TObject);
-begin
-  if hPrevInst <> 0 then begin
-    MessageDlg("��������� ��� ��������!", mtError, [mbOk], 0);
-    Application.Terminate;
-  end;
-  RS = TReedSolomon.Create(Self);
-  MainThreadHandle = GetCurrentThread;
-  form1.Caption = MODEM_CAPTION+" - Ver "+MODEM_VERSION;
-  cur_dir = ExtractFilePath(Application.ExeName);
-  fft_window_init;
-  detector_init;
-  kiss_init;
-  agw_init;
-  ax25_init;
-  init_raduga;
-  waterfall_init;
-  ReadIni;
-  show_combobox;
-  show_grid_title;
-  show_mode_panels;
-  show_panels;
-  wf_pointer(1);
-  wf_pointer(2);
-  wf_Scale;
-  ChangePriority;
-  Visible = TRUE;
-  StartAGW;
-  StartKISS;
-  PTTOpen;
-  startrx;
-  TimerEvent = TIMER_EVENT_OFF;
-  if (debugmode and DEBUG_TIMER) = 0 then create_timer1;
-  if MinOnStart then WindowState = wsMinimized;
-end;
-
-procedure TForm1.TrackBar1Change(Sender TObject);
-begin
-  dcd_threshold = TrackBar1.position;
-end;
-*/
-
-void Timer_Event2()
-{
-	if (TimerStat2 == TIMER_BUSY || TimerStat2 == TIMER_OFF)
-		return;
-
-	TimerStat2 = TIMER_BUSY;
-
-//	show_grid();
-
-	/*
-
-	if (mod_icon_status = MOD_WAIT) then inc(icon_timer);
-	if icon_timer = 10 then mod_icon_status = MOD_IDLE;
-	if (mod_icon_status<>MOD_WAIT) and (mod_icon_status<>last_mod_icon_status) then
-	begin
-	  icon_timer = 0;
-	  case mod_icon_status of
-		MOD_IDLE form1.CoolTrayIcon1.IconIndex = 0;
-		MOD_RX begin form1.CoolTrayIcon1.IconIndex = 1; mod_icon_status = MOD_WAIT; end;
-		MOD_TX form1.CoolTrayIcon1.IconIndex = 2;
-	  end;
-	  last_mod_icon_status = mod_icon_status;
-	end;
-	//*/
-
-	TimerStat2 = TIMER_FREE;
-}
-
-/*
-
-procedure TimeProc1(uTimerId, uMesssage UINT; dwUser, dw1, dw2 DWORD); stdcall;
-begin
-  TimerEvent = TIMER_EVENT_ON;
-end;
-
-procedure TForm1.create_timer1;
-var
-  TimeEpk cardinal;
-begin
-  TimeEpk = 100;
-  TimerId1 = TimeSetEvent(TimeEpk,0,@TimeProc1,0,TIME_PERIODIC);
-end;
-
-procedure TForm1.free_timer1;
-begin
-  TimerStat1 = TIMER_OFF;
-  timeKillEvent(TimerId1);
-end;
-
-*/
-
-/*
-
-
-procedure TForm1.PaintBox1MouseMove(Sender TObject; Shift TShiftState; X,
-  Y Integer);
-var
-  low,high word;
-begin
-  if CheckBox1.Checked then exit;
-  if not mouse_down[1] then Exit;
-  rx_freq[1] = round(x*RX_SampleRate/fft_size);
-  low = round(rx_shift[1]/2+Rcvr[1]*rcvr_offset[1]+1);
-  high = round(rx_samplerate/2-(rx_shift[1]/2+Rcvr[1]*rcvr_offset[1]));
-  if (rx_freq[1]-low)<0 then rx_freq[1] = low;
-  if (high-rx_freq[1])<0 then rx_freq[1] = high;
-  tx_freq[1] = rx_freq[1];
-  show_freq_a;
-end;
-
-procedure TForm1.PaintBox3MouseMove(Sender TObject; Shift TShiftState; X,
-  Y Integer);
-var
-  low,high word;
-begin
-  if CheckBox1.Checked then exit;
-  if not mouse_down[2] then Exit;
-  rx_freq[2] = round(x*RX_SampleRate/fft_size);
-  low = round(rx_shift[2]/2+Rcvr[2]*rcvr_offset[2]+1);
-  high = round(rx_samplerate/2-(rx_shift[2]/2+Rcvr[2]*rcvr_offset[2]));
-  if (rx_freq[2]-low)<0 then rx_freq[2] = low;
-  if (high-rx_freq[2])<0 then rx_freq[2] = high;
-  tx_freq[2] = rx_freq[2];
-  show_freq_b;
-end;
-
-procedure TForm1.PaintBox1MouseUp(Sender TObject; Button TMouseButton;
-  Shift TShiftState; X, Y Integer);
-begin
-  mouse_down[1] = FALSE;
-end;
-
-procedure TForm1.PaintBox3MouseUp(Sender TObject; Button TMouseButton;
-  Shift TShiftState; X, Y Integer);
-begin
-  mouse_down[2] = FALSE;
-end;
-
-procedure TForm1.PaintBox1MouseDown(Sender TObject; Button TMouseButton;
-  Shift TShiftState; X, Y Integer);
-var
-  low,high word;
-begin
-  if CheckBox1.Checked then exit;
-  if not (ssLeft in shift) then Exit;
-  mouse_down[1] = TRUE;
-  rx_freq[1] = round(x*RX_SampleRate/fft_size);
-  low = round(rx_shift[1]/2+Rcvr[1]*rcvr_offset[1]+1);
-  high = round(rx_samplerate/2-(rx_shift[1]/2+Rcvr[1]*rcvr_offset[1]));
-  if (rx_freq[1]-low)<0 then rx_freq[1] = low;
-  if (high-rx_freq[1])<0 then rx_freq[1] = high;
-  tx_freq[1] = rx_freq[1];
-  show_freq_a;
-end;
-
-procedure TForm1.PaintBox3MouseDown(Sender TObject; Button TMouseButton;
-  Shift TShiftState; X, Y Integer);
-var
-  low,high word;
-begin
-  if CheckBox1.Checked then exit;
-  if not (ssLeft in shift) then Exit;
-  mouse_down[2] = TRUE;
-  rx_freq[2] = round(x*RX_SampleRate/fft_size);
-  low = round(rx_shift[2]/2+Rcvr[2]*rcvr_offset[2]+1);
-  high = round(rx_samplerate/2-(rx_shift[2]/2+Rcvr[2]*rcvr_offset[2]));
-  if (rx_freq[2]-low)<0 then rx_freq[2] = low;
-  if (high-rx_freq[2])<0 then rx_freq[2] = high;
-  tx_freq[2] = rx_freq[2];
-  show_freq_b;
-end;
-
-procedure TForm1.ServerSocket1ClientRead(Sender TObject;
-  Socket TCustomWinSocket);
-var
-  s string;
-begin
-  s = Socket.ReceiveText;
-  AGW_explode_frame(Socket.sockethandle,s);
-end;
-
-procedure TForm1.ServerSocket1ClientDisconnect(Sender TObject;
-  Socket TCustomWinSocket);
-begin
-  del_incoming_mycalls_by_sock(socket.SocketHandle);
-  AGW_del_socket(socket.SocketHandle);
-end;
-
-procedure TForm1.ServerSocket1ClientError(Sender TObject;
-  Socket TCustomWinSocket; ErrorEvent TErrorEvent;
-  var ErrorCode Integer);
-begin
-  del_incoming_mycalls_by_sock(socket.SocketHandle);
-  AGW_del_socket(socket.SocketHandle);
-  ErrorCode = 0;
-end;
-
-procedure TForm1.ServerSocket1ClientConnect(Sender TObject;
-  Socket TCustomWinSocket);
-begin
-  agw_add_socket(Socket.sockethandle);
-end;
-
-procedure TForm1.OutputVolume1Click(Sender TObject);
-var
-  s string;
-begin
-  s = "SndVol32.exe -D"+inttostr(SND_TX_DEVICE);
-  WinExec(pchar(s),SW_SHOWNORMAL);
-end;
-
-procedure TForm1.InputVolume1Click(Sender TObject);
-var
-  s string;
-begin
-  s = "SndVol32.exe -R -D"+inttostr(SND_RX_DEVICE);
-  WinExec(pchar(s),SW_SHOWNORMAL);
-end;
-
-procedure TForm1.CoolTrayIcon1Click(Sender TObject);
-begin
-  CoolTrayIcon1.ShowMainForm;
-end;
-
-procedure TForm1.CoolTrayIcon1Cycle(Sender TObject; NextIndex Integer);
-begin
-  CoolTrayIcon1.IconIndex = 2;
-  CoolTrayIcon1.CycleIcons = FALSE;
-end;
-
-procedure TForm1.ABout1Click(Sender TObject);
-begin
-  Form2.ShowModal;
-end;
-
-procedure TForm1.put_frame(snd_ch byte; frame,code string; tx_stat,excluded boolean);
-var
-  s string;
-begin
-  if RxRichedit1.Focused then Windows.SetFocus(0);
-  if code = "NON-AX25" then
-    s = inttostr(snd_ch)+" <NON-AX25 frame Len = "+inttostr(length(frame)-2)+"> ["+FormatDateTime("hhmmss",now)+"R]"
-  else
-    s = inttostr(snd_ch)+""+frame_monitor(frame,code,tx_stat);
-  //RxRichedit1.Lines.BeginUpdate;
-  RxRichedit1.SelStart = length(RxRichedit1.text);
-  RxRichEdit1.SelLength = length(s);
-  case tx_stat of
-    TRUE   RxRichEdit1.SelAttributes.Color = clMaroon;
-    FALSE  RxRichEdit1.SelAttributes.Color = clBlack;
-  end;
-  if excluded then RxRichEdit1.SelAttributes.Color = clGreen;
-  RxRichedit1.SelText = s+#10;
-  if RxRichedit1.Lines.Count>nr_monitor_lines then
-  repeat
-    RxRichedit1.Lines.Delete(0);
-  until RxRichedit1.Lines.Count = nr_monitor_lines;
-  RxRichedit1.HideSelection = FALSE;
-  RxRichedit1.SelStart = length(RxRichedit1.text);
-  RxRichedit1.SelLength = 0;
-  RxRichedit1.HideSelection = TRUE;
-  //RxRichedit1.Lines.EndUpdate;
-end;
-
-procedure TForm1.show_mode_panels;
-begin
-  panel8.Align = alNone;
-  panel6.Align = alNone;
-  if dualchan then panel6.Visible = TRUE else panel6.Visible = FALSE;
-  panel8.Align = alLeft;
-  panel6.Align = alLeft;
-end;
-
-procedure TForm1.show_panels;
-var
-  i byte;
-begin
-  panel1.Align = alNone;
-  panel2.Align = alNone;
-  panel3.Align = alNone;
-  panel4.Align = alNone;
-  panel5.Align = alNone;
-  for i = 1 to 5 do
-  case i of
-    1  panel1.Visible = panels[i];
-    2  panel5.Visible = panels[i];
-    3  panel3.Visible = panels[i];
-    4  panel4.Visible = panels[i];
-    5  panel2.Visible = panels[i];
-  end;
-  panel1.Align = alBottom;
-  panel5.Align = alBottom;
-  panel3.Align = alBottom;
-  panel2.Align = alTop;
-  panel4.Align = alClient;
-end;
-
-procedure TForm1.Secondwaterfall1Click(Sender TObject);
-begin
-  case Secondwaterfall1.Checked of
-    TRUE   Secondwaterfall1.Checked = FALSE;
-    FALSE  Secondwaterfall1.Checked = TRUE;
-  end;
-  panels[1] = Secondwaterfall1.Checked;
-  show_panels;
-end;
-
-
-
-procedure TForm1.Firstwaterfall1Click(Sender TObject);
-begin
-  case Firstwaterfall1.Checked of
-    TRUE   Firstwaterfall1.Checked = FALSE;
-    FALSE  Firstwaterfall1.Checked = TRUE;
-  end;
-  panels[2] = Firstwaterfall1.Checked;
-  show_panels;
-end;
-
-procedure TForm1.Statustable1Click(Sender TObject);
-begin
-  case Statustable1.Checked of
-    TRUE   Statustable1.Checked = FALSE;
-    FALSE  Statustable1.Checked = TRUE;
-  end;
-  panels[3] = Statustable1.Checked;
-  show_panels;
-end;
-
-procedure TForm1.Monitor1Click(Sender TObject);
-begin
-  case Monitor1.Checked of
-    TRUE   Monitor1.Checked = FALSE;
-    FALSE  Monitor1.Checked = TRUE;
-  end;
-  panels[4] = Monitor1.Checked;
-  show_panels;
-end;
-
-procedure TForm1.Devices1Click(Sender TObject);
-begin
-  if (ptt = "EXT") or (ptt = "CAT") then Form3.Button3.Enabled = TRUE else Form3.Button3.Enabled = FALSE;
-  Form3.GetDeviceInfo;
-  form3.ShowModal;
-end;
-
-procedure TForm1.FormPaint(Sender TObject);
-begin
-  RxRichedit1.HideSelection = FALSE;
-  RxRichedit1.SelStart = length(RxRichedit1.text);
-  RxRichedit1.SelLength = 0;
-  RxRichedit1.HideSelection = TRUE;
-end;
-
-procedure TForm1.Filters1Click(Sender TObject);
-begin
-  Form5.Show_modem_settings;
-end;
-
-procedure TForm1.Clearmonitor1Click(Sender TObject);
-begin
-  RxRichEdit1.Clear;
-  frame_count = 0;
-  single_frame_count = 0;
-end;
-
-procedure TForm1.Copytext1Click(Sender TObject);
-begin
-  RxRichEdit1.CopyToClipboard;
-end;
-
-procedure TForm1.ApplicationEvents1Minimize(Sender TObject);
-begin
-  if stop_wf then w_state = WIN_MINIMIZED;
-end;
-
-procedure TForm1.ApplicationEvents1Restore(Sender TObject);
-begin
-  w_state = WIN_MAXIMIZED;
-end;
-
-procedure TForm1.ServerSocket2ClientConnect(Sender TObject;
-  Socket TCustomWinSocket);
-begin
-  KISS_add_stream(socket.sockethandle);
-end;
-
-procedure TForm1.ServerSocket2ClientDisconnect(Sender TObject;
-  Socket TCustomWinSocket);
-begin
-  KISS_del_stream(socket.sockethandle);
-end;
-
-procedure TForm1.ServerSocket2ClientError(Sender TObject;
-  Socket TCustomWinSocket; ErrorEvent TErrorEvent;
-  var ErrorCode Integer);
-begin
-  KISS_del_stream(socket.sockethandle);
-  ErrorCode = 0;
-end;
-
-procedure TForm1.ServerSocket2ClientRead(Sender TObject;
-  Socket TCustomWinSocket);
-var
-  data string;
-begin
-  data = socket.ReceiveText;
-  KISS_on_data_in(socket.sockethandle,data);
-end;
-
-procedure TForm1.Font1Click(Sender TObject);
-begin
-  FontDialog1.Font = RXRichEdit1.Font;
-  if FontDialog1.Execute then
-  begin
-    RXRichEdit1.SelStart = 0;
-    RXRichEdit1.SelLength = Length(RXRichEdit1.Text);
-    RXRichEdit1.SelAttributes.Size = FontDialog1.Font.Size;
-    RXRichEdit1.SelAttributes.Name = FontDialog1.Font.Name;
-    RXRichEdit1.Font.Size = FontDialog1.Font.Size;
-    RXRichEdit1.Font.Name = FontDialog1.Font.Name;
-    WriteIni;
-  end;
-end;
-
-procedure TForm1.Calibration1Click(Sender TObject);
-begin
-  Form6.ShowModal;
-end;
-
-procedure TForm1.ComboBox1Change(Sender TObject);
-begin
-  Speed[1] = get_idx_by_name(ComboBox1.Text);
-  init_speed(1);
-  windows.setfocus(0);
-end;
-
-procedure TForm1.ComboBox1KeyDown(Sender TObject; var Key Word;
-  Shift TShiftState);
-begin
-  key = 0;
-  windows.SetFocus(0);
-end;
-
-procedure TForm1.ComboBox1KeyPress(Sender TObject; var Key Char);
-begin
-  key = #0;
-  windows.SetFocus(0);
-end;
-
-procedure TForm1.ComboBox2Change(Sender TObject);
-begin
-  Speed[2] = get_idx_by_name(ComboBox2.Text);
-  init_speed(2);
-  windows.setfocus(0);
-end;
-
-procedure TForm1.FormDestroy(Sender TObject);
-var
-  snd_ch byte;
-begin
-  stoprx;
-  for snd_ch = 1 to 2 do if snd_status[snd_ch] = SND_TX then stoptx(snd_ch);
-  if (debugmode and DEBUG_TIMER) = 0 then free_timer1;
-  TimerStat2 = TIMER_OFF;
-  PTTClose;
-  ax25_free;
-  agw_free;
-  kiss_free;
-  detector_free;
-  RS.Free;
-  waterfall_free;
-  WriteIni;
-end;
-
-end.
-*/
\ No newline at end of file
diff --git a/symbols.h b/symbols.h
new file mode 100644
index 0000000..5ed91ad
--- /dev/null
+++ b/symbols.h
@@ -0,0 +1,19 @@
+
+/* symbols.h */
+
+void symbols_init (void);
+
+void symbols_list (void);
+
+void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, char *symbol);
+
+int symbols_into_dest (char symtab, char symbol, char *dest);
+
+void symbols_get_description (char symtab, char symbol, char *description, size_t desc_size);
+
+int symbols_code_from_description (char overlay, char *description, char *symtab, char *symbol);
+
+void symbols_to_tones (char symtab, char symbol, char *tones, size_t tonessize);
+
+
+/* end symbols.h */
diff --git a/telemetry.h b/telemetry.h
new file mode 100644
index 0000000..4ef9b62
--- /dev/null
+++ b/telemetry.h
@@ -0,0 +1,15 @@
+
+
+/* telemetry.h */
+
+void telemetry_data_original (char *station, char *info, int quiet, char *output, size_t outputsize, char *comment, size_t commentsize);
+ 
+void telemetry_data_base91 (char *station, char *cdata, char *output, size_t outputsize);
+ 
+void telemetry_name_message (char *station, char *msg);
+ 
+void telemetry_unit_label_message (char *station, char *msg);
+
+void telemetry_coefficents_message (char *station, char *msg, int quiet);
+
+void telemetry_bit_sense_message (char *station, char *msg, int quiet);
diff --git a/tq.h b/tq.h
new file mode 100644
index 0000000..37599d5
--- /dev/null
+++ b/tq.h
@@ -0,0 +1,41 @@
+
+/*------------------------------------------------------------------
+ *
+ * Module:      tq.h
+ *
+ * Purpose:   	Transmit queue - hold packets for transmission until the channel is clear.
+ *		
+ *---------------------------------------------------------------*/
+
+#ifndef TQ_H
+#define TQ_H 1
+
+#include "ax25_pad.h"
+#include "audio.h"
+
+#define TQ_NUM_PRIO 2				/* Number of priorities. */
+
+#define TQ_PRIO_0_HI 0
+#define TQ_PRIO_1_LO 1
+
+
+
+void tq_init (struct audio_s *audio_config_p);
+
+void tq_append (int chan, int prio, packet_t pp);
+
+void lm_data_request (int chan, int prio, packet_t pp);
+
+void lm_seize_request (int chan);
+
+void tq_wait_while_empty (int chan);
+
+packet_t tq_remove (int chan, int prio);
+
+packet_t tq_peek (int chan, int prio);
+
+int tq_count (int chan, int prio, char *source, char *dest, int bytes);
+
+#endif
+
+/* end tq.h */
diff --git a/tt_text.h b/tt_text.h
new file mode 100644
index 0000000..7cab3b8
--- /dev/null
+++ b/tt_text.h
@@ -0,0 +1,38 @@
+
+/* tt_text.h */
+
+
+/* Encode normal human readable to DTMF representation. */
+
+int tt_text_to_multipress (const char *text, int quiet, char *buttons);
+
+int tt_text_to_two_key (const char *text, int quiet, char *buttons);
+
+int tt_text_to_call10 (const char *text, int quiet, char *buttons);
+
+int tt_text_to_mhead (const char *text, int quiet, char *buttons, size_t buttonsiz);
+
+int tt_text_to_satsq (const char *text, int quiet, char *buttons, size_t buttonsiz);
+
+int tt_text_to_ascii2d (const char *text, int quiet, char *buttons);
+
+
+/* Decode DTMF to normal human readable form. */
+
+int tt_multipress_to_text (const char *buttons, int quiet, char *text);
+
+int tt_two_key_to_text (const char *buttons, int quiet, char *text);
+
+int tt_call10_to_text (const char *buttons, int quiet, char *text);
+
+int tt_call5_suffix_to_text (const char *buttons, int quiet, char *text);
+
+int tt_mhead_to_text (const char *buttons, int quiet, char *text, size_t textsiz);
+
+int tt_satsq_to_text (const char *buttons, int quiet, char *text);
+
+int tt_ascii2d_to_text (const char *buttons, int quiet, char *text);
+
+
+
+/* end tt_text.h */
\ No newline at end of file
diff --git a/tt_user.h b/tt_user.h
new file mode 100644
index 0000000..4ff2ec8
--- /dev/null
+++ b/tt_user.h
@@ -0,0 +1,15 @@
+
+/* tt_user.h */
+
+
+#include "audio.h"
+
+void tt_user_init (struct audio_s *p_audio_config, struct tt_config_s *p);
+
+int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *loc_text, double latitude, 
+		double longitude, int ambiguity, char *freq, char *ctcss, char *comment, char mic_e, char *dao);
+
+int tt_3char_suffix_search (char *suffix, char *callsign);
+
+void tt_user_background (void);
+void tt_user_dump (void);
\ No newline at end of file
diff --git a/tune.h b/tune.h
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/tune.h
@@ -0,0 +1 @@
+ 
diff --git a/version.h b/version.h
new file mode 100644
index 0000000..a09490c
--- /dev/null
+++ b/version.h
@@ -0,0 +1,21 @@
+
+/* Dire Wolf version 1.6 */
+
+// Put in destination field to identify the equipment used.
+
+#define APP_TOCALL "APDW"		// Assigned by WB4APR in tocalls.txt
+
+// This now comes from compile command line options.
+
+//#define MAJOR_VERSION 1
+//#define MINOR_VERSION 6
+//#define EXTRA_VERSION "Beta Test"
+
+
+// For user-defined data format.
+// APRS protocol spec Chapter 18 and http://www.aprs.org/aprs11/expfmts.txt
+
+#define USER_DEF_USER_ID 'D'		// user id D for direwolf
+
+#define USER_DEF_TYPE_AIS 'A'		// data type A for AIS NMEA sentence
+#define USER_DEF_TYPE_EAS 'E'		// data type E for EAS broadcasts
diff --git a/waypoint.h b/waypoint.h
new file mode 100644
index 0000000..3ba6f1c
--- /dev/null
+++ b/waypoint.h
@@ -0,0 +1,24 @@
+
+/* 
+ * Name:	waypoint.h
+ */
+
+
+#include "ax25_pad.h"		/* for packet_t */
+
+#include "config.h"		/* for struct misc_config_s */
+
+
+void waypoint_init (struct misc_config_s *misc_config);
+
+void waypoint_set_debug (int n);
+
+void waypoint_send_sentence (char *wname_in, double dlat, double dlong, char symtab, char symbol, 
+			float alt, float course, float speed, char *comment_in);
+
+void waypoint_send_ais (char *sentence);
+
+void waypoint_term ();
+
+
+/* end waypoint.h */
diff --git a/xid.h b/xid.h
new file mode 100644
index 0000000..a221b73
--- /dev/null
+++ b/xid.h
@@ -0,0 +1,32 @@
+
+
+/* xid.h */
+
+
+#include "ax25_pad.h"		// for enum ax25_modulo_e
+
+
+struct xid_param_s {
+
+	int full_duplex;
+	
+	// Order is important because negotiation keeps the lower value of
+	// REJ  (srej_none),  SREJ (default without negotiation), Multi-SREJ (if both agree).
+
+	enum srej_e { srej_none=0, srej_single=1, srej_multi=2, srej_not_specified=3 } srej;
+
+	enum ax25_modulo_e modulo;
+
+	int i_field_length_rx;	/* In bytes.  XID has it in bits. */
+
+	int window_size_rx;
+
+	int ack_timer;		/* "T1" in mSec. */
+
+	int retries;		/* "N1" */
+};
+
+
+int xid_parse (unsigned char *info, int info_len, struct xid_param_s *result, char *desc, int desc_size);
+
+int xid_encode (struct xid_param_s *param, unsigned char *info, cmdres_t cr);
\ No newline at end of file
diff --git a/xmit.h b/xmit.h
new file mode 100644
index 0000000..248037d
--- /dev/null
+++ b/xmit.h
@@ -0,0 +1,27 @@
+
+
+#ifndef XMIT_H
+#define XMIT_H 1
+
+#include "audio.h"	/* for struct audio_s */
+
+
+extern void xmit_init (struct audio_s *p_modem, int debug_xmit_packet);
+
+extern void xmit_set_txdelay (int channel, int value);
+
+extern void xmit_set_persist (int channel, int value);
+
+extern void xmit_set_slottime (int channel, int value);
+
+extern void xmit_set_txtail (int channel, int value);
+
+extern void xmit_set_fulldup (int channel, int value);
+
+
+extern int xmit_speak_it (char *script, int c, char *msg);
+
+#endif
+
+/* end xmit.h */
+