From b1d1488de60dfff8b89872f863098a884133852f Mon Sep 17 00:00:00 2001 From: Dave Hibberd Date: Tue, 23 Jul 2024 21:26:30 +0100 Subject: [PATCH] New upstream version 0.0.0.72.1 --- ALSASound.c | 325 ++- ARDOPC.c | 189 -- Config.cpp | 44 +- Config.cpp.bak | 481 ++++ ModemDialog.ui | 1064 +++++---- Modulate.c | 2 +- QtSoundModem.cpp | 300 ++- QtSoundModem.cpp.bak | 3380 +++++++++++++++++++++++++++ QtSoundModem.h | 6 + QtSoundModem.vcxproj | 2 +- QtSoundModem.vcxproj.filters | 14 +- QtSoundModem.vcxproj.user | 8 +- QtSoundModem_resource.aps | Bin 0 -> 82400 bytes SMMain.c | 48 +- UZ7HOStuff.h | 38 +- UZ7HOUtils.c | 25 +- Waveout.c | 10 +- ax25_agw.c | 2 +- ax25_demod.c | 18 +- ax25_mod.c | 26 +- debug/moc_predefs-LAPTOP-Q6S4RP5Q.h | 12 + debug/moc_predefs-notpi4-64.h | 12 + debug/moc_predefs-skigdebian.h | 12 + devicesDialog.ui | 79 +- dw9600.c | 4 + il2p.c | 413 +++- kiss_mode.c | 19 +- main.cpp | 3 + sm_main.c | 12 +- sm_main.c.bak | 1431 ++++++++++++ tcpCode.cpp | 140 ++ tcpCode.h | 11 + 32 files changed, 7186 insertions(+), 944 deletions(-) create mode 100644 Config.cpp.bak create mode 100644 QtSoundModem.cpp.bak create mode 100644 QtSoundModem_resource.aps create mode 100644 debug/moc_predefs-LAPTOP-Q6S4RP5Q.h create mode 100644 debug/moc_predefs-notpi4-64.h create mode 100644 debug/moc_predefs-skigdebian.h create mode 100644 sm_main.c.bak diff --git a/ALSASound.c b/ALSASound.c index 5764e94..fc950a4 100644 --- a/ALSASound.c +++ b/ALSASound.c @@ -42,10 +42,19 @@ along with QtSoundModem. If not, see http://www.gnu.org/licenses #define VOID void +char * strlop(char * buf, char delim); + +int gethints(); + +struct timespec pttclk; + extern int Closing; int SoundMode = 0; int stdinMode = 0; +int onlyMixSnoop = 0; + +int txLatency; //#define SHARECAPTURE // if defined capture device is opened and closed for each transission @@ -291,11 +300,11 @@ void platformInit() void txSleep(int mS) { - // called while waiting for next TX buffer or to delay response. - // Run background processes - // called while waiting for next TX buffer. Run background processes + if (mS < 0) + return; + while (mS > 50) { PollReceivedSamples(); // discard any received samples @@ -626,7 +635,8 @@ int OpenSoundPlayback(char * PlaybackDevice, int m_sampleRate, int channels, int { int err = 0; - char buf1[100]; + char buf1[256]; + char buf2[256]; char * ptr; if (playhandle) @@ -638,18 +648,20 @@ int OpenSoundPlayback(char * PlaybackDevice, int m_sampleRate, int channels, int strcpy(SavedPlaybackDevice, PlaybackDevice); // Saved so we can reopen in error recovery SavedPlaybackRate = m_sampleRate; - if (strstr(PlaybackDevice, "plug") == 0 && strchr(PlaybackDevice, ':')) - sprintf(buf1, "plug%s", PlaybackDevice); + strcpy(buf2, PlaybackDevice); + + ptr = strchr(buf2, ' '); + if (ptr) *ptr = 0; // Get Device part of name + + + if (strstr(buf2, "plug") == 0 && strchr(buf2, ':')) + sprintf(buf1, "plug%s", buf2); else - strcpy(buf1, PlaybackDevice); + strcpy(buf1, buf2); if (Report) Debugprintf("Real Device %s", buf1); - - ptr = strchr(buf1, ' '); - if (ptr) *ptr = 0; // Get Device part of name - snd_pcm_hw_params_t *hw_params; if ((err = snd_pcm_open(&playhandle, buf1, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) @@ -734,7 +746,9 @@ int OpenSoundCapture(char * CaptureDevice, int m_sampleRate, int Report) { int err = 0; - char buf1[100]; + char buf1[256]; + char buf2[256]; + char * ptr; snd_pcm_hw_params_t *hw_params; @@ -755,16 +769,19 @@ int OpenSoundCapture(char * CaptureDevice, int m_sampleRate, int Report) strcpy(SavedCaptureDevice, CaptureDevice); // Saved so we can reopen in error recovery SavedCaptureRate = m_sampleRate; - if (strstr(CaptureDevice, "plug") == 0 && strchr(CaptureDevice, ':')) - sprintf(buf1, "plug%s", CaptureDevice); + strcpy(buf2, CaptureDevice); + + ptr = strchr(buf2, ' '); + if (ptr) *ptr = 0; // Get Device part of name + + if (strstr(buf2, "plug") == 0 && strchr(buf2, ':')) + sprintf(buf1, "plug%s", buf2); else - strcpy(buf1, CaptureDevice); + strcpy(buf1, buf2); if (Report) Debugprintf("Real Device %s", buf1); - ptr = strchr(buf1, ' '); - if (ptr) *ptr = 0; // Get Device part of name if ((err = snd_pcm_open (&rechandle, buf1, SND_PCM_STREAM_CAPTURE, 0)) < 0) { Debugprintf("cannot open capture audio device %s (%s)", buf1, snd_strerror(err)); @@ -882,17 +899,14 @@ int OpenSoundCapture(char * CaptureDevice, int m_sampleRate, int Report) if (Report) Debugprintf("Capture using %d channels", m_recchannels); - int i; short buf[256]; - for (i = 0; i < 10; ++i) + if ((err = snd_pcm_readi(rechandle, buf, 12)) != 12) { - if ((err = snd_pcm_readi (rechandle, buf, 128)) != 128) - { - Debugprintf("read from audio interface failed (%s)", snd_strerror (err)); - } + Debugprintf("read from audio interface failed (%s)", snd_strerror(err)); } + // Debugprintf("Read got %d", err); return TRUE; @@ -957,11 +971,11 @@ int SoundCardWrite(short * input, int nSamples) // Stop Capture - if (rechandle) - { - snd_pcm_close(rechandle); - rechandle = NULL; - } +// if (rechandle) +// { +// snd_pcm_close(rechandle); +// rechandle = NULL; +// } avail = snd_pcm_avail_update(playhandle); // Debugprintf("avail before play returned %d", (int)avail); @@ -1189,30 +1203,37 @@ short * SoundInit(); void GetSoundDevices() { - if (SoundMode == 0) + if (onlyMixSnoop) { - GetInputDeviceCollection(); - GetOutputDeviceCollection(); + gethints(); } - else if (SoundMode == 1) + else { - PlaybackCount = 3; + if (SoundMode == 0) + { + GetInputDeviceCollection(); + GetOutputDeviceCollection(); + } + else if (SoundMode == 1) + { + PlaybackCount = 3; - strcpy(&PlaybackNames[0][0], "/dev/dsp0"); - strcpy(&PlaybackNames[1][0], "/dev/dsp1"); - strcpy(&PlaybackNames[2][0], "/dev/dsp2"); + strcpy(&PlaybackNames[0][0], "/dev/dsp0"); + strcpy(&PlaybackNames[1][0], "/dev/dsp1"); + strcpy(&PlaybackNames[2][0], "/dev/dsp2"); - CaptureCount = 3; + CaptureCount = 3; - strcpy(&CaptureNames[0][0], "/dev/dsp0"); - strcpy(&CaptureNames[1][0], "/dev/dsp1"); - strcpy(&CaptureNames[2][0], "/dev/dsp2"); - } - else if (SoundMode == 2) - { - // Pulse + strcpy(&CaptureNames[0][0], "/dev/dsp0"); + strcpy(&CaptureNames[1][0], "/dev/dsp1"); + strcpy(&CaptureNames[2][0], "/dev/dsp2"); + } + else if (SoundMode == 2) + { + // Pulse - listpulse(); + listpulse(); + } } } @@ -1300,7 +1321,18 @@ void PollReceivedSamples() // if still not enough, too bad! if (bytes != ReceiveSize * 2) + { + // This seems to happen occasionally even when we shouldn't be in stdin mode. Exit + Debugprintf("Short Read %d", bytes); + closeTraceLog(); + + Closing = TRUE; + + sleep(1); + exit(1); + } + // convert to stereo @@ -1338,7 +1370,7 @@ void PollReceivedSamples() { lastlevelGUI = Now; - if ((Now - lastlevelreport) > 10000) // 10 Secs + if ((Now - lastlevelreport) > 60000) // 60 Secs { char HostCmd[64]; lastlevelreport = Now; @@ -1403,6 +1435,11 @@ short * SoundInit() // Called at end of transmission +int useTimedPTT = 1; + +extern int SampleNo; +int pttOnTime(); + void SoundFlush() { // Append Trailer then send remaining samples @@ -1418,43 +1455,71 @@ void SoundFlush() // Wait for tx to complete - Debugprintf("Flush Soundmode = %d", SoundMode); +// Debugprintf("Flush Soundmode = %d", SoundMode); if (SoundMode == 0) // ALSA { - usleep(100000); - - while (1 && playhandle) + if (useTimedPTT) { - snd_pcm_sframes_t avail = snd_pcm_avail_update(playhandle); + // Calulate PTT Time from Number of samples and samplerate - // Debugprintf("Waiting for complete. Avail %d Max %d", avail, MaxAvail); + // samples sent is is in SampleNo, Time PTT was raised in timeval pttclk + // txLatency is extra ptt time to compenstate for time soundcard takes to start outputting samples - snd_pcm_status_alloca(&status); // alloca allocates once per function, does not need a free + struct timespec pttnow; -// Debugprintf("Waiting for complete. Avail %d Max %d last %d", avail, MaxAvail, lastavail); + clock_gettime(CLOCK_MONOTONIC, &pttnow); - if ((err = snd_pcm_status(playhandle, status)) != 0) + time_t pttontimemS = (pttclk.tv_sec * 1000) + (pttclk.tv_nsec / 1000000); + time_t nowtimemS = (pttnow.tv_sec * 1000) + (pttnow.tv_nsec / 1000000); + + // We have already added latency to tail, so don't add again + + int txlenMs = (1000 * SampleNo / TX_Samplerate); // 12000 samples per sec. + + Debugprintf("Tx Time %d Time till end = %d", txlenMs, (nowtimemS - pttontimemS)); + + txSleep(txlenMs - (nowtimemS - pttontimemS)); + + } + else + { + usleep(100000); + + while (1 && playhandle) { - Debugprintf("snd_pcm_status() failed: %s", snd_strerror(err)); - break; + snd_pcm_sframes_t avail = snd_pcm_avail_update(playhandle); + + // Debugprintf("Waiting for complete. Avail %d Max %d", avail, MaxAvail); + + snd_pcm_status_alloca(&status); // alloca allocates once per function, does not need a free + + // Debugprintf("Waiting for complete. Avail %d Max %d last %d", avail, MaxAvail, lastavail); + + if ((err = snd_pcm_status(playhandle, status)) != 0) + { + Debugprintf("snd_pcm_status() failed: %s", snd_strerror(err)); + break; + } + + res = snd_pcm_status_get_state(status); + + // Debugprintf("PCM Status = %d", res); + + if (res != SND_PCM_STATE_RUNNING || lastavail == avail) // If sound system is not running then it needs data + // if (res != SND_PCM_STATE_RUNNING) // If sound system is not running then it needs data + // if (MaxAvail - avail < 100) + { + // Send complete - Restart Capture + + OpenSoundCapture(SavedCaptureDevice, SavedCaptureRate, 0); + break; + } + lastavail = avail; + usleep(50000); } - res = snd_pcm_status_get_state(status); - // Debugprintf("PCM Status = %d", res); - - if (res != SND_PCM_STATE_RUNNING || lastavail == avail) // If sound system is not running then it needs data -// if (res != SND_PCM_STATE_RUNNING) // If sound system is not running then it needs data - // if (MaxAvail - avail < 100) - { - // Send complete - Restart Capture - - OpenSoundCapture(SavedCaptureDevice, SavedCaptureRate, 0); - break; - } - lastavail = avail; - usleep(50000); } // I think we should turn round the link here. I dont see the point in // waiting for MainPoll @@ -1819,7 +1884,7 @@ VOID COMClearRTS(HANDLE fd) HANDLE OpenCOMPort(char * Port, int speed, BOOL SetDTR, BOOL SetRTS, BOOL Quiet, int Stopbits) { - char buf[100]; + char buf[256]; // Linux Version. @@ -1928,3 +1993,119 @@ VOID CloseCOMPort(HANDLE fd) close(fd); } +// "hints" processing for looking for SNOOP/MIX devices + +int gethints() +{ + const char *iface = "pcm"; + void **hints; + char **n; + int err; + char hwdev[256]; + snd_pcm_t *pcm = NULL; + char NameString[256]; + + CloseSoundCard(); + + Debugprintf("Available Mix/Snoop Devices\n"); + + PlaybackCount = 0; + CaptureCount = 0; + + err = snd_device_name_hint(-1, iface, &hints); + + if (err < 0) + Debugprintf("snd_device_name_hint error: %s", snd_strerror(err)); + + n = (char **)hints; + + while (*n != NULL) + { + if (memcmp(*n, "NAMEmix", 7) == 0) //NAMEmix00|DESCQtSM Mix for hw0:0 + { + char Hint[256]; + char * ptr; + snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; + + strcpy(Hint, *n); + + ptr = strchr(Hint, '|'); + + if (ptr) + { + *ptr++ = 0; + } + + strcpy(hwdev, &Hint[4]); + + err = snd_pcm_open(&pcm, hwdev, stream, SND_PCM_NONBLOCK); + + if (err) + { + Debugprintf("Error %d opening output device %s ", err, hwdev); + goto nextdevice; + } + + // Add device to list + + if (ptr) + sprintf(NameString, "%s %s", hwdev, &ptr[4]); + else + strcpy(NameString, hwdev); + + Debugprintf(NameString); + + strcpy(PlaybackNames[PlaybackCount++], NameString); + snd_pcm_close(pcm); + pcm = NULL; + } + else if (memcmp(*n, "NAMEsnoop", 9) == 0) + { + char Hint[256]; + char * ptr; + snd_pcm_stream_t stream = SND_PCM_STREAM_CAPTURE; + + strcpy(Hint, *n); + + ptr = strchr(Hint, '|'); + + if (ptr) + { + *ptr++ = 0; + } + + strcpy(hwdev, &Hint[4]); + + err = snd_pcm_open(&pcm, hwdev, stream, SND_PCM_NONBLOCK); + + if (err) + { + Debugprintf("Error %d opening input device %s ", err, hwdev); + goto nextdevice; + } + + // Add device to list + + if (ptr) + sprintf(NameString, "%s %s", hwdev, &ptr[4]); + else + strcpy(NameString, hwdev); + + Debugprintf(NameString); + + strcpy(CaptureNames[CaptureCount++], NameString); + snd_pcm_close(pcm); + pcm = NULL; + } + + nextdevice: + + n++; + + } + snd_device_name_free_hint(hints); + return 0; +} + + + diff --git a/ARDOPC.c b/ARDOPC.c index 3f93242..2652808 100644 --- a/ARDOPC.c +++ b/ARDOPC.c @@ -1024,195 +1024,6 @@ extern int kk; // Info Symbols BOOL blnErrorsCorrected; #define NEWRS - -BOOL xRSDecode(UCHAR * bytRcv, int Length, int CheckLen, BOOL * blnRSOK) -{ -#ifdef NEWRS - - // 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; - MaxErrors = 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; -} - -#else - - // Old (Rick's) code - - // Sets blnRSOK if OK without correction - - // Returns TRUE if OK oe Corrected - // False if Can't correct - - - UCHAR intTemp[256]; // Work Area to pass to Decoder - int i; - int intStartIndex; - UCHAR * ptr2 = intTemp; - UCHAR * ptr1 = bytRcv; - BOOL RSWasOK; - - 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; - tt = sqrt(NPAR); - kk = 255-CheckLen; - generate_gf(); - gen_poly(); - } - - intStartIndex = 255 - Length; // set the start point for shortened RS codes - - // We always work on a 255 byte buffer, prepending zeros if neccessary - - // Clear padding - - memset(ptr2, 0, PadLength); - ptr2 += PadLength; - - memcpy(ptr2, ptr1, Length); - - // convert to indexed form - - for(i = 0; i < 256; i++) - { -// intIsave = i; -// intIndexSave = index_of[intTemp[i]]; - recd[i] = index_of[intTemp[i]]; - } - -// printtick("entering decode_rs"); - - blnErrorsCorrected = FALSE; - - RSWasOK = decode_rs(); - -// printtick("decode_rs Done"); - - *blnRSOK = RSWasOK; - - if (RSWasOK) - return TRUE; - - if(blnErrorsCorrected) - { - for (i = 0; i < DataLen; i++) - { - bytRcv[i] = recd[i + intStartIndex]; - } - return TRUE; - } - - return FALSE; -} -#endif - - - // Function to encode ConnectRequest frame diff --git a/Config.cpp b/Config.cpp index f660aaa..e2239f9 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 @@ -29,8 +29,12 @@ 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 onlyMixSnoop; + +//extern "C" int RX_SR; +//extern "C" int TX_SR; +extern "C" int txLatency; + extern "C" int multiCore; extern "C" char * Wisdom; extern int WaterfallMin; @@ -60,6 +64,10 @@ extern "C" int RSID_SABM[4]; extern "C" int RSID_UI[4]; extern "C" int RSID_SetModem[4]; +extern "C" int nonGUIMode; + + + extern QFont Font; @@ -139,8 +147,12 @@ void getSettings() 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(); +// RX_SR = settings->value("Init/RXSampleRate", 12000).toInt(); +// TX_SR = settings->value("Init/TXSampleRate", 12000).toInt(); + txLatency = settings->value("Init/txLatency", 50).toInt(); + + + onlyMixSnoop = settings->value("Init/onlyMixSnoop", 0).toInt(); strcpy(CaptureDevice, settings->value("Init/SndRXDeviceName", "hw:1,0").toString().toUtf8()); strcpy(PlaybackDevice, settings->value("Init/SndTXDeviceName", "hw:1,0").toString().toUtf8()); @@ -166,6 +178,10 @@ void getSettings() HamLibPort = settings->value("Init/HamLibPort", 4532).toInt(); strcpy(HamLibHost, settings->value("Init/HamLibHost", "127.0.0.1").toString().toUtf8()); + FLRigPort = settings->value("Init/FLRigPort", 12345).toInt(); + strcpy(FLRigHost, settings->value("Init/FLRigHost", "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(); @@ -209,8 +225,8 @@ void getSettings() 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; +// 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(); @@ -365,7 +381,9 @@ void saveSettings() settings->setValue("PointSize", Font.pointSize()); settings->setValue("Weight", Font.weight()); - settings->setValue("PSKWindow", constellationDialog->geometry()); + if (nonGUIMode == 0) + settings->setValue("PSKWindow", constellationDialog->geometry()); + settings->setValue("Init/SoundMode", SoundMode); settings->setValue("Init/UDPClientPort", UDPClientPort); settings->setValue("Init/UDPServerPort", UDPServerPort); @@ -375,9 +393,12 @@ void saveSettings() settings->setValue("Init/UDPHost", UDPHost); - settings->setValue("Init/TXSampleRate", TX_SR); - settings->setValue("Init/RXSampleRate", RX_SR); +// settings->setValue("Init/TXSampleRate", TX_SR); +// settings->setValue("Init/RXSampleRate", RX_SR); + settings->setValue("Init/txLatency", txLatency); + settings->setValue("Init/onlyMixSnoop", onlyMixSnoop); + settings->setValue("Init/SndRXDeviceName", CaptureDevice); settings->setValue("Init/SndTXDeviceName", PlaybackDevice); @@ -400,6 +421,9 @@ void saveSettings() settings->setValue("Init/CM108Addr", CM108Addr); settings->setValue("Init/HamLibPort", HamLibPort); settings->setValue("Init/HamLibHost", HamLibHost); + settings->setValue("Init/FLRigPort", FLRigPort); + settings->setValue("Init/FLRigHost", FLRigHost); + settings->setValue("Init/MinimizetoTray", MintoTray); settings->setValue("Init/multiCore", multiCore); settings->setValue("Init/Wisdom", Wisdom); diff --git a/Config.cpp.bak b/Config.cpp.bak new file mode 100644 index 0000000..d5d7a67 --- /dev/null +++ b/Config.cpp.bak @@ -0,0 +1,481 @@ +/*extern "C" +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 +#include + +#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" char * Wisdom; +extern int WaterfallMin; +extern int WaterfallMax; + +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 QDialog * constellationDialog; +extern QRect PSKRect; + +extern char CWIDCall[128]; +extern "C" char CWIDMark[32]; +extern int CWIDInterval; +extern int CWIDLeft; +extern int CWIDRight; +extern int CWIDType; +extern bool afterTraffic; +extern bool darkTheme; + +extern "C" int RSID_SABM[4]; +extern "C" int RSID_UI[4]; +extern "C" int RSID_SetModem[4]; + +extern QFont Font; + + +QSettings* settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat); + +// 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]; + QVariant Q; + QByteArray x; + sprintf(fullKey, "%s/%s", Prefix, key); + Q = settings->value(fullKey, Default); + x = Q.toString().toUtf8(); + + return Q; +} + +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(); + 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()); + 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(); + il2p_crc[Chan] = getAX25Param("IL2PCRC", 0).toInt(); + RSID_UI[Chan] = getAX25Param("RSID_UI", 0).toInt(); + RSID_SABM[Chan] = getAX25Param("RSID_SABM", 0).toInt(); + RSID_SetModem[Chan] = getAX25Param("RSID_SetModem", 0).toInt(); +} + +void getSettings() +{ + int snd_ch; + + 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(); + 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(); + 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(); + 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(); + + 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(); + CWIDType = settings->value("Modem/CWIDType", 1).toInt(); // on/off + afterTraffic = settings->value("Modem/afterTraffic", false).toBool(); + + 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; + + } + + darkTheme = settings->value("Init/darkTheme", false).toBool(); + + 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]); + saveAX25Param("MEMRecovery", MEMRecovery[Chan]); + saveAX25Param("IPOLL", IPOLL[Chan]); + saveAX25Param("MyDigiCall", MyDigiCall[Chan]); + saveAX25Param("FX25", fx25_mode[Chan]); + saveAX25Param("IL2P", il2p_mode[Chan]); + saveAX25Param("IL2PCRC", il2p_crc[Chan]); + saveAX25Param("RSID_UI", RSID_UI[Chan]); + saveAX25Param("RSID_SABM", RSID_SABM[Chan]); + saveAX25Param("RSID_SetModem", RSID_SetModem[Chan]); +} + +void saveSettings() +{ + QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat); + + settings->setValue("FontFamily", Font.family()); + settings->setValue("PointSize", Font.pointSize()); + settings->setValue("Weight", Font.weight()); + + settings->setValue("PSKWindow", constellationDialog->geometry()); + 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); + 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 + + 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/CWIDMark", CWIDMark); + settings->setValue("Modem/CWIDInterval", CWIDInterval); + settings->setValue("Modem/CWIDLeft", CWIDLeft); + settings->setValue("Modem/CWIDRight", CWIDRight); + settings->setValue("Modem/CWIDType", CWIDType); + settings->setValue("Modem/afterTraffic", afterTraffic); + + settings->setValue("Init/darkTheme", darkTheme); + + saveAX25Params(0); + saveAX25Params(1); + saveAX25Params(2); + saveAX25Params(3); + + settings->sync(); + + delete(settings); +} diff --git a/ModemDialog.ui b/ModemDialog.ui index 9b5b856..54b5220 100644 --- a/ModemDialog.ui +++ b/ModemDialog.ui @@ -6,8 +6,8 @@ 0 0 - 614 - 599 + 597 + 637 @@ -21,8 +21,8 @@ 10 24 - 591 - 471 + 583 + 533 @@ -37,8 +37,8 @@ -1 -1 - 583 - 441 + 605 + 499 @@ -49,17 +49,471 @@ 0 0 - 577 - 439 + 576 + 494 + + + + 0 + 11 + 281 + 477 + + + + Modem params + + + + + 100 + 181 + 61 + 23 + + + + + + + 100 + 21 + 41 + 23 + + + + + + + 100 + 50 + 41 + 23 + + + + + + + 100 + 151 + 61 + 23 + + + + + + + 100 + 221 + 69 + 22 + + + + + NONE + + + + + SINGLE + + + + + + + 10 + 20 + 71 + 23 + + + + TX Delay + + + + + + 10 + 49 + 71 + 23 + + + + TX Tail + + + + + + 10 + 150 + 71 + 23 + + + + Add RX + + + + + + 10 + 180 + 71 + 23 + + + + Add RX Shift + + + + + + 10 + 220 + 91 + 23 + + + + Bits Recovery + + + + + + 176 + 21 + 71 + 23 + + + + msec + + + + + + 176 + 50 + 71 + 23 + + + + msec + + + + + + 176 + 151 + 71 + 23 + + + + pairs + + + + + + 175 + 181 + 71 + 23 + + + + Hz + + + + + + 100 + 251 + 65 + 22 + + + + + None + + + + + RX Only + + + + + RX+TX + + + + + + + 10 + 250 + 86 + 23 + + + + FX25 Mode + + + + + + 10 + 79 + 71 + 23 + + + + Frack + + + + + + 176 + 80 + 71 + 23 + + + + secs + + + + + + 100 + 80 + 41 + 23 + + + + + + + 10 + 396 + 171 + 20 + + + + Send RSID before UI + + + + + + 10 + 422 + 251 + 20 + + + + Send RSID before SABM/UA + + + + + + 10 + 448 + 171 + 20 + + + + Set Modem from RSID + + + + + + 164 + 396 + 101 + 23 + + + + Send RSID + + + + + + 98 + 358 + 181 + 23 + + + + + + + 10 + 358 + 71 + 23 + + + + DigiCalls + + + + + + 10 + 280 + 86 + 23 + + + + IL2P Mode + + + + + + 100 + 280 + 81 + 22 + + + + + None + + + + + RX Only + + + + + RX+TX + + + + + il2p Only + + + + + + + 130 + 111 + 71 + 23 + + + + maxFrame + + + + + + 200 + 112 + 41 + 23 + + + + + + + 70 + 111 + 41 + 23 + + + + + + + 10 + 110 + 71 + 23 + + + + Retries + + + + + + 24 + 308 + 93 + 17 + + + + Send CRC + + + + + + 124 + 308 + 115 + 17 + + + + Check CRC + + + 290 - 10 - 283 - 349 + 12 + 297 + 283 @@ -310,447 +764,6 @@ - - - - 0 - 10 - 281 - 431 - - - - Modem params - - - - - 100 - 181 - 61 - 23 - - - - - - - 100 - 21 - 41 - 23 - - - - - - - 100 - 50 - 41 - 23 - - - - - - - 100 - 151 - 61 - 23 - - - - - - - 100 - 221 - 69 - 22 - - - - - NONE - - - - - SINGLE - - - - - - - 10 - 20 - 71 - 23 - - - - TX Delay - - - - - - 10 - 49 - 71 - 23 - - - - TX Tail - - - - - - 10 - 150 - 71 - 23 - - - - Add RX - - - - - - 10 - 180 - 71 - 23 - - - - Add RX Shift - - - - - - 10 - 220 - 91 - 23 - - - - Bits Recovery - - - - - - 176 - 21 - 71 - 23 - - - - msec - - - - - - 176 - 50 - 71 - 23 - - - - msec - - - - - - 176 - 151 - 71 - 23 - - - - pairs - - - - - - 175 - 181 - 71 - 23 - - - - Hz - - - - - - 100 - 251 - 81 - 22 - - - - - None - - - - - RX Only - - - - - RX+TX - - - - - - - 10 - 250 - 86 - 23 - - - - FX25 Mode - - - - - - 10 - 79 - 71 - 23 - - - - Frack - - - - - - 176 - 80 - 71 - 23 - - - - secs - - - - - - 100 - 80 - 41 - 23 - - - - - - - 10 - 348 - 171 - 20 - - - - Send RSID before UI - - - - - - 10 - 374 - 251 - 20 - - - - Send RSID before SABM/UA - - - - - - 10 - 400 - 171 - 20 - - - - Set Modem from RSID - - - - - - 164 - 348 - 101 - 23 - - - - Send RSID - - - - - - 100 - 310 - 181 - 23 - - - - - - - 10 - 310 - 71 - 23 - - - - DigiCalls - - - - - - 10 - 279 - 86 - 23 - - - - IL2P Mode - - - - - - 100 - 280 - 81 - 22 - - - - - None - - - - - RX Only - - - - - RX+TX - - - - - il2p Only - - - - - - - 130 - 111 - 71 - 23 - - - - maxFrame - - - - - - 200 - 112 - 41 - 23 - - - - - - - 70 - 111 - 41 - 23 - - - - - - - 10 - 110 - 71 - 23 - - - - Retries - - - - - - 200 - 280 - 53 - 21 - - - - CRC - - - @@ -776,14 +789,14 @@ 0 0 570 - 439 + 502 290 - 10 + 11 281 287 @@ -1083,7 +1096,7 @@ 0 10 281 - 431 + 477 @@ -1344,7 +1357,7 @@ 164 - 348 + 396 101 23 @@ -1357,7 +1370,7 @@ 10 - 348 + 396 171 20 @@ -1370,7 +1383,7 @@ 10 - 400 + 448 171 20 @@ -1383,7 +1396,7 @@ 10 - 374 + 422 251 20 @@ -1396,7 +1409,7 @@ 10 - 310 + 358 71 23 @@ -1409,7 +1422,7 @@ 100 - 310 + 358 181 23 @@ -1458,17 +1471,30 @@ - + - 200 - 280 - 53 - 21 + 124 + 308 + 129 + 17 - CRC + Check CRC + + + + + + 24 + 308 + 97 + 17 + + + + Send CRC @@ -1543,7 +1569,7 @@ 0 0 570 - 581 + 502 @@ -1850,7 +1876,7 @@ 0 10 281 - 441 + 477 @@ -2111,7 +2137,7 @@ 10 - 374 + 422 251 20 @@ -2124,7 +2150,7 @@ 10 - 400 + 448 171 20 @@ -2137,7 +2163,7 @@ 10 - 348 + 396 171 20 @@ -2150,7 +2176,7 @@ 164 - 348 + 396 101 23 @@ -2163,7 +2189,7 @@ 10 - 310 + 358 71 23 @@ -2176,7 +2202,7 @@ 100 - 310 + 358 181 23 @@ -2228,17 +2254,30 @@ - + - 200 - 280 - 53 - 21 + 124 + 308 + 107 + 17 - CRC + Check CRC + + + + + + 24 + 308 + 95 + 17 + + + + Send CRC @@ -2309,8 +2348,8 @@ 0 0 - 570 - 581 + 575 + 502 @@ -2617,7 +2656,7 @@ 0 10 281 - 431 + 477 @@ -2878,7 +2917,7 @@ 10 - 374 + 422 251 20 @@ -2891,7 +2930,7 @@ 10 - 400 + 448 171 20 @@ -2904,7 +2943,7 @@ 10 - 348 + 396 171 20 @@ -2917,7 +2956,7 @@ 164 - 348 + 396 101 23 @@ -2930,7 +2969,7 @@ 10 - 310 + 358 71 23 @@ -2943,7 +2982,7 @@ 100 - 310 + 358 181 23 @@ -2995,17 +3034,30 @@ - + - 200 - 280 - 53 - 21 + 124 + 308 + 129 + 17 - CRC + Check CRC + + + + + + 24 + 308 + 99 + 17 + + + + Send CRC @@ -3060,7 +3112,7 @@ 127 - 550 + 596 276 33 @@ -3111,7 +3163,7 @@ 12 - 516 + 562 71 20 @@ -3124,7 +3176,7 @@ 78 - 516 + 562 71 20 @@ -3137,7 +3189,7 @@ 340 - 516 + 562 61 20 @@ -3153,7 +3205,7 @@ 396 - 516 + 562 101 20 @@ -3174,7 +3226,7 @@ 158 - 516 + 562 61 20 @@ -3187,7 +3239,7 @@ 208 - 516 + 562 31 20 @@ -3197,7 +3249,7 @@ 248 - 516 + 562 61 20 @@ -3210,7 +3262,7 @@ 318 - 516 + 562 31 20 @@ -3226,7 +3278,7 @@ 500 - 516 + 562 91 20 diff --git a/Modulate.c b/Modulate.c index 1f996db..86900e3 100644 --- a/Modulate.c +++ b/Modulate.c @@ -5,7 +5,7 @@ #define ARDOPBufferSize 12000 * 100 -extern char CWIDMark[32]; +char CWIDMark[32] = ""; extern short ARDOPTXBuffer[4][ARDOPBufferSize]; // Enough to hold whole frame of samples diff --git a/QtSoundModem.cpp b/QtSoundModem.cpp index fe1a717..37c6c46 100644 --- a/QtSoundModem.cpp +++ b/QtSoundModem.cpp @@ -47,8 +47,10 @@ along with QtSoundModem. If not, see http://www.gnu.org/licenses #include #include #include +#include #include "UZ7HOStuff.h" +#include QImage *Constellation[4]; QImage *Waterfall = 0; @@ -93,6 +95,8 @@ extern "C" char CaptureNames[16][256]; extern "C" char PlaybackNames[16][256]; extern "C" int SoundMode; +extern "C" int onlyMixSnoop; + extern "C" int multiCore; extern "C" int refreshModems; @@ -112,6 +116,8 @@ extern "C" int using48000; // Set if using 48K sample rate (ie RUH Modem activ extern "C" int ReceiveSize; extern "C" int SendSize; // 100 mS for now +extern "C" int txLatency; + extern "C" { int InitSound(BOOL Report); @@ -132,6 +138,7 @@ extern "C" void sendRSID(int Chan, int dropTX); void RSIDinitfft(); void il2p_init(int il2p_debug); + void closeTraceLog(); } void make_graph_buf(float * buf, short tap, QPainter * bitmap); @@ -147,7 +154,7 @@ int FreqD = 1500; int DCD = 50; char CWIDCall[128] = ""; -extern "C" char CWIDMark[32] = ""; +extern "C" char CWIDMark[32]; int CWIDInterval = 0; int CWIDLeft = 0; int CWIDRight = 0; @@ -220,8 +227,13 @@ extern "C" int TXPort; extern char UDPHost[64]; QTimer *cwidtimer; +QTimer *PTTWatchdog; + QWidget * mythis; +QElapsedTimer pttOnTimer; + + QSystemTrayIcon * trayIcon = nullptr; @@ -318,9 +330,6 @@ void QtSoundModem::resizeEvent(QResizeEvent* event) QRect r = geometry(); - QRect r1 = ui.monWindow->geometry(); - QRect r2 = ui.centralWidget->geometry(); - int modemBoxHeight = 34; int Width = r.width(); @@ -500,6 +509,7 @@ void DoPSKWindows() } QTimer *wftimer; +extern "C" struct timespec pttclk; QtSoundModem::QtSoundModem(QWidget *parent) : QMainWindow(parent) { @@ -507,6 +517,12 @@ QtSoundModem::QtSoundModem(QWidget *parent) : QMainWindow(parent) int csize; QFont::Weight weight; +#ifndef WIN32 + clock_getres(CLOCK_MONOTONIC, &pttclk); + printf("CLOCK_MONOTONIC %d, %d\n", pttclk.tv_sec, pttclk.tv_nsec); +#endif + + ui.setupUi(this); mythis = this; @@ -759,6 +775,11 @@ QtSoundModem::QtSoundModem(QWidget *parent) : QMainWindow(parent) QObject::connect(t, SIGNAL(updateDCD(int, int)), this, SLOT(doupdateDCD(int, int)), Qt::QueuedConnection); QObject::connect(t, SIGNAL(startCWIDTimer()), this, SLOT(startCWIDTimerSlot()), Qt::QueuedConnection); + QObject::connect(t, SIGNAL(setWaterfallImage()), this, SLOT(setWaterfallImage()), Qt::QueuedConnection); + QObject::connect(t, SIGNAL(setLevelImage()), this, SLOT(setLevelImage()), Qt::QueuedConnection); + QObject::connect(t, SIGNAL(setConstellationImage(int, int)), this, SLOT(setConstellationImage(int, int)), Qt::QueuedConnection); + QObject::connect(t, SIGNAL(startWatchdog()), this, SLOT(StartWatchdog()), Qt::QueuedConnection); + QObject::connect(t, SIGNAL(stopWatchdog()), this, SLOT(StopWatchdog()), Qt::QueuedConnection); connect(ui.RXOffsetA, SIGNAL(returnPressed()), this, SLOT(returnPressed())); connect(ui.RXOffsetB, SIGNAL(returnPressed()), this, SLOT(returnPressed())); @@ -771,11 +792,13 @@ QtSoundModem::QtSoundModem(QWidget *parent) : QMainWindow(parent) wftimer = new QTimer(this); connect(wftimer, SIGNAL(timeout()), this, SLOT(doRestartWF())); - wftimer->start(1000 * 300); +// wftimer->start(1000 * 300); cwidtimer = new QTimer(this); connect(cwidtimer, SIGNAL(timeout()), this, SLOT(CWIDTimer())); + PTTWatchdog = new QTimer(this); + connect(PTTWatchdog, SIGNAL(timeout()), this, SLOT(PTTWatchdogExpired())); if (CWIDInterval && afterTraffic == false) cwidtimer->start(CWIDInterval * 60000); @@ -1023,7 +1046,7 @@ void QtSoundModem::clickedSlotI(int i) if (strcmp(Name, "centerB") == 0) { - if (i > 299) + if (i > 300) { QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat); ui.centerB->setValue(Freq_Change(1, i)); @@ -1452,10 +1475,14 @@ void QtSoundModem::doModems() Dlg->IL2PModeC->setCurrentIndex(il2p_mode[2]); Dlg->IL2PModeD->setCurrentIndex(il2p_mode[3]); - Dlg->CRC_A->setChecked(il2p_crc[0]); - Dlg->CRC_B->setChecked(il2p_crc[1]); - Dlg->CRC_C->setChecked(il2p_crc[2]); - Dlg->CRC_D->setChecked(il2p_crc[3]); + Dlg->CRCTX_A->setChecked((il2p_crc[0] & 1)); + Dlg->CRCRX_A->setChecked((il2p_crc[0] & 2)); + Dlg->CRCTX_B->setChecked((il2p_crc[1] & 1)); + Dlg->CRCRX_B->setChecked((il2p_crc[1] & 2)); + Dlg->CRCTX_C->setChecked((il2p_crc[2] & 1)); + Dlg->CRCRX_C->setChecked((il2p_crc[2] & 2)); + Dlg->CRCTX_D->setChecked((il2p_crc[3] & 1)); + Dlg->CRCRX_D->setChecked((il2p_crc[3] & 2)); Dlg->CWIDCall->setText(CWIDCall); Dlg->CWIDInterval->setText(QString::number(CWIDInterval)); @@ -1681,10 +1708,21 @@ void QtSoundModem::modemSave() il2p_mode[2] = Dlg->IL2PModeC->currentIndex(); il2p_mode[3] = Dlg->IL2PModeD->currentIndex(); - il2p_crc[0] = Dlg->CRC_A->isChecked(); - il2p_crc[1] = Dlg->CRC_B->isChecked(); - il2p_crc[2] = Dlg->CRC_C->isChecked(); - il2p_crc[3] = Dlg->CRC_D->isChecked(); + il2p_crc[0] = Dlg->CRCTX_A->isChecked(); + if (Dlg->CRCRX_A->isChecked()) + il2p_crc[0] |= 2; + + il2p_crc[1] = Dlg->CRCTX_B->isChecked(); + if (Dlg->CRCRX_B->isChecked()) + il2p_crc[1] |= 2; + + il2p_crc[2] = Dlg->CRCTX_C->isChecked(); + if (Dlg->CRCRX_C->isChecked()) + il2p_crc[2] |= 2; + + il2p_crc[3] = Dlg->CRCTX_D->isChecked(); + if (Dlg->CRCRX_D->isChecked()) + il2p_crc[3] |= 2; recovery[0] = Dlg->recoverBitA->currentIndex(); recovery[1] = Dlg->recoverBitB->currentIndex(); @@ -1822,6 +1860,8 @@ char NewPTTPort[80]; int newSoundMode = 0; int oldSoundMode = 0; +int oldSnoopMix = 0; +int newSnoopMix = 0; void QtSoundModem::SoundModeChanged(bool State) { @@ -1829,6 +1869,8 @@ void QtSoundModem::SoundModeChanged(bool State) // Mustn't change SoundMode until dialog is accepted + newSnoopMix = Dev->onlyMixSnoop->isChecked(); + if (Dev->UDP->isChecked()) newSoundMode = 3; else if (Dev->PULSE->isChecked()) @@ -1914,6 +1956,17 @@ void QtSoundModem::PTTPortChanged(int Selected) Dev->PTTOn->setText(HamLibHost); Dev->PTTOn->setVisible(true); } + else if (strcmp(NewPTTPort, "FLRIG") == 0) + { + Dev->CM108Label->setVisible(true); + Dev->CM108Label->setText("FLRig Port"); + Dev->VIDPID->setText(QString::number(FLRigPort)); + Dev->VIDPID->setVisible(true); + Dev->PTTOnLab->setText("FLRig Host"); + Dev->PTTOnLab->setVisible(true); + Dev->PTTOn->setText(FLRigHost); + Dev->PTTOn->setVisible(true); + } else { Dev->RTSDTR->setVisible(true); @@ -1974,26 +2027,35 @@ void QtSoundModem::doDevices() newSoundMode = SoundMode; oldSoundMode = SoundMode; + oldSnoopMix = newSnoopMix = onlyMixSnoop; #ifdef WIN32 Dev->ALSA->setText("WaveOut"); Dev->OSS->setVisible(0); Dev->PULSE->setVisible(0); -#endif - + Dev->onlyMixSnoop->setVisible(0); + Dev->ALSA->setChecked(1); +#else if (SoundMode == 0) + { + Dev->onlyMixSnoop->setVisible(1); 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); +#endif + + Dev->onlyMixSnoop->setChecked(onlyMixSnoop); 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))); + connect(Dev->onlyMixSnoop, SIGNAL(toggled(bool)), this, SLOT(SoundModeChanged(bool))); for (i = 0; i < PlaybackCount; i++) Dev->outputDevice->addItem(&PlaybackNames[i][0]); @@ -2025,6 +2087,8 @@ void QtSoundModem::doDevices() } Dev->inputDevice->setCurrentIndex(i); + Dev->txLatency->setText(QString::number(txLatency)); + Dev->Modem_1_Chan->setCurrentIndex(soundChannel[0]); Dev->Modem_2_Chan->setCurrentIndex(soundChannel[1]); Dev->Modem_3_Chan->setCurrentIndex(soundChannel[2]); @@ -2101,6 +2165,7 @@ void QtSoundModem::doDevices() //#endif Dev->PTTPort->addItem("HAMLIB"); + Dev->PTTPort->addItem("FLRIG"); for (const QString &info : items) { @@ -2186,7 +2251,7 @@ void QtSoundModem::deviceaccept() } } - if (oldSoundMode != newSoundMode) + if (oldSoundMode != newSoundMode || oldSnoopMix != newSnoopMix) { QMessageBox msgBox; @@ -2201,6 +2266,7 @@ void QtSoundModem::deviceaccept() if (i == QMessageBox::Ok) { SoundMode = newSoundMode; + onlyMixSnoop = newSnoopMix; saveSettings(); Closing = 1; @@ -2229,6 +2295,12 @@ void QtSoundModem::deviceaccept() cardChanged = 1; } + if (onlyMixSnoop != Dev->onlyMixSnoop->isChecked()) + { + onlyMixSnoop = Dev->onlyMixSnoop->isChecked(); + cardChanged = 1; + } + CaptureIndex = Dev->inputDevice->currentIndex(); Q = Dev->outputDevice->currentText(); @@ -2241,6 +2313,9 @@ void QtSoundModem::deviceaccept() PlayBackIndex = Dev->outputDevice->currentIndex(); + Q = Dev->txLatency->text(); + txLatency = Q.toInt(); + soundChannel[0] = Dev->Modem_1_Chan->currentIndex(); soundChannel[1] = Dev->Modem_2_Chan->currentIndex(); soundChannel[2] = Dev->Modem_3_Chan->currentIndex(); @@ -2343,6 +2418,12 @@ void QtSoundModem::deviceaccept() Q = Dev->PTTOn->text(); strcpy(HamLibHost, Q.toString().toUtf8()); } + else if (strcmp(PTTPort, "FLRIG") == 0) + { + FLRigPort = Q.toInt(); + Q = Dev->PTTOn->text(); + strcpy(FLRigHost, Q.toString().toUtf8()); + } Q = Dev->WaterfallMax->currentText(); newMax = Q.toInt(); @@ -2424,16 +2505,21 @@ void QtSoundModem::handleButton(int Port, int Type) + void QtSoundModem::doRestartWF() { - if (inWaterfall) + return; + + if (((tx_status[0] | tx_status[1] | tx_status[2] | tx_status[3]) != TX_SILENCE) || inWaterfall) { // in waterfall update thread wftimer->start(5000); return; } - + + wftimer->start(1000 * 300); + lockWaterfall = true; if (Firstwaterfall | Secondwaterfall) @@ -2580,7 +2666,7 @@ void RefreshLevel(unsigned int Level, unsigned int LevelR) RXLevel->setPixel(x, y, white); } } - RXLevelCopy->setPixmap(QPixmap::fromImage(*RXLevel)); +// RXLevelCopy->setPixmap(QPixmap::fromImage(*RXLevel)); for (x = 0; x < 150; x++) { @@ -2599,7 +2685,10 @@ void RefreshLevel(unsigned int Level, unsigned int LevelR) RXLevel2->setPixel(x, y, white); } } - RXLevel2Copy->setPixmap(QPixmap::fromImage(*RXLevel2)); + + emit t->setLevelImage(); + +/// RXLevel2Copy->setPixmap(QPixmap::fromImage(*RXLevel2)); } extern "C" unsigned char CurrentLevel; @@ -2888,7 +2977,9 @@ extern "C" void displayWaterfall() else WaterfallCopy->setAlignment(Qt::AlignTop | Qt::AlignLeft); - WaterfallCopy->setPixmap(QPixmap::fromImage(*Waterfall)); +// WaterfallCopy->setPixmap(QPixmap::fromImage(*Waterfall)); + + emit t->setWaterfallImage(); } extern "C" float aFFTAmpl[1024]; @@ -2904,6 +2995,9 @@ void doWaterfallThread(void * param) if (Configuring) return; + if (inWaterfall) + return; + inWaterfall = true; // don't allow restart waterfall if (snd_ch == 1 && UsingLeft == 0) // Only using right @@ -3003,7 +3097,9 @@ void doWaterfallThread(void * param) SMUpdateBusyDetector(snd_ch, RealOut, ImagOut); - // we always do fft so we can get centre freq and do busy detect. But only upodate waterfall if on display + + + // we always do fft so we can get centre freq and do busy detect. But only update waterfall if on display if (bm == 0) { @@ -3042,6 +3138,8 @@ void doWaterfallThread(void * param) // Scroll + + int TopLine = NextWaterfallLine[snd_ch]; int TopScanLine = WaterfallHeaderPixels; @@ -3053,10 +3151,24 @@ void doWaterfallThread(void * param) memcpy(&WaterfallLines[snd_ch][NextWaterfallLine[snd_ch]++][0], Line, 4096); if (NextWaterfallLine[snd_ch] > 79) NextWaterfallLine[snd_ch] = 0; + + // Sanity check + + if ((79 + TopScanLine) >= bm->height()) + { + printf("Invalid WFMaxLine %d \n", bm->height()); + exit(1); + } + for (int j = 79; j > 0; j--) { p = bm->scanLine(j + TopScanLine); + if (p == nullptr) + { + printf("Invalid WF Pointer \n"); + exit(1); + } memcpy(p, &WaterfallLines[snd_ch][TopLine][0], lineLen); TopLine++; if (TopLine > 79) @@ -3066,6 +3178,26 @@ void doWaterfallThread(void * param) inWaterfall = false; } +void QtSoundModem::setWaterfallImage() +{ + ui.Waterfall->setPixmap(QPixmap::fromImage(*Waterfall)); +} + +void QtSoundModem::setLevelImage() +{ + RXLevelCopy->setPixmap(QPixmap::fromImage(*RXLevel)); + RXLevel2Copy->setPixmap(QPixmap::fromImage(*RXLevel2)); +} + +void QtSoundModem::setConstellationImage(int chan, int Qual) +{ + char QualText[64]; + sprintf(QualText, "Chan %c Qual = %d", chan + 'A', Qual); + QualLabel[chan]->setText(QualText); + constellationLabel[chan]->setPixmap(QPixmap::fromImage(*Constellation[chan])); +} + + void QtSoundModem::changeEvent(QEvent* e) { @@ -3109,6 +3241,8 @@ void QtSoundModem::closeEvent(QCloseEvent *event) QtSoundModem::~QtSoundModem() { qDebug() << "Saving Settings"; + + closeTraceLog(); QSettings mysettings("QtSoundModem.ini", QSettings::IniFormat); mysettings.setValue("geometry", saveGeometry()); @@ -3134,7 +3268,7 @@ void QtSoundModem::show_grid() int snd_ch, i, num_rows, row_idx; QTableWidgetItem *item; - const char * msg; + const char * msg = ""; int speed_tx, speed_rx; @@ -3279,7 +3413,7 @@ void QtSoundModem::onTEselectionChanged() extern "C" int SMUpdatePhaseConstellation(int chan, float * Phases, float * Mags, int intPSKPhase, int Count) { - // Subroutine to update bmpConstellation plot for PSK modes... + // Subroutine to update Constellation plot for PSK modes... // Skip plotting and calculations of intPSKPhase(0) as this is a reference phase (9/30/2014) float dblPhaseError; @@ -3347,13 +3481,121 @@ extern "C" int SMUpdatePhaseConstellation(int chan, float * Phases, float * Mags if (nonGUIMode == 0) { - char QualText[64]; - sprintf(QualText, "Chan %c Qual = %d", chan + 'A', intQuality); - QualLabel[chan]->setText(QualText); - constellationLabel[chan]->setPixmap(QPixmap::fromImage(*Constellation[chan])); + emit t->setConstellationImage(chan, intQuality); +// char QualText[64]; +// sprintf(QualText, "Chan %c Qual = %d", chan + 'A', intQuality); +// QualLabel[chan]->setText(QualText); +// constellationLabel[chan]->setPixmap(QPixmap::fromImage(*Constellation[chan])); } return intQuality; } +QFile tracefile("Tracelog.txt"); + + +extern "C" int openTraceLog() +{ + if (!tracefile.open(QIODevice::Append | QIODevice::Text)) + return 0; + + return 1; +} + +extern "C" qint64 writeTraceLog(char * Data, char Dirn) +{ + return tracefile.write(Data); +} + +extern "C" void closeTraceLog() +{ + tracefile.close(); +} + +extern "C" void debugTimeStamp(char * Text, char Dirn) +{ +#ifndef LOGTX + + if (Dirn == 'T') + return; + +#endif + +#ifndef LOGRX + + if (Dirn == 'R') + return; + +#endif + + + QTime Time(QTime::currentTime()); + QString String = Time.toString("hh:mm:ss.zzz"); + char Msg[2048]; + + sprintf(Msg, "%s %s\n", String.toUtf8().data(), Text); + qint64 ret = writeTraceLog(Msg, Dirn); +} + + + +// Timer functions need to run in GUI Thread + +extern "C" int SampleNo; + + +extern "C" int pttOnTime() +{ + return pttOnTimer.elapsed(); +} + +extern "C" void startpttOnTimer() +{ + pttOnTimer.start(); +} + + +extern "C" void StartWatchdog() +{ + // Get Monotonic clock for PTT drop time calculation + +#ifndef WIN32 + clock_gettime(CLOCK_MONOTONIC, &pttclk); +#endif + debugTimeStamp((char *)"PTT On", 'T'); + emit t->startWatchdog(); + pttOnTimer.start(); +} + +extern "C" void StopWatchdog() +{ + int txlenMs = (1000 * SampleNo / TX_Samplerate); + + Debugprintf("Samples Sent %d, Calc Time %d, PTT Time %d", SampleNo, txlenMs, pttOnTime()); + debugTimeStamp((char *)"PTT Off", 'T'); + closeTraceLog(); + openTraceLog(); + debugTimeStamp((char *)"Log Reopened", 'T'); + + emit t->stopWatchdog(); +} + + + +void QtSoundModem::StartWatchdog() +{ + PTTWatchdog->start(60 * 1000); +} + + void QtSoundModem::StopWatchdog() +{ + PTTWatchdog->stop(); +} + + + void QtSoundModem::PTTWatchdogExpired() + { + PTTWatchdog->stop(); + } + diff --git a/QtSoundModem.cpp.bak b/QtSoundModem.cpp.bak new file mode 100644 index 0000000..1dd6aa1 --- /dev/null +++ b/QtSoundModem.cpp.bak @@ -0,0 +1,3380 @@ +/* +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 +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "UZ7HOStuff.h" + + +QImage *Constellation[4]; +QImage *Waterfall = 0; +QLabel *DCDLabel[4]; +QLineEdit *chanOffsetLabel[4]; +QImage *DCDLed[4]; + +QImage *RXLevel; +QImage *RXLevel2; + +QLabel *WaterfallCopy; + +QLabel * RXLevelCopy; +QLabel * RXLevel2Copy; + +QTextEdit * monWindowCopy; + +extern workerThread *t; +extern QtSoundModem * w; +extern QCoreApplication * a; + +QList Ports = QSerialPortInfo::availablePorts(); + +void saveSettings(); +void getSettings(); +void DrawModemFreqRange(); + +extern "C" void CloseSound(); +extern "C" void GetSoundDevices(); +extern "C" char modes_name[modes_count][21]; +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; +int NeedPSKRefresh; + +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 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); + 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 FourierTransform(int NumSamples, short * RealIn, float * RealOut, float * ImagOut, int InverseTransform); + void dofft(short * in, float * outr, float * outi); + void init_raduga(); + void DrawFreqTicks(); + 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] = ""; +extern "C" char CWIDMark[32] = ""; +int CWIDInterval = 0; +int CWIDLeft = 0; +int CWIDRight = 0; +int CWIDType = 1; // on/off +bool afterTraffic = 0; +bool cwidtimerisActive = false; + +int WaterfallMin = 00; +int WaterfallMax = 6000; + +int Configuring = 0; +bool lockWaterfall = false; +bool inWaterfall = false; + +extern "C" int NeedWaterfallHeaders; +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 + +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); + +QRgb txText = qRgb(192, 0, 0); +QRgb rxText = qRgb(0, 0, 192); + +bool darkTheme = true; +bool minimizeonStart = true; + +// 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, 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; +QWidget * mythis; + + +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: 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(); + + QRect r1 = ui.monWindow->geometry(); + QRect r2 = ui.centralWidget->geometry(); + + int modemBoxHeight = 34; + + int Width = r.width(); + int Height = r.height() - 25; + + int monitorTop; + int monitorHeight; + int sessionTop; + int sessionHeight = 0; + int waterfallsTop; + int waterfallsHeight = WaterfallImageHeight; + + 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; + + ui.Waterfall->setVisible(0); + + monitorTop = modemBoxHeight + 1; + + // Now have one waterfall label containing headers and waterfalls + + if (Firstwaterfall || Secondwaterfall) + ui.Waterfall->setVisible(1); + + if (AGWServ) + { + sessionTable->setVisible(true); + sessionHeight = 150; + } + else + { + sessionTable->setVisible(false); + } + + // if only displaying one Waterfall, change height of waterfall area + + if (UsingBothChannels == 0 || (Firstwaterfall == 0) || (Secondwaterfall == 0)) + { + waterfallsHeight /= 2; + } + + if ((Firstwaterfall == 0) && (Secondwaterfall == 0)) + waterfallsHeight = 0; + + monitorHeight = Height - sessionHeight - waterfallsHeight - modemBoxHeight; + waterfallsTop = Height - waterfallsHeight; + sessionTop = Height - (sessionHeight + waterfallsHeight); + + ui.monWindow->setGeometry(QRect(0, monitorTop, Width, monitorHeight)); + + if (AGWServ) + sessionTable->setGeometry(QRect(0, sessionTop, Width, sessionHeight)); + + if (waterfallsHeight) + ui.Waterfall->setGeometry(QRect(0, waterfallsTop, Width, waterfallsHeight + 2)); +} + +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(QObject::sender()); + + int state = Act->isChecked(); + + if (Act == actWaterfall1) + Firstwaterfall = state; + + else if (Act == actWaterfall2) + Secondwaterfall = state; + + initWaterfall(Firstwaterfall | Secondwaterfall); + + saveSettings(); +} + +void QtSoundModem::initWaterfall(int state) +{ + if (state == 1) + { + // if (ui.Waterfall) + // { + // delete ui.Waterfall; + // ui.Waterfall = new QLabel(ui.centralWidget); + // } + WaterfallCopy = ui.Waterfall; + + Waterfall = new QImage(1024, WaterfallImageHeight + 2, QImage::Format_RGB32); + + NeedWaterfallHeaders = 1; + + } + else + { + delete(Waterfall); + Waterfall = 0; + } + + QSize Size(800, 602); // Not actually used, but Event constructor needs it + + QResizeEvent *event = new QResizeEvent(Size, Size); + QApplication::sendEvent(this, event); +} + +QRect PSKRect = { 100,100,100,100 }; + +QDialog * constellationDialog; +QLabel * constellationLabel[4]; +QLabel * QualLabel[4]; + +// Local copies + +QLabel *RXOffsetLabel; +QSlider *RXOffset; + +QFont Font; + +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 (soundChannel[i] && 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); +} + +QTimer *wftimer; + +QtSoundModem::QtSoundModem(QWidget *parent) : QMainWindow(parent) +{ + QString family; + int csize; + QFont::Weight weight; + + ui.setupUi(this); + + mythis = this; + + getSettings(); + + QSettings mysettings("QtSoundModem.ini", QSettings::IniFormat); + + family = mysettings.value("FontFamily", "Courier New").toString(); + csize = mysettings.value("PointSize", 0).toInt(); + weight = (QFont::Weight)mysettings.value("Weight", 50).toInt(); + + Font = QFont(family); + Font.setPointSize(csize); + Font.setWeight(weight); + + QApplication::setFont(Font); + + 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]; + 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))); + } + + 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()); + + sessionTable = new QTableWidget(ui.centralWidget); + + 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"; + + mysetstyle(); + + 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())); + + actFont = new QAction("Setup Font", this); + actFont->setObjectName("actFont"); + setupMenu->addAction(actFont); + + connect(actFont, 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())); + + RXLevel = new QImage(150, 10, QImage::Format_RGB32); + RXLevel->fill(white); + ui.RXLevel->setPixmap(QPixmap::fromImage(*RXLevel)); + RXLevelCopy = ui.RXLevel; + + RXLevel2 = new QImage(150, 10, QImage::Format_RGB32); + RXLevel2->fill(white); + ui.RXLevel2->setPixmap(QPixmap::fromImage(*RXLevel2)); + RXLevel2Copy = ui.RXLevel2; + + 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; + + WaterfallCopy = ui.Waterfall; + + initWaterfall(Firstwaterfall | Secondwaterfall); + + monWindowCopy = ui.monWindow; + + ui.monWindow->document()->setMaximumBlockCount(10000); + +// connect(ui.monWindow, SIGNAL(selectionChanged()), this, SLOT(onTEselectionChanged())); + + 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))); + + 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]); + 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); + + QObject::connect(t, SIGNAL(startCWIDTimer()), this, SLOT(startCWIDTimerSlot()), 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); + + 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())); + + + if (CWIDInterval && afterTraffic == false) + cwidtimer->start(CWIDInterval * 60000); + + if (RSID_SetModem[0]) + { + RSID_WF = 1; + RSIDinitfft(); + } +// il2p_init(1); + + QTimer::singleShot(200, this, &QtSoundModem::updateFont); + +} + +void QtSoundModem::updateFont() +{ + QApplication::setFont(Font); +} + +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); + +extern "C" void checkforCWID() +{ + emit(t->startCWIDTimer()); +}; + +extern "C" void QtSoundModem::startCWIDTimerSlot() +{ + if (CWIDInterval && afterTraffic == 1 && cwidtimerisActive == false) + { + cwidtimerisActive = true; + QTimer::singleShot(CWIDInterval * 60000, this, &QtSoundModem::CWIDTimer); + } +} + +void QtSoundModem::CWIDTimer() +{ + cwidtimerisActive = false; + sendCWID(CWIDCall, CWIDType, 0); + calib_mode[0] = 4; +} + +void extSetOffset(int chan) +{ + char valChar[32]; + sprintf(valChar, "%d", chanOffset[chan]); + chanOffsetLabel[chan]->setText(valChar); + + NeedWaterfallHeaders = true; + + 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]); + } + if (NeedPSKRefresh) + { + NeedPSKRefresh = 0; + DoPSKWindows(); + } + + if (NeedWaterfallHeaders) + { + NeedWaterfallHeaders = 0; + + if (Waterfall) + { + Waterfall->fill(black); + DrawModemFreqRange(); + DrawFreqTicks(); + } + } + + 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 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) +{ + char Name[32]; + + strcpy(Name, sender()->objectName().toUtf8()); + + 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; + } + + 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; + } + + 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; + } + + 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; + } + + if (strcmp(Name, "centerA") == 0) + { + if (i > 299) + { + 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 > 299) + { + 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 > 299) + { + 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 > 299) + { + 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); + + NeedWaterfallHeaders = true; + + 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; + } + + if (strcmp(Name, "actFont") == 0) + { + bool ok; + Font = QFontDialog::getFont(&ok, QFont(Font, this)); + + if (ok) + { + // the user clicked OK and font is set to the font the user selected + QApplication::setFont(Font); + sessionTable->horizontalHeader()->setFont(Font); + + saveSettings(); + } + else + { + // the user canceled the dialog; font is set to the initial + // value, in this case Helvetica [Cronyx], 10 + +// QApplication::setFont(Font); + + } + 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", 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]); + 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->CRC_A->setChecked(il2p_crc[0]); + Dlg->CRC_B->setChecked(il2p_crc[1]); + Dlg->CRC_C->setChecked(il2p_crc[2]); + Dlg->CRC_D->setChecked(il2p_crc[3]); + + Dlg->CWIDCall->setText(CWIDCall); + Dlg->CWIDInterval->setText(QString::number(CWIDInterval)); + Dlg->CWIDMark->setText(CWIDMark); + + if (CWIDType) + Dlg->radioButton_2->setChecked(1); + else + Dlg->CWIDType->setChecked(1); + + Dlg->afterTraffic->setChecked(afterTraffic); + + 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(); + + AGW_Report_Modem_Change(0); + AGW_Report_Modem_Change(1); + AGW_Report_Modem_Change(2); + AGW_Report_Modem_Change(3); + + 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->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(); + + 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(); + + il2p_crc[0] = Dlg->CRC_A->isChecked(); + il2p_crc[1] = Dlg->CRC_B->isChecked(); + il2p_crc[2] = Dlg->CRC_C->isChecked(); + il2p_crc[3] = Dlg->CRC_D->isChecked(); + + 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()); + strcpy(CWIDMark, Dlg->CWIDMark->text().toUtf8().toUpper()); + CWIDInterval = Dlg->CWIDInterval->text().toInt(); + CWIDType = Dlg->radioButton_2->isChecked(); + + afterTraffic = Dlg->afterTraffic->isChecked(); + + if (CWIDInterval && afterTraffic == false) + 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]); + } + +/* + Q = Dlg->LPFWidthA->text(); + lpf[0] = Q.toInt(); + + Q = Dlg->LPFWidthB->text(); + lpf[1] = Q.toInt(); + + Q = Dlg->LPFWidthC->text(); + lpf[2] = Q.toInt(); + + Q = Dlg->LPFWidthD->text(); + lpf[3] = Q.toInt(); +*/ + +} + +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(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(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); + } + + // If we are using a user specifed device add it + + i = Dev->PTTPort->findText(PTTPort, Qt::MatchFixedString); + + if (i == -1) + { + // Add our device to list + + Dev->PTTPort->insertItem(0, PTTPort); + i = Dev->PTTPort->findText(PTTPort, Qt::MatchContains); + } + + Dev->PTTPort->setCurrentIndex(i); + + PTTPortChanged(0); // Force reevaluation + + Dev->txRotation->setChecked(TX_rotate); + Dev->DualPTT->setChecked(DualPTT); + + Dev->multiCore->setChecked(multiCore); + Dev->darkTheme->setChecked(darkTheme); + + 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())); + + UI.exec(); + +} + +void QtSoundModem::mysetstyle() +{ + if (darkTheme) + { + qApp->setStyleSheet( + "QWidget {color: white; background-color: black}" + "QTabBar::tab {color: rgb(127, 127, 127); background-color: black}" + "QTabBar::tab::selected {color: white}" + "QPushButton {border-style: outset; border-width: 2px; border-color: rgb(127, 127, 127)}" + "QPushButton::default {border-style: outset; border-width: 2px; border-color: white}"); + + sessionTable->setStyleSheet("QHeaderView::section { background-color:rgb(40, 40, 40) }"); + + txText = qRgb(255, 127, 127); + rxText = qRgb(173, 216, 230); + } + else + { + qApp->setStyleSheet(""); + + sessionTable->setStyleSheet("QHeaderView::section { background-color:rgb(224, 224, 224) }"); + + txText = qRgb(192, 0, 0); + rxText = qRgb(0, 0, 192); + } +} + +void QtSoundModem::deviceaccept() +{ + QVariant Q = Dev->inputDevice->currentText(); + int cardChanged = 0; + char portString[32]; + int newMax; + int newMin; + + 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("

Changes not saved

")); + + 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(); + + char temp[256]; + + strcpy(temp, Q.toString().toUtf8()); + + if (strlen(temp)) + strcpy(PTTPort, temp); + + DualPTT = Dev->DualPTT->isChecked(); + TX_rotate = Dev->txRotation->isChecked(); + multiCore = Dev->multiCore->isChecked(); + darkTheme = Dev->darkTheme->isChecked(); + mysetstyle(); + + 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()); + } + + 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(); + + NeedWaterfallHeaders = true; + + 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%s/%d%s", VersionString, AGWPort, AGWServ ? "*": "", KISSPort, KISSServ ? "*" : ""); + 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 (inWaterfall) + { + // in waterfall update thread + + wftimer->start(5000); + return; + } + + lockWaterfall = true; + + if (Firstwaterfall | Secondwaterfall) + { + initWaterfall(0); + initWaterfall(1); + } + + delete(RXLevel); + delete(ui.RXLevel); + ui.RXLevel = new QLabel(ui.centralWidget); + RXLevelCopy = ui.RXLevel; + ui.RXLevel->setGeometry(QRect(780, 14, 150, 11)); + ui.RXLevel->setFrameShape(QFrame::Box); + ui.RXLevel->setFrameShadow(QFrame::Sunken); + + delete(RXLevel2); + delete(ui.RXLevel2); + + ui.RXLevel2 = new QLabel(ui.centralWidget); + RXLevel2Copy = ui.RXLevel2; + + ui.RXLevel2->setGeometry(QRect(780, 23, 150, 11)); + ui.RXLevel2->setFrameShape(QFrame::Box); + ui.RXLevel2->setFrameShadow(QFrame::Sunken); + + RXLevel = new QImage(150, 10, QImage::Format_RGB32); + RXLevel->fill(cyan); + + RXLevel2 = new QImage(150, 10, QImage::Format_RGB32); + RXLevel2->fill(white); + + ui.RXLevel->setVisible(1); + ui.RXLevel2->setVisible(1); + + lockWaterfall = false; +} + + +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->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->setPixel(i, val, Yellow); + if (val < 62) + Waterfall->setPixel(i, val + 1, Gold); + Data++; + } + + ui.Waterfall->setPixmap(QPixmap::fromImage(*Waterfall)); + +} + +void RefreshLevel(unsigned int Level, unsigned int LevelR) +{ + // Redraw the RX Level Bar Graph + + unsigned int x, y; + + for (x = 0; x < 150; x++) + { + for (y = 0; y < 10; y++) + { + if (x < Level) + { + if (Level < 50) + RXLevel->setPixel(x, y, yellow); + else if (Level > 135) + RXLevel->setPixel(x, y, red); + else + RXLevel->setPixel(x, y, green); + } + else + RXLevel->setPixel(x, y, white); + } + } + RXLevelCopy->setPixmap(QPixmap::fromImage(*RXLevel)); + + for (x = 0; x < 150; x++) + { + for (y = 0; y < 10; y++) + { + if (x < LevelR) + { + if (LevelR < 50) + RXLevel2->setPixel(x, y, yellow); + else if (LevelR > 135) + RXLevel2->setPixel(x, y, red); + else + RXLevel2->setPixel(x, y, green); + } + else + RXLevel2->setPixel(x, y, white); + } + } + RXLevel2Copy->setPixmap(QPixmap::fromImage(*RXLevel2)); +} + +extern "C" unsigned char CurrentLevel; +extern "C" unsigned char CurrentLevelR; + +void QtSoundModem::RefreshWaterfall(int snd_ch, unsigned char * Data) +{ + int j; + unsigned char * Line; + int len = Waterfall->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->scanLine(j); + memcpy(Line, &WaterfallLines[snd_ch][TopLine++][0], len); + if (TopLine > 63) + TopLine = 0; + } + + ui.Waterfall->setPixmap(QPixmap::fromImage(*Waterfall)); +} + +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(txText); + else + monWindowCopy->setTextColor(rxText); + + 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 DrawFreqTicks() +{ + if (nonGUIMode) + return; + + // Draw Frequency Markers on waterfall header(s); + + int x, i; + char Textxx[20]; + QImage * bm = Waterfall; + + QPainter qPainter(bm); + qPainter.setBrush(Qt::black); + qPainter.setPen(Qt::white); + + int Chan; + +#ifdef WIN32 + int Top = 3; +#else + int Top = 4; +#endif + int Base = 0; + + for (Chan = 0; Chan < 2; Chan++) + { + if (Chan == 1 || ((UsingBothChannels == 0) && (UsingRight == 1))) + sprintf(Textxx, "Right"); + else + sprintf(Textxx, "Left"); + + 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(PixelsPerMarker * i); + if (x < 1025) + { + if ((Freq % 500) == 0) + qPainter.drawLine(x, Base + 22, x, Base + 15); + else + qPainter.drawLine(x, Base + 22, x, Base + 18); + + if ((Freq % 500) == 0) + { + sprintf(Textxx, "%d", Freq); + + if (x < 924) + qPainter.drawText(x - 12, Top, 100, 20, 0, Textxx); + } + } + Freq += 100; + } + + if (UsingBothChannels == 0) + break; + + Top += WaterfallTotalPixels; + Base = WaterfallTotalPixels; + } + +} + +// These draws the frequency Markers on the Waterfall + +void DrawModemFreqRange() +{ + if (nonGUIMode) + return; + + // Draws the modem freq bars on waterfall header(s) + + + int x1, x2, k, pos1, pos2, pos3; + QImage * bm = Waterfall; + + QPainter qPainter(bm); + qPainter.setBrush(Qt::NoBrush); + qPainter.setPen(Qt::white); + + int Chan; + int LRtoDisplay = LEFT; + int top = 0; + + for (Chan = 0; Chan < 2; Chan++) + { + if (Chan == 1 || ((UsingBothChannels == 0) && (UsingRight == 1))) + LRtoDisplay = RIGHT; + + // bm->fill(black); + + // qPainter.fillRect(top, 23, 1024, 10, Qt::black); + + // We drew markers every 100 Hz or 100 / binsize pixels + + float PixelsPerHz = 1.0 / BinSize; + k = 26 + top; + + // 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 (soundChannel[i] != LRtoDisplay) + continue; + + 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; + + 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] - 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); + } + + k += 3; + } + if (UsingBothChannels == 0) + break; + + LRtoDisplay = RIGHT; + top = WaterfallTotalPixels; + } +} + + +void doWaterfallThread(void * param); + +extern "C" void doWaterfall(int snd_ch) +{ + if (nonGUIMode) + return; + + if (Closing) + return; + + if (lockWaterfall) + return; + +// if (multiCore) // Run modems in separate threads +// _beginthread(doWaterfallThread, 0, xx); +// else + doWaterfallThread((void *)(size_t)snd_ch); + +} + +extern "C" void displayWaterfall() +{ + // if we are using both channels but only want right need to extract correct half of Image + + if (Waterfall == nullptr) + return; + + if (UsingBothChannels && (Firstwaterfall == 0)) + WaterfallCopy->setAlignment(Qt::AlignBottom | Qt::AlignLeft); + else + WaterfallCopy->setAlignment(Qt::AlignTop | Qt::AlignLeft); + + WaterfallCopy->setPixmap(QPixmap::fromImage(*Waterfall)); +} + +extern "C" float aFFTAmpl[1024]; +extern "C" void SMUpdateBusyDetector(int LR, float * Real, float *Imag); + +void doWaterfallThread(void * param) +{ + int snd_ch = (int)(size_t)param; + + if (lockWaterfall) + return; + + if (Configuring) + return; + + inWaterfall = true; // don't allow restart waterfall + + if (snd_ch == 1 && UsingLeft == 0) // Only using right + snd_ch = 0; // Samples are in first buffer + + QImage * bm = Waterfall; + + int i; + single mag; + UCHAR * p; + UCHAR Line[4096] = ""; // 4 bytes per pixel + + int lineLen, Start, End; + word hFFTSize; + Byte n; + float RealOut[8192] = { 0 }; + float ImagOut[8192]; + + + RefreshLevel(CurrentLevel, CurrentLevelR); // Signal Level + + hFFTSize = FFTSize / 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?) + + 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 < hFFTSize; 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 = Start; i < End; 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; // for Freq Guess + fft_disp[snd_ch][i] = round(mag); + } + } + + SMUpdateBusyDetector(snd_ch, RealOut, ImagOut); + + + + // we always do fft so we can get centre freq and do busy detect. But only update waterfall if on display + + if (bm == 0) + { + inWaterfall = false; + return; + } + if ((Firstwaterfall == 0 && snd_ch == 0) || (Secondwaterfall == 0 && snd_ch == 1)) + { + inWaterfall = false; + return; + } + + p = Line; + lineLen = 4096; + + if (raduga == DISP_MONO) + { + for (i = Start; i < End; i++) + { + n = fft_disp[snd_ch][i]; + *(p++) = n; // all colours the same + *(p++) = n; + *(p++) = n; + p++; + } + } + else + { + for (i = Start; i < End; i++) + { + n = fft_disp[snd_ch][i]; + memcpy(p, &RGBWF[n], 4); + p += 4; + } + } + + // Scroll + + + + int TopLine = NextWaterfallLine[snd_ch]; + int TopScanLine = WaterfallHeaderPixels; + + if (snd_ch) + TopScanLine += WaterfallTotalPixels; + + // 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; + + // Sanity check + + int WFMaxLine = bm->height(); + + + if ((79 + TopScanLine) >= bm->height()) + { + printf("Invalid WFMaxLine %d \n", bm->height()); + exit(1); + } + + + for (int j = 79; j > 0; j--) + { + p = bm->scanLine(j + TopScanLine); + if (p == nullptr) + { + printf("Invalid WF Pointer \n"); + exit(1); + } + memcpy(p, &WaterfallLines[snd_ch][TopLine][0], lineLen); + TopLine++; + if (TopLine > 79) + TopLine = 0; + } + + inWaterfall = false; +} + + +void QtSoundModem::changeEvent(QEvent* e) +{ + if (e->type() == QEvent::WindowStateChange) + { + QWindowStateChangeEvent* ev = static_cast(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 + +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(QObject::sender()); + 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; + + if (Count == 0) + return 0; + + if (nonGUIMode == 0) + { + Constellation[chan]->fill(black); + + for (i = 0; i < 120; i++) + { + Constellation[chan]->setPixel(xCenter, i, cyan); + Constellation[chan]->setPixel(i, xCenter, cyan); + } + } + 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; + + if (nonGUIMode == 0) + { + 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 + + if (nonGUIMode == 0) + { + char QualText[64]; + sprintf(QualText, "Chan %c Qual = %d", chan + 'A', intQuality); + QualLabel[chan]->setText(QualText); + constellationLabel[chan]->setPixmap(QPixmap::fromImage(*Constellation[chan])); + } + return intQuality; +} + + + diff --git a/QtSoundModem.h b/QtSoundModem.h index 626ddd9..b02f9c2 100644 --- a/QtSoundModem.h +++ b/QtSoundModem.h @@ -74,8 +74,14 @@ private slots: void preEmphAllDChanged(int state); void menuChecked(); void onTEselectionChanged(); + void StartWatchdog(); + void StopWatchdog(); + void PTTWatchdogExpired(); void clickedSlot(); void startCWIDTimerSlot(); + void setWaterfallImage(); + void setLevelImage(); + void setConstellationImage(int chan, int Qual); protected: diff --git a/QtSoundModem.vcxproj b/QtSoundModem.vcxproj index 7f39cd1..765fd37 100644 --- a/QtSoundModem.vcxproj +++ b/QtSoundModem.vcxproj @@ -383,6 +383,7 @@ + @@ -406,7 +407,6 @@ - diff --git a/QtSoundModem.vcxproj.filters b/QtSoundModem.vcxproj.filters index c50daf6..2d05c65 100644 --- a/QtSoundModem.vcxproj.filters +++ b/QtSoundModem.vcxproj.filters @@ -98,9 +98,6 @@ Source Files - - Source Files - Source Files @@ -140,14 +137,14 @@ Source Files + + Generated Files + Header Files - - Header Files - Header Files @@ -187,4 +184,9 @@ Resource Files + + + Header Files + + \ No newline at end of file diff --git a/QtSoundModem.vcxproj.user b/QtSoundModem.vcxproj.user index 059beae..3d36629 100644 --- a/QtSoundModem.vcxproj.user +++ b/QtSoundModem.vcxproj.user @@ -20,15 +20,15 @@ WindowsLocalDebugger - 2023-11-24T17:50:14.6138870Z + 2024-06-21T13:50:20.1736205Z - 2023-11-24T17:50:14.7454647Z + 2024-06-21T13:50:20.3245934Z - 2023-11-24T17:50:14.8872599Z + 2024-06-21T13:50:20.4555992Z - 2023-11-24T17:50:15.0594798Z + 2024-06-21T13:50:20.6366033Z \ No newline at end of file diff --git a/QtSoundModem_resource.aps b/QtSoundModem_resource.aps new file mode 100644 index 0000000000000000000000000000000000000000..74c8a003e6c2ac3fde455e4af34bbaf7746e48c9 GIT binary patch literal 82400 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 literal 0 HcmV?d00001 diff --git a/SMMain.c b/SMMain.c index 24f6159..bf93779 100644 --- a/SMMain.c +++ b/SMMain.c @@ -170,7 +170,6 @@ void SampleSink(int LR, short Sample) { DMABuffer[2 * Number] = Sample; DMABuffer[1 + 2 * Number] = Sample; - } else { @@ -537,15 +536,21 @@ void doCalib(int Chan, int Act) if (Chan == 1 && calib_mode[0]) return; - calib_mode[Chan] = Act; - if (Act == 0) { + calib_mode[Chan] = 0; tx_status[Chan] = TX_SILENCE; // Stop TX Flush(); RadioPTT(Chan, 0); Debugprintf("Stop Calib"); } + else + { + if (calib_mode[Chan] == 0) + SampleNo = 0; + + calib_mode[Chan] = Act; + } } int Freq_Change(int Chan, int Freq) @@ -681,7 +686,7 @@ void DoTX(int Chan) if (tx_status[Chan] == TX_NO_DATA) { Flush(); - Debugprintf("TX Complete"); + Debugprintf("TX Complete %d", SampleNo); RadioPTT(Chan, 0); Continuation[Chan] = 0; @@ -762,7 +767,7 @@ void DoTX(int Chan) return; } - Debugprintf("TX Complete"); + Debugprintf("TX Complete %d", SampleNo); RadioPTT(Chan, 0); Continuation[Chan] = 0; @@ -954,7 +959,10 @@ BOOL useGPIO = FALSE; BOOL gotGPIO = FALSE; int HamLibPort = 4532; -char HamLibHost[32] = "192.168.1.14"; +char HamLibHost[32] = "127.0.0.1"; + +int FLRigPort = 12345; +char FLRigHost[32] = "127.0.0.1"; char CM108Addr[80] = ""; @@ -1128,6 +1136,12 @@ void OpenPTTPort() HAMLIBSetPTT(0); // to open port return; } + else if (stricmp(PTTPort, "FLRIG") == 0) + { + PTTMode |= PTTFLRIG; + FLRigSetPTT(0); // to open port + return; + } else // Not GPIO { @@ -1207,6 +1221,7 @@ void CM108_set_ptt(int PTTState) float amplitudes[4] = { 32000, 32000, 32000, 32000 }; extern float amplitude; +void startpttOnTimer(); void RadioPTT(int snd_ch, BOOL PTTState) { @@ -1216,11 +1231,13 @@ void RadioPTT(int snd_ch, BOOL PTTState) { txmax = txmin = 0; amplitude = amplitudes[snd_ch]; + StartWatchdog(); } else { Debugprintf("Output peaks = %d, %d, amp %f", txmin, txmax, amplitude); amplitudes[snd_ch] = amplitude; + StopWatchdog(); } #ifdef __ARM_ARCH @@ -1230,7 +1247,7 @@ void RadioPTT(int snd_ch, BOOL PTTState) gpioWrite(pttGPIOPinR, (pttGPIOInvert ? (1 - PTTState) : (PTTState))); else gpioWrite(pttGPIOPin, (pttGPIOInvert ? (1 - PTTState) : (PTTState))); - + startpttOnTimer(); return; } @@ -1239,17 +1256,29 @@ void RadioPTT(int snd_ch, BOOL PTTState) if ((PTTMode & PTTCM108)) { CM108_set_ptt(PTTState); + startpttOnTimer(); return; } if ((PTTMode & PTTHAMLIB)) { HAMLIBSetPTT(PTTState); + startpttOnTimer(); return; } - if (hPTTDevice == 0) - return; + if ((PTTMode & PTTFLRIG)) + { + FLRigSetPTT(PTTState); + startpttOnTimer(); + return; + } + + if (hPTTDevice == 0) + { + startpttOnTimer(); + return; + } if ((PTTMode & PTTCAT)) { if (PTTState) @@ -1277,6 +1306,7 @@ void RadioPTT(int snd_ch, BOOL PTTState) COMClearRTS(hPTTDevice); } } + startpttOnTimer(); } diff --git a/UZ7HOStuff.h b/UZ7HOStuff.h index b27e76f..c4146ff 100644 --- a/UZ7HOStuff.h +++ b/UZ7HOStuff.h @@ -4,9 +4,12 @@ // My port of UZ7HO's Soundmodem // -#define VersionString "0.0.0.72 Beta 1" +#define VersionString "0.0.0.72" #define VersionBytes {0, 0, 0, 72} +//#define LOGTX +//#define LOGRX + // Added FX25. 4x100 FEC and V27 not Working and disabled // 0.8 V27 now OK. @@ -179,6 +182,25 @@ // Report and set fx.25 and il2p flags to/from BPQ // .72 Fix IL2P for RUH modems +// Fix crash when closing in non-gui mode +// Fix loop in chk_dcd1 +// Change method of timing PTT +// Add FLRIG PTT Support + +// Beta 13 revert to new ptt timing +// Beta 14 Add display of mix/snoop devices + +// Change phase map of QPSK3600 mode to match Nino TNC + + + +// As far as I can see txtail is only there to make sure all bits get through the tx filter, +// so it shouldn't really matter what is sent. Code worked in characters, so resolution of txtail +// is 24 mS at 300 baud. I don't see why I can't work in bits, or even samples. Any reason why I shouldn't send +// a single tone during tail? . +// +// I'm currently sending reversals with timing resolution of bits (~3 mS at 300 baud) + #include @@ -511,6 +533,7 @@ typedef struct TAX25Port_t #define PTTCAT 4 #define PTTCM108 8 #define PTTHAMLIB 16 +#define PTTFLRIG 32 // Status flags @@ -654,9 +677,9 @@ extern int SendSize; #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 1800 +#define MODEM_Q3600_TXBPF 2000 +#define MODEM_Q3600_LPF 600 #define MODEM_Q3600_BPF_TAP 256 #define MODEM_Q3600_LPF_TAP 128 // @@ -947,6 +970,8 @@ extern int PID; extern char CM108Addr[80]; extern int HamLibPort; extern char HamLibHost[]; +extern int FLRigPort; +extern char FLRigHost[]; extern int SCO; extern int DualPTT; @@ -1060,6 +1085,7 @@ void AGW_Raw_monitor(int snd_ch, string * data); // Delphi emulation functions string * Strings(TStringList * Q, int Index); +void replaceString(TStringList * Q, int Index, string * item); void Clear(TStringList * Q); int Count(TStringList * List); @@ -1140,6 +1166,10 @@ struct il2p_context_s { extern int NeedWaterfallHeaders; +#define stringAdd(s1, s2, c) mystringAdd(s1, s2, c, __FILE__, __LINE__) + +string * mystringAdd(string * Msg, UCHAR * Chars, int Count, char * FILE, int LINE); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/UZ7HOUtils.c b/UZ7HOUtils.c index a2a8fe7..70d27e2 100644 --- a/UZ7HOUtils.c +++ b/UZ7HOUtils.c @@ -96,12 +96,24 @@ void freeString(string * Msg) string * Strings(TStringList * Q, int Index) { + // Gets string at index in stringlist + if (Index >= Q->Count) return NULL; return Q->Items[Index]; } +void replaceString(TStringList * Q, int Index, string * item) +{ + // Gets string at index in stringlist + + if (Index >= Q->Count) + return; + + Q->Items[Index] = item; +} + int Add(TStringList * Q, string * Entry) { Q->Items = realloc(Q->Items,(Q->Count + 1) * sizeof(void *)); @@ -166,16 +178,27 @@ void setlength(string * Msg, int Count) Msg->Length = Count; } -string * stringAdd(string * Msg, UCHAR * Chars, int Count) +string * mystringAdd(string * Msg, UCHAR * Chars, int Count, char * FILE, int LINE) { // Add Chars to string + if (Count < 0 || Count > 65536) + { + printf("stringAdd Strange Count %d called from %s %d\r\n", Count, FILE, LINE); + } + if (Msg->Length + Count > Msg->AllocatedLength) { Msg->AllocatedLength += Count + 256; Msg->Data = realloc(Msg->Data, Msg->AllocatedLength); } + if (Msg->Data == 0) + { + printf("realloc failed\r\n"); + exit(1); + } + memcpy(&Msg->Data[Msg->Length], Chars, Count); Msg->Length += Count; diff --git a/Waveout.c b/Waveout.c index 6b5c987..22f05d3 100644 --- a/Waveout.c +++ b/Waveout.c @@ -95,6 +95,8 @@ int PlaybackCount = 0; char CaptureNames[16][256]= {""}; char PlaybackNames[16][256]= {""}; +int txLatency; + WAVEFORMATEX wfx = { WAVE_FORMAT_PCM, 2, 12000, 48000, 4, 16, 0 }; HWAVEOUT hWaveOut = 0; @@ -190,6 +192,9 @@ void txSleep(int mS) { // called while waiting for next TX buffer. Run background processes + if (mS < 0) + return; + while (mS > 50) { if (SoundMode == 3) @@ -356,6 +361,7 @@ void GetSoundDevices() HANDLE hStdin; +int onlyMixSnoop = 0; int InitSound(BOOL Report) { @@ -563,7 +569,7 @@ void PollReceivedSamples() lastlevelGUI = Now; - if ((Now - lastlevelreport) > 10000) // 10 Secs + if ((Now - lastlevelreport) > 60000) // 60 Secs { char HostCmd[64]; lastlevelreport = Now; @@ -703,6 +709,8 @@ extern int Number; // Number of samples waiting to be sent // Subroutine to add trailer before filtering +extern int SampleNo; + void SoundFlush() { // Append Trailer then wait for TX to complete diff --git a/ax25_agw.c b/ax25_agw.c index ec73b2b..12526ff 100644 --- a/ax25_agw.c +++ b/ax25_agw.c @@ -189,7 +189,7 @@ void AGW_del_socket(void * socket) void AGW_add_socket(void * socket) { AGWUser * User = (struct AGWUser_t *)malloc(sizeof(struct AGWUser_t)); // One Client - memset(User, 0, sizeof(struct AGWUser_t)); + AGWUsers = realloc(AGWUsers, (AGWConCount + 1) * sizeof(void *)); diff --git a/ax25_demod.c b/ax25_demod.c index f1b0b36..06d66f5 100644 --- a/ax25_demod.c +++ b/ax25_demod.c @@ -335,7 +335,7 @@ void chk_dcd1(int snd_ch, int buf_size) // ? does this work as Andy passes aborted frames to decoder Byte port; - word i; + int i; single tick; word active; boolean ind_dcd; @@ -423,9 +423,9 @@ void chk_dcd1(int snd_ch, int buf_size) if (TX_rotate) { - for (int i = 0; i < 4; i++) + for (int n = 0; n < 4; n++) { - if (snd_status[i] == SND_TX) + if (snd_status[n] == SND_TX) dcd[snd_ch] = TRUE; } } @@ -435,7 +435,7 @@ void chk_dcd1(int snd_ch, int buf_size) if (!dcd[snd_ch] && resptime_tick[snd_ch] >= resptime[snd_ch]) { - i = 0; + int n = 0; port = new_tx_port[snd_ch]; do @@ -463,9 +463,9 @@ void chk_dcd1(int snd_ch, int buf_size) if (all_frame_buf[snd_ch].Count > 0) new_tx_port[snd_ch] = port; - i++; + n++; - } while (all_frame_buf[snd_ch].Count == 0 && i < port_num); + } while (all_frame_buf[snd_ch].Count == 0 && n < port_num); // Add KISS frames @@ -475,7 +475,7 @@ void chk_dcd1(int snd_ch, int buf_size) if (all_frame_buf[snd_ch].Count > 0) { - for (n = 0; n < all_frame_buf[snd_ch].Count; n++) + for (int n = 0; n < all_frame_buf[snd_ch].Count; n++) { KISS_on_data_out(snd_ch, Strings(&all_frame_buf[snd_ch], n), 1); // Mon TX } @@ -485,14 +485,14 @@ void chk_dcd1(int snd_ch, int buf_size) if (KISS.buffer[snd_ch].Count > 0) { - for (n = 0; n < KISS.buffer[snd_ch].Count; n++) + for (int k = 0; k < KISS.buffer[snd_ch].Count; k++) { if (AGWServ) AGW_Raw_monitor(snd_ch, Strings(&KISS.buffer[snd_ch], n)); // Need to add copy as clear will free original - Add(&all_frame_buf[snd_ch], duplicateString(Strings(&KISS.buffer[snd_ch], n))); + Add(&all_frame_buf[snd_ch], duplicateString(Strings(&KISS.buffer[snd_ch], k))); } Clear(&KISS.buffer[snd_ch]); } diff --git a/ax25_mod.c b/ax25_mod.c index f8fd4bc..c8b9eef 100644 --- a/ax25_mod.c +++ b/ax25_mod.c @@ -866,7 +866,6 @@ int get_new_bit_tail(UCHAR snd_ch, UCHAR bit) } else { - Debugprintf("End TXTAIL %d", SampleNo); tx_status[snd_ch] = TX_WAIT_BPF; } @@ -881,7 +880,6 @@ int get_new_bit_tail(UCHAR snd_ch, UCHAR bit) } else { - Debugprintf("End TXTAIL %d", SampleNo); tx_status[snd_ch] = TX_WAIT_BPF; } break; @@ -1199,7 +1197,7 @@ float make_samples(unsigned char snd_ch, unsigned char * bitptr) tx_status[snd_ch] = TX_FRAME; if (tx_status[snd_ch] == TX_TAIL) - bit = get_new_bit_tail(snd_ch, bit); + bit = il2p_get_new_bit_tail(snd_ch, bit); if (tx_status[snd_ch] == TX_FRAME) bit = il2p_get_new_bit(snd_ch, bit); @@ -1230,6 +1228,7 @@ float make_samples(unsigned char snd_ch, unsigned char * bitptr) *bitptr = tx_nrzi(snd_ch, bit); } + } // BPSK Mode @@ -1240,13 +1239,10 @@ float make_samples(unsigned char snd_ch, unsigned char * bitptr) 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 + // il2p generates TXDELAY as part of the frame, so go straight to TX_FRAME if (tx_status[snd_ch] == TX_DELAY) if (il2p_mode[snd_ch] >= IL2P_MODE_TXRX) @@ -1255,7 +1251,12 @@ float make_samples(unsigned char snd_ch, unsigned char * bitptr) bit = get_new_bit_delay(snd_ch, bit); if (tx_status[snd_ch] == TX_TAIL) - bit = get_new_bit_tail(snd_ch, bit); + { + if (il2p_mode[snd_ch] >= IL2P_MODE_TXRX) + bit = il2p_get_new_bit_tail(snd_ch, bit); + else + bit = get_new_bit_tail(snd_ch, bit); + } if (tx_status[snd_ch] == TX_FRAME) { @@ -1266,6 +1267,7 @@ float make_samples(unsigned char snd_ch, unsigned char * bitptr) else bit = get_new_bit(snd_ch, bit); } + // ?? *bitptr = tx_nrzi(snd_ch, bit); if (bit == 0) @@ -1297,7 +1299,7 @@ float make_samples(unsigned char snd_ch, unsigned char * bitptr) 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); + bit = il2p_get_new_bit_tail(snd_ch, bit); if (tx_status[snd_ch] == TX_FRAME) bit = il2p_get_new_bit(snd_ch, bit); @@ -1372,7 +1374,6 @@ float make_samples(unsigned char snd_ch, unsigned char * bitptr) if (tx_status[snd_ch] == TX_SILENCE) { tx_delay_cnt[snd_ch] = 0; - Debugprintf("Start TXD"); tx_status[snd_ch] = TX_DELAY; } @@ -1382,7 +1383,7 @@ float make_samples(unsigned char snd_ch, unsigned char * bitptr) 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); + bit = il2p_get_new_bit_tail(snd_ch, bit); if (tx_status[snd_ch] == TX_FRAME) bit = il2p_get_new_bit(snd_ch, bit); @@ -1490,7 +1491,7 @@ float make_samples(unsigned char snd_ch, unsigned char * bitptr) 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); + bit = il2p_get_new_bit_tail(snd_ch, bit); if (tx_status[snd_ch] == TX_FRAME) bit = il2p_get_new_bit(snd_ch, bit); @@ -1590,7 +1591,6 @@ float make_samples(unsigned char snd_ch, unsigned char * bitptr) if (tx_status[snd_ch] == TX_SILENCE) { tx_delay_cnt[snd_ch] = 0; - Debugprintf("Start TXD"); tx_status[snd_ch] = TX_DELAY; } diff --git a/debug/moc_predefs-LAPTOP-Q6S4RP5Q.h b/debug/moc_predefs-LAPTOP-Q6S4RP5Q.h new file mode 100644 index 0000000..62e1baa --- /dev/null +++ b/debug/moc_predefs-LAPTOP-Q6S4RP5Q.h @@ -0,0 +1,12 @@ +#define _MSC_EXTENSIONS +#define _INTEGRAL_MAX_BITS 64 +#define _MSC_VER 1916 +#define _MSC_FULL_VER 191627051 +#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/debug/moc_predefs-notpi4-64.h b/debug/moc_predefs-notpi4-64.h new file mode 100644 index 0000000..6d708a1 --- /dev/null +++ b/debug/moc_predefs-notpi4-64.h @@ -0,0 +1,12 @@ +#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 _DEBUG +#define _MT +#define _DLL diff --git a/debug/moc_predefs-skigdebian.h b/debug/moc_predefs-skigdebian.h new file mode 100644 index 0000000..6d708a1 --- /dev/null +++ b/debug/moc_predefs-skigdebian.h @@ -0,0 +1,12 @@ +#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 _DEBUG +#define _MT +#define _DLL diff --git a/devicesDialog.ui b/devicesDialog.ui index b090d6b..9fef8ab 100644 --- a/devicesDialog.ui +++ b/devicesDialog.ui @@ -60,7 +60,7 @@ 108 25 - 261 + 371 22
@@ -73,7 +73,7 @@ 108 55 - 261 + 371 22
@@ -99,7 +99,7 @@ 13 55 - 71 + 91 19
@@ -159,63 +159,37 @@ Minimize window on startup
- + - 108 - 124 + 220 + 140 63 20 - 12000 - - - - - - 304 - 124 - 63 - 20 - - - - 12000 + 0 13 - 125 - 91 + 140 + 201 18 - TX SampleRate - - - - - - 190 - 125 - 91 - 18 - - - - RX SampleRate + Soundcard TX latency mS - 70 - 164 + 72 + 170 86 20 @@ -245,7 +219,7 @@ 12 - 166 + 172 61 16 @@ -258,7 +232,7 @@ 180 - 164 + 170 86 20 @@ -283,7 +257,7 @@ 165 - 166 + 172 21 16 @@ -352,7 +326,7 @@ 168 90 - 91 + 131 20 @@ -406,7 +380,7 @@ 290 - 164 + 170 86 20 @@ -431,7 +405,7 @@ 272 - 166 + 172 21 16 @@ -444,7 +418,7 @@ 385 - 166 + 172 21 16 @@ -457,7 +431,7 @@ 400 - 164 + 170 86 20 @@ -595,6 +569,19 @@ Dark Theme + + + + 30 + 110 + 421 + 21 + + + + Only Mix/Snoop Devices (needs custom .asoundrc) + + diff --git a/dw9600.c b/dw9600.c index 8e6db00..da9b3c7 100644 --- a/dw9600.c +++ b/dw9600.c @@ -23,6 +23,10 @@ typedef struct TStringList_T #include #include "dw9600.h" +#define stringAdd(s1, s2, c) mystringAdd(s1, s2, c, __FILE__, __LINE__) + +string * mystringAdd(string * Msg, unsigned char * Chars, int Count, char * FILE, int LINE); + extern int fx25_mode[4]; extern int il2p_mode[4]; extern int il2p_crc[4]; diff --git a/il2p.c b/il2p.c index 895feaa..c28c85c 100644 --- a/il2p.c +++ b/il2p.c @@ -46,6 +46,10 @@ along with QtSoundModem. If not, see http://www.gnu.org/licenses #include // for uint64_t +void debugHexDump(unsigned char * Data, int Len, char Dirn); +extern void debugTimeStamp(char * Text, char Dirn); +extern int useTimedPTT; + // Oct 2023 Nino has added an optional crc // Hamming(7,4) Encoding Table @@ -83,6 +87,10 @@ uint16_t Hamming74DecodeTable[128] = { \ void Debugprintf(const char * format, ...); int SMUpdatePhaseConstellation(int chan, float * Phases, float * Mags, int intPSKPhase, int Count); +extern int openTraceLog(); +extern uint64_t writeTraceLog(char * Data, char Dirn); +extern void closeTraceLog(); + #define MAX_ADEVS 3 #define MAX_RADIO_CHANS ((MAX_ADEVS) * 2) @@ -1042,12 +1050,19 @@ void multi_modem_process_rec_packet(int snd_ch, int subchan, int slice, packet_t string * data = newString(); char Mode[32] = "IL2P"; int Quality = 0; + int CRCOK = 1; + char debugmsg[256]; sprintf(Mode, "IL2P %d", centreFreq); + + unsigned char * axcall = &pp->frame_data[7]; + char call[10]; + + call[ConvFromAX25(axcall, call)] = 0; // check crc if enabled - if (il2p_crc[snd_ch]) + if (il2p_crc[snd_ch] & 1) { unsigned short CRCMSG; unsigned short CRCCALC; @@ -1065,16 +1080,32 @@ void multi_modem_process_rec_packet(int snd_ch, int subchan, int slice, packet_t crc[2] = Hamming74DecodeTable[(pp->crc[2] & 0x7f)]; crc[3] = Hamming74DecodeTable[(pp->crc[3] & 0x7f)]; + debugTimeStamp("CRC after Hamming decode is", 'R'); + debugHexDump(crc, 4, 'R'); + CRCMSG = crc[0] << 12 | crc[1] << 8 | crc[2] << 4 | crc[3]; CRCCALC = get_fcs(pp->frame_data, pp->frame_len); if (CRCCALC != CRCMSG) { - Debugprintf("CRC Error Decoder %d Received %x Sent %x", subchan, CRCCALC, CRCMSG); - freeString(data); - ax25_delete(pp); - return; + CRCOK = 0; + if ((il2p_crc[snd_ch] & 2) == 0) // Ignore CRC Error + { + Debugprintf("CRC Error from %s Decoder %d Calculated %x Received %x FEC corrections %d But ignore CRC Set", call, subchan, CRCCALC, CRCMSG, retries); + sprintf(debugmsg, "CRC Error from %s Decoder %d Calculated %x Received %x FEC corrections %d But ignore CRC Set", call, subchan, CRCCALC, CRCMSG, retries); + debugTimeStamp(debugmsg, 'R'); + + } + + + else + { + Debugprintf("CRC Error from %s Decoder %d Calculated %x Received %x FEC corrections %d", call, subchan, CRCCALC, CRCMSG, retries); + freeString(data); + ax25_delete(pp); + return; + } } } @@ -1095,16 +1126,59 @@ void multi_modem_process_rec_packet(int snd_ch, int subchan, int slice, packet_t pDET->errors = 0; } - if (detect_list[snd_ch].Count > 0 && - my_indexof(&detect_list[snd_ch], data) >= 0) + if (detect_list[snd_ch].Count > 0) { - // Already have a copy of this frame + int index = my_indexof(&detect_list[snd_ch], data); - freeString(data); - Debugprintf("Discarding copy rcvr %d emph %d", subchan, 0); - return; + if (index >= 0) + { + // Already have a copy of this frame + + // See if new one has fewer corrections + + string * xx = Strings(&detect_list_c[snd_ch], index); // Should be corresponding frame info + string * olddata = Strings(&detect_list[snd_ch], index); + + if (xx) + { + int oldRetries = xx->Data[255]; + int oldCRCOK = xx->Data[254]; + + if ((oldCRCOK == 0 && CRCOK == 1) || (oldRetries > retries)) + { + replaceString(&detect_list[snd_ch], index, data); + freeString(olddata); + + // Just update the metadata + + Debugprintf("Replacing il2p frame from %s rcvr %d emph %d FEC corrections %d CRCOK %d", call, subchan, slice, retries, CRCOK); + + 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); + } + + xx->Length= sprintf(xx->Data, "%s", Mode); + xx->Data[254] = CRCOK; + xx->Data[255] = retries; + + return; + } + } + + freeString(data); + Debugprintf("Discarding copy rcvr %d emph %d FEC corrections %d", subchan, slice, retries); + return; + } } + Debugprintf("Good il2p frame from %s rcvr %d emph %d FEC corrections %d", call, subchan, slice, retries); + sprintf(debugmsg, "Good il2p frame from %s rcvr %d emph %d FEC corrections %d", call, subchan, slice, retries); + debugTimeStamp(debugmsg, 'R'); + string * xx = newString(); memset(xx->Data, 0, 16); @@ -1121,7 +1195,11 @@ 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)); + xx->Data[254] = CRCOK; + xx->Data[255] = retries; + closeTraceLog(); + openTraceLog(); return; @@ -2234,6 +2312,8 @@ void il2p_init(int il2p_debug) } } + openTraceLog(); + } // end il2p_init @@ -2838,6 +2918,9 @@ int il2p_encode_frame(packet_t pp, int max_fec, unsigned char *iout) int e; int out_len = 0; + debugTimeStamp("TX Raw Packet is", 'T'); + debugHexDump(pp->frame_data, pp->frame_len, 'T'); + e = il2p_type_1_header(pp, max_fec, hdr); if (e >= 0) { il2p_scramble_block(hdr, iout, IL2P_HEADER_SIZE); @@ -2846,6 +2929,10 @@ int il2p_encode_frame(packet_t pp, int max_fec, unsigned char *iout) if (e == 0) { // Success. No info part. + + debugTimeStamp("TX Type 1 IL2P Packet no info is", 'T'); + debugHexDump(iout, out_len, 'R'); + return (out_len); } @@ -2859,6 +2946,9 @@ int il2p_encode_frame(packet_t pp, int max_fec, unsigned char *iout) if (k > 0) { out_len += k; // Success. Info part was <= 1023 bytes. + debugTimeStamp("TX Type 1 IL2P Packet is", 'T'); + debugHexDump(iout, out_len, 'T'); + return (out_len); } @@ -2885,6 +2975,10 @@ int il2p_encode_frame(packet_t pp, int max_fec, unsigned char *iout) if (k > 0) { out_len += k; // Success. Entire AX.25 frame <= 1023 bytes. + + debugTimeStamp("TX Type 2 IL2P Packet is", 'T'); + debugHexDump(iout, out_len, 'T'); + return (out_len); } // Something went wrong with the payload encoding. @@ -4017,6 +4111,9 @@ void il2p_rec_bit(int chan, int subchan, int slice, int dbit) // Determine Centre Freq centreFreq[chan] = GuessCentreFreq(chan); + + debugTimeStamp("SYNC Detected", 'R'); + } 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. @@ -4062,7 +4159,7 @@ void il2p_rec_bit(int chan, int subchan, int slice, int dbit) F->eplen = il2p_payload_compute(&plprop, len, max_fec); - if (il2p_get_debug() >= 1) + if (il2p_get_debug() >= 2) { 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); @@ -4157,11 +4254,21 @@ void il2p_rec_bit(int chan, int subchan, int slice, int dbit) { // have all crc bytes. enter DECODE + debugTimeStamp("CRC Complete Header is", 'R'); + debugHexDump(F->shdr, 15, 'R'); + if (F->pc) + { + debugTimeStamp("Payload is", 'R'); + debugHexDump(F->spayload, F->pc, 'R'); + } + debugTimeStamp("CRC is", 'R'); + debugHexDump(F->crc, 4, 'R'); + F->state = IL2P_DECODE; } } - break; + break; case IL2P_DECODE: @@ -4182,10 +4289,12 @@ void il2p_rec_bit(int chan, int subchan, int slice, int dbit) { // Most likely too many FEC errors. Debugprintf("FAILED to construct frame in %s.\n", __func__); + debugTimeStamp("Packet Decode failed", 'R'); } } - if (pp != NULL) { + 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. @@ -4195,6 +4304,9 @@ void il2p_rec_bit(int chan, int subchan, int slice, int dbit) // if using crc pass received crc to packet object + debugTimeStamp("Decoded Packet is", 'R'); + debugHexDump(pp->frame_data, pp->frame_len, 'R'); + if (il2p_crc[chan]) { //copy crc bytes to packet object @@ -4205,14 +4317,15 @@ void il2p_rec_bit(int chan, int subchan, int slice, int dbit) pp->crc[3] = F->crc[3]; } + debugTimeStamp("CRC raw bytes", 'R'); + debugHexDump(pp->crc, 4, 'R'); + 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) { - + if (il2p_get_debug() >= 2) Debugprintf("-----"); - } F->state = IL2P_SEARCHING; break; @@ -4426,7 +4539,7 @@ string * il2p_send_frame(int chan, packet_t pp, int max_fec, int polarity) // if we are using crc add it now. elen should point to end of data // crc should be at pp->frame_data[pp->frame_len] - if (il2p_crc[chan]) + if (il2p_crc[chan] & 1) { // The four encoded CRC bytes are arranged : // | CRC3 | CRC2 | CRC1 | CRC0 | @@ -4440,10 +4553,9 @@ string * il2p_send_frame(int chan, packet_t pp, int max_fec, int polarity) encoded[elen++] = Hamming74EncodeTable[crc1 &0xf]; } - number_of_bits_sent[chan] = 0; - if (il2p_get_debug() >= 1) { + if (il2p_get_debug() >= 2) { Debugprintf("IL2P frame, max_fec = %d, %d encoded bytes total", max_fec, elen); // fx_hex_dump(encoded, elen); } @@ -4475,13 +4587,15 @@ string * il2p_send_frame(int chan, packet_t pp, int max_fec, int polarity) stringAdd(packet, encoded, elen); + // Add bytes for tail and TX padding, but don't send if another packet is available (?? how ??) + + number_of_bits_sent[chan] = 0; + 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 @@ -4522,6 +4636,11 @@ string * fill_il2p_data(int snd_ch, string * data) result = il2p_send_frame(snd_ch, pp, 1, 0); + ax25_delete(pp); + + debugTimeStamp("TX Complete packet including Preamble and CRC and Tail", 'T'); + debugHexDump(result->Data, result->Length, 'T'); + return result; } @@ -4657,6 +4776,10 @@ int il2p_get_new_bit(int snd_ch, Byte bit) break; case FRAME_NO_FRAME: + + // I dont really like this state machine. We have run out of frames to send so + // should go straight to tail. This way we add an extra bit. Or does this really matter ?? + tx_tail_cnt[snd_ch] = 0; tx_frame_status[snd_ch] = FRAME_EMPTY; tx_status[snd_ch] = TX_TAIL; @@ -4666,5 +4789,251 @@ int il2p_get_new_bit(int snd_ch, Byte bit) return bit; } +extern int txLatency; +extern int useTImedPTT; + +int il2p_get_new_bit_tail(UCHAR snd_ch, UCHAR bit) +{ + // This sends reversals. It is an experiment + + int tailbits = (txtail[snd_ch] * tx_baudrate[snd_ch]) / 1000; + +#ifndef WIN32 + if (useTimedPTT) + tailbits += (txLatency * tx_baudrate[snd_ch]) / 1000; // add padding to tx buffer to make sure we don't send silence +#endif + if (tx_tail_cnt[snd_ch]++ > tailbits) + tx_status[snd_ch] = TX_WAIT_BPF; + + return (tx_tail_cnt[snd_ch] & 1); // altenating 1/0 + +} +void debugHexDump(unsigned char * Data, int Len, char Dirn) +{ + char Line[256]; + +#ifndef LOGTX + + if (Dirn == 'T') + return; + +#endif + +#ifndef LOGRX + + if (Dirn == 'R') + return; + +#endif + + while (Len > 0) + { + sprintf(Line, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + Data[0], Data[1], Data[2], Data[3], Data[4], Data[5], Data[6], Data[7], + Data[8], Data[9], Data[10], Data[11], Data[12], Data[13], Data[14], Data[15]); + + if (Len < 16) + { + Line[Len * 3] = 10; + Line[Len * 3 + 1] = 0; + } + writeTraceLog(Line, Dirn); + + Data += 16; + Len -= 16; + } +} + + +// Hamming experiments + +// from https://github.com/nasserkessas/hamming-codes/blob/master/hamming.c + +#define block unsigned short // 16 bits +#define bit uint8_t // 8 bits (only last is used) + +int multipleXor(int *indicies, int len) +{ + int val = indicies[0]; + for (int i = 1; i < len; i++) + { + val = val ^ indicies[i]; + } + return val; +} + +bit getBit(unsigned short b, int i) +{ + return (b << i) & (int)pow(2, (sizeof(unsigned short) * 8 - 1)); +} + + +unsigned short toggleBit(unsigned short b, int i) +{ + return b ^ (1 << i); +} + + +bit getCharBit(char b, int i) +{ + return (b << i) & (int)pow(2, (sizeof(char) * 8 - 1)); +} + +block modifyBit(block n, int p, bit b) +{ + return ((n & ~(1 << (sizeof(block) * 8 - 1 - p))) | (b << (sizeof(block) * 8 - 1 - p))); +} + +void encode(char *input, int len, FILE *ptr) { + + // Amount of bits in a block // + int bits = sizeof(block) * 8; + + // Amount of bits per block used to carry the message // + int messageBits = bits - log2(bits) - 1; + + // Amount of blocks needed to encode message // + int blocks = ceil((float)len / messageBits); + + // Array of encoded blocks // + block encoded[16]; + + // Loop through each block // + for (int i = 0; i < blocks + 1; i++) { + + printf("On Block %d:\n", i); + + // Final encoded block variable // + block thisBlock = 0; + + // Amount of "skipped" bits (used for parity) // + int skipped = 0; + + // Count of how many bits are "on" // + int onCount = 0; + + // Array of "on" bits // + int onList[64]; + + // Loop through each message bit in this block to populate final block // + for (int j = 0; j < bits; j++) { + + // Skip bit if reserved for parity bit // + if ((j & (j - 1)) == 0) { // Check if j is a power of two or 0 + skipped++; + continue; + } + + bit thisBit; + + if (i != blocks) { + + // Current overall bit number // + int currentBit = i * messageBits + (j - skipped); + + // Current character // + int currentChar = currentBit / (sizeof(char) * 8); // int division + + // Value of current bit // + thisBit = currentBit < len * sizeof(char) * 8 ? getCharBit(input[currentChar], currentBit - currentChar * 8) : 0; + } + + else { + thisBit = getBit(len / 8, j - skipped + (sizeof(block) * 8 - messageBits)); + } + + // If bit is "on", add to onList and onCount // + if (thisBit) { + onList[onCount] = j; + onCount++; + } + + // Populate final message block // + thisBlock = modifyBit(thisBlock, j, thisBit); + } + + // Calculate values of parity bits // + block parityBits = multipleXor(onList, onCount); + + // Loop through skipped bits (parity bits) // + for (int k = 1; k < skipped; k++) { // skip bit 0 + + // If bit is "on", add to onCount + if (getBit(parityBits, sizeof(block) * 8 - skipped + k)) { + onCount++; + } + + // Add parity bit to final block // + thisBlock = modifyBit(thisBlock, (int)pow(2, skipped - k - 1), getBit(parityBits, sizeof(block) * 8 - skipped + k)); + } + + // Add overall parity bit (total parity of onCount) // + thisBlock = modifyBit(thisBlock, 0, onCount & 1); + + // Output final block // +// printBlock(thisBlock); +// putchar('\n'); + + // Add block to encoded blocks // + encoded[i] = thisBlock; + } + + // Write encoded message to file // + fwrite(encoded, sizeof(block), blocks + 1, ptr); +} + + +void decode(block input[], int len, FILE *ptr) +{ + + // Amount of bits in a block // + int bits = sizeof(block) * 8; + + for (int b = 0; b < (len / sizeof(block)); b++) { + + printf("On Block %d:\n", b); + + // Print initial block // +// printBlock(input[b]); + + // Count of how many bits are "on" // + int onCount = 0; + + // Array of "on" bits // + int onList[64]; + + // Populate onCount and onList // + for (int i = 1; i < bits; i++) { + getBit(input[b], i); + if (getBit(input[b], i)) { + onList[onCount] = i; + onCount++; + } + } + + // Check for single errors // + int errorLoc = multipleXor(onList, onCount); + + if (errorLoc) { + + // Check for multiple errors // + if (!(onCount & 1 ^ getBit(input[b], 0))) { // last bit of onCount (total parity) XOR first bit of block (parity bit) + printf("\nMore than one error detected. Aborting.\n"); + exit(1); + } + + // Flip error bit // + else { + printf("\nDetected error at position %d, flipping bit.\n", errorLoc); + input[b] = toggleBit(input[b], (bits - 1) - errorLoc); + + // Re-print block for comparison // +// printBlock(input[b]); + } + } + + putchar('\n'); + } +} diff --git a/kiss_mode.c b/kiss_mode.c index 72688a7..56feb2a 100644 --- a/kiss_mode.c +++ b/kiss_mode.c @@ -22,20 +22,6 @@ along with QtSoundModem. If not, see http://www.gnu.org/licenses #include "UZ7HOStuff.h" -/* -uses sysutils,classes; - - procedure KISS_init; - procedure KISS_free; - procedure KISS_add_stream(socket: integer); - procedure KISS_del_stream(socket: integer); - procedure KISS_on_data_in(socket: integer; data: string); - procedure KISS_on_data_out(port: byte; frame: string); - procedure KISS_send_ack(port: byte; data: string); - procedure KISS_send_ack1(port: byte); -*/ -// I don't like this. maybe fine for Dephi but overcomlicated for C - // I think I need a struct for each connection, but a simple array of entries should be fine // My normal ** and count system // Each needs an input buffer of max size kiss frame and length (or maybe string is a good idea) @@ -89,13 +75,14 @@ end; void KISS_add_stream(void * Socket) { - // Add a new connection. Called when QT accepts an incoming call} + // Add a new connection. Called wheKISSn QT accepts an incoming call} TKISSMode * KISS; KissConnections = realloc(KissConnections, (KISSConCount + 1) * sizeof(void *)); - KISS = KissConnections[KISSConCount++] = malloc(sizeof(KISS)); + KISS = KissConnections[KISSConCount++] = malloc(sizeof(*KISS)); + memset(KISS, 0, sizeof(*KISS)); KISS->Socket = Socket; KISS->data_in = newString(); diff --git a/main.cpp b/main.cpp index 43ea7cd..a3b08d4 100644 --- a/main.cpp +++ b/main.cpp @@ -77,6 +77,9 @@ int main(int argc, char *argv[]) } QObject::connect(&m1, SIGNAL(HLSetPTT(int)), &m1, SLOT(doHLSetPTT(int)), Qt::QueuedConnection); + QObject::connect(&m1, SIGNAL(FLRigSetPTT(int)), &m1, SLOT(doFLRigSetPTT(int)), Qt::QueuedConnection); + + QObject::connect(&m1, SIGNAL(startTimer(int)), &m1, SLOT(dostartTimer(int)), Qt::QueuedConnection); QObject::connect(&m1, SIGNAL(stopTimer()), &m1, SLOT(dostopTimer()), Qt::QueuedConnection); diff --git a/sm_main.c b/sm_main.c index d2c6cfd..fba2f2f 100644 --- a/sm_main.c +++ b/sm_main.c @@ -74,8 +74,8 @@ int Channels = 2; int BitsPerSample = 16; float TX_Samplerate = 12000; float RX_Samplerate = 12000; -int RX_SR = 11025; -int TX_SR = 11025; +//int RX_SR = 11025; +//int TX_SR = 11025; int RX_PPM = 0; int TX_PPM = 0; int tx_bufsize = 512; @@ -448,11 +448,12 @@ void init_Q4800(int snd_ch) void init_Q3600(int snd_ch) { - qpsk_set[snd_ch].mode = QPSK_SM; + qpsk_set[snd_ch].mode = QPSK_V26; // QPSK_SM; modem_mode[snd_ch] = MODE_QPSK; rx_shift[snd_ch] = 1800; rx_baudrate[snd_ch] = 1800; tx_bitrate[snd_ch] = 3600; + pskStates[snd_ch] = 4; if (modem_def[snd_ch]) get_filter_values(snd_ch); } @@ -1041,7 +1042,10 @@ void BufferFull(short * Samples, int nSamples) // These are Stereo Samples // 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]; @@ -1087,7 +1091,7 @@ void BufferFull(short * Samples, int nSamples) // These are Stereo Samples data2 += 2; } if (((Toggle & 1) == 0)) - doWaterfall(1); + doWaterfall(1); } if (Firstwaterfall || Secondwaterfall) diff --git a/sm_main.c.bak b/sm_main.c.bak new file mode 100644 index 0000000..b5b6499 --- /dev/null +++ b/sm_main.c.bak @@ -0,0 +1,1431 @@ +/* +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. +newsamp + +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" + +void make_core_BPF(UCHAR snd_ch, short freq, short width); +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 dw9600ProcessSample(int snd_ch, short Sample); +void init_RUH48(int snd_ch); +void init_RUH96(int snd_ch); + +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", + "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 { + unsigned short * lpData; /* pointer to locked data buffer */ + int dwBufferLength; /* length of data buffer */ + int dwBytesRecorded; /* used for input only */ + int * dwUser; /* for client's use */ + int dwFlags; /* assorted flags (see defines) */ + int dwLoops; /* loop control counter */ + struct wavehdr_tag *lpNext; /* reserved for driver */ + int * reserved; /* reserved for driver */ +} WAVEHDR, *PWAVEHDR, * NPWAVEHDR, * LPWAVEHDR; + +extern int pnt_change[5]; +int debugmode = 0; +extern float src_buf[5][2048]; +extern Byte RCVR[5]; + +int SatelliteMode = 0; + +int UDPServerPort = 8884; +int UDPClientPort = 8888; +int TXPort = 8884; + +BOOL Firstwaterfall = 1; +BOOL Secondwaterfall = 1; +int multiCore = FALSE; + +BOOL MinOnStart = 0; +//RS TReedSolomon; +// Form1 TForm1; +// WaveFormat TWaveFormatEx; +int Channels = 2; +int BitsPerSample = 16; +float TX_Samplerate = 12000; +float RX_Samplerate = 12000; +int RX_SR = 11025; +int TX_SR = 11025; +int RX_PPM = 0; +int TX_PPM = 0; +int tx_bufsize = 512; +int rx_bufsize = 512; +int tx_bufcount = 16; +int rx_bufcount = 16; +int mouse_down[2] = {0, 0}; +//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; +UCHAR calib_mode[5] = {0,0,0,0}; +UCHAR snd_status [5] = {0,0,0,0}; +UCHAR buf_status [5] = {0,0,0,0}; +UCHAR tx_buf_num1 [5] = {0,0,0,0}; +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}; + +short fft_buf[2][8192]; +UCHAR fft_disp[2][1024]; +int fftCount = 0; // FTF samples collected + +// bm array[1..4] of TBitMap; +// bm1,bm2,bm3 TBitMap; + +// WaveInHandle hWaveIn; +// WaveOutHandle array[1..4] of hWaveOut; +int RXBufferLength; + +int grid_time = 0; +int fft_mult = 0; +int fft_spd = 3; +int grid_timer = 0; +int stop_wf = 0; +int raduga = DISP_RGB; +char snd_rx_device_name[32] = ""; +char snd_tx_device_name[32] = ""; +int snd_rx_device = 0; +int snd_tx_device = 0; +UCHAR mod_icon_status = MOD_IDLE; +UCHAR last_mod_icon_status = MOD_IDLE; +UCHAR icon_timer = 0; +// TelIni TIniFile; +char cur_dir[] = ""; +// TimerId1 cardinal; +// TimerId2 cardinal; +UCHAR TimerStat1 = TIMER_FREE; +UCHAR TimerStat2 = TIMER_FREE; +int stat_log = FALSE; + +int PTT_device = FALSE; +int RX_device = FALSE; +int TX_device = FALSE; +int TX_rotate = FALSE; +int UsingBothChannels = FALSE; +int UsingLeft = FALSE; +int UsingRight = FALSE; + +int SCO = FALSE; +int DualPTT = TRUE; +UCHAR DebugMode = 0; +UCHAR TimerEvent = TIMER_EVENT_OFF; +int nr_monitor_lines = 50; +int UTC_Time = FALSE; +int MainPriority = 0; +// MainThreadHandle THandle; +UCHAR w_state = WIN_MAXIMIZED; + + +void get_filter_values(UCHAR snd_ch) +{ + //, unsigned short dbpf, +//unsigned short dtxbpf, +//unsigned short dbpftap, +//unsigned short dlpf, +//unsigned short dlpftap) +// speed[snd_ch], bpf[snd_ch], txbpf[snd_ch], bpf_tap[snd_ch], lpf[snd_ch], lpf_tap[snd_ch]); + + switch (speed[snd_ch]) + { + case SPEED_8P4800: + + lpf[snd_ch] = MODEM_8P4800_LPF; + bpf[snd_ch] = MODEM_8P4800_BPF; + txbpf[snd_ch] = MODEM_8P4800_TXBPF; + BPF_tap[snd_ch] = MODEM_8P4800_BPF_TAP; + LPF_tap[snd_ch] = MODEM_8P4800_LPF_TAP; + break; + + case SPEED_MP400: + + lpf[snd_ch] = MODEM_MP400_LPF; + bpf[snd_ch] = MODEM_MP400_BPF; + txbpf[snd_ch] = MODEM_MP400_TXBPF; + BPF_tap[snd_ch] = MODEM_MP400_BPF_TAP; + LPF_tap[snd_ch] = MODEM_MP400_LPF_TAP; + + break; + + + case SPEED_Q4800: + + lpf[snd_ch] = MODEM_Q4800_LPF; + bpf[snd_ch] = MODEM_Q4800_BPF; + txbpf[snd_ch] = MODEM_Q4800_TXBPF; + BPF_tap[snd_ch] = MODEM_Q4800_BPF_TAP; + LPF_tap[snd_ch] = MODEM_Q4800_LPF_TAP; + + break; + + case SPEED_Q3600: + + lpf[snd_ch] = MODEM_Q3600_LPF; + bpf[snd_ch] = MODEM_Q3600_BPF; + txbpf[snd_ch] = MODEM_Q3600_TXBPF; + BPF_tap[snd_ch] = MODEM_Q3600_BPF_TAP; + LPF_tap[snd_ch] = MODEM_Q3600_LPF_TAP; + break; + + case SPEED_Q2400: + + lpf[snd_ch] = MODEM_Q2400_LPF; + bpf[snd_ch] = MODEM_Q2400_BPF; + txbpf[snd_ch] = MODEM_Q2400_TXBPF; + BPF_tap[snd_ch] = MODEM_Q2400_BPF_TAP; + LPF_tap[snd_ch] = MODEM_Q2400_LPF_TAP; + + break; + + case SPEED_DW2400: + case SPEED_2400V26B: + + + lpf[snd_ch] = MODEM_DW2400_LPF; + bpf[snd_ch] = MODEM_DW2400_BPF; + txbpf[snd_ch] = MODEM_DW2400_TXBPF; + BPF_tap[snd_ch] = MODEM_DW2400_BPF_TAP; + LPF_tap[snd_ch] = MODEM_DW2400_LPF_TAP; + break; + + case SPEED_P2400: + + lpf[snd_ch] = MODEM_P2400_LPF; + bpf[snd_ch] = MODEM_P2400_BPF; + txbpf[snd_ch] = MODEM_P2400_TXBPF; + BPF_tap[snd_ch] = MODEM_P2400_BPF_TAP; + LPF_tap[snd_ch] = MODEM_P2400_LPF_TAP; + break; + + case SPEED_P1200: + + 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; + + case SPEED_P600: + + lpf[snd_ch] = MODEM_P600_LPF; + bpf[snd_ch] = MODEM_P600_BPF; + txbpf[snd_ch] = MODEM_P600_TXBPF; + BPF_tap[snd_ch] = MODEM_P600_BPF_TAP; + LPF_tap[snd_ch] = MODEM_P600_LPF_TAP; + break; + + case SPEED_P300: + + 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_300: + + lpf[snd_ch] = MODEM_300_LPF; + bpf[snd_ch] = MODEM_300_BPF; + txbpf[snd_ch] = MODEM_300_TXBPF; + BPF_tap[snd_ch] = MODEM_300_BPF_TAP; + LPF_tap[snd_ch] = MODEM_300_LPF_TAP; + + break; + + case SPEED_600: + + lpf[snd_ch] = MODEM_600_LPF; + bpf[snd_ch] = MODEM_600_BPF; + txbpf[snd_ch] = MODEM_600_TXBPF; + BPF_tap[snd_ch] = MODEM_600_BPF_TAP; + LPF_tap[snd_ch] = MODEM_600_LPF_TAP; + + break; + + case SPEED_1200: + + lpf[snd_ch] = MODEM_1200_LPF; + bpf[snd_ch] = MODEM_1200_BPF; + txbpf[snd_ch] = MODEM_1200_TXBPF; + BPF_tap[snd_ch] = MODEM_1200_BPF_TAP; + LPF_tap[snd_ch] = MODEM_1200_LPF_TAP; + break; + + case SPEED_2400: + + lpf[snd_ch] = MODEM_2400_LPF; + bpf[snd_ch] = MODEM_2400_BPF; + txbpf[snd_ch] = MODEM_2400_TXBPF; + 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; +*/ + } +} + + +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); +} + +void init_1200(int snd_ch) +{ + modem_mode[snd_ch] = MODE_FSK; + rx_shift[snd_ch] = 1000; + + if (stdtones) + 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); +} + +void init_600(int snd_ch) +{ + modem_mode[snd_ch] = MODE_FSK; + 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); +} + +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); +} + +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); +} + + +void init_8P4800(int snd_ch) +{ + modem_mode[snd_ch] = MODE_8PSK; + if (stdtones) + rx_freq[snd_ch] = 1800; + + 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_V26B2400(int snd_ch) +{ + qpsk_set[snd_ch].mode = QPSK_V26; + modem_mode[snd_ch] = MODE_PI4QPSK; + + if (stdtones) + rx_freq[snd_ch] = 1800; + + 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); +} + +void init_DW2400(int snd_ch) +{ + qpsk_set[snd_ch].mode = QPSK_V26; + modem_mode[snd_ch] = MODE_QPSK; + + if (stdtones) + rx_freq[snd_ch] = 1800; + + 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); +} + +void init_Q4800(int snd_ch) +{ + qpsk_set[snd_ch].mode = QPSK_SM; + 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); +} + +void init_Q3600(int snd_ch) +{ + qpsk_set[snd_ch].mode = QPSK_SM; + 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); +} + +void init_Q2400(int snd_ch) +{ + qpsk_set[snd_ch].mode = QPSK_SM; + 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); +} + +void init_P2400(int snd_ch) +{ + modem_mode[snd_ch] = MODE_BPSK; + rx_shift[snd_ch] = 2400; + rx_baudrate[snd_ch] = 2400; + tx_bitrate[snd_ch] = 2400; + pskStates[snd_ch] = 2; + + if (modem_def[snd_ch]) + get_filter_values(snd_ch); +} + +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); +} + +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); +} + +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); +} + +void init_ARDOP(int snd_ch) +{ + modem_mode[snd_ch] = MODE_ARDOP; + 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); +} + + +void init_speed(int snd_ch); + +void set_speed(int snd_ch, int Modem) +{ + speed[snd_ch] = Modem; + + init_speed(snd_ch); + +} + +int needPSKRefresh = 0; + +void init_speed(int snd_ch) +{ + int low, high; + + pskStates[snd_ch] = 0; // Not PSK + + switch (speed[snd_ch]) + { + case SPEED_8P4800: + init_8P4800(snd_ch); + break; + + case SPEED_2400V26B: + init_V26B2400(snd_ch); + + break; + + case SPEED_DW2400: + init_DW2400(snd_ch); + break; + + case SPEED_MP400: + init_MP400(snd_ch); + break; + case SPEED_Q4800: + init_Q4800(snd_ch); + break; + + case SPEED_Q3600: + init_Q3600(snd_ch); + break; + + case SPEED_Q2400: + init_Q2400(snd_ch); + break; + + case SPEED_P2400: + init_P2400(snd_ch); + break; + + case SPEED_P1200: + init_P1200(snd_ch); + break; + +// case SPEED_Q1200: +// init_Q1200(snd_ch); +// break; + + case SPEED_P600: + init_P600(snd_ch); + break; + + case SPEED_P300: + 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); + break; + + case SPEED_600: + + init_600(snd_ch); + break; + + case SPEED_1200: + + init_1200(snd_ch); + break; + + case SPEED_2400: + + init_2400(snd_ch); + break; + + case SPEED_ARDOP: + + 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) + { + case QPSK_SM: + + memcpy(&qpsk_set[snd_ch].tx[0], "\0\1\2\3", 4); + memcpy(&qpsk_set[snd_ch].rx[0], "\x0\x20\x40\x60", 4); + break; + + case QPSK_V26: + + memcpy(&qpsk_set[snd_ch].tx[0], "\2\3\1\0", 4); + memcpy(&qpsk_set[snd_ch].rx[0], "\x60\x40\0\x20", 4); + break; + } + } + + tx_shift[snd_ch] = rx_shift[snd_ch]; + tx_baudrate[snd_ch] = rx_baudrate[snd_ch]; + low = roundf(rx_shift[snd_ch] / 2 + RCVR[snd_ch] * rcvr_offset[snd_ch] + 1); + high = roundf(RX_Samplerate / 2 - (rx_shift[snd_ch] / 2 + RCVR[snd_ch] * rcvr_offset[snd_ch])); + + if (rx_freq[snd_ch] - low < 0) rx_freq[snd_ch] = low; + if (high - rx_freq[snd_ch] < 0) rx_freq[snd_ch] = high; + + tx_freq[snd_ch] = rx_freq[snd_ch]; + + make_core_BPF(snd_ch, rx_freq[snd_ch], bpf[snd_ch]); + make_core_TXBPF(snd_ch, tx_freq[snd_ch], txbpf[snd_ch]); + make_core_INTR(snd_ch); + make_core_LPF(snd_ch, lpf[snd_ch]); + + /* + for i = 0 to 16 do + for j = 0 to nr_emph do with DET[j,i] do + begin + minamp[snd_ch] = 0; + maxamp[snd_ch] = 0; + ones[snd_ch] = 0; + zeros[snd_ch] = 0; + sample_cnt[snd_ch] = 0; + bit_cnt[snd_ch] = 0; + bit_osc[snd_ch] = 0; + frame_status[snd_ch] = FRAME_WAIT; + end; + form1.show_modes; + form1.show_freq_a; + form1.show_freq_b; + */ + NeedWaterfallHeaders = TRUE; + + CheckPSKWindows(); +} + + +void chk_snd_buf(float * buf, int len) +{ + word i; + boolean good; + single prev_amp; + + if (len < 2) + return; + + good = FALSE; + i = 1; + prev_amp = buf[0]; + do + { + if (buf[i++] != prev_amp) + good = TRUE; + + } while (good == FALSE && i < len); + + // Make noise + if (!good) + for (i = 0; i < len; i++) + buf[i] = rand(); +} + +#ifdef WIN32 + +typedef void *HANDLE; +typedef unsigned long DWORD; + +#define WINAPI __stdcall +__declspec(dllimport) +DWORD +WINAPI +WaitForSingleObject( + __in HANDLE hHandle, + __in DWORD dwMilliseconds +); + + + + +#define pthread_t uintptr_t + +uintptr_t _beginthread(void(__cdecl *start_address)(void *), unsigned stack_size, void *arglist); +#else + +#include + +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"); + + return thread; +} + +#endif + +void runModemthread(void * param); + +void runModems() +{ + int snd_ch, res; + pthread_t thread[4] = { 0,0,0,0 }; + + for (snd_ch = 0; snd_ch < 4; snd_ch++) + { + if (soundChannel[snd_ch] == 0) // Unused channed + continue; + + if (modem_mode[snd_ch] == MODE_ARDOP) + continue; // Processed above + + 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); + else + runModemthread((void *)(size_t)snd_ch); + } + + if (multiCore) + { +#ifdef WIN32 + if (thread[0]) WaitForSingleObject(&thread[0], 2000); + if (thread[1]) WaitForSingleObject(&thread[1], 2000); + if (thread[2]) WaitForSingleObject(&thread[2], 2000); + if (thread[3]) WaitForSingleObject(&thread[3], 2000); +#else + if (thread[0]) pthread_join(thread[0], &res); + if (thread[1]) pthread_join(thread[1], &res); + if (thread[2]) pthread_join(thread[2], &res); + if (thread[3]) pthread_join(thread[3], &res); +#endif + } +} + +Byte rcvr_idx; + +void runModemthread(void * param) +{ + int snd_ch = (int)(size_t)param; + + // I want to run lowest to highest to simplify my display + + int offset = -(RCVR[snd_ch] * rcvr_offset[snd_ch]); // lowest + int lastrx = RCVR[snd_ch] * 2; + + if (soundChannel[snd_ch] == 0) // Unused channed + return; + + for (rcvr_idx = 0; rcvr_idx <= lastrx; rcvr_idx++) + { + active_rx_freq[snd_ch] = rxOffset + chanOffset[snd_ch] + rx_freq[snd_ch] + offset; + offset += rcvr_offset[snd_ch]; + + Demodulator(snd_ch, rcvr_idx, src_buf[modemtoSoundLR[snd_ch]], rcvr_idx == lastrx, offset == 0); + } +} + +// I think this processes a buffer of samples + +int Toggle = 0; + +void BufferFull(short * Samples, int nSamples) // These are Stereo Samples +{ + word i, i1, j; + Byte snd_ch, rcvr_idx; + int buf_offset; + int Needed; + short * data1; + short * data2 = 0; + + // if UDP server active send as UDP Datagram + + if (UDPServ) // Extract just left + { + short Buff[1024]; + + i1 = 0; + + for (i = 0; i < rx_bufsize; i++) + { + Buff[i] = Samples[i1]; + i1 += 2; + } + + sendSamplestoUDP(Buff, 512, TXPort); + } + + // Do RSID processing (can we also use this for waterfall?? + + RSIDProcessSamples(Samples, nSamples); + + // Do FFT on every 4th buffer (2048 samples) + + // if in Satellite Mode look for a Tuning signal + +// if (SatelliteMode) +// { +// doTuning(Samples, nSamples); +// } + + for (snd_ch = 0; snd_ch < 4; snd_ch++) + { + if (soundChannel[snd_ch] == 0) // Unused channed + continue; + + if (pnt_change[snd_ch]) + { + make_core_BPF(snd_ch, rx_freq[snd_ch], bpf[snd_ch]); + make_core_TXBPF(snd_ch, tx_freq[snd_ch], txbpf[snd_ch]); + pnt_change[snd_ch] = FALSE; + } + + } + + // I don't think we should process RX if either is sending + + if (snd_status[0] != SND_TX && snd_status[1] != SND_TX && snd_status[2] != SND_TX && snd_status[3] != SND_TX) + { + for (snd_ch = 0; snd_ch < 4; snd_ch++) + { + if (soundChannel[snd_ch] == 0) // Unused channed + continue; + + if (modem_mode[snd_ch] == MODE_ARDOP) + { + short ardopbuff[1200]; + i1 = 0; + + 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++) + { + 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++; + } + } + 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 + + // We could skip extracting other channel if only one in use - is it worth it?? + + if (UsingBothChannels) + { + for (i = 0; i < nSamples; i++) + { + src_buf[0][i] = data1[i1]; + i1++; + src_buf[1][i] = data1[i1]; + i1++; + } + } + else if (UsingRight) + { + // Extract just right + + i1 = 1; + + for (i = 0; i < nSamples; i++) + { + src_buf[1][i] = data1[i1]; + i1 += 2; + } + } + else + { + // Extract just left + + for (i = 0; i < nSamples; i++) + { + src_buf[0][i] = data1[i1]; + i1 += 2; + } + } + + // Run modems before waterfall so fft buffer has values from before sync was detected + + runModems(); + + // Do whichever waterfall is needed + + // We need to run the waterfall FFT for the frequency guessing to work + + // testing + + + + int FirstWaterfallChan = 0; + + doWaterfall(FirstWaterfallChan); + if (Firstwaterfall || Secondwaterfall) + displayWaterfall(); + + + + +/* + + short * ptr1 = &fft_buf[0][fftCount]; + short * ptr2 = &fft_buf[1][fftCount]; + + int remainingSamples = rx_bufsize; + + if (UsingLeft == 0) + { + FirstWaterfallChan = 1; + data1++; // to Right Samples + } + + if (UsingBothChannels) // Second is always Right + data2 = &Samples[1]; // to Right Samples + + + // 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 + + Toggle++; + + 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++) + { + *ptr1++ = *data1; + data1 += 2; + } + + if ((Toggle & 1) || (UsingBothChannels == 0)) + doWaterfall(FirstWaterfallChan); + + if (data2) + { + for (i = 0; i < Needed; i++) + { + *ptr2++ = *data2; + data2 += 2; + } + if (((Toggle & 1) == 0)) + doWaterfall(1); + } + + if (Firstwaterfall || Secondwaterfall) + displayWaterfall(); + + 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(); +// timer_event2(); + } +} + + /* + +procedure TForm1.BufferFull1(var Msg TMessage); +var + i,snd_ch byte; +begin + for snd_ch = 1 to 2 do + if pnt_change[snd_ch] then + begin + make_core_BPF(snd_ch,rx_freq[snd_ch],bpf[snd_ch]); + make_core_TXBPF(snd_ch,tx_freq[snd_ch],txbpf[snd_ch]); + pnt_change[snd_ch] = FALSE; + end; + snd_ch = 0; + for i = 1 to 2 do if msg.WParam = WaveOutHandle[i] then snd_ch = i; + if (snd_ch = 0) then exit; + if (snd_status[snd_ch]<>SND_TX) then exit; + inc(tx_buf_num[snd_ch]); if tx_buf_num[snd_ch]>tx_bufcount then tx_buf_num[snd_ch] = 1; + if (buf_status[snd_ch] = BUF_EMPTY) and (tx_buf_num[snd_ch] = tx_buf_num1[snd_ch]) then TX2RX(snd_ch); + if buf_status[snd_ch] = BUF_FULL then + beginf + make_wave_buf(snd_ch,TX_pbuf[snd_ch][tx_buf_num[snd_ch]]); + WaveOutWrite(WaveOutHandle[snd_ch],@TX_header[snd_ch][tx_buf_num[snd_ch]],sizeof(TWaveHdr)); + inc(tx_buf_num1[snd_ch]); if tx_buf_num1[snd_ch]>tx_bufcount then tx_buf_num1[snd_ch] = 1; + end; +end; + +procedure TForm1.TX2RX(snd_ch byte); +begin + if snd_status[snd_ch] = SND_TX then stoptx(snd_ch); + if snd_status[snd_ch] = SND_IDLE then begin pttoff(snd_ch); end; +end; +*/ + + +// Monitor Code - from moncode.asm + + +#define CMDBIT 4 // CURRENT MESSAGE IS A COMMAND +#define RESP 2 // CURRENT MSG IS RESPONSE +#define VER1 1 // CURRENT MSG IS VERSION 1 + + +#define UI 3 +#define SABM 0x2F +#define DISC 0x43 +#define DM 0x0F +#define UA 0x63 +#define FRMR 0x87 +#define RR 1 +#define RNR 5 +#define REJ 9 + +#define SREJ 0x0D +#define SABME 0x6F +#define XID 0xAF +#define TEST 0xE3 + + +#define PFBIT 0x10 // POLL/FINAL BIT IN CONTROL BYTE + +#define NETROM_PID 0xCF +#define IP_PID 0xCC +#define ARP_PID 0xCD + +#define NODES_SIG 0xFF + +char FrameData[1024] = ""; + +char * frame_monitor(string * frame, char * code, int tx_stat) +{ + char mon_frm[512]; + char AGW_path[256]; + string * AGW_data; + + const Byte * frm = "???"; + Byte * datap; + Byte _data[512] = ""; + Byte * p_data = _data; + int _datalen; + + char agw_port; + char CallFrom[10], CallTo[10], Digi[80]; + + char TR = 'R'; + char codestr[64] = ""; + + integer i; + char time_now[32]; + int len; + + AGWUser * AGW; + + Byte pid, nr, ns, f_type, f_id; + Byte rpt, cr, pf; + Byte path[80]; + char c; + const char * p; + + string * data = newString(); + + if (code[0] && strlen(code) < 60) + sprintf(codestr, "[%s]", code); + + if (tx_stat) + TR = 'T'; + + decode_frame(frame->Data, frame->Length, path, data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr); + + datap = data->Data; + + len = data->Length; + + // if (pid == 0xCF) + // data = parse_NETROM(data, f_id); + // IP parsing + // else if (pid == 0xCC) + // data = parse_IP(data); + // ARP parsing + // else if (pid == 0xCD) + // data = parse_ARP(data); + // + + if (len > 0) + { + for (i = 0; i < len; i++) + { + if (datap[i] > 31 || datap[i] == 13 || datap[i] == 9) + *(p_data++) = datap[i]; + } + } + + _datalen = p_data - _data; + + if (_datalen) + { + Byte * ptr = _data; + i = 0; + + // remove successive cr or cr on end while (i < _datalen) + + while (i < _datalen) + { + if ((_data[i] == 13) && (_data[i + 1] == 13)) + i++; + else + *(ptr++) = _data[i++]; + } + + if (*(ptr - 1) == 13) + ptr--; + + *ptr = 0; + + _datalen = ptr - _data; + } + + get_monitor_path(path, CallTo, CallFrom, Digi); + + if (cr) + { + c = 'C'; + if (pf) + p = " P"; + else p = ""; + } + else + { + c = 'R'; + if (pf) + p = " F"; + else + p = ""; + } + + switch (f_id) + { + case I_I: + + frm = "I"; + break; + + case S_RR: + + frm = "RR"; + break; + + case S_RNR: + + frm = "RNR"; + break; + + case S_REJ: + + frm = "REJ"; + break; + + case S_SREJ: + + frm = "SREJ"; + break; + + case U_SABM: + + frm = "SABM"; + break; + + case SABME: + + frm = "SABME"; + break; + + case U_DISC: + + frm = "DISC"; + break; + + case U_DM: + + frm = "DM"; + break; + + case U_UA: + + frm = "UA"; + break; + + case U_FRMR: + + frm = "FRMR"; + break; + + 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 + sprintf(AGW_path, "Fm %s To %s <%s %c %s", CallFrom, CallTo, frm, c, p); + + + switch (f_type) + { + case I_FRM: + + //mon_frm = AGW_path + ctrl + ' R' + inttostr(nr) + ' S' + inttostr(ns) + ' pid=' + dec2hex(pid) + ' Len=' + inttostr(len) + ' >' + time_now + #13 + _data + #13#13; + sprintf(mon_frm, "%s R%d S%d pid=%X Len=%d>[%s%c]%s\r%s\r", AGW_path, nr, ns, pid, len, ShortDateTime(), TR, codestr, _data); + + break; + + case U_FRM: + + if (f_id == U_UI) + { + sprintf(mon_frm, "%s pid=%X Len=%d>[%s%c]%s\r%s\r", AGW_path, pid, len, ShortDateTime(), TR, codestr, _data); // "= AGW_path + ctrl + '>' + time_now + #13; + } + else if (f_id == U_FRMR) + { + sprintf(mon_frm, "%s>%02x %02x %02x[%s]\r", AGW_path, datap[0], datap[1], datap[2], ShortDateTime()); // "= AGW_path + ctrl + '>' + time_now + #13; + } + else + sprintf(mon_frm, "%s>[%s%c]%s\r", AGW_path, ShortDateTime(), TR, codestr); // "= AGW_path + ctrl + '>' + time_now + #13; + + break; + + case S_FRM: + + // mon_frm = AGW_path + ctrl + ' R' + inttostr(nr) + ' >' + time_now + #13; + sprintf(mon_frm, "%s R%d>[%s%c]%s\r", AGW_path, nr, ShortDateTime(), TR, codestr); // "= AGW_path + ctrl + '>' + time_now + #13; + + break; + + } + sprintf(FrameData, "%s", mon_frm); + return FrameData; +} + diff --git a/tcpCode.cpp b/tcpCode.cpp index 97d5ab0..d6be891 100644 --- a/tcpCode.cpp +++ b/tcpCode.cpp @@ -244,6 +244,10 @@ QTcpSocket * HAMLIBsock; int HAMLIBConnected = 0; int HAMLIBConnecting = 0; +QTcpSocket * FLRIGsock; +int FLRIGConnected = 0; +int FLRIGConnecting = 0; + void mynet::HAMLIBdisplayError(QAbstractSocket::SocketError socketError) { switch (socketError) @@ -332,6 +336,142 @@ extern "C" void HAMLIBSetPTT(int PTTState) emit m1.HLSetPTT(PTTState); } +extern "C" void FLRigSetPTT(int PTTState) +{ + // Won't work in non=gui mode + + emit m1.FLRigSetPTT(PTTState); +} + + +QTcpSocket * FLRigsock; +int FLRigConnected = 0; +int FLRigConnecting = 0; + +void mynet::FLRigdisplayError(QAbstractSocket::SocketError socketError) +{ + switch (socketError) + { + case QAbstractSocket::RemoteHostClosedError: + break; + + case QAbstractSocket::HostNotFoundError: + QMessageBox::information(nullptr, tr("QtSM"), + "FLRig host was not found. Please check the " + "host name and portsettings->"); + + break; + + case QAbstractSocket::ConnectionRefusedError: + + qDebug() << "FLRig Connection Refused"; + break; + + default: + + qDebug() << "FLRig Connection Failed"; + break; + + } + + FLRigConnecting = 0; + FLRigConnected = 0; +} + +void mynet::FLRigreadyRead() +{ + unsigned char Buffer[4096]; + QTcpSocket* Socket = static_cast(QObject::sender()); + + // read the data from the socket. Don't do anyhing with it at the moment + + Socket->read((char *)Buffer, 4095); +} + +void mynet::onFLRigSocketStateChanged(QAbstractSocket::SocketState socketState) +{ + if (socketState == QAbstractSocket::UnconnectedState) + { + // Close any connections + + FLRigConnected = 0; + FLRigConnecting = 0; + + // delete (FLRigsock); + // FLRigsock = 0; + + qDebug() << "FLRig Connection Closed"; + + } + else if (socketState == QAbstractSocket::ConnectedState) + { + FLRigConnected = 1; + FLRigConnecting = 0; + qDebug() << "FLRig Connected"; + } +} + + +void mynet::ConnecttoFLRig() +{ + delete(FLRigsock); + + FLRigConnected = 0; + FLRigConnecting = 1; + + FLRigsock = new QTcpSocket(); + + connect(FLRigsock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(FLRigdisplayError(QAbstractSocket::SocketError))); + connect(FLRigsock, SIGNAL(readyRead()), this, SLOT(FLRigreadyRead())); + connect(FLRigsock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onFLRigSocketStateChanged(QAbstractSocket::SocketState))); + + FLRigsock->connectToHost(FLRigHost, FLRigPort); + + return; +} + +static char MsgHddr[] = "POST /RPC2 HTTP/1.1\r\n" +"User-Agent: XMLRPC++ 0.8\r\n" +"Host: 127.0.0.1:7362\r\n" +"Content-Type: text/xml\r\n" +"Content-length: %d\r\n" +"\r\n%s"; + +static char Req[] = "\r\n" +"%s\r\n" +"%s" +"\r\n"; + + +void mynet::doFLRigSetPTT(int c) +{ + int Len; + char ReqBuf[512]; + char SendBuff[512]; + char ValueString[256] = ""; + + sprintf(ValueString, "%d", c); + + Len = sprintf(ReqBuf, Req, "rig.set_ptt", ValueString); + Len = sprintf(SendBuff, MsgHddr, Len, ReqBuf); + + if (FLRigsock == nullptr || FLRigsock->state() != QAbstractSocket::ConnectedState) + ConnecttoFLRig(); + + if (FLRigsock == nullptr || FLRigsock->state() != QAbstractSocket::ConnectedState) + return; + + FLRigsock->write(SendBuff); + + FLRigsock->waitForBytesWritten(3000); + + QByteArray datas = FLRigsock->readAll(); + + qDebug(datas.data()); +} + + + extern "C" void startTimer(int Time) { // Won't work in non=gui mode diff --git a/tcpCode.h b/tcpCode.h index 9255a49..ea5b5a1 100644 --- a/tcpCode.h +++ b/tcpCode.h @@ -11,6 +11,7 @@ class mynet : public QObject signals: void HLSetPTT(int c); + void FLRigSetPTT(int c); void startTimer(int Time); void stopTimer(); @@ -33,6 +34,10 @@ public slots: void sendtoKISS(void * sock, unsigned char * Msg, int Len); + void FLRigdisplayError(QAbstractSocket::SocketError socketError); + void FLRigreadyRead(); + void onFLRigSocketStateChanged(QAbstractSocket::SocketState socketState); + void ConnecttoFLRig(); void HAMLIBdisplayError(QAbstractSocket::SocketError socketError); void HAMLIBreadyRead(); void onHAMLIBSocketStateChanged(QAbstractSocket::SocketState socketState); @@ -40,6 +45,7 @@ public slots: void dostartTimer(int Time); void dostopTimer(); void doHLSetPTT(int c); + void doFLRigSetPTT(int c); void readPendingDatagrams(); void socketError(); @@ -65,6 +71,11 @@ signals: void sendtoKISS(void *, unsigned char *, int); void openSockets(); void startCWIDTimer(); + void setWaterfallImage(); + void setLevelImage(); + void setConstellationImage(int, int); + void startWatchdog(); + void stopWatchdog(); private: