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 0000000..e605463
Binary files /dev/null and b/libfftw3f-64-3.lib differ
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 */
+