qtsoundmodem/QtSoundModem.cpp

3889 lines
92 KiB
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
// UZ7HO Soundmodem Port
// Not Working 4psk100 FEC
// Thoughts on Waterfall Display.
// Original used a 2048 sample FFT giving 5.859375 Hz bins. We plotted 1024 points, giving a 0 to 6000 specrum
// If we want say 300 to 3300 we need about half the bin size so twice the fft size. But should we also fit required range to window size?
// Unless we resize the most displayed bit of the screen in around 900 pixels. So each bin should be 3300 / 900 = 3.66667 Hz or a FFT size of around 3273
#include "QtSoundModem.h"
#include <qheaderview.h>
//#include <QDebug>
#include <QHostAddress>
#include <QAbstractSocket>
#include <QSettings>
#include <QPainter>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include <QMessageBox>
#include <QTimer>
#include <qevent.h>
#include <QStandardItemModel>
#include <QScrollBar>
#include <QFontDialog>
#include <QFile>
#include "UZ7HOStuff.h"
#include <time.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;
extern serialThread *serial;
QList<QSerialPortInfo> 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" bool onlyMixSnoop;
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 txLatency;
extern "C" int BusyDet;
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 closeTraceLog();
}
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;
int MgmtPort = 0;
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;
extern "C" bool useKISSControls;
// 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;
QTimer *PTTWatchdog;
QWidget * mythis;
QElapsedTimer pttOnTimer;
QSystemTrayIcon * trayIcon = nullptr;
int MintoTray = 1;
int RSID_WF = 0; // Set to use RSID FFT for Waterfall.
char SixPackDevice[256] = "";
int SixPackPort = 0;
int SixPackEnable = 0;
// Stats
uint64_t PTTonTime[4] = { 0 };
uint64_t PTTActivemS[4] = { 0 }; // For Stats
uint64_t BusyonTime[4] = { 0 };
uint64_t BusyActivemS[4] = { 0 };
int AvPTT[4] = { 0 };
int AvBusy[4] = { 0 };
extern "C" void WriteDebugLog(char * Mess)
{
qDebug() << Mess;
}
void QtSoundModem::doupdateDCD(int Chan, int State)
{
DCDLabel[Chan]->setVisible(State);
// This also tries to get a percentage on time over a minute
uint64_t Time = QDateTime::currentMSecsSinceEpoch();
if (State)
{
BusyonTime[Chan] = Time;
return;
}
if (BusyonTime[Chan])
{
BusyActivemS[Chan] += Time - BusyonTime[Chan];
BusyonTime[Chan] = 0;
}
}
extern "C" char * frame_monitor(string * frame, char * code, bool tx_stat);
extern "C" char * ShortDateTime();
extern "C" void mon_rsid(int snd_ch, char * RSID)
{
int Len;
char * Msg = (char *)malloc(1024); // Cant pass local variable via signal/slot
sprintf(Msg, "%d:%s [%s%c]", snd_ch + 1, RSID, ShortDateTime(), 'R');
Len = strlen(Msg);
if (Msg[Len - 1] != '\r')
{
Msg[Len++] = '\r';
Msg[Len] = 0;
}
emit t->sendtoTrace(Msg, 0);
}
extern "C" void put_frame(int snd_ch, string * frame, char * code, int tx, int excluded)
{
UNUSED(excluded);
int Len;
char * Msg = (char *)malloc(1024); // Cant pass local variable via signal/slot
if (strcmp(code, "NON-AX25") == 0)
sprintf(Msg, "%d: <NON-AX25 frame Len = %d [%s%c]\r", snd_ch, frame->Length, ShortDateTime(), 'R');
else
sprintf(Msg, "%d:%s", snd_ch + 1, frame_monitor(frame, code, tx));
Len = strlen(Msg);
if (Msg[Len - 1] != '\r')
{
Msg[Len++] = '\r';
Msg[Len] = 0;
}
emit t->sendtoTrace(Msg, tx);
}
extern "C" void updateDCD(int Chan, bool State)
{
emit t->updateDCD(Chan, State);
}
bool QtSoundModem::eventFilter(QObject* obj, QEvent *evt)
{
UNUSED(obj);
if (evt->type() == QEvent::Resize)
{
return QWidget::event(evt);
}
if (evt->type() == QEvent::WindowStateChange)
{
if (windowState().testFlag(Qt::WindowMinimized) == true)
w_state = WIN_MINIMIZED;
else
w_state = WIN_MAXIMIZED;
}
// if (evt->type() == QGuiApplication::applicationStateChanged) - this is a sigma;
// {
// qDebug() << "App State changed =" << evt->type() << endl;
// }
return QWidget::event(evt);
}
void QtSoundModem::resizeEvent(QResizeEvent* event)
{
QMainWindow::resizeEvent(event);
QRect r = geometry();
int 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<QAction*>(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;
extern "C" struct timespec pttclk;
QtSoundModem::QtSoundModem(QWidget *parent) : QMainWindow(parent)
{
QString family;
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;
getSettings();
serial = new serialThread;
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);
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()));
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);
QTimer *statstimer = new QTimer(this);
connect(statstimer, SIGNAL(timeout()), this, SLOT(StatsTimer()));
statstimer->start(60000); // One Minute
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()));
PTTWatchdog = new QTimer(this);
connect(PTTWatchdog, SIGNAL(timeout()), this, SLOT(PTTWatchdogExpired()));
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);
connect(serial, &serialThread::request, this, &QtSoundModem::showRequest);
}
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;
}
extern TMgmtMode ** MgmtConnections;
extern int MgmtConCount;
extern QList<QTcpSocket*> _MgmtSockets;
extern "C" void doAGW2MinTimer();
#define FEND 0xc0
#define QTSMKISSCMD 7
int AGW2MinTimer = 0;
void QtSoundModem::StatsTimer()
{
// Calculate % Busy over last minute
for (int n = 0; n < 4; n++)
{
if (soundChannel[n] == 0) // Channel not used
continue;
AvPTT[n] = PTTActivemS[n] / 600; // ms but want %
PTTActivemS[n] = 0;
AvBusy[n] = BusyActivemS[n] / 600;
BusyActivemS[n] = 0;
// send to any connected Mgmt streams
char Msg[64];
uint64_t ret;
if (!useKISSControls)
{
for (QTcpSocket* socket : _MgmtSockets)
{
// Find Session
TMgmtMode * MGMT = NULL;
for (int i = 0; i < MgmtConCount; i++)
{
if (MgmtConnections[i]->Socket == socket)
{
MGMT = MgmtConnections[i];
break;
}
}
if (MGMT == NULL)
continue;
if (MGMT->BPQPort[n])
{
sprintf(Msg, "STATS %d %d %d\r", MGMT->BPQPort[n], AvPTT[n], AvBusy[n]);
ret = socket->write(Msg);
}
}
}
else // useKISSControls set
{
UCHAR * Control = (UCHAR *)malloc(32);
int len = sprintf((char *)Control, "%c%cSTATS %d %d%c", FEND, (n) << 4 | QTSMKISSCMD, AvPTT[n], AvBusy[n], FEND);
KISSSendtoServer(NULL, Control, len);
}
}
AGW2MinTimer++;
if (AGW2MinTimer > 1)
{
AGW2MinTimer = 0;
doAGW2MinTimer();
}
}
// PTT Stats
extern "C" void UpdatePTTStats(int Chan, int State)
{
uint64_t Time = QDateTime::currentMSecsSinceEpoch();
if (State)
{
PTTonTime[Chan] = Time;
// Cancel Busy timer (stats include ptt on time in port active
if (BusyonTime[Chan])
{
BusyActivemS[Chan] += (Time - BusyonTime[Chan]);
BusyonTime[Chan] = 0;
}
}
else
{
if (PTTonTime[Chan])
{
PTTActivemS[Chan] += (Time - PTTonTime[Chan]);
PTTonTime[Chan] = 0;
}
}
}
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 > 300)
{
QSettings * settings = new QSettings("QtSoundModem.ini", QSettings::IniFormat);
ui.centerB->setValue(Freq_Change(1, i));
settings->setValue("Modem/RXFreq2", ui.centerB->value());
AGW_Report_Modem_Change(1);
}
return;
}
if (strcmp(Name, "centerC") == 0)
{
if (i > 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;
BusyDet = i / 10; // for ardop busy detect code
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, "Cal1500") == 0)
{
char call[] = "1500TONE";
sendCWID(call, 0, 0);
calib_mode[0] = 4;
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->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));
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->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();
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;
int oldSnoopMix = 0;
int newSnoopMix = 0;
void QtSoundModem::SoundModeChanged(bool State)
{
UNUSED(State);
// Mustn't change SoundMode until dialog is accepted
newSnoopMix = Dev->onlyMixSnoop->isChecked();
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 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);
Dev->CAT->setVisible(true);
if (Dev->CAT->isChecked())
{
Dev->PTTOnLab->setVisible(true);
Dev->PTTOnLab->setText("PTT On String");
Dev->PTTOn->setText(PTTOnString);
Dev->PTTOn->setVisible(true);
Dev->PTTOff->setVisible(true);
Dev->PTTOff->setText(PTTOffString);
Dev->PTTOffLab->setVisible(true);
Dev->CATLabel->setVisible(true);
Dev->CATSpeed->setVisible(true);
}
}
}
bool myResize::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::Resize)
{
QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
QSize size = resizeEvent->size();
int h = size.height();
int w = size.width();
if (obj == deviceUI)
Dev->scrollArea->setGeometry(QRect(5, 5, w - 10, h - 10));
else
Dlg->scrollArea->setGeometry(QRect(5, 5, w - 10, h - 10));
return true;
}
return QObject::eventFilter(obj, event);
}
void QtSoundModem::doDevices()
{
char valChar[10];
QStringList items;
Dev = new(Ui_devicesDialog);
QDialog UI;
int i;
Dev->setupUi(&UI);
deviceUI = &UI;
modemUI = 0;
myResize *resize = new myResize();
UI.installEventFilter(resize);
// Set serial names
for (const QSerialPortInfo &info : Ports)
{
items.append(info.portName());
}
items.sort();
Dev->SixPackSerial->addItem("None");
for (const QString &info : items)
{
Dev->SixPackSerial->addItem(info);
}
newSoundMode = SoundMode;
oldSoundMode = SoundMode;
oldSnoopMix = newSnoopMix = onlyMixSnoop;
#ifdef WIN32
Dev->ALSA->setText("WaveOut");
Dev->OSS->setVisible(0);
Dev->PULSE->setVisible(0);
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]);
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->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]);
Dev->Modem_4_Chan->setCurrentIndex(soundChannel[3]);
// Disable "None" option in first modem
QStandardItemModel *model = dynamic_cast<QStandardItemModel *>(Dev->Modem_1_Chan->model());
QStandardItem * item = model->item(0, 0);
item->setEnabled(false);
Dev->useKISSControls->setChecked(useKISSControls);
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->MgmtPort->setText(QString::number(MgmtPort));
// If we are using a user specifed device add it
i = Dev->SixPackSerial->findText(SixPackDevice, Qt::MatchFixedString);
if (i == -1)
{
// Add our device to list
Dev->SixPackSerial->insertItem(0, SixPackDevice);
i = Dev->SixPackSerial->findText(SixPackDevice, Qt::MatchContains);
}
Dev->SixPackSerial->setCurrentIndex(i);
sprintf(valChar, "%d", SixPackPort);
Dev->SixPackTCP->setText(valChar);
Dev->SixPackEnable->setChecked(SixPackEnable);
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);
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);
Dev->PTTPort->addItem("None");
Dev->PTTPort->addItem("CM108");
//#ifdef __ARM_ARCH
Dev->PTTPort->addItem("GPIO");
//#endif
Dev->PTTPort->addItem("HAMLIB");
Dev->PTTPort->addItem("FLRIG");
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 || oldSnoopMix != newSnoopMix)
{
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;
onlyMixSnoop = newSnoopMix;
saveSettings();
Closing = 1;
return;
}
if (oldSoundMode == 0)
Dev->ALSA->setChecked(1);
else if (oldSoundMode == 1)
Dev->OSS->setChecked(1);
else if (oldSoundMode == 2)
Dev->PULSE->setChecked(1);
else if (oldSoundMode == 3)
Dev->UDP->setChecked(1);
QMessageBox::about(this, tr("Info"),
tr("<p align = 'center'>Changes not saved</p>"));
return;
}
if (strcmp(CaptureDevice, Q.toString().toUtf8()) != 0)
{
strcpy(CaptureDevice, Q.toString().toUtf8());
cardChanged = 1;
}
if (onlyMixSnoop != Dev->onlyMixSnoop->isChecked())
{
onlyMixSnoop = Dev->onlyMixSnoop->isChecked();
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();
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();
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;
useKISSControls = Dev->useKISSControls->isChecked();
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->MgmtPort->text();
MgmtPort = Q.toInt();
Q = Dev->SixPackSerial->currentText();
char temp[256];
strcpy(temp, Q.toString().toUtf8());
if (strlen(temp))
strcpy(SixPackDevice, temp);
Q = Dev->SixPackTCP->text();
SixPackPort = Q.toInt();
SixPackEnable = Dev->SixPackEnable->isChecked();
Q = Dev->PTTPort->currentText();
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());
}
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();
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()
{
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)
{
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.Cal1500, 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);
}
}
emit t->setLevelImage();
/// 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));
emit t->setWaterfallImage();
}
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;
if (inWaterfall)
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
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::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)
{
if (e->type() == QEvent::WindowStateChange)
{
QWindowStateChangeEvent* ev = static_cast<QWindowStateChangeEvent*>(e);
qDebug() << windowState();
if (!(ev->oldState() & Qt::WindowMinimized) && windowState() & Qt::WindowMinimized)
{
if (trayIcon)
setVisible(false);
}
// if (!(ev->oldState() != Qt::WindowNoState) && windowState() == Qt::WindowNoState)
// {
// QMessageBox::information(this, "", "Window has been restored");
// }
}
QWidget::changeEvent(e);
}
#include <QCloseEvent>
void QtSoundModem::closeEvent(QCloseEvent *event)
{
UNUSED(event);
QSettings mysettings("QtSoundModem.ini", QSettings::IniFormat);
mysettings.setValue("geometry", QWidget::saveGeometry());
mysettings.setValue("windowState", saveState());
Closing = TRUE;
qDebug() << "Closing";
QThread::msleep(100);
}
QtSoundModem::~QtSoundModem()
{
qDebug() << "Saving Settings";
closeTraceLog();
QSettings mysettings("QtSoundModem.ini", QSettings::IniFormat);
mysettings.setValue("geometry", saveGeometry());
mysettings.setValue("windowState", saveState());
saveSettings();
Closing = TRUE;
qDebug() << "Closing";
QThread::msleep(100);
}
extern "C" void QSleep(int ms)
{
QThread::msleep(ms);
}
int upd_time = 30;
void QtSoundModem::show_grid()
{
// This refeshes the session list
int snd_ch, i, num_rows, row_idx;
QTableWidgetItem *item;
const char * msg = "";
int speed_tx, speed_rx;
if (grid_time < 10)
{
grid_time++;
return;
}
grid_time = 0;
//label7.Caption = inttostr(stat_r_mem); mem_arq
num_rows = 0;
row_idx = 0;
for (snd_ch = 0; snd_ch < 4; snd_ch++)
{
for (i = 0; i < port_num; i++)
{
if (AX25Port[snd_ch][i].status != STAT_NO_LINK)
num_rows++;
}
}
if (num_rows == 0)
{
sessionTable->clearContents();
sessionTable->setRowCount(0);
sessionTable->setRowCount(1);
}
else
sessionTable->setRowCount(num_rows);
for (snd_ch = 0; snd_ch < 4; snd_ch++)
{
for (i = 0; i < port_num; i++)
{
if (AX25Port[snd_ch][i].status != STAT_NO_LINK)
{
switch (AX25Port[snd_ch][i].status)
{
case STAT_NO_LINK:
msg = "No link";
break;
case STAT_LINK:
msg = "Link";
break;
case STAT_CHK_LINK:
msg = "Chk link";
break;
case STAT_WAIT_ANS:
msg = "Wait ack";
break;
case STAT_TRY_LINK:
msg = "Try link";
break;
case STAT_TRY_UNLINK:
msg = "Try unlink";
}
item = new QTableWidgetItem((char *)AX25Port[snd_ch][i].mycall);
sessionTable->setItem(row_idx, 0, item);
item = new QTableWidgetItem(AX25Port[snd_ch][i].kind);
sessionTable->setItem(row_idx, 11, item);
item = new QTableWidgetItem((char *)AX25Port[snd_ch][i].corrcall);
sessionTable->setItem(row_idx, 1, item);
item = new QTableWidgetItem(msg);
sessionTable->setItem(row_idx, 2, item);
item = new QTableWidgetItem(QString::number(AX25Port[snd_ch][i].info.stat_s_pkt));
sessionTable->setItem(row_idx, 3, item);
item = new QTableWidgetItem(QString::number(AX25Port[snd_ch][i].info.stat_s_byte));
sessionTable->setItem(row_idx, 4, item);
item = new QTableWidgetItem(QString::number(AX25Port[snd_ch][i].info.stat_r_pkt));
sessionTable->setItem(row_idx, 5, item);
item = new QTableWidgetItem(QString::number(AX25Port[snd_ch][i].info.stat_r_byte));
sessionTable->setItem(row_idx, 6, item);
item = new QTableWidgetItem(QString::number(AX25Port[snd_ch][i].info.stat_r_fc));
sessionTable->setItem(row_idx, 7, item);
item = new QTableWidgetItem(QString::number(AX25Port[snd_ch][i].info.stat_fec_count));
sessionTable->setItem(row_idx, 8, item);
if (grid_timer != upd_time)
grid_timer++;
else
{
grid_timer = 0;
speed_tx = round(abs(AX25Port[snd_ch][i].info.stat_s_byte - AX25Port[snd_ch][i].info.stat_l_s_byte) / upd_time);
speed_rx = round(abs(AX25Port[snd_ch][i].info.stat_r_byte - AX25Port[snd_ch][i].info.stat_l_r_byte) / upd_time);
item = new QTableWidgetItem(QString::number(speed_tx));
sessionTable->setItem(row_idx, 9, item);
item = new QTableWidgetItem(QString::number(speed_rx));
sessionTable->setItem(row_idx, 10, item);
AX25Port[snd_ch][i].info.stat_l_r_byte = AX25Port[snd_ch][i].info.stat_r_byte;
AX25Port[snd_ch][i].info.stat_l_s_byte = AX25Port[snd_ch][i].info.stat_s_byte;
}
row_idx++;
}
}
}
}
// "Copy on Select" Code
void QtSoundModem::onTEselectionChanged()
{
QTextEdit * x = static_cast<QTextEdit*>(QObject::sender());
x->copy();
}
#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 Constellation 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)
{
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)
{
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);
writeTraceLog(Msg);
}
// 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();
}
// KISS Serial Port code - mainly for 6Pack but should work with KISS as well
// Serial Read needs to block and signal the main thread whenever a character is received. TX can probably be uncontrolled
void serialThread::startSlave(const QString &portName, int waitTimeout, const QString &response)
{
QMutexLocker locker(&mutex);
this->portName = portName;
this->waitTimeout = waitTimeout;
this->response = response;
if (!isRunning())
start();
}
void serialThread::run()
{
QSerialPort serial;
bool currentPortNameChanged = false;
mutex.lock();
QString currentPortName;
if (currentPortName != portName) {
currentPortName = portName;
currentPortNameChanged = true;
}
int currentWaitTimeout = waitTimeout;
QString currentRespone = response;
mutex.unlock();
if (currentPortName.isEmpty())
{
Debugprintf("Port not set");
return;
}
serial.setPortName(currentPortName);
if (!serial.open(QIODevice::ReadWrite))
{
Debugprintf("Can't open %s, error code %d", portName, serial.error());
return;
}
while (1)
{
if (serial.waitForReadyRead(currentWaitTimeout))
{
// read request
QByteArray requestData = serial.readAll();
while (serial.waitForReadyRead(10))
requestData += serial.readAll();
// Pass data to 6pack handler
emit this->request(requestData);
}
else {
Debugprintf("Serial read request timeout");
}
}
}
void Process6PackData(unsigned char * Bytes, int Len);
void QtSoundModem::showRequest(QByteArray Data)
{
Process6PackData((unsigned char *)Data.data(), Data.length());
}