#include "spectrumscope.h" #include "logcategories.h" spectrumScope::spectrumScope(QWidget *parent) : QWidget{parent} { QMutexLocker locker(&mutex); spectrum = new QCustomPlot(); layout = new QVBoxLayout(this); splitter = new QSplitter(this); layout->addWidget(splitter); splitter->setOrientation(Qt::Vertical); spectrum = new QCustomPlot(); waterfall = new QCustomPlot(); splitter->addWidget(spectrum); splitter->addWidget(waterfall); splitter->setHandleWidth(5); spectrum->axisRect()->setMargins(QMargins(0,0,0,0)); waterfall->axisRect()->setMargins(QMargins(0,0,0,0)); // Spectrum Plot setup passbandIndicator = new QCPItemRect(spectrum); passbandIndicator->setAntialiased(true); passbandIndicator->setPen(QPen(Qt::red)); passbandIndicator->setBrush(QBrush(Qt::red)); passbandIndicator->setSelectable(true); pbtIndicator = new QCPItemRect(spectrum); pbtIndicator->setAntialiased(true); pbtIndicator->setPen(QPen(Qt::red)); pbtIndicator->setBrush(QBrush(Qt::red)); pbtIndicator->setSelectable(true); pbtIndicator->setVisible(false); freqIndicatorLine = new QCPItemLine(spectrum); freqIndicatorLine->setAntialiased(true); freqIndicatorLine->setPen(QPen(Qt::blue)); oorIndicator = new QCPItemText(spectrum); oorIndicator->setVisible(false); oorIndicator->setAntialiased(true); oorIndicator->setPen(QPen(Qt::red)); oorIndicator->setBrush(QBrush(Qt::red)); oorIndicator->setFont(QFont(font().family(), 14)); oorIndicator->setPositionAlignment(Qt::AlignVCenter | Qt::AlignHCenter); oorIndicator->position->setType(QCPItemPosition::ptAxisRectRatio); // Positioned relative to the current plot rect oorIndicator->setText("SCOPE OUT OF RANGE"); oorIndicator->position->setCoords(0.5f,0.5f); ovfIndicator = new QCPItemText(spectrum); ovfIndicator->setVisible(false); ovfIndicator->setAntialiased(true); ovfIndicator->setPen(QPen(Qt::red)); ovfIndicator->setColor(Qt::red); ovfIndicator->setFont(QFont(font().family(), 10)); ovfIndicator->setPositionAlignment(Qt::AlignLeft | Qt::AlignTop); ovfIndicator->position->setType(QCPItemPosition::ptAxisRectRatio); // Positioned relative to the current plot rect ovfIndicator->setText(" OVF "); ovfIndicator->position->setCoords(0.01f,0.0f); spectrum->addGraph(); // primary spectrum->addGraph(0, 0); // secondary, peaks, same axis as first. spectrum->addLayer( "Top Layer", spectrum->layer("main")); spectrum->graph(0)->setLayer("Top Layer"); QColor color(20+200/4.0*1,70*(1.6-1/4.0), 150, 150); spectrum->graph(1)->setLineStyle(QCPGraph::lsLine); spectrum->graph(1)->setPen(QPen(color.lighter(200))); spectrum->graph(1)->setBrush(QBrush(color)); freqIndicatorLine->start->setCoords(0.5, 0); freqIndicatorLine->end->setCoords(0.5, 160); passbandIndicator->topLeft->setCoords(0.5, 0); passbandIndicator->bottomRight->setCoords(0.5, 160); pbtIndicator->topLeft->setCoords(0.5, 0); pbtIndicator->bottomRight->setCoords(0.5, 160); // Waterfall setup waterfall->addGraph(); colorMap = new QCPColorMap(waterfall->xAxis, waterfall->yAxis); colorMapData = NULL; #if QCUSTOMPLOT_VERSION < 0x020001 this->addPlottable(colorMap); #endif } bool spectrumScope::prepareWf(uint wf) { QMutexLocker locker(&mutex); bool ret=true; this->wfLength = wf; this->wfLengthMax = 1024; // Initialize before use! QByteArray empty((int)spectWidth, '\x01'); spectrumPeaks = QByteArray( (int)spectWidth, '\x01' ); if((unsigned int)wfimage.size() < wfLengthMax) { unsigned int i=0; unsigned int oldSize = wfimage.size(); for(i=oldSize; i<(wfLengthMax); i++) { wfimage.append(empty); } } wfimage.squeeze(); //colorMap->clearData(); colorMap->data()->clear(); colorMap->data()->setValueRange(QCPRange(0, wfLength-1)); colorMap->data()->setKeyRange(QCPRange(0, spectWidth-1)); colorMap->setDataRange(QCPRange(plotFloor, plotCeiling)); colorMap->setGradient(static_cast(currentTheme)); if(colorMapData != Q_NULLPTR) { delete colorMapData; } colorMapData = new QCPColorMapData(spectWidth, wfLength, QCPRange(0, spectWidth-1), QCPRange(0, wfLength-1)); colorMap->setData(colorMapData); waterfall->yAxis->setRangeReversed(true); waterfall->xAxis->setVisible(false); scopePrepared = true; return ret; } void spectrumScope::setRange(int floor, int ceiling) { maxAmp = ceiling; if (spectrum != Q_NULLPTR) spectrum->yAxis->setRange(QCPRange(floor, ceiling)); if (colorMap != Q_NULLPTR) colorMap->setDataRange(QCPRange(floor,ceiling)); } void spectrumScope::colorPreset(colorPrefsType *cp) { if (cp == Q_NULLPTR) { return; } colors = *cp; spectrum->setBackground(cp->plotBackground); spectrum->xAxis->grid()->setPen(cp->gridColor); spectrum->yAxis->grid()->setPen(cp->gridColor); spectrum->legend->setTextColor(cp->textColor); spectrum->legend->setBorderPen(cp->gridColor); spectrum->legend->setBrush(cp->gridColor); spectrum->xAxis->setTickLabelColor(cp->textColor); spectrum->xAxis->setLabelColor(cp->gridColor); spectrum->yAxis->setTickLabelColor(cp->textColor); spectrum->yAxis->setLabelColor(cp->gridColor); spectrum->xAxis->setBasePen(cp->axisColor); spectrum->xAxis->setTickPen(cp->axisColor); spectrum->yAxis->setBasePen(cp->axisColor); spectrum->yAxis->setTickPen(cp->axisColor); freqIndicatorLine->setPen(QPen(cp->tuningLine)); passbandIndicator->setPen(QPen(cp->passband)); passbandIndicator->setBrush(QBrush(cp->passband)); pbtIndicator->setPen(QPen(cp->pbt)); pbtIndicator->setBrush(QBrush(cp->pbt)); spectrum->graph(0)->setPen(QPen(cp->spectrumLine)); spectrum->graph(0)->setBrush(QBrush(cp->spectrumFill)); spectrum->graph(1)->setPen(QPen(cp->underlayLine)); spectrum->graph(1)->setBrush(QBrush(cp->underlayFill)); waterfall->yAxis->setBasePen(cp->wfAxis); waterfall->yAxis->setTickPen(cp->wfAxis); waterfall->xAxis->setBasePen(cp->wfAxis); waterfall->xAxis->setTickPen(cp->wfAxis); waterfall->xAxis->setLabelColor(cp->wfGrid); waterfall->yAxis->setLabelColor(cp->wfGrid); waterfall->xAxis->setTickLabelColor(cp->wfText); waterfall->yAxis->setTickLabelColor(cp->wfText); waterfall->setBackground(cp->wfBackground); } bool spectrumScope::update(scopeData data) { if (!scopePrepared ) { return false; } bool updateRange = false; if (data.startFreq != lowerFreq || data.endFreq != upperFreq) { if(underlayMode == underlayPeakHold) { // TODO: create non-button function to do this // This will break if the button is ever moved or renamed. clearPeaks(); } else { plasmaPrepared = false; preparePlasma(); } // Inform other threads (cluster) that the frequency range has changed. //emit frequencyRange(data.startFreq, data.endFreq); } lowerFreq = data.startFreq; upperFreq = data.endFreq; //qInfo(logSystem()) << "start: " << data.startFreq << " end: " << data.endFreq; quint16 specLen = data.data.length(); QVector x(spectWidth), y(spectWidth), y2(spectWidth); for(int i=0; i < spectWidth; i++) { x[i] = (i * (data.endFreq-data.startFreq)/spectWidth) + data.startFreq; } for(int i=0; i< specLen; i++) { y[i] = (unsigned char)data.data.at(i); if(underlayMode == underlayPeakHold) { if((unsigned char)data.data.at(i) > (unsigned char)spectrumPeaks.at(i)) { spectrumPeaks[i] = data.data.at(i); } y2[i] = (unsigned char)spectrumPeaks.at(i); } } plasmaMutex.lock(); spectrumPlasma.push_front(data.data); if(spectrumPlasma.size() > (int)spectrumPlasmaSize) { spectrumPlasma.pop_back(); } plasmaMutex.unlock(); QMutexLocker locker(&mutex); if ((plotFloor != oldPlotFloor) || (plotCeiling != oldPlotCeiling)){ updateRange = true; } #if QCUSTOMPLOT_VERSION < 0x020000 spectrum->graph(0)->setData(x, y); #else spectrum->graph(0)->setData(x, y, true); #endif if((freq.MHzDouble < data.endFreq) && (freq.MHzDouble > data.startFreq)) { freqIndicatorLine->start->setCoords(freq.MHzDouble, 0); freqIndicatorLine->end->setCoords(freq.MHzDouble, maxAmp); double pbStart = 0.0; double pbEnd = 0.0; switch (rigMode.mk) { case modeLSB: case modeRTTY: case modePSK_R: pbStart = freq.MHzDouble - passbandCenterFrequency - (passbandWidth / 2); pbEnd = freq.MHzDouble - passbandCenterFrequency + (passbandWidth / 2); break; case modeCW: if (passbandWidth < 0.0006) { pbStart = freq.MHzDouble - (passbandWidth / 2); pbEnd = freq.MHzDouble + (passbandWidth / 2); } else { pbStart = freq.MHzDouble + passbandCenterFrequency - passbandWidth; pbEnd = freq.MHzDouble + passbandCenterFrequency; } break; case modeCW_R: if (passbandWidth < 0.0006) { pbStart = freq.MHzDouble - (passbandWidth / 2); pbEnd = freq.MHzDouble + (passbandWidth / 2); } else { pbStart = freq.MHzDouble - passbandCenterFrequency; pbEnd = freq.MHzDouble + passbandWidth - passbandCenterFrequency; } break; default: pbStart = freq.MHzDouble + passbandCenterFrequency - (passbandWidth / 2); pbEnd = freq.MHzDouble + passbandCenterFrequency + (passbandWidth / 2); break; } passbandIndicator->topLeft->setCoords(pbStart, 0); passbandIndicator->bottomRight->setCoords(pbEnd, maxAmp); if ((rigMode.mk == modeCW || rigMode.mk == modeCW_R) && passbandWidth > 0.0006) { pbtDefault = round((passbandWidth - (cwPitch / 1000000.0)) * 200000.0) / 200000.0; } else { pbtDefault = 0.0; } if ((PBTInner - pbtDefault || PBTOuter - pbtDefault) && passbandAction != passbandResizing && rigMode.mk != modeFM) { pbtIndicator->setVisible(true); } else { pbtIndicator->setVisible(false); } /* pbtIndicator displays the intersection between PBTInner and PBTOuter */ if (rigMode.mk == modeLSB || rigMode.mk == modeCW || rigMode.mk == modeRTTY) { pbtIndicator->topLeft->setCoords(qMax(pbStart - (PBTInner / 2) + (pbtDefault / 2), pbStart - (PBTOuter / 2) + (pbtDefault / 2)), 0); pbtIndicator->bottomRight->setCoords(qMin(pbStart - (PBTInner / 2) + (pbtDefault / 2) + passbandWidth, pbStart - (PBTOuter / 2) + (pbtDefault / 2) + passbandWidth), maxAmp); } else { pbtIndicator->topLeft->setCoords(qMax(pbStart + (PBTInner / 2) - (pbtDefault / 2), pbStart + (PBTOuter / 2) - (pbtDefault / 2)), 0); pbtIndicator->bottomRight->setCoords(qMin(pbStart + (PBTInner / 2) - (pbtDefault / 2) + passbandWidth, pbStart + (PBTOuter / 2) - (pbtDefault / 2) + passbandWidth), maxAmp); } //qDebug() << "Default" << pbtDefault << "Inner" << PBTInner << "Outer" << PBTOuter << "Pass" << passbandWidth << "Center" << passbandCenterFrequency << "CW" << cwPitch; } #if QCUSTOMPLOT_VERSION < 0x020000 if (underlayMode == underlayPeakHold) { spectrum->graph(1)->setData(x, y2); // peaks } else if (underlayMode != underlayNone) { computePlasma(); spectrum->graph(1)->setData(x, spectrumPlasmaLine); } else { spectrum->graph(1)->setData(x, y2); // peaks, but probably cleared out } #else if (underlayMode == underlayPeakHold) { spectrum->graph(1)->setData(x, y2, true); // peaks } else if (underlayMode != underlayNone) { computePlasma(); spectrum->graph(1)->setData(x, spectrumPlasmaLine, true); } else { spectrum->graph(1)->setData(x, y2, true); // peaks, but probably cleared out } #endif if(updateRange) spectrum->yAxis->setRange(plotFloor, plotCeiling); spectrum->xAxis->setRange(data.startFreq, data.endFreq); spectrum->replot(); if(specLen == spectWidth) { wfimage.prepend(data.data); wfimage.pop_back(); QByteArray wfRow; // Waterfall: for(int row = 0; row < wfLength; row++) { wfRow = wfimage.at(row); for(int col = 0; col < spectWidth; col++) { colorMap->data()->setCell( col, row, (unsigned char)wfRow.at(col)); } } if(updateRange) { colorMap->setDataRange(QCPRange(wfFloor, wfCeiling)); } waterfall->yAxis->setRange(0,wfLength - 1); waterfall->xAxis->setRange(0, spectWidth-1); waterfall->replot(); /* #if defined (USB_CONTROLLER) // Send to USB Controllers if requested auto i = usbDevices.begin(); while (i != usbDevices.end()) { if (i.value().connected && i.value().type.model == usbDeviceType::StreamDeckPlus && i.value().lcd == funcLCDWaterfall ) { lcdImage = waterfall->toPixmap().toImage(); emit sendControllerRequest(&i.value(), usbFeatureType::featureLCD, 0, "", &lcdImage); } else if (i.value().connected && i.value().type.model == usbDeviceType::StreamDeckPlus && i.value().lcd == funcLCDSpectrum) { lcdImage = spectrum->toPixmap().toImage(); emit sendControllerRequest(&i.value(), usbFeatureType::featureLCD, 0, "", &lcdImage); } ++i; } #endif */ } oldPlotFloor = plotFloor; oldPlotCeiling = plotCeiling; if (data.oor && !oorIndicator->visible()) { oorIndicator->setVisible(true); //oorIndicator->position->setCoords((oldLowerFreq+oldUpperFreq)/2,ui->topLevelSlider->value() - 20); qInfo(logSystem()) << "Scope out of range"; } else if (!data.oor && oorIndicator->visible()) { oorIndicator->setVisible(false); } //ovfIndicator->setVisible(true); return true; } // Plasma functions void spectrumScope::preparePlasma() { QMutexLocker locker(&plasmaMutex); if(plasmaPrepared) return; if(spectrumPlasmaSize == 0) spectrumPlasmaSize = 128; spectrumPlasma.clear(); spectrumPlasma.squeeze(); plasmaPrepared = true; } void spectrumScope::resizePlasmaBuffer(int size) { QMutexLocker locker(&plasmaMutex); QByteArray empty((int)spectWidth, '\x01'); int oldSize = spectrumPlasma.size(); if(oldSize < size) { spectrumPlasma.resize(size); for(int p=oldSize; p < size; p++) { spectrumPlasma.append(empty); } } else if (oldSize > size){ for(int p = oldSize; p > size; p--) { spectrumPlasma.pop_back(); } } spectrumPlasma.squeeze(); } void spectrumScope::clearPeaks() { spectrumPeaks = QByteArray( (int)spectWidth, '\x01' ); clearPlasma(); } void spectrumScope::clearPlasma() { QMutexLocker locker(&plasmaMutex); QByteArray empty((int)spectWidth, '\x01'); int pSize = spectrumPlasma.size(); for(int i=0; i < pSize; i++) { spectrumPlasma[i] = empty; } } void spectrumScope::computePlasma() { QMutexLocker locker(&plasmaMutex); spectrumPlasmaLine.clear(); spectrumPlasmaLine.resize(spectWidth); int specPlasmaSize = spectrumPlasma.size(); if(underlayMode == underlayAverageBuffer) { for(int col=0; col < spectWidth; col++) { for(int pos=0; pos < specPlasmaSize; pos++) { spectrumPlasmaLine[col] += (unsigned char)spectrumPlasma[pos][col]; } spectrumPlasmaLine[col] = spectrumPlasmaLine[col] / specPlasmaSize; } } else if (underlayMode == underlayPeakBuffer){ // peak mode, running peak display for(int col=0; col < spectWidth; col++) { for(int pos=0; pos < specPlasmaSize; pos++) { if((double)((unsigned char)spectrumPlasma[pos][col]) > spectrumPlasmaLine[col]) spectrumPlasmaLine[col] = (unsigned char)spectrumPlasma[pos][col]; } } } }