From 5ceddcfa92eb381704431b1223aee82fb4d44d64 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Sun, 16 Aug 2015 22:31:14 -0400 Subject: [PATCH 01/19] Activate demodulator from session if there's only one --- src/AppFrame.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index df7e79f..4ceeb81 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -843,6 +843,9 @@ bool AppFrame::loadSession(std::string fileName) { DataNode *demodulators = l.rootNode()->getNext("demodulators"); + int numDemodulators = 0; + DemodulatorInstance *loadedDemod = NULL; + while (demodulators->hasAnother("demodulator")) { DataNode *demod = demodulators->getNext("demodulator"); @@ -860,6 +863,8 @@ bool AppFrame::loadSession(std::string fileName) { float gain = demod->hasAnother("gain") ? (float) *demod->getNext("gain") : 1.0; DemodulatorInstance *newDemod = wxGetApp().getDemodMgr().newThread(); + loadedDemod = newDemod; + numDemodulators++; newDemod->setDemodulatorType(type); newDemod->setBandwidth(bandwidth); newDemod->setFrequency(freq); @@ -897,6 +902,13 @@ bool AppFrame::loadSession(std::string fileName) { std::cout << "\t\tStereo: " << (stereo ? "true" : "false") << std::endl; std::cout << "\t\tOutput Device: " << output_device << std::endl; } + + if ((numDemodulators == 1) && loadedDemod) { + loadedDemod->setActive(true); + loadedDemod->setFollow(true); + loadedDemod->setTracking(true); + wxGetApp().getDemodMgr().setActiveDemodulator(loadedDemod); + } } catch (DataInvalidChildException &e) { std::cout << e.what() << std::endl; return false; From 9de1abd539ce0f54bb63707eadd5612377f5b0f1 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Mon, 17 Aug 2015 00:59:38 -0400 Subject: [PATCH 02/19] Add mute button and 'M' to toggle mute on waterfall hover --- src/AppFrame.cpp | 35 ++++++++++++++++++++++++++++++- src/AppFrame.h | 1 + src/demod/DemodulatorInstance.cpp | 10 +++++++++ src/demod/DemodulatorInstance.h | 4 ++++ src/demod/DemodulatorThread.cpp | 15 ++++++++++++- src/demod/DemodulatorThread.h | 4 ++++ src/visual/ModeSelectorCanvas.cpp | 26 +++++++++++++++++++++-- src/visual/ModeSelectorCanvas.h | 7 +++++++ src/visual/PrimaryGLContext.cpp | 25 +++++++++++++++++----- src/visual/WaterfallCanvas.cpp | 6 ++++++ 10 files changed, 124 insertions(+), 9 deletions(-) diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index 4ceeb81..adafee8 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -103,12 +103,25 @@ AppFrame::AppFrame() : demodTray->AddSpacer(1); + wxBoxSizer *demodGainTray = new wxBoxSizer(wxVERTICAL); + demodGainMeter = new MeterCanvas(this, attribList); demodGainMeter->setMax(2.0); demodGainMeter->setHelpTip("Current Demodulator Gain Level. Click / Drag to set Gain level."); demodGainMeter->setShowUserInput(false); - demodTray->Add(demodGainMeter, 1, wxEXPAND | wxALL, 0); + demodGainTray->Add(demodGainMeter, 8, wxEXPAND | wxALL, 0); + demodGainTray->AddSpacer(1); + + demodMuteButton = new ModeSelectorCanvas(this, attribList); + demodMuteButton->addChoice(1, "M"); + demodMuteButton->setHelpTip("Demodulator Mute Toggle"); + demodMuteButton->setToggleMode(true); + + demodGainTray->Add(demodMuteButton, 1, wxEXPAND | wxALL, 0); + + demodTray->Add(demodGainTray, 1, wxEXPAND | wxALL, 0); + vbox->Add(demodTray, 12, wxEXPAND | wxALL, 0); vbox->AddSpacer(1); @@ -663,6 +676,7 @@ void AppFrame::OnIdle(wxIdleEvent& event) { outputDeviceMenuItems[outputDevice]->Check(true); int dType = demod->getDemodulatorType(); demodModeSelector->setSelection(dType); + demodMuteButton->setSelection(demod->isMuted()?1:-1); } if (demodWaterfallCanvas->getDragState() == WaterfallCanvas::WF_DRAG_NONE) { long long centerFreq = demod->getFrequency(); @@ -694,6 +708,22 @@ void AppFrame::OnIdle(wxIdleEvent& event) { demod->setDemodulatorType(dSelection); } + int muteMode = demodMuteButton->getSelection(); + if (demodMuteButton->modeChanged()) { + if (demod->isMuted() && muteMode == -1) { + demod->setMuted(false); + } else if (!demod->isMuted() && muteMode == 1) { + demod->setMuted(true); + } + demodMuteButton->clearModeChanged(); + } else { + if (demod->isMuted() && muteMode == -1) { + demodMuteButton->setSelection(1); + } else if (!demod->isMuted() && muteMode == 1) { + demodMuteButton->setSelection(-1); + } + } + demodWaterfallCanvas->setBandwidth(demodBw); demodSpectrumCanvas->setBandwidth(demodBw); } @@ -812,6 +842,7 @@ void AppFrame::saveSession(std::string fileName) { *demod->newChild("stereo") = (*instance_i)->isStereo() ? 1 : 0; *demod->newChild("output_device") = outputDevices[(*instance_i)->getOutputDevice()].name; *demod->newChild("gain") = (*instance_i)->getGain(); + *demod->newChild("muted") = (*instance_i)->isMuted() ? 1 : 0; } s.SaveToFileXML(fileName); @@ -859,6 +890,7 @@ bool AppFrame::loadSession(std::string fileName) { float squelch_level = demod->hasAnother("squelch_level") ? (float) *demod->getNext("squelch_level") : 0; int squelch_enabled = demod->hasAnother("squelch_enabled") ? (int) *demod->getNext("squelch_enabled") : 0; int stereo = demod->hasAnother("stereo") ? (int) *demod->getNext("stereo") : 0; + int muted = demod->hasAnother("muted") ? (int) *demod->getNext("muted") : 0; std::string output_device = demod->hasAnother("output_device") ? string(*(demod->getNext("output_device"))) : ""; float gain = demod->hasAnother("gain") ? (float) *demod->getNext("gain") : 1.0; @@ -870,6 +902,7 @@ bool AppFrame::loadSession(std::string fileName) { newDemod->setFrequency(freq); newDemod->setGain(gain); newDemod->updateLabel(freq); + newDemod->setMuted(muted?true:false); if (squelch_enabled) { newDemod->setSquelchEnabled(true); newDemod->setSquelchLevel(squelch_level); diff --git a/src/AppFrame.h b/src/AppFrame.h index 42ea5e9..58fb5cc 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -81,6 +81,7 @@ private: // UITestCanvas *testCanvas; MeterCanvas *spectrumAvgMeter; MeterCanvas *waterfallSpeedMeter; + ModeSelectorCanvas *demodMuteButton; DemodulatorInstance *activeDemodulator; diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index d308327..c3440bc 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -10,6 +10,7 @@ DemodulatorInstance::DemodulatorInstance() : active.store(false); squelch.store(false); stereo.store(false); + muted.store(false); tracking.store(false); follow.store(false); currentAudioSampleRate.store(0); @@ -412,6 +413,15 @@ void DemodulatorInstance::setTracking(bool tracking) { this->tracking = tracking; } +bool DemodulatorInstance::isMuted() { + return demodulatorThread->isMuted(); +} + +void DemodulatorInstance::setMuted(bool muted) { + this->muted = muted; + demodulatorThread->setMuted(muted); +} + DemodulatorThreadInputQueue *DemodulatorInstance::getIQInputDataPipe() { return pipeIQInputData; } diff --git a/src/demod/DemodulatorInstance.h b/src/demod/DemodulatorInstance.h index d211746..cc9da67 100644 --- a/src/demod/DemodulatorInstance.h +++ b/src/demod/DemodulatorInstance.h @@ -73,6 +73,9 @@ public: bool isTracking(); void setTracking(bool tracking); + + bool isMuted(); + void setMuted(bool muted); DemodulatorThreadInputQueue *getIQInputDataPipe(); @@ -98,6 +101,7 @@ private: std::atomic_bool active; std::atomic_bool squelch; std::atomic_bool stereo; + std::atomic_bool muted; std::atomic_llong currentFrequency; std::atomic_int currentBandwidth; diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index becc261..485fd54 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -14,6 +14,7 @@ DemodulatorThread::DemodulatorThread() : IOThread(), iqAutoGain(NULL), amOutputCeil(1), amOutputCeilMA(1), amOutputCeilMAA(1), audioSampleRate(0), squelchLevel(0), signalLevel(0), squelchEnabled(false), iqInputQueue(NULL), audioOutputQueue(NULL), audioVisOutputQueue(NULL), threadQueueControl(NULL), threadQueueNotify(NULL) { stereo.store(false); + muted.store(false); agcEnabled.store(false); demodulatorType.store(DEMOD_TYPE_FM); @@ -404,7 +405,11 @@ void DemodulatorThread::run() { } if (ati != NULL) { - audioOutputQueue->push(ati); + if (!muted.load()) { + audioOutputQueue->push(ati); + } else { + ati->setRefCount(0); + } } if (!threadQueueControl->empty()) { @@ -510,6 +515,14 @@ bool DemodulatorThread::isStereo() { return stereo.load(); } +bool DemodulatorThread::isMuted() { + return muted.load(); +} + +void DemodulatorThread::setMuted(bool muted) { + this->muted.store(muted); +} + void DemodulatorThread::setAGC(bool state) { agcEnabled.store(state); } diff --git a/src/demod/DemodulatorThread.h b/src/demod/DemodulatorThread.h index eb179c5..7f7e417 100644 --- a/src/demod/DemodulatorThread.h +++ b/src/demod/DemodulatorThread.h @@ -27,6 +27,9 @@ public: void setAGC(bool state); bool getAGC(); + void setMuted(bool state); + bool isMuted(); + float getSignalLevel(); void setSquelchLevel(float signal_level_in); float getSquelchLevel(); @@ -64,6 +67,7 @@ protected: float amOutputCeilMAA; std::atomic_bool stereo; + std::atomic_bool muted; std::atomic_bool agcEnabled; std::atomic_int demodulatorType; int audioSampleRate; diff --git a/src/visual/ModeSelectorCanvas.cpp b/src/visual/ModeSelectorCanvas.cpp index 5058291..c26de45 100644 --- a/src/visual/ModeSelectorCanvas.cpp +++ b/src/visual/ModeSelectorCanvas.cpp @@ -25,7 +25,7 @@ EVT_ENTER_WINDOW(ModeSelectorCanvas::OnMouseEnterWindow) wxEND_EVENT_TABLE() ModeSelectorCanvas::ModeSelectorCanvas(wxWindow *parent, int *attribList) : -InteractiveCanvas(parent, attribList), numChoices(0), currentSelection(-1) { +InteractiveCanvas(parent, attribList), numChoices(0), currentSelection(-1), toggleMode(false), inputChanged(false) { glContext = new ModeSelectorContext(this, &wxGetApp().GetContext(this)); } @@ -97,10 +97,21 @@ void ModeSelectorCanvas::OnMouseReleased(wxMouseEvent& event) { const wxSize ClientSize = GetClientSize(); + int selectedButton = currentSelection; if (mouseTracker.getOriginDeltaMouseX() < 2.0 / ClientSize.y) { - currentSelection = getHoveredSelection(); + selectedButton = getHoveredSelection(); } + if (toggleMode && (currentSelection == selectedButton)) { + selectedButton = -1; + } + + if (currentSelection != selectedButton) { + inputChanged = true; + } + + currentSelection = selectedButton; + SetCursor (wxCURSOR_ARROW); } @@ -148,4 +159,15 @@ int ModeSelectorCanvas::getSelection() { return selections[currentSelection].value; } +void ModeSelectorCanvas::setToggleMode(bool toggleMode) { + this->toggleMode = toggleMode; +} + +bool ModeSelectorCanvas::modeChanged() { + return inputChanged; +} + +void ModeSelectorCanvas::clearModeChanged() { + inputChanged = false; +} diff --git a/src/visual/ModeSelectorCanvas.h b/src/visual/ModeSelectorCanvas.h index 7dcf12b..68de86e 100644 --- a/src/visual/ModeSelectorCanvas.h +++ b/src/visual/ModeSelectorCanvas.h @@ -35,6 +35,11 @@ public: void setSelection(int value); int getSelection(); + void setToggleMode(bool toggleMode); + + bool modeChanged(); + void clearModeChanged(); + private: void setNumChoices(int numChoices_in); @@ -53,6 +58,8 @@ private: std::string helpTip; int numChoices; int currentSelection; + bool toggleMode; + bool inputChanged; std::vector selections; // wxDECLARE_EVENT_TABLE(); diff --git a/src/visual/PrimaryGLContext.cpp b/src/visual/PrimaryGLContext.cpp index 0ff82dc..ba02874 100644 --- a/src/visual/PrimaryGLContext.cpp +++ b/src/visual/PrimaryGLContext.cpp @@ -94,7 +94,12 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, RGB3f color, lo glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glColor4f(0, 0, 0, 0.35); + if (demod->isMuted()) { + glColor4f(0.8, 0, 0, 0.35); + } else { + glColor4f(0, 0, 0, 0.35); + } + glBegin(GL_QUADS); glVertex3f(uxPos - ofsLeft, hPos + labelHeight, 0.0); glVertex3f(uxPos - ofsLeft, -1.0, 0.0); @@ -128,12 +133,18 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, RGB3f color, lo glColor4f(1.0, 1.0, 1.0, 0.8); + std::string demodLabel = demod->getLabel(); + + if (demod->isMuted()) { + demodLabel.append("[M]"); + } + if (demod->getDemodulatorType() == DEMOD_TYPE_USB) { - GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demod->getLabel(), uxPos, hPos, 16, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER); + GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demodLabel, uxPos, hPos, 16, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER); } else if (demod->getDemodulatorType() == DEMOD_TYPE_LSB) { - GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demod->getLabel(), uxPos, hPos, 16, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER); + GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demodLabel, uxPos, hPos, 16, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER); } else { - GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demod->getLabel(), uxPos, hPos, 16, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER); + GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demodLabel, uxPos, hPos, 16, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER); } glDisable(GL_BLEND); @@ -230,7 +241,11 @@ void PrimaryGLContext::DrawDemod(DemodulatorInstance *demod, RGB3f color, long l glColor3f(0, 0, 0); GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demodStr, 2.0 * (uxPos - 0.5) + xOfs, -1.0 + hPos - yOfs, 16, demodAlign, GLFont::GLFONT_ALIGN_CENTER); - glColor3f(0.8, 0.8, 0.8); + if (demod->isMuted()) { + glColor3f(0.8, 0.2, 0.2); + } else { + glColor3f(0.8, 0.2, 0.2); + } GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demodStr, 2.0 * (uxPos - 0.5), -1.0 + hPos, 16, demodAlign, GLFont::GLFONT_ALIGN_CENTER); glDisable(GL_BLEND); diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index f2d2578..c91fb98 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -322,6 +322,12 @@ void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) { wxGetApp().removeDemodulator(activeDemod); wxGetApp().getDemodMgr().deleteThread(activeDemod); break; + case 'M': + if (!activeDemod) { + break; + } + activeDemod->setMuted(!activeDemod->isMuted()); + break; case 'S': if (!activeDemod) { break; From 58fb313d6ff81282849c0392769ffd1235eb8ca9 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Mon, 17 Aug 2015 01:07:57 -0400 Subject: [PATCH 03/19] Update waterfall helptip --- src/visual/WaterfallCanvas.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index c91fb98..204473f 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -363,18 +363,15 @@ void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) { if (freq < minFreq) { wxGetApp().setFrequency(freq+(wxGetApp().getSampleRate()/2)); - setStatusText("Set center frequency: %s", freq); } if (freq > maxFreq) { wxGetApp().setFrequency(freq-(wxGetApp().getSampleRate()/2)); - setStatusText("Set center frequency: %s", freq); } } else { if (spectrumCanvas) { spectrumCanvas->setCenterFrequency(freq); } wxGetApp().setFrequency(freq); - setStatusText("Set center frequency: %s", freq); } } @@ -412,7 +409,6 @@ void WaterfallCanvas::OnMouseMoved(wxMouseEvent& event) { } demod->setBandwidth(currentBW); - setStatusText("Set demodulator bandwidth: %s", demod->getBandwidth()); } if (dragState == WF_DRAG_FREQUENCY) { @@ -430,8 +426,6 @@ void WaterfallCanvas::OnMouseMoved(wxMouseEvent& event) { currentFreq = demod->getFrequency(); demod->updateLabel(currentFreq); } - - setStatusText("Set demodulator frequency: %s", demod->getFrequency()); } } else if (mouseTracker.mouseRightDown()) { mouseZoom = mouseZoom + ((1.0 - (mouseTracker.getDeltaMouseY() * 4.0)) - mouseZoom) * 0.1; @@ -511,14 +505,14 @@ void WaterfallCanvas::OnMouseMoved(wxMouseEvent& event) { mouseTracker.setVertDragLock(true); mouseTracker.setHorizDragLock(false); - setStatusText("Click and drag to change demodulator bandwidth. SPACE for direct frequency input. D to delete, S for stereo."); + setStatusText("Click and drag to change demodulator bandwidth. SPACE for direct frequency input. M for mute, D to delete, S for stereo."); } else { SetCursor(wxCURSOR_SIZING); nextDragState = WF_DRAG_FREQUENCY; mouseTracker.setVertDragLock(true); mouseTracker.setHorizDragLock(false); - setStatusText("Click and drag to change demodulator frequency; SPACE for direct input. D to delete, S for stereo."); + setStatusText("Click and drag to change demodulator frequency; SPACE for direct input. M for mute, D to delete, S for stereo."); } } else { SetCursor(wxCURSOR_CROSS); From 4df66ea0e32bb5edba558dc4007846956e7ff3d4 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Mon, 17 Aug 2015 01:11:42 -0400 Subject: [PATCH 04/19] Move [M] to avoid confusion with Mhz --- src/visual/PrimaryGLContext.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/visual/PrimaryGLContext.cpp b/src/visual/PrimaryGLContext.cpp index ba02874..15d96f8 100644 --- a/src/visual/PrimaryGLContext.cpp +++ b/src/visual/PrimaryGLContext.cpp @@ -136,7 +136,7 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, RGB3f color, lo std::string demodLabel = demod->getLabel(); if (demod->isMuted()) { - demodLabel.append("[M]"); + demodLabel = std::string("[M] ") + demodLabel; } if (demod->getDemodulatorType() == DEMOD_TYPE_USB) { @@ -241,11 +241,7 @@ void PrimaryGLContext::DrawDemod(DemodulatorInstance *demod, RGB3f color, long l glColor3f(0, 0, 0); GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demodStr, 2.0 * (uxPos - 0.5) + xOfs, -1.0 + hPos - yOfs, 16, demodAlign, GLFont::GLFONT_ALIGN_CENTER); - if (demod->isMuted()) { - glColor3f(0.8, 0.2, 0.2); - } else { - glColor3f(0.8, 0.2, 0.2); - } + glColor3f(1, 1, 1); GLFont::getFont(GLFont::GLFONT_SIZE16).drawString(demodStr, 2.0 * (uxPos - 0.5), -1.0 + hPos, 16, demodAlign, GLFont::GLFONT_ALIGN_CENTER); glDisable(GL_BLEND); From 62858abbf582160d422f35b8c40c2e7d15548a73 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Mon, 17 Aug 2015 21:52:38 -0400 Subject: [PATCH 05/19] Spectrum peak and floor dB are now displayed --- src/AppFrame.cpp | 1 + src/panel/SpectrumPanel.cpp | 59 ++++- src/panel/SpectrumPanel.h | 11 +- src/panel/WaterfallPanel.cpp | 2 +- src/process/SpectrumVisualProcessor.cpp | 5 +- src/ui/GLPanel.cpp | 97 ++++---- src/ui/GLPanel.h | 15 +- src/ui/UITestContext.cpp | 12 +- src/visual/ColorTheme.cpp | 280 ++++++++++++------------ src/visual/ColorTheme.h | 61 +++--- src/visual/PrimaryGLContext.cpp | 8 +- src/visual/PrimaryGLContext.h | 8 +- src/visual/SpectrumCanvas.cpp | 9 + src/visual/SpectrumCanvas.h | 2 + src/visual/TuningCanvas.cpp | 8 +- src/visual/TuningContext.cpp | 4 +- src/visual/TuningContext.h | 4 +- 17 files changed, 336 insertions(+), 250 deletions(-) diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index adafee8..9091e36 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -128,6 +128,7 @@ AppFrame::AppFrame() : wxBoxSizer *spectrumSizer = new wxBoxSizer(wxHORIZONTAL); wxGetApp().getSpectrumProcessor()->setup(2048); spectrumCanvas = new SpectrumCanvas(this, attribList); + spectrumCanvas->setShowDb(true); wxGetApp().getSpectrumProcessor()->attachOutput(spectrumCanvas->getVisualDataQueue()); spectrumAvgMeter = new MeterCanvas(this, attribList); diff --git a/src/panel/SpectrumPanel.cpp b/src/panel/SpectrumPanel.cpp index 81f62f6..c615490 100644 --- a/src/panel/SpectrumPanel.cpp +++ b/src/panel/SpectrumPanel.cpp @@ -2,15 +2,24 @@ #include #include +#include #include "ColorTheme.h" -SpectrumPanel::SpectrumPanel() : floorValue(0), ceilValue(1) { +SpectrumPanel::SpectrumPanel() : floorValue(0), ceilValue(1), showDb(false) { setFill(GLPANEL_FILL_GRAD_Y); setFillColor(ThemeMgr::mgr.currentTheme->fftBackground * 2.0, ThemeMgr::mgr.currentTheme->fftBackground); + + dbPanelCeil.setMarginPx(0); + dbPanelCeil.setFill(GLPanel::GLPANEL_FILL_GRAD_X); + dbPanelCeil.setFillColor(RGBA4f(0.2,0.2,0.2,5.0), RGBA4f(0.2,0.2,0.2,0.0)); + + dbPanelFloor.setMarginPx(0); + dbPanelFloor.setFill(GLPanel::GLPANEL_FILL_GRAD_X); + dbPanelFloor.setFillColor(RGBA4f(0.2,0.2,0.2,5.), RGBA4f(0.2,0.2,0.2,0.0)); } -float SpectrumPanel::getFloorValue() const { +float SpectrumPanel::getFloorValue() { return floorValue; } @@ -18,7 +27,7 @@ void SpectrumPanel::setFloorValue(float floorValue) { this->floorValue = floorValue; } -float SpectrumPanel::getCeilValue() const { +float SpectrumPanel::getCeilValue() { return ceilValue; } @@ -42,6 +51,23 @@ long long SpectrumPanel::getBandwidth() { return bandwidth; } +void SpectrumPanel::setShowDb(bool showDb) { + this->showDb = showDb; + if (showDb) { + addChild(&dbPanelCeil); + addChild(&dbPanelFloor); + } else { + removeChild(&dbPanelCeil); + removeChild(&dbPanelFloor); + } + +} + +bool SpectrumPanel::getShowDb() { + return showDb; +} + + void SpectrumPanel::setPoints(std::vector &points) { this->points.assign(points.begin(), points.end()); } @@ -95,14 +121,14 @@ void SpectrumPanel::drawPanelContents() { glDrawArrays(GL_LINE_STRIP, 0, points.size() / 2); glDisableClientState(GL_VERTEX_ARRAY); } - - glLoadMatrixf(transform); - + GLint vp[4]; glGetIntegerv( GL_VIEWPORT, vp); float viewHeight = (float) vp[3]; float viewWidth = (float) vp[2]; + glLoadMatrixf(transform); + long long leftFreq = (double) freq - ((double) bandwidth / 2.0); long long rightFreq = leftFreq + (double) bandwidth; @@ -162,4 +188,25 @@ void SpectrumPanel::drawPanelContents() { } glLineWidth(1.0); + + + if (showDb) { + float dbPanelWidth = (1.0/viewWidth)*75.0; + float dbPanelHeight = (1.0/viewHeight)*14.0; + + + std::stringstream ssLabel; + ssLabel << std::fixed << std::setprecision(1) << (20.0 * log10(2.0*(getCeilValue())/2048.0)) << "dB"; + + dbPanelCeil.setText(ssLabel.str(), GLFont::GLFONT_ALIGN_RIGHT); + dbPanelCeil.setSize(dbPanelWidth, dbPanelHeight); + dbPanelCeil.setPosition(-1.0 + dbPanelWidth, 1.0 - dbPanelHeight); + + ssLabel.str(""); + ssLabel << (20.0 * log10(2.0*(getFloorValue())/2048.0)) << "dB"; + + dbPanelFloor.setText(ssLabel.str(), GLFont::GLFONT_ALIGN_RIGHT); + dbPanelFloor.setSize(dbPanelWidth, dbPanelHeight); + dbPanelFloor.setPosition(-1.0 + dbPanelWidth, - 1.0 + dbPanelHeight); + } } diff --git a/src/panel/SpectrumPanel.h b/src/panel/SpectrumPanel.h index af2d2f7..6ffa3f8 100644 --- a/src/panel/SpectrumPanel.h +++ b/src/panel/SpectrumPanel.h @@ -8,10 +8,10 @@ public: void setPoints(std::vector &points); - float getFloorValue() const; + float getFloorValue(); void setFloorValue(float floorValue); - float getCeilValue() const; + float getCeilValue(); void setCeilValue(float ceilValue); void setFreq(long long freq); @@ -20,6 +20,9 @@ public: void setBandwidth(long long bandwidth); long long getBandwidth(); + void setShowDb(bool showDb); + bool getShowDb(); + protected: void drawPanelContents(); @@ -28,4 +31,8 @@ private: long long freq; long long bandwidth; std::vector points; + + GLTextPanel dbPanelCeil; + GLTextPanel dbPanelFloor; + bool showDb; }; \ No newline at end of file diff --git a/src/panel/WaterfallPanel.cpp b/src/panel/WaterfallPanel.cpp index d3b0224..5de721a 100644 --- a/src/panel/WaterfallPanel.cpp +++ b/src/panel/WaterfallPanel.cpp @@ -1,7 +1,7 @@ #include "WaterfallPanel.h" WaterfallPanel::WaterfallPanel() : GLPanel(), fft_size(0), waterfall_lines(0), waterfall_slice(NULL), activeTheme(NULL) { - setFillColor(RGB3f(0,0,0)); + setFillColor(RGBA4f(0,0,0)); for (int i = 0; i < 2; i++) { waterfall[i] = 0; } diff --git a/src/process/SpectrumVisualProcessor.cpp b/src/process/SpectrumVisualProcessor.cpp index 06c65c2..baa9099 100644 --- a/src/process/SpectrumVisualProcessor.cpp +++ b/src/process/SpectrumVisualProcessor.cpp @@ -283,9 +283,6 @@ void SpectrumVisualProcessor::process() { } } - fft_ceil += 0.25; - fft_floor -= 1; - fft_ceil_ma = fft_ceil_ma + (fft_ceil - fft_ceil_ma) * 0.05; fft_ceil_maa = fft_ceil_maa + (fft_ceil_ma - fft_ceil_maa) * 0.05; @@ -293,7 +290,7 @@ void SpectrumVisualProcessor::process() { fft_floor_maa = fft_floor_maa + (fft_floor_ma - fft_floor_maa) * 0.05; for (int i = 0, iMax = fftSize; i < iMax; i++) { - float v = (log10(fft_result_maa[i] - fft_floor_maa) / log10(fft_ceil_maa - fft_floor_maa)); + float v = (log10(fft_result_maa[i]+0.25 - (fft_floor_maa-1.0)) / log10((fft_ceil_maa+0.25) - (fft_floor_maa-1.0))); output->spectrum_points[i * 2] = ((float) i / (float) iMax); output->spectrum_points[i * 2 + 1] = v; } diff --git a/src/ui/GLPanel.cpp b/src/ui/GLPanel.cpp index c7e2bca..b10a642 100644 --- a/src/ui/GLPanel.cpp +++ b/src/ui/GLPanel.cpp @@ -9,9 +9,9 @@ GLPanel::GLPanel() : fillType(GLPANEL_FILL_SOLID), contentsVisible(true), transf pos[1] = 0.0f; size[0] = 1.0f; size[1] = 1.0f; - fill[0] = RGB3f(0.5,0.5,0.5); - fill[1] = RGB3f(0.1,0.1,0.1); - borderColor = RGB3f(0.8, 0.8, 0.8); + fill[0] = RGBA4f(0.5,0.5,0.5); + fill[1] = RGBA4f(0.1,0.1,0.1); + borderColor = RGBA4f(0.8, 0.8, 0.8); setCoordinateSystem(GLPANEL_Y_UP); setMarginPx(0); setBorderPx(0); @@ -23,8 +23,8 @@ void GLPanel::genArrays() { if (fillType == GLPANEL_FILL_SOLID || fillType == GLPANEL_FILL_GRAD_X || fillType == GLPANEL_FILL_GRAD_Y) { glPoints.reserve(2 * 4); glPoints.resize(2 * 4); - glColors.reserve(3 * 4); - glColors.resize(3 * 4); + glColors.reserve(4 * 4); + glColors.resize(4 * 4); float pts[2 * 4] = { min, min, @@ -33,7 +33,7 @@ void GLPanel::genArrays() { max, min }; - RGB3f c[4]; + RGBA4f c[4]; if (fillType == GLPANEL_FILL_SOLID) { c[0] = c[1] = c[2] = c[3] = fill[0]; @@ -45,22 +45,22 @@ void GLPanel::genArrays() { c[1] = c[2] = fill[1]; } - float clr[3 * 4] = { - c[0].r, c[0].g, c[0].b, - c[1].r, c[1].g, c[1].b, - c[2].r, c[2].g, c[2].b, - c[3].r, c[3].g, c[3].b - }; + float clr[4 * 4] = { + c[0].r, c[0].g, c[0].b, c[0].a, + c[1].r, c[1].g, c[1].b, c[1].a, + c[2].r, c[2].g, c[2].b, c[2].a, + c[3].r, c[3].g, c[3].b, c[3].a + }; glPoints.assign(pts, pts + (2 * 4)); - glColors.assign(clr, clr + (3 * 4)); + glColors.assign(clr, clr + (4 * 4)); } else { glPoints.reserve(2 * 8); glPoints.resize(2 * 8); - glColors.reserve(3 * 8); - glColors.resize(3 * 8); + glColors.reserve(4 * 8); + glColors.resize(4 * 8); - RGB3f c[8]; + RGBA4f c[8]; if (fillType == GLPANEL_FILL_GRAD_BAR_X) { float pts[2 * 8] = { @@ -103,18 +103,18 @@ void GLPanel::genArrays() { c[5] = c[6] = fill[0]; } - float clr[3 * 8] = { - c[0].r, c[0].g, c[0].b, - c[1].r, c[1].g, c[1].b, - c[2].r, c[2].g, c[2].b, - c[3].r, c[3].g, c[3].b, - c[4].r, c[4].g, c[4].b, - c[5].r, c[5].g, c[5].b, - c[6].r, c[6].g, c[6].b, - c[7].r, c[7].g, c[7].b + float clr[4 * 8] = { + c[0].r, c[0].g, c[0].b, c[0].a, + c[1].r, c[1].g, c[1].b, c[1].a, + c[2].r, c[2].g, c[2].b, c[2].a, + c[3].r, c[3].g, c[3].b, c[3].a, + c[4].r, c[4].g, c[4].b, c[4].a, + c[5].r, c[5].g, c[5].b, c[5].a, + c[6].r, c[6].g, c[6].b, c[6].a, + c[7].r, c[7].g, c[7].b, c[7].a }; - glColors.assign(clr, clr + (3 * 8)); + glColors.assign(clr, clr + (4 * 8)); } } @@ -175,12 +175,12 @@ void GLPanel::setFill(GLPanelFillType fill_mode) { genArrays(); } -void GLPanel::setFillColor(RGB3f color1) { +void GLPanel::setFillColor(RGBA4f color1) { fill[0] = color1; genArrays(); } -void GLPanel::setFillColor(RGB3f color1, RGB3f color2) { +void GLPanel::setFillColor(RGBA4f color1, RGBA4f color2) { fill[0] = color1; fill[1] = color2; genArrays(); @@ -191,7 +191,7 @@ void GLPanel::setMarginPx(float marg) { } -void GLPanel::setBorderColor(RGB3f clr) { +void GLPanel::setBorderColor(RGBA4f clr) { borderColor = clr; } @@ -207,7 +207,19 @@ void GLPanel::setBorderPx(float bordl, float bordr, float bordt, float bordb) { } void GLPanel::addChild(GLPanel *childPanel) { - children.push_back(childPanel); + std::vector::iterator i = std::find(children.begin(), children.end(), childPanel); + + if (i == children.end()) { + children.push_back(childPanel); + } +} + +void GLPanel::removeChild(GLPanel *childPanel) { + std::vector::iterator i = std::find(children.begin(), children.end(), childPanel); + + if (i != children.end()) { + children.erase(i); + } } void GLPanel::drawChildren() { @@ -265,10 +277,12 @@ void GLPanel::draw() { glLoadMatrixf(transform); if (fillType != GLPANEL_FILL_NONE) { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glVertexPointer(2, GL_FLOAT, 0, &glPoints[0]); - glColorPointer(3, GL_FLOAT, 0, &glColors[0]); + glColorPointer(4, GL_FLOAT, 0, &glColors[0]); glDrawArrays(GL_QUADS, 0, glPoints.size() / 2); @@ -277,7 +291,7 @@ void GLPanel::draw() { if (borderPx.left || borderPx.right || borderPx.top || borderPx.bottom) { glEnable(GL_LINE_SMOOTH); - glColor3f(borderColor.r, borderColor.g, borderColor.b); + glColor4f(borderColor.r, borderColor.g, borderColor.b, borderColor.a); if (borderPx.left) { glLineWidth(borderPx.left); @@ -313,6 +327,7 @@ void GLPanel::draw() { glDisable(GL_LINE_SMOOTH); } + glDisable(GL_BLEND); } if (contentsVisible) { @@ -337,6 +352,8 @@ void GLPanel::draw() { GLTextPanel::GLTextPanel() : GLPanel() { coord = GLPANEL_Y_UP; + horizAlign = GLFont::GLFONT_ALIGN_CENTER; + vertAlign = GLFont::GLFONT_ALIGN_CENTER; } void GLTextPanel::drawPanelContents() { @@ -346,19 +363,19 @@ void GLTextPanel::drawPanelContents() { float size; - if (pdim.y < 16) { + if (pdim.y <= 16) { sz = GLFont::GLFONT_SIZE12; size = 12; - } else if (pdim.y < 18) { + } else if (pdim.y <= 18) { sz = GLFont::GLFONT_SIZE16; size = 16; - } else if(pdim.y < 24) { + } else if(pdim.y <= 24) { sz = GLFont::GLFONT_SIZE18; size = 18; - } else if(pdim.y < 32) { + } else if(pdim.y <= 32) { sz = GLFont::GLFONT_SIZE24; size = 24; - } else if(pdim.y < 48) { + } else if(pdim.y <= 48) { sz = GLFont::GLFONT_SIZE32; size = 32; } else { @@ -367,11 +384,13 @@ void GLTextPanel::drawPanelContents() { } - GLFont::getFont(sz).drawString(textVal, mid, mid, size, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER, (int)pdim.x, (int)pdim.y); + GLFont::getFont(sz).drawString(textVal, mid, mid, size, horizAlign, vertAlign, (int)pdim.x, (int)pdim.y); } -void GLTextPanel::setText(std::string text) { +void GLTextPanel::setText(std::string text, GLFont::Align hAlign, GLFont::Align vAlign) { textVal = text; + horizAlign = hAlign; + vertAlign = vAlign; } std::string GLTextPanel::getText() { diff --git a/src/ui/GLPanel.h b/src/ui/GLPanel.h index db16fe2..b87bac2 100644 --- a/src/ui/GLPanel.h +++ b/src/ui/GLPanel.h @@ -42,8 +42,8 @@ public: GLPanelCoordinateSystem coord; float marginPx; GLPanelEdges borderPx; - RGB3f fill[2]; - RGB3f borderColor; + RGBA4f fill[2]; + RGBA4f borderColor; bool contentsVisible; CubicVR::mat4 transform; CubicVR::mat4 localTransform; @@ -68,15 +68,16 @@ public: void setCoordinateSystem(GLPanelCoordinateSystem coord); void setFill(GLPanelFillType fill_mode); - void setFillColor(RGB3f color1); - void setFillColor(RGB3f color1, RGB3f color2); + void setFillColor(RGBA4f color1); + void setFillColor(RGBA4f color1, RGBA4f color2); void setMarginPx(float marg); - void setBorderColor(RGB3f clr); + void setBorderColor(RGBA4f clr); void setBorderPx(float bord); void setBorderPx(float bordl, float bordr, float bordt, float bordb); void addChild(GLPanel *childPanel); + void removeChild(GLPanel *childPanel); void drawChildren(); virtual void drawPanelContents(); @@ -88,11 +89,13 @@ public: class GLTextPanel : public GLPanel { private: std::string textVal; + GLFont::Align horizAlign; + GLFont::Align vertAlign; public: GLTextPanel(); void drawPanelContents(); - void setText(std::string text); + void setText(std::string text, GLFont::Align hAlign = GLFont::GLFONT_ALIGN_CENTER, GLFont::Align vAlign = GLFont::GLFONT_ALIGN_CENTER); std::string getText(); }; diff --git a/src/ui/UITestContext.cpp b/src/ui/UITestContext.cpp index ec2722d..bb91cdc 100644 --- a/src/ui/UITestContext.cpp +++ b/src/ui/UITestContext.cpp @@ -9,30 +9,30 @@ PrimaryGLContext(canvas, sharedContext) { testPanel.setSize(1.0, 1.0); testPanel.setMarginPx(10); testPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_BAR_Y); - testPanel.setFillColor(RGB3f(0.0,0.0,1.0), RGB3f(0.0,1.0,0.0)); + testPanel.setFillColor(RGBA4f(0.0,0.0,1.0), RGBA4f(0.0,1.0,0.0)); testChildPanel.setPosition(0.0, 0.0); testChildPanel.setMarginPx(5); testChildPanel.setSize(1.0, 0.33); testChildPanel.setCoordinateSystem(GLPanel::GLPANEL_Y_DOWN_ZERO_ONE); testChildPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_BAR_X); - testChildPanel.setFillColor(RGB3f(0.0,0.0,1.0), RGB3f(0.0,1.0,0.0)); + testChildPanel.setFillColor(RGBA4f(0.0,0.0,1.0), RGBA4f(0.0,1.0,0.0)); testChildPanel.setBorderPx(1); testChildPanel2.setPosition(0.0, -0.66); testChildPanel2.setSize(1.0, 0.33); testChildPanel2.setMarginPx(5); testChildPanel2.setFill(GLPanel::GLPANEL_FILL_GRAD_X); - testChildPanel2.setFillColor(RGB3f(0.0,0.0,1.0), RGB3f(0.0,1.0,0.0)); - testChildPanel2.setBorderColor(RGB3f(1.0,0.0,0.0)); + testChildPanel2.setFillColor(RGBA4f(0.0,0.0,1.0), RGBA4f(0.0,1.0,0.0)); + testChildPanel2.setBorderColor(RGBA4f(1.0,0.0,0.0)); testChildPanel2.setBorderPx(1); testChildPanel3.setPosition(0.0, 0.66); testChildPanel3.setSize(1.0, 0.33); testChildPanel3.setMarginPx(5); testChildPanel3.setFill(GLPanel::GLPANEL_FILL_GRAD_X); - testChildPanel3.setFillColor(RGB3f(0.0,0.0,1.0), RGB3f(0.0,1.0,0.0)); - testChildPanel3.setBorderColor(RGB3f(1.0,0.0,0.0)); + testChildPanel3.setFillColor(RGBA4f(0.0,0.0,1.0), RGBA4f(0.0,1.0,0.0)); + testChildPanel3.setBorderColor(RGBA4f(1.0,0.0,0.0)); testChildPanel3.setBorderPx(1); testText1.setText("Testing 123.."); diff --git a/src/visual/ColorTheme.cpp b/src/visual/ColorTheme.cpp index 7c0b5ef..3590c83 100644 --- a/src/visual/ColorTheme.cpp +++ b/src/visual/ColorTheme.cpp @@ -41,27 +41,27 @@ DefaultColorTheme::DefaultColorTheme() { waterfallGradient.addColor(GradientColor(1.0, 1.0, 0)); waterfallGradient.addColor(GradientColor(1.0, 0.2, 0.0)); waterfallGradient.generate(256); - waterfallHighlight = RGB3f(1, 1, 1); - waterfallNew = RGB3f(0, 1, 0); - waterfallHover = RGB3f(1, 1, 0); - waterfallDestroy = RGB3f(1, 0, 0); - fftLine = RGB3f(0.9, 0.9, 0.9); - fftHighlight = RGB3f(1, 1, 1); - scopeLine = RGB3f(0.9, 0.9, 0.9); - tuningBarLight = RGB3f(0.2, 0.2, 0.9); - tuningBarDark = RGB3f(0.0, 0.0, 0.35); - tuningBarUp = RGB3f(1.0, 139.0/255.0, 96.0/255.0); - tuningBarDown = RGB3f(148.0/255.0, 148.0/255.0, 1.0); - meterLevel = RGB3f(0.1, 0.75, 0.1); - meterValue = RGB3f(0.75, 0.1, 0.1); - text = RGB3f(1, 1, 1); - freqLine = RGB3f(1, 1, 1); - button = RGB3f(0.65, 0.65, 0.65); - buttonHighlight = RGB3f(1, 1, 0); + waterfallHighlight = RGBA4f(1, 1, 1); + waterfallNew = RGBA4f(0, 1, 0); + waterfallHover = RGBA4f(1, 1, 0); + waterfallDestroy = RGBA4f(1, 0, 0); + fftLine = RGBA4f(0.9, 0.9, 0.9); + fftHighlight = RGBA4f(1, 1, 1); + scopeLine = RGBA4f(0.9, 0.9, 0.9); + tuningBarLight = RGBA4f(0.2, 0.2, 0.9); + tuningBarDark = RGBA4f(0.0, 0.0, 0.35); + tuningBarUp = RGBA4f(1.0, 139.0/255.0, 96.0/255.0); + tuningBarDown = RGBA4f(148.0/255.0, 148.0/255.0, 1.0); + meterLevel = RGBA4f(0.1, 0.75, 0.1); + meterValue = RGBA4f(0.75, 0.1, 0.1); + text = RGBA4f(1, 1, 1); + freqLine = RGBA4f(1, 1, 1); + button = RGBA4f(0.65, 0.65, 0.65); + buttonHighlight = RGBA4f(1, 1, 0); - scopeBackground = RGB3f(0.1, 0.1, 0.1); - fftBackground = RGB3f(0.1, 0.1, 0.1); - generalBackground = RGB3f(0.1, 0.1, 0.1); + scopeBackground = RGBA4f(0.1, 0.1, 0.1); + fftBackground = RGBA4f(0.1, 0.1, 0.1); + generalBackground = RGBA4f(0.1, 0.1, 0.1); } @@ -72,27 +72,27 @@ RadarColorTheme::RadarColorTheme() { waterfallGradient.addColor(GradientColor(40.0 / 255.0, 240.0 / 255.0, 60.0 / 255.0)); waterfallGradient.addColor(GradientColor(250.0 / 255.0, 250.0 / 255.0, 250.0 / 255.0)); waterfallGradient.generate(256); - waterfallHighlight = RGB3f(1, 1, 1); - waterfallNew = RGB3f(0, 1, 0); - waterfallHover = RGB3f(1, 1, 0); - waterfallDestroy = RGB3f(1, 0, 0); - fftLine = RGB3f(0.8, 1.0, 0.8); - fftHighlight = RGB3f(1, 1, 1); - scopeLine = RGB3f(0.8, 1.0, 0.8); - tuningBarLight = RGB3f(0.0, 0.45, 0.0); - tuningBarDark = RGB3f(0.0, 0.1, 0.0); - tuningBarUp = RGB3f(1.0, 139.0/255.0, 96.0/255.0); - tuningBarDown = RGB3f(148.0/255.0, 0.0, 0.0); - meterLevel = RGB3f(0, 0.5, 0); - meterValue = RGB3f(0, 0.5, 0); - text = RGB3f(0.8, 1.0, 0.8); - freqLine = RGB3f(1, 1, 1); - button = RGB3f(0.65, 0.75, 0.65); - buttonHighlight = RGB3f(0.65, 1.0, 0.65); + waterfallHighlight = RGBA4f(1, 1, 1); + waterfallNew = RGBA4f(0, 1, 0); + waterfallHover = RGBA4f(1, 1, 0); + waterfallDestroy = RGBA4f(1, 0, 0); + fftLine = RGBA4f(0.8, 1.0, 0.8); + fftHighlight = RGBA4f(1, 1, 1); + scopeLine = RGBA4f(0.8, 1.0, 0.8); + tuningBarLight = RGBA4f(0.0, 0.45, 0.0); + tuningBarDark = RGBA4f(0.0, 0.1, 0.0); + tuningBarUp = RGBA4f(1.0, 139.0/255.0, 96.0/255.0); + tuningBarDown = RGBA4f(148.0/255.0, 0.0, 0.0); + meterLevel = RGBA4f(0, 0.5, 0); + meterValue = RGBA4f(0, 0.5, 0); + text = RGBA4f(0.8, 1.0, 0.8); + freqLine = RGBA4f(1, 1, 1); + button = RGBA4f(0.65, 0.75, 0.65); + buttonHighlight = RGBA4f(0.65, 1.0, 0.65); - scopeBackground = RGB3f(0.05, 0.1, 0.05); - fftBackground = RGB3f(0.05, 0.1, 0.05); - generalBackground = RGB3f(0.05, 0.1, 0.05); + scopeBackground = RGBA4f(0.05, 0.1, 0.05); + fftBackground = RGBA4f(0.05, 0.1, 0.05); + generalBackground = RGBA4f(0.05, 0.1, 0.05); } BlackAndWhiteColorTheme::BlackAndWhiteColorTheme() { @@ -101,27 +101,27 @@ BlackAndWhiteColorTheme::BlackAndWhiteColorTheme() { waterfallGradient.addColor(GradientColor(0.75, 0.75, 0.75)); waterfallGradient.addColor(GradientColor(1.0, 1.0, 1.0)); waterfallGradient.generate(256); - waterfallHighlight = RGB3f(1, 1, 0.9); - waterfallNew = RGB3f(0, 1, 0); - waterfallHover = RGB3f(1, 1, 0); - waterfallDestroy = RGB3f(1, 0, 0); - fftLine = RGB3f(0.9, 0.9, 0.9); - fftHighlight = RGB3f(1, 1, 0.9); - scopeLine = RGB3f(0.9, 0.9, 0.9); - tuningBarLight = RGB3f(0.4, 0.4, 0.4); - tuningBarDark = RGB3f(0.1, 0.1, 0.1); - tuningBarUp = RGB3f(0.8, 0.8, 0.8); - tuningBarDown = RGB3f(0.4, 0.4, 0.4); - meterLevel = RGB3f(0.5, 0.5, 0.5); - meterValue = RGB3f(0.5, 0.5, 0.5); - text = RGB3f(1, 1, 1); - freqLine = RGB3f(1, 1, 1); - button = RGB3f(0.65, 0.65, 0.65); - buttonHighlight = RGB3f(1, 1, 1); + waterfallHighlight = RGBA4f(1, 1, 0.9); + waterfallNew = RGBA4f(0, 1, 0); + waterfallHover = RGBA4f(1, 1, 0); + waterfallDestroy = RGBA4f(1, 0, 0); + fftLine = RGBA4f(0.9, 0.9, 0.9); + fftHighlight = RGBA4f(1, 1, 0.9); + scopeLine = RGBA4f(0.9, 0.9, 0.9); + tuningBarLight = RGBA4f(0.4, 0.4, 0.4); + tuningBarDark = RGBA4f(0.1, 0.1, 0.1); + tuningBarUp = RGBA4f(0.8, 0.8, 0.8); + tuningBarDown = RGBA4f(0.4, 0.4, 0.4); + meterLevel = RGBA4f(0.5, 0.5, 0.5); + meterValue = RGBA4f(0.5, 0.5, 0.5); + text = RGBA4f(1, 1, 1); + freqLine = RGBA4f(1, 1, 1); + button = RGBA4f(0.65, 0.65, 0.65); + buttonHighlight = RGBA4f(1, 1, 1); - scopeBackground = RGB3f(0.1, 0.1, 0.1); - fftBackground = RGB3f(0.1, 0.1, 0.1); - generalBackground = RGB3f(0.1, 0.1, 0.1); + scopeBackground = RGBA4f(0.1, 0.1, 0.1); + fftBackground = RGBA4f(0.1, 0.1, 0.1); + generalBackground = RGBA4f(0.1, 0.1, 0.1); } @@ -139,27 +139,27 @@ SharpColorTheme::SharpColorTheme() { waterfallGradient.addColor(GradientColor(1.0, 0.25, 0.0)); waterfallGradient.addColor(GradientColor(0.5, 0.1, 0.0)); waterfallGradient.generate(256); - waterfallHighlight = RGB3f(0.9, 0.9, 1.0); - waterfallNew = RGB3f(0, 1, 0); - waterfallHover = RGB3f(1, 1, 0); - waterfallDestroy = RGB3f(1, 0, 0); - fftLine = RGB3f(0.9, 0.9, 1.0); - fftHighlight = RGB3f(0.9, 0.9, 1.0); - scopeLine = RGB3f(0.85, 0.85, 1.0); - tuningBarLight = RGB3f(28.0 / 255.0, 106.0 / 255.0, 179.0 / 255.0); - tuningBarDark = RGB3f(14.0 / 255.0, 53.0 / 255.0, 89.5 / 255.0); - tuningBarUp = RGB3f(0.7, 0.7, 0.7); - tuningBarDown = RGB3f(1.0, 0.0, 0.0); - meterLevel = RGB3f(28.0 / 255.0, 106.0 / 255.0, 179.0 / 255.0); - meterValue = RGB3f(190.0 / 255.0, 190.0 / 255.0, 60.0 / 255.0); - text = RGB3f(0.9, 0.9, 1); - freqLine = RGB3f(0.85, 0.85, 1.0); - button = RGB3f(217.0 / 255.0, 218.0 / 255.0, 228.0 / 255.0); - buttonHighlight = RGB3f(208.0 / 255.0, 249.0 / 255.0, 255.0 / 255.0); + waterfallHighlight = RGBA4f(0.9, 0.9, 1.0); + waterfallNew = RGBA4f(0, 1, 0); + waterfallHover = RGBA4f(1, 1, 0); + waterfallDestroy = RGBA4f(1, 0, 0); + fftLine = RGBA4f(0.9, 0.9, 1.0); + fftHighlight = RGBA4f(0.9, 0.9, 1.0); + scopeLine = RGBA4f(0.85, 0.85, 1.0); + tuningBarLight = RGBA4f(28.0 / 255.0, 106.0 / 255.0, 179.0 / 255.0); + tuningBarDark = RGBA4f(14.0 / 255.0, 53.0 / 255.0, 89.5 / 255.0); + tuningBarUp = RGBA4f(0.7, 0.7, 0.7); + tuningBarDown = RGBA4f(1.0, 0.0, 0.0); + meterLevel = RGBA4f(28.0 / 255.0, 106.0 / 255.0, 179.0 / 255.0); + meterValue = RGBA4f(190.0 / 255.0, 190.0 / 255.0, 60.0 / 255.0); + text = RGBA4f(0.9, 0.9, 1); + freqLine = RGBA4f(0.85, 0.85, 1.0); + button = RGBA4f(217.0 / 255.0, 218.0 / 255.0, 228.0 / 255.0); + buttonHighlight = RGBA4f(208.0 / 255.0, 249.0 / 255.0, 255.0 / 255.0); - scopeBackground = RGB3f(0.05, 0.05, 0.15); - fftBackground = RGB3f(0.05, 0.05, 0.15); - generalBackground = RGB3f(0.05, 0.05, 0.15); + scopeBackground = RGBA4f(0.05, 0.05, 0.15); + fftBackground = RGBA4f(0.05, 0.05, 0.15); + generalBackground = RGBA4f(0.05, 0.05, 0.15); } RadColorTheme::RadColorTheme() { @@ -170,27 +170,27 @@ RadColorTheme::RadColorTheme() { waterfallGradient.addColor(GradientColor(1.0, 40.0 / 255.0, 40.0 / 255.0)); waterfallGradient.addColor(GradientColor(1.0, 1.0, 1.0)); waterfallGradient.generate(256); - waterfallHighlight = RGB3f(1, 1, 1); - waterfallNew = RGB3f(0, 1, 0); - waterfallHover = RGB3f(1, 1, 0); - waterfallDestroy = RGB3f(1, 0, 0); - fftLine = RGB3f(1.0, 0.9, 0.9); - fftHighlight = RGB3f(1, 1, 1); - scopeLine = RGB3f(1.0, 0.9, 0.9); - tuningBarLight = RGB3f(0.0, 0.45, 0.0); - tuningBarDark = RGB3f(0.0, 0.1, 0.0); - tuningBarUp = RGB3f(1.0, 0.0, 0.0); - tuningBarDown = RGB3f(0.0, 0.5, 1.0); - meterLevel = RGB3f(0, 0.5, 0); - meterValue = RGB3f(0.5, 0, 0); - text = RGB3f(1, 1, 1); - freqLine = RGB3f(1, 1, 1); - button = RGB3f(0.65, 0.65, 0.65); - buttonHighlight = RGB3f(0.76, 0.65, 0); + waterfallHighlight = RGBA4f(1, 1, 1); + waterfallNew = RGBA4f(0, 1, 0); + waterfallHover = RGBA4f(1, 1, 0); + waterfallDestroy = RGBA4f(1, 0, 0); + fftLine = RGBA4f(1.0, 0.9, 0.9); + fftHighlight = RGBA4f(1, 1, 1); + scopeLine = RGBA4f(1.0, 0.9, 0.9); + tuningBarLight = RGBA4f(0.0, 0.45, 0.0); + tuningBarDark = RGBA4f(0.0, 0.1, 0.0); + tuningBarUp = RGBA4f(1.0, 0.0, 0.0); + tuningBarDown = RGBA4f(0.0, 0.5, 1.0); + meterLevel = RGBA4f(0, 0.5, 0); + meterValue = RGBA4f(0.5, 0, 0); + text = RGBA4f(1, 1, 1); + freqLine = RGBA4f(1, 1, 1); + button = RGBA4f(0.65, 0.65, 0.65); + buttonHighlight = RGBA4f(0.76, 0.65, 0); - scopeBackground = RGB3f(13.0 / 255.0, 47.0 / 255.0, 9.0 / 255.0); - fftBackground = RGB3f(0, 0, 50.0 / 255.0); - generalBackground = RGB3f(13.0 / 255.0, 47.0 / 255.0, 9.0 / 255.0); + scopeBackground = RGBA4f(13.0 / 255.0, 47.0 / 255.0, 9.0 / 255.0); + fftBackground = RGBA4f(0, 0, 50.0 / 255.0); + generalBackground = RGBA4f(13.0 / 255.0, 47.0 / 255.0, 9.0 / 255.0); } TouchColorTheme::TouchColorTheme() { @@ -204,27 +204,27 @@ TouchColorTheme::TouchColorTheme() { waterfallGradient.addColor(GradientColor(255.0 / 255.0, 0.0 / 255.0, 0.0 / 255.0)); waterfallGradient.addColor(GradientColor(255.0 / 255.0, 255.0 / 255.0, 255.0 / 255.0)); waterfallGradient.generate(256); - waterfallHighlight = RGB3f(1, 1, 1); - waterfallNew = RGB3f(0, 1, 0); - waterfallHover = RGB3f(1, 1, 0); - waterfallDestroy = RGB3f(1, 0, 0); - fftLine = RGB3f(234.0 / 255.0, 232.0 / 255.0, 247.0 / 255.0); - fftHighlight = RGB3f(1.0, 1.0, 1.0); - scopeLine = RGB3f(234.0 / 255.0, 232.0 / 255.0, 247.0 / 255.0); - tuningBarLight = RGB3f(0.2, 0.2, 0.7); - tuningBarDark = RGB3f(0.1, 0.1, 0.45); - tuningBarUp = RGB3f(0.5, 139.0/255.0, 96.0/255.0); - tuningBarDown = RGB3f(0.6, 108.0/255.0, 1.0); - meterLevel = RGB3f(61.0 / 255.0, 57.0 / 255.0, 88.0 / 255.0); - meterValue = RGB3f(61.0 / 255.0, 57.0 / 255.0, 88.0 / 255.0); - text = RGB3f(1, 1, 1); - freqLine = RGB3f(1, 1, 1); - button = RGB3f(1.0, 1.0, 1.0); - buttonHighlight = RGB3f(208.0 / 255.0, 202.0 / 255.0, 247.0 / 255.0); + waterfallHighlight = RGBA4f(1, 1, 1); + waterfallNew = RGBA4f(0, 1, 0); + waterfallHover = RGBA4f(1, 1, 0); + waterfallDestroy = RGBA4f(1, 0, 0); + fftLine = RGBA4f(234.0 / 255.0, 232.0 / 255.0, 247.0 / 255.0); + fftHighlight = RGBA4f(1.0, 1.0, 1.0); + scopeLine = RGBA4f(234.0 / 255.0, 232.0 / 255.0, 247.0 / 255.0); + tuningBarLight = RGBA4f(0.2, 0.2, 0.7); + tuningBarDark = RGBA4f(0.1, 0.1, 0.45); + tuningBarUp = RGBA4f(0.5, 139.0/255.0, 96.0/255.0); + tuningBarDown = RGBA4f(0.6, 108.0/255.0, 1.0); + meterLevel = RGBA4f(61.0 / 255.0, 57.0 / 255.0, 88.0 / 255.0); + meterValue = RGBA4f(61.0 / 255.0, 57.0 / 255.0, 88.0 / 255.0); + text = RGBA4f(1, 1, 1); + freqLine = RGBA4f(1, 1, 1); + button = RGBA4f(1.0, 1.0, 1.0); + buttonHighlight = RGBA4f(208.0 / 255.0, 202.0 / 255.0, 247.0 / 255.0); - scopeBackground = RGB3f(39.0 / 255.0, 36.0 / 255.0, 56.0 / 255.0); - fftBackground = RGB3f(39.0 / 255.0, 36.0 / 255.0, 56.0 / 255.0); - generalBackground = RGB3f(61.0 / 255.0, 57.0 / 255.0, 88.0 / 255.0); + scopeBackground = RGBA4f(39.0 / 255.0, 36.0 / 255.0, 56.0 / 255.0); + fftBackground = RGBA4f(39.0 / 255.0, 36.0 / 255.0, 56.0 / 255.0); + generalBackground = RGBA4f(61.0 / 255.0, 57.0 / 255.0, 88.0 / 255.0); } @@ -239,27 +239,27 @@ HDColorTheme::HDColorTheme() { waterfallGradient.addColor(GradientColor(255.0 / 255.0, 235.0 / 255.0, 100.0 / 255.0)); waterfallGradient.addColor(GradientColor(250.0 / 255.0, 250.0 / 255.0, 250.0 / 255.0)); waterfallGradient.generate(256); - waterfallHighlight = RGB3f(1, 1, 1); - waterfallNew = RGB3f(0, 1, 0); - waterfallHover = RGB3f(1, 1, 0); - waterfallDestroy = RGB3f(1, 0, 0); - fftLine = RGB3f(0.9, 0.9, 0.9); - fftHighlight = RGB3f(1, 1, 1); - scopeLine = RGB3f(0.9, 0.9, 0.9); - tuningBarLight = RGB3f(0.4, 0.4, 1.0); - tuningBarDark = RGB3f(0.1, 0.1, 0.45); - tuningBarUp = RGB3f(1.0, 139.0/255.0, 96.0/255.0); - tuningBarDown = RGB3f(148.0/255.0, 148.0/255.0, 1.0); - meterLevel = RGB3f(0, 0.5, 0); - meterValue = RGB3f(0.0, 0.0, 1.0); - text = RGB3f(1, 1, 1); - freqLine = RGB3f(1, 1, 1); - button = RGB3f(0, 0.7, 0.7); - buttonHighlight = RGB3f(1, 1, 1); + waterfallHighlight = RGBA4f(1, 1, 1); + waterfallNew = RGBA4f(0, 1, 0); + waterfallHover = RGBA4f(1, 1, 0); + waterfallDestroy = RGBA4f(1, 0, 0); + fftLine = RGBA4f(0.9, 0.9, 0.9); + fftHighlight = RGBA4f(1, 1, 1); + scopeLine = RGBA4f(0.9, 0.9, 0.9); + tuningBarLight = RGBA4f(0.4, 0.4, 1.0); + tuningBarDark = RGBA4f(0.1, 0.1, 0.45); + tuningBarUp = RGBA4f(1.0, 139.0/255.0, 96.0/255.0); + tuningBarDown = RGBA4f(148.0/255.0, 148.0/255.0, 1.0); + meterLevel = RGBA4f(0, 0.5, 0); + meterValue = RGBA4f(0.0, 0.0, 1.0); + text = RGBA4f(1, 1, 1); + freqLine = RGBA4f(1, 1, 1); + button = RGBA4f(0, 0.7, 0.7); + buttonHighlight = RGBA4f(1, 1, 1); - scopeBackground = RGB3f(0.0, 0.0, 48.0 / 255.0); - fftBackground = RGB3f(0.0, 0.0, 48.0 / 255.0); - generalBackground = RGB3f(0.0, 0.0, 0.0); + scopeBackground = RGBA4f(0.0, 0.0, 48.0 / 255.0); + fftBackground = RGBA4f(0.0, 0.0, 48.0 / 255.0); + generalBackground = RGBA4f(0.0, 0.0, 0.0); } diff --git a/src/visual/ColorTheme.h b/src/visual/ColorTheme.h index 52b431a..6f35324 100644 --- a/src/visual/ColorTheme.h +++ b/src/visual/ColorTheme.h @@ -15,55 +15,56 @@ #define COLOR_THEME_RADAR 6 #define COLOR_THEME_MAX 7 -class RGB3f { +class RGBA4f { public: - float r, g, b; - RGB3f(float r, float g, float b) : - r(r), g(g), b(b) { + float r, g, b, a; + RGBA4f(float r, float g, float b, float a = 1.0) : + r(r), g(g), b(b), a(a) { } - RGB3f() : - RGB3f(0, 0, 0) { + RGBA4f() : + RGBA4f(0, 0, 0) { } - ~RGB3f() { + ~RGBA4f() { } - RGB3f & operator=(const RGB3f &other) { + RGBA4f & operator=(const RGBA4f &other) { r = other.r; g = other.g; b = other.b; + a = other.a; return *this; } - RGB3f operator*(float v) { return RGB3f(r*v, g*v, b*v); } + RGBA4f operator*(float v) { return RGBA4f(r*v, g*v, b*v); } }; class ColorTheme { public: - RGB3f waterfallHighlight; - RGB3f waterfallNew; - RGB3f wfHighlight; - RGB3f waterfallHover; - RGB3f waterfallDestroy; - RGB3f fftLine; - RGB3f fftHighlight; - RGB3f scopeLine; - RGB3f tuningBarLight; - RGB3f tuningBarDark; - RGB3f tuningBarUp; - RGB3f tuningBarDown; - RGB3f meterLevel; - RGB3f meterValue; - RGB3f text; - RGB3f freqLine; - RGB3f button; - RGB3f buttonHighlight; + RGBA4f waterfallHighlight; + RGBA4f waterfallNew; + RGBA4f wfHighlight; + RGBA4f waterfallHover; + RGBA4f waterfallDestroy; + RGBA4f fftLine; + RGBA4f fftHighlight; + RGBA4f scopeLine; + RGBA4f tuningBarLight; + RGBA4f tuningBarDark; + RGBA4f tuningBarUp; + RGBA4f tuningBarDown; + RGBA4f meterLevel; + RGBA4f meterValue; + RGBA4f text; + RGBA4f freqLine; + RGBA4f button; + RGBA4f buttonHighlight; - RGB3f scopeBackground; - RGB3f fftBackground; - RGB3f generalBackground; + RGBA4f scopeBackground; + RGBA4f fftBackground; + RGBA4f generalBackground; Gradient waterfallGradient; diff --git a/src/visual/PrimaryGLContext.cpp b/src/visual/PrimaryGLContext.cpp index 15d96f8..29d2391 100644 --- a/src/visual/PrimaryGLContext.cpp +++ b/src/visual/PrimaryGLContext.cpp @@ -59,7 +59,7 @@ PrimaryGLContext::PrimaryGLContext(wxGLCanvas *canvas, wxGLContext *sharedContex //#endif } -void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, RGB3f color, long long center_freq, long long srate) { +void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, RGBA4f color, long long center_freq, long long srate) { if (!demod) { return; } @@ -151,7 +151,7 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, RGB3f color, lo } -void PrimaryGLContext::DrawDemod(DemodulatorInstance *demod, RGB3f color, long long center_freq, long long srate) { +void PrimaryGLContext::DrawDemod(DemodulatorInstance *demod, RGBA4f color, long long center_freq, long long srate) { if (!demod) { return; } @@ -248,7 +248,7 @@ void PrimaryGLContext::DrawDemod(DemodulatorInstance *demod, RGB3f color, long l } -void PrimaryGLContext::DrawFreqSelector(float uxPos, RGB3f color, float w, long long center_freq, long long srate) { +void PrimaryGLContext::DrawFreqSelector(float uxPos, RGBA4f color, float w, long long center_freq, long long srate) { DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); long long bw = 0; @@ -299,7 +299,7 @@ void PrimaryGLContext::DrawFreqSelector(float uxPos, RGB3f color, float w, long } -void PrimaryGLContext::DrawRangeSelector(float uxPos1, float uxPos2, RGB3f color) { +void PrimaryGLContext::DrawRangeSelector(float uxPos1, float uxPos2, RGBA4f color) { if (uxPos2 < uxPos1) { float temp = uxPos2; uxPos2=uxPos1; diff --git a/src/visual/PrimaryGLContext.h b/src/visual/PrimaryGLContext.h index 4f87e4d..254d2e7 100644 --- a/src/visual/PrimaryGLContext.h +++ b/src/visual/PrimaryGLContext.h @@ -21,10 +21,10 @@ public: void BeginDraw(float r, float g, float b); void EndDraw(); - void DrawFreqSelector(float uxPos, RGB3f color, float w = 0, long long center_freq = -1, long long srate = 0); - void DrawRangeSelector(float uxPos1, float uxPos2, RGB3f color); - void DrawDemod(DemodulatorInstance *demod, RGB3f color, long long center_freq = -1, long long srate = 0); - void DrawDemodInfo(DemodulatorInstance *demod, RGB3f color, long long center_freq = -1, long long srate = 0); + void DrawFreqSelector(float uxPos, RGBA4f color, float w = 0, long long center_freq = -1, long long srate = 0); + void DrawRangeSelector(float uxPos1, float uxPos2, RGBA4f color); + void DrawDemod(DemodulatorInstance *demod, RGBA4f color, long long center_freq = -1, long long srate = 0); + void DrawDemodInfo(DemodulatorInstance *demod, RGBA4f color, long long center_freq = -1, long long srate = 0); void setHoverAlpha(float hoverAlpha); diff --git a/src/visual/SpectrumCanvas.cpp b/src/visual/SpectrumCanvas.cpp index a35aca5..bc8870e 100644 --- a/src/visual/SpectrumCanvas.cpp +++ b/src/visual/SpectrumCanvas.cpp @@ -73,6 +73,8 @@ void SpectrumCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { spectrumPanel.calcTransform(CubicVR::mat4::identity()); spectrumPanel.draw(); + glLoadIdentity(); + std::vector &demods = wxGetApp().getDemodMgr().getDemodulators(); for (int i = 0, iMax = demods.size(); i < iMax; i++) { @@ -81,6 +83,8 @@ void SpectrumCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { glContext->EndDraw(); + spectrumPanel.drawChildren(); + SwapBuffers(); } @@ -126,6 +130,11 @@ void SpectrumCanvas::moveCenterFrequency(long long freqChange) { } } +void SpectrumCanvas::setShowDb(bool showDb) { + spectrumPanel.setShowDb(showDb); +} + + void SpectrumCanvas::OnMouseMoved(wxMouseEvent& event) { InteractiveCanvas::OnMouseMoved(event); if (mouseTracker.mouseDown()) { diff --git a/src/visual/SpectrumCanvas.h b/src/visual/SpectrumCanvas.h index af07f6c..6160111 100644 --- a/src/visual/SpectrumCanvas.h +++ b/src/visual/SpectrumCanvas.h @@ -19,6 +19,8 @@ public: void attachWaterfallCanvas(WaterfallCanvas *canvas_in); void moveCenterFrequency(long long freqChange); + void setShowDb(bool showDb); + SpectrumVisualDataQueue *getVisualDataQueue(); private: diff --git a/src/visual/TuningCanvas.cpp b/src/visual/TuningCanvas.cpp index b6fa267..a9fe015 100644 --- a/src/visual/TuningCanvas.cpp +++ b/src/visual/TuningCanvas.cpp @@ -98,10 +98,10 @@ void TuningCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { 0.75, mouseTracker.getOriginMouseX(), mouseTracker.getMouseX()); } - RGB3f clr = top ? ThemeMgr::mgr.currentTheme->tuningBarUp : ThemeMgr::mgr.currentTheme->tuningBarDown; + RGBA4f clr = top ? ThemeMgr::mgr.currentTheme->tuningBarUp : ThemeMgr::mgr.currentTheme->tuningBarDown; - RGB3f clrDark = ThemeMgr::mgr.currentTheme->tuningBarDark; - RGB3f clrMid = ThemeMgr::mgr.currentTheme->tuningBarLight; + RGBA4f clrDark = ThemeMgr::mgr.currentTheme->tuningBarDark; + RGBA4f clrMid = ThemeMgr::mgr.currentTheme->tuningBarLight; glContext->DrawTunerBarIndexed(1, 3, 11, freqDP, freqW, clrMid, 0.25, true, true); // freq glContext->DrawTunerBarIndexed(4, 6, 11, freqDP, freqW, clrDark, 0.25, true, true); @@ -143,7 +143,7 @@ void TuningCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { glContext->DrawTuner(freq, 11, freqDP, freqW); int snap = wxGetApp().getFrequencySnap(); if (snap != 1) { - glContext->DrawTunerDigitBox((int)log10(snap), 11, freqDP, freqW, RGB3f(1.0,0.0,0.0)); + glContext->DrawTunerDigitBox((int)log10(snap), 11, freqDP, freqW, RGBA4f(1.0,0.0,0.0)); } } glContext->DrawTuner(bw, 7, bwDP, bwW); diff --git a/src/visual/TuningContext.cpp b/src/visual/TuningContext.cpp index bd9ca4a..a499da0 100644 --- a/src/visual/TuningContext.cpp +++ b/src/visual/TuningContext.cpp @@ -112,7 +112,7 @@ void TuningContext::DrawTuner(long long freq, int count, float displayPos, float } -void TuningContext::DrawTunerDigitBox(int index, int count, float displayPos, float displayWidth, RGB3f c) { +void TuningContext::DrawTunerDigitBox(int index, int count, float displayPos, float displayWidth, RGBA4f c) { GLint vp[4]; glGetIntegerv( GL_VIEWPORT, vp); @@ -152,7 +152,7 @@ int TuningContext::GetTunerDigitIndex(float mPos, int count, float displayPos, f return count - index; } -void TuningContext::DrawTunerBarIndexed(int start, int end, int count, float displayPos, float displayWidth, RGB3f color, float alpha, bool top, +void TuningContext::DrawTunerBarIndexed(int start, int end, int count, float displayPos, float displayWidth, RGBA4f color, float alpha, bool top, bool bottom) { float ofs = (displayWidth / (float) count); float p2 = displayPos + ofs * (float) (count - start + 1); diff --git a/src/visual/TuningContext.h b/src/visual/TuningContext.h index 61dc9f3..3bbec2f 100644 --- a/src/visual/TuningContext.h +++ b/src/visual/TuningContext.h @@ -14,9 +14,9 @@ public: void DrawBegin(); void Draw(float r, float g, float b, float a, float p1, float p2); void DrawTuner(long long freq, int count, float displayPos, float displayWidth); - void DrawTunerDigitBox(int index, int count, float displayPos, float displayWidth, RGB3f c); + void DrawTunerDigitBox(int index, int count, float displayPos, float displayWidth, RGBA4f c); int GetTunerDigitIndex(float mPos, int count, float displayPos, float displayWidth); - void DrawTunerBarIndexed(int start, int end, int count, float displayPos, float displayWidth, RGB3f color, float alpha, bool top, bool bottom); + void DrawTunerBarIndexed(int start, int end, int count, float displayPos, float displayWidth, RGBA4f color, float alpha, bool top, bool bottom); void DrawDemodFreqBw(long long freq, unsigned int bw, long long center); void DrawEnd(); From 3c822e1bd86947426d28b8ebbbf759d98d06fc24 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Mon, 17 Aug 2015 23:31:22 -0400 Subject: [PATCH 06/19] Make decibels display toggleable --- src/visual/SpectrumCanvas.cpp | 7 +++++-- src/visual/SpectrumCanvas.h | 1 + src/visual/WaterfallCanvas.cpp | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/visual/SpectrumCanvas.cpp b/src/visual/SpectrumCanvas.cpp index bc8870e..adcc96c 100644 --- a/src/visual/SpectrumCanvas.cpp +++ b/src/visual/SpectrumCanvas.cpp @@ -126,7 +126,6 @@ void SpectrumCanvas::moveCenterFrequency(long long freqChange) { freq -= freqChange; } wxGetApp().setFrequency(freq); - setStatusText("Set center frequency: %s", freq); } } @@ -134,6 +133,10 @@ void SpectrumCanvas::setShowDb(bool showDb) { spectrumPanel.setShowDb(showDb); } +bool SpectrumCanvas::getShowDb() { + return spectrumPanel.getShowDb(); +} + void SpectrumCanvas::OnMouseMoved(wxMouseEvent& event) { InteractiveCanvas::OnMouseMoved(event); @@ -144,7 +147,7 @@ void SpectrumCanvas::OnMouseMoved(wxMouseEvent& event) { moveCenterFrequency(freqChange); } } else { - setStatusText("Click and drag to adjust center frequency."); + setStatusText("Click and drag to adjust center frequency. 'B' to toggle decibels display."); } } diff --git a/src/visual/SpectrumCanvas.h b/src/visual/SpectrumCanvas.h index 6160111..238c48f 100644 --- a/src/visual/SpectrumCanvas.h +++ b/src/visual/SpectrumCanvas.h @@ -20,6 +20,7 @@ public: void moveCenterFrequency(long long freqChange); void setShowDb(bool showDb); + bool getShowDb(); SpectrumVisualDataQueue *getVisualDataQueue(); diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index 204473f..c2571d7 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -338,6 +338,11 @@ void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) { activeDemod->setStereo(true); } break; + case 'B': + if (spectrumCanvas) { + spectrumCanvas->setShowDb(!spectrumCanvas->getShowDb()); + } + break; case WXK_SPACE: wxGetApp().showFrequencyInput(); break; From 2b7edb9b0fe4eedff8be0332db550418e22b503d Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Tue, 18 Aug 2015 00:08:22 -0400 Subject: [PATCH 07/19] spectrum visual spacing/floor tweak --- src/AppFrame.cpp | 3 +++ src/process/SpectrumVisualProcessor.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index 9091e36..d4ac828 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -784,6 +784,9 @@ void AppFrame::OnIdle(wxIdleEvent& event) { if (val < 0.01) { val = 0.01; } + if (val > 0.99) { + val = 0.99; + } spectrumAvgMeter->setLevel(val); proc->setFFTAverageRate(val); diff --git a/src/process/SpectrumVisualProcessor.cpp b/src/process/SpectrumVisualProcessor.cpp index baa9099..9322462 100644 --- a/src/process/SpectrumVisualProcessor.cpp +++ b/src/process/SpectrumVisualProcessor.cpp @@ -290,7 +290,7 @@ void SpectrumVisualProcessor::process() { fft_floor_maa = fft_floor_maa + (fft_floor_ma - fft_floor_maa) * 0.05; for (int i = 0, iMax = fftSize; i < iMax; i++) { - float v = (log10(fft_result_maa[i]+0.25 - (fft_floor_maa-1.0)) / log10((fft_ceil_maa+0.25) - (fft_floor_maa-1.0))); + float v = (log10(fft_result_maa[i]+0.25 - (fft_floor_maa-0.75)) / log10((fft_ceil_maa+0.25) - (fft_floor_maa-0.75))); output->spectrum_points[i * 2] = ((float) i / (float) iMax); output->spectrum_points[i * 2 + 1] = v; } From db1374045b3eb38eb4400b07db7a312c690169b2 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Tue, 18 Aug 2015 18:43:55 -0400 Subject: [PATCH 08/19] missing include --- src/ui/GLPanel.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ui/GLPanel.cpp b/src/ui/GLPanel.cpp index b10a642..00fa2be 100644 --- a/src/ui/GLPanel.cpp +++ b/src/ui/GLPanel.cpp @@ -1,6 +1,7 @@ #include "GLPanel.h" #include "cubic_math.h" +#include using namespace CubicVR; @@ -208,7 +209,7 @@ void GLPanel::setBorderPx(float bordl, float bordr, float bordt, float bordb) { void GLPanel::addChild(GLPanel *childPanel) { std::vector::iterator i = std::find(children.begin(), children.end(), childPanel); - + if (i == children.end()) { children.push_back(childPanel); } @@ -216,7 +217,7 @@ void GLPanel::addChild(GLPanel *childPanel) { void GLPanel::removeChild(GLPanel *childPanel) { std::vector::iterator i = std::find(children.begin(), children.end(), childPanel); - + if (i != children.end()) { children.erase(i); } From b236a9cb3ecf93dd2c2667e99c5ff2ebe18cc37a Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Tue, 18 Aug 2015 19:21:31 -0400 Subject: [PATCH 09/19] fix for spectrum disconnect after zooming in/out --- src/visual/WaterfallCanvas.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index c2571d7..1a5958e 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -161,7 +161,13 @@ void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { if (spectrumCanvas) { if ((spectrumCanvas->getCenterFrequency() != centerFreq) || (spectrumCanvas->getBandwidth() != bw)) { - spectrumCanvas->setView(centerFreq,bw); + if (getViewState()) { + spectrumCanvas->setView(centerFreq,bw); + } else { + spectrumCanvas->disableView(); + spectrumCanvas->setCenterFrequency(centerFreq); + spectrumCanvas->setBandwidth(bw); + } } } } From 8b5500fc9b543c52756a8b99793cd9ba82a8fd7f Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Wed, 19 Aug 2015 00:48:52 -0400 Subject: [PATCH 10/19] Smooth keyboard navigation while zoomed. --- src/visual/WaterfallCanvas.cpp | 85 ++++++++++++++++++++++------------ src/visual/WaterfallCanvas.h | 4 ++ 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index 1a5958e..bc3140f 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -36,7 +36,7 @@ wxEND_EVENT_TABLE() WaterfallCanvas::WaterfallCanvas(wxWindow *parent, int *attribList) : InteractiveCanvas(parent, attribList), dragState(WF_DRAG_NONE), nextDragState(WF_DRAG_NONE), fft_size(0), waterfall_lines(0), - dragOfs(0), mouseZoom(1), zoom(1), hoverAlpha(1.0) { + dragOfs(0), mouseZoom(1), zoom(1), freqMove(0.0), freqMoving(false), hoverAlpha(1.0) { glContext = new PrimaryGLContext(this, &wxGetApp().GetContext(this)); @@ -102,6 +102,19 @@ void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { } } + if (freqMove != 0.0) { + long long newFreq = getCenterFrequency() + (long long)((long double)getBandwidth()*freqMove) * 0.01; + + updateCenterFrequency(newFreq); + + if (!freqMoving) { + freqMove -= (freqMove * 0.2); + if (fabs(freqMove) < 0.01) { + freqMove = 0.0; + } + } + } + long long bw; if (currentZoom != 1) { long long freq = wxGetApp().getFrequency(); @@ -280,6 +293,12 @@ void WaterfallCanvas::OnKeyUp(wxKeyEvent& event) { zoom = 1.0; mouseZoom = 1.05; break; + case WXK_LEFT: + case WXK_NUMPAD_LEFT: + case WXK_RIGHT: + case WXK_NUMPAD_RIGHT: + freqMoving = false; + break; } } @@ -306,18 +325,20 @@ void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) { break; case WXK_RIGHT: case WXK_NUMPAD_RIGHT: - if (shiftDown) { - freq += getBandwidth() * 10; + if (isView) { + freqMove = shiftDown?5.0:1.0; + freqMoving = true; } else { - freq += getBandwidth() / 2; + freq += shiftDown?(getBandwidth() * 10):(getBandwidth() / 2); } break; case WXK_LEFT: case WXK_NUMPAD_LEFT: - if (shiftDown) { - freq -= getBandwidth() * 10; + if (isView) { + freqMove = shiftDown?-5.0:-1.0; + freqMoving = true; } else { - freq -= getBandwidth() / 2; + freq -= shiftDown?(getBandwidth() * 10):(getBandwidth() / 2); } break; case 'D': @@ -363,27 +384,7 @@ void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) { } if (freq != originalFreq) { - if (isView) { - setView(freq, getBandwidth()); - if (spectrumCanvas) { - spectrumCanvas->setView(freq, getBandwidth()); - } - - long long minFreq = wxGetApp().getFrequency()-(wxGetApp().getSampleRate()/2); - long long maxFreq = wxGetApp().getFrequency()+(wxGetApp().getSampleRate()/2); - - if (freq < minFreq) { - wxGetApp().setFrequency(freq+(wxGetApp().getSampleRate()/2)); - } - if (freq > maxFreq) { - wxGetApp().setFrequency(freq-(wxGetApp().getSampleRate()/2)); - } - } else { - if (spectrumCanvas) { - spectrumCanvas->setCenterFrequency(freq); - } - wxGetApp().setFrequency(freq); - } + updateCenterFrequency(freq); } } @@ -532,7 +533,7 @@ void WaterfallCanvas::OnMouseMoved(wxMouseEvent& event) { setStatusText("Click to create a new demodulator or hold ALT to drag range, SPACE for direct center frequency input."); } else { setStatusText( - "Click to move active demodulator frequency or hold ALT to drag range; hold SHIFT to create new. Right drag or A / Z to Zoom. Arrow keys (+SHIFT) to move center frequency; SPACE for direct input."); + "Click to move active demodulator frequency or hold ALT to drag range; hold SHIFT to create new. Right drag or wheel to Zoom. Arrow keys to navigate/zoom."); } } @@ -751,3 +752,29 @@ void WaterfallCanvas::OnMouseRightReleased(wxMouseEvent& event) { SpectrumVisualDataQueue *WaterfallCanvas::getVisualDataQueue() { return &visualDataQueue; } + +void WaterfallCanvas::updateCenterFrequency(long long freq) { + if (isView) { + setView(freq, getBandwidth()); + if (spectrumCanvas) { + spectrumCanvas->setView(freq, getBandwidth()); + } + + long long minFreq = wxGetApp().getFrequency()-(wxGetApp().getSampleRate()/2); + long long maxFreq = wxGetApp().getFrequency()+(wxGetApp().getSampleRate()/2); + + if (freq < minFreq) { + wxGetApp().setFrequency(freq+(wxGetApp().getSampleRate()/2)); + } + if (freq > maxFreq) { + wxGetApp().setFrequency(freq-(wxGetApp().getSampleRate()/2)); + } + } else { + if (spectrumCanvas) { + spectrumCanvas->setCenterFrequency(freq); + } + wxGetApp().setFrequency(freq); + } + +} + diff --git a/src/visual/WaterfallCanvas.h b/src/visual/WaterfallCanvas.h index 2f51ec3..28d0070 100644 --- a/src/visual/WaterfallCanvas.h +++ b/src/visual/WaterfallCanvas.h @@ -45,6 +45,8 @@ private: void OnMouseEnterWindow(wxMouseEvent& event); void OnMouseLeftWindow(wxMouseEvent& event); + void updateCenterFrequency(long long freq); + std::vector spectrum_points; SpectrumCanvas *spectrumCanvas; @@ -59,6 +61,8 @@ private: int dragOfs; float mouseZoom, zoom; + bool freqMoving; + long double freqMove; float hoverAlpha; SpectrumVisualDataQueue visualDataQueue; From 03c8619c5a203afc04c018d10b73411e10b0951e Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Wed, 19 Aug 2015 17:06:06 -0400 Subject: [PATCH 11/19] Carry / preset mute state; mute button visual tweaks --- src/AppFrame.cpp | 15 +++++++++++++++ src/demod/DemodulatorMgr.cpp | 9 +++++++++ src/demod/DemodulatorMgr.h | 5 ++++- src/visual/ModeSelectorCanvas.cpp | 28 ++++++++++++++++++++++------ src/visual/ModeSelectorCanvas.h | 6 ++++++ src/visual/ModeSelectorContext.cpp | 12 +++++++++--- src/visual/ModeSelectorContext.h | 2 +- src/visual/WaterfallCanvas.cpp | 1 + 8 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index d4ac828..e843109 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -115,6 +115,8 @@ AppFrame::AppFrame() : demodMuteButton = new ModeSelectorCanvas(this, attribList); demodMuteButton->addChoice(1, "M"); + demodMuteButton->setPadding(-1,-1); + demodMuteButton->setHighlightColor(RGBA4f(0.8,0.2,0.2)); demodMuteButton->setHelpTip("Demodulator Mute Toggle"); demodMuteButton->setToggleMode(true); @@ -492,6 +494,7 @@ void AppFrame::OnMenu(wxCommandEvent& event) { wxGetApp().setFrequency(100000000); wxGetApp().getDemodMgr().setLastDemodulatorType(DEMOD_TYPE_FM); demodModeSelector->setSelection(1); + wxGetApp().getDemodMgr().setLastMuted(false); wxGetApp().getDemodMgr().setLastStereo(false); wxGetApp().getDemodMgr().setLastBandwidth(DEFAULT_DEMOD_BW); wxGetApp().getDemodMgr().setLastGain(1.0); @@ -716,12 +719,15 @@ void AppFrame::OnIdle(wxIdleEvent& event) { } else if (!demod->isMuted() && muteMode == 1) { demod->setMuted(true); } + wxGetApp().getDemodMgr().setLastMuted(demod->isMuted()); demodMuteButton->clearModeChanged(); } else { if (demod->isMuted() && muteMode == -1) { demodMuteButton->setSelection(1); + wxGetApp().getDemodMgr().setLastMuted(demod->isMuted()); } else if (!demod->isMuted() && muteMode == 1) { demodMuteButton->setSelection(-1); + wxGetApp().getDemodMgr().setLastMuted(demod->isMuted()); } } @@ -762,6 +768,15 @@ void AppFrame::OnIdle(wxIdleEvent& event) { spectrumCanvas->setCenterFrequency(wxGetApp().getFrequency()); waterfallCanvas->setCenterFrequency(wxGetApp().getFrequency()); } + if (demodMuteButton->modeChanged()) { + int muteMode = demodMuteButton->getSelection(); + if (muteMode == -1) { + wxGetApp().getDemodMgr().setLastMuted(false); + } else if (muteMode == 1) { + wxGetApp().getDemodMgr().setLastMuted(true); + } + demodMuteButton->clearModeChanged(); + } } if (demodTuner->getMouseTracker()->mouseInView()) { diff --git a/src/demod/DemodulatorMgr.cpp b/src/demod/DemodulatorMgr.cpp index 4a9a11d..4d0671e 100644 --- a/src/demod/DemodulatorMgr.cpp +++ b/src/demod/DemodulatorMgr.cpp @@ -223,3 +223,12 @@ void DemodulatorMgr::setLastStereo(bool lastStereo) { this->lastStereo = lastStereo; } + +bool DemodulatorMgr::isLastMuted() const { + return lastMuted; +} + +void DemodulatorMgr::setLastMuted(bool lastMuted) { + this->lastMuted = lastMuted; +} + diff --git a/src/demod/DemodulatorMgr.h b/src/demod/DemodulatorMgr.h index 24baeb1..56f752f 100644 --- a/src/demod/DemodulatorMgr.h +++ b/src/demod/DemodulatorMgr.h @@ -40,6 +40,9 @@ public: bool isLastStereo() const; void setLastStereo(bool lastStereo); + bool isLastMuted() const; + void setLastMuted(bool lastMuted); + private: void garbageCollect(); void updateLastState(); @@ -55,5 +58,5 @@ private: bool lastSquelchEnabled; float lastSquelch; float lastGain; - bool lastStereo; + bool lastStereo, lastMuted; }; diff --git a/src/visual/ModeSelectorCanvas.cpp b/src/visual/ModeSelectorCanvas.cpp index c26de45..425fad6 100644 --- a/src/visual/ModeSelectorCanvas.cpp +++ b/src/visual/ModeSelectorCanvas.cpp @@ -25,9 +25,11 @@ EVT_ENTER_WINDOW(ModeSelectorCanvas::OnMouseEnterWindow) wxEND_EVENT_TABLE() ModeSelectorCanvas::ModeSelectorCanvas(wxWindow *parent, int *attribList) : -InteractiveCanvas(parent, attribList), numChoices(0), currentSelection(-1), toggleMode(false), inputChanged(false) { +InteractiveCanvas(parent, attribList), numChoices(0), currentSelection(-1), toggleMode(false), inputChanged(false), padX(4.0), padY(4.0), highlightOverride(false) { glContext = new ModeSelectorContext(this, &wxGetApp().GetContext(this)); + + highlightColor = RGBA4f(1.0,1.0,1.0,1.0); } ModeSelectorCanvas::~ModeSelectorCanvas() { @@ -59,10 +61,15 @@ void ModeSelectorCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { int yval = getHoveredSelection(); for (int i = 0; i < numChoices; i++) { - if (yval == i) { - glContext->DrawSelector(selections[i].label, i, numChoices, true, ThemeMgr::mgr.currentTheme->buttonHighlight.r, ThemeMgr::mgr.currentTheme->buttonHighlight.g, ThemeMgr::mgr.currentTheme->buttonHighlight.b, 1.0); + if (yval == i && !highlightOverride) { + RGBA4f hc = ThemeMgr::mgr.currentTheme->buttonHighlight; + glContext->DrawSelector(selections[i].label, i, numChoices, true, hc.r, hc.g, hc.b, 1.0, padX, padY); } else { - glContext->DrawSelector(selections[i].label, i, numChoices, i == currentSelection, ThemeMgr::mgr.currentTheme->button.r, ThemeMgr::mgr.currentTheme->button.g, ThemeMgr::mgr.currentTheme->button.b, 1.0); + RGBA4f hc = ThemeMgr::mgr.currentTheme->button; + if (highlightOverride) { + hc = highlightColor; + } + glContext->DrawSelector(selections[i].label, i, numChoices, i == currentSelection, hc.r, hc.g, hc.b, 1.0, padX, padY); } } @@ -112,7 +119,7 @@ void ModeSelectorCanvas::OnMouseReleased(wxMouseEvent& event) { currentSelection = selectedButton; - SetCursor (wxCURSOR_ARROW); + SetCursor (wxCURSOR_HAND); } void ModeSelectorCanvas::OnMouseLeftWindow(wxMouseEvent& event) { @@ -123,7 +130,7 @@ void ModeSelectorCanvas::OnMouseLeftWindow(wxMouseEvent& event) { void ModeSelectorCanvas::OnMouseEnterWindow(wxMouseEvent& event) { InteractiveCanvas::mouseTracker.OnMouseEnterWindow(event); - SetCursor (wxCURSOR_ARROW); + SetCursor (wxCURSOR_HAND); if (!helpTip.empty()) { setStatusText(helpTip); } @@ -171,3 +178,12 @@ void ModeSelectorCanvas::clearModeChanged() { inputChanged = false; } +void ModeSelectorCanvas::setPadding(float padX, float padY) { + this->padX = padX; + this->padY = padY; +} + +void ModeSelectorCanvas::setHighlightColor(RGBA4f hc) { + this->highlightColor = hc; + this->highlightOverride = true; +} diff --git a/src/visual/ModeSelectorCanvas.h b/src/visual/ModeSelectorCanvas.h index 68de86e..2c76294 100644 --- a/src/visual/ModeSelectorCanvas.h +++ b/src/visual/ModeSelectorCanvas.h @@ -40,6 +40,9 @@ public: bool modeChanged(); void clearModeChanged(); + void setPadding(float padX, float padY); + void setHighlightColor(RGBA4f hc); + private: void setNumChoices(int numChoices_in); @@ -61,6 +64,9 @@ private: bool toggleMode; bool inputChanged; std::vector selections; + float padX, padY; + RGBA4f highlightColor; + bool highlightOverride; // wxDECLARE_EVENT_TABLE(); }; diff --git a/src/visual/ModeSelectorContext.cpp b/src/visual/ModeSelectorContext.cpp index 2e0f2eb..d92b06b 100644 --- a/src/visual/ModeSelectorContext.cpp +++ b/src/visual/ModeSelectorContext.cpp @@ -22,7 +22,7 @@ void ModeSelectorContext::DrawBegin() { glDisable(GL_TEXTURE_2D); } -void ModeSelectorContext::DrawSelector(std::string label, int c, int cMax, bool on, float r, float g, float b, float a) { +void ModeSelectorContext::DrawSelector(std::string label, int c, int cMax, bool on, float r, float g, float b, float a, float px, float py) { GLint vp[4]; glGetIntegerv( GL_VIEWPORT, vp); @@ -42,15 +42,21 @@ void ModeSelectorContext::DrawSelector(std::string label, int c, int cMax, bool float y = 1.0 - ((float) (c+1) / (float) cMax * 2.0); float height = (2.0 / (float) cMax); - float padX = (4.0 / viewWidth); - float padY = (4.0 / viewHeight); + float padX = (px / viewWidth); + float padY = (py / viewHeight); + if (a < 1.0) { + glEnable(GL_BLEND); + } glBegin(on?GL_QUADS:GL_LINE_LOOP); glVertex2f(-1.0 + padX, y + padY); glVertex2f(1.0 - padX, y + padY); glVertex2f(1.0 - padX, y + height - padY); glVertex2f(-1.0 + padX, y + height - padY); glEnd(); + if (a < 1.0) { + glDisable(GL_BLEND); + } if (on) { glColor4f(0, 0, 0, a); diff --git a/src/visual/ModeSelectorContext.h b/src/visual/ModeSelectorContext.h index 0e66e44..2d84fa3 100644 --- a/src/visual/ModeSelectorContext.h +++ b/src/visual/ModeSelectorContext.h @@ -12,6 +12,6 @@ public: ModeSelectorContext(ModeSelectorCanvas *canvas, wxGLContext *sharedContext); void DrawBegin(); - void DrawSelector(std::string label, int c, int cMax, bool on, float r, float g, float b, float a); + void DrawSelector(std::string label, int c, int cMax, bool on, float r, float g, float b, float a, float padx, float pady); void DrawEnd(); }; diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index bc3140f..108a1e2 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -612,6 +612,7 @@ void WaterfallCanvas::OnMouseReleased(wxMouseEvent& event) { demod->setSquelchEnabled(mgr->isLastSquelchEnabled()); demod->setStereo(mgr->isLastStereo()); demod->setGain(mgr->getLastGain()); + demod->setMuted(mgr->isLastMuted()); demod->run(); From 7a0f523eaf9463aa1efa09ee9fa1e6fe6498adb2 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Wed, 19 Aug 2015 23:22:46 -0400 Subject: [PATCH 12/19] Test of draggable scope area + fixes - Will be able to drag back/forth to cycle scope/spectrum/plot - Fix for two crashes --- external/cubicvr2/math/mat4.h | 33 +++++++++-- src/AppFrame.cpp | 5 +- src/panel/ScopePanel.cpp | 8 ++- src/ui/GLPanel.cpp | 8 +++ src/ui/GLPanel.h | 1 + src/util/ThreadQueue.h | 2 + src/visual/ScopeCanvas.cpp | 108 ++++++++++++++++++++++++++++++++-- src/visual/ScopeCanvas.h | 14 ++++- 8 files changed, 161 insertions(+), 18 deletions(-) diff --git a/external/cubicvr2/math/mat4.h b/external/cubicvr2/math/mat4.h index 0059cd8..b847d26 100644 --- a/external/cubicvr2/math/mat4.h +++ b/external/cubicvr2/math/mat4.h @@ -74,13 +74,24 @@ namespace CubicVR { return mOut; } - - static mat4 perspective(__float fovy, __float aspect, __float znear, __float zfar) { + static mat4 frustum(__float left, __float right, __float bottom, __float top, __float zNear, __float zFar) { + __float A = (right + left) / (right - left); + __float B = (top + bottom) / (top - bottom); + __float C = - (zFar + zNear) / (zFar - zNear); + __float D = - (-2.0 * zFar * zNear) / (zFar - zNear); + + + return mat4((2.0 * zNear) / (right - left), 0, A, 0, + 0, (2.0 * zNear) / (top - bottom), B, 0, + 0, 0, C, D, + 0, 0, -1, 0); + }; + static mat4 perspective(__float fovy, __float aspect, __float zNear, __float zFar) { __float yFac = tan(fovy * (float)M_PI / 360.0f); __float xFac = yFac * aspect; - return mat4( - 1.0f / xFac, 0, 0, 0, 0, 1.0f / yFac, 0, 0, 0, 0, -(zfar + znear) / (zfar - znear), -1, 0, 0, -(2.0f * zfar * znear) / (zfar - znear), 0); + return mat4::frustum(-xFac, xFac, -yFac, yFac, zNear, zFar); + }; static mat4 ortho(__float left,__float right,__float bottom,__float top,__float znear,__float zfar) { return mat4(2.0f / (right - left), 0, 0, 0, 0, 2.0f / (top - bottom), 0, 0, 0, 0, -2.0f / (zfar - znear), 0, -(left + right) / (right - left), -(top + bottom) / (top - bottom), -(zfar + znear) / (zfar - znear), 1); @@ -300,8 +311,18 @@ namespace CubicVR { return mat4::translate(-eyex,-eyey,-eyez) * mat4( side[0], up[0], -forward[0], 0, side[1], up[1], -forward[1], 0, side[2], up[2], -forward[2], 0, 0, 0, 0, 1); }; + + static vec3 unProject(mat4 pMatrix, mat4 mvMatrix, float width, float height, float winx, float winy, float winz) { + vec4 p(((winx / width) * 2.0) - 1.0, -(((winy / height) * 2.0) - 1.0), 1.0, 1.0); + + vec4 invp = mat4::vec4_multiply(mat4::vec4_multiply(p, mat4::inverse(pMatrix)), mat4::inverse(mvMatrix)); + + vec3 result(invp[0] / invp[3], invp[1] / invp[3], invp[2] / invp[3]); + + return result; + }; }; - -} + } + #endif /* defined(__CubicVR2__mat4__) */ diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index e843109..f85d2b2 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -153,11 +153,12 @@ AppFrame::AppFrame() : waterfallCanvas->setup(2048, 512); waterfallDataThread = new FFTVisualDataThread(); - t_FFTData = new std::thread(&FFTVisualDataThread::threadMain, waterfallDataThread); waterfallDataThread->setInputQueue("IQDataInput", wxGetApp().getWaterfallVisualQueue()); waterfallDataThread->setOutputQueue("FFTDataOutput", waterfallCanvas->getVisualDataQueue()); - + + t_FFTData = new std::thread(&FFTVisualDataThread::threadMain, waterfallDataThread); + waterfallSpeedMeter = new MeterCanvas(this, attribList); waterfallSpeedMeter->setHelpTip("Waterfall speed, click or drag to adjust (max 1024 lines per second)"); waterfallSpeedMeter->setMax(sqrt(1024)); diff --git a/src/panel/ScopePanel.cpp b/src/panel/ScopePanel.cpp index c50fa08..f72a14c 100644 --- a/src/panel/ScopePanel.cpp +++ b/src/panel/ScopePanel.cpp @@ -2,6 +2,7 @@ #include "ColorTheme.h" ScopePanel::ScopePanel() : GLPanel(), scopeMode(SCOPE_MODE_Y) { + setFill(GLPanelFillType::GLPANEL_FILL_NONE); bgPanel.setFill(GLPanelFillType::GLPANEL_FILL_GRAD_BAR_Y); bgPanelStereo[0].setFill(GLPanelFillType::GLPANEL_FILL_GRAD_BAR_Y); bgPanelStereo[0].setPosition(0, 0.5); @@ -21,14 +22,13 @@ void ScopePanel::setPoints(std::vector &points) { } void ScopePanel::drawPanelContents() { - - glLineWidth(1.0); if (scopeMode == SCOPE_MODE_Y) { bgPanel.setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground, ThemeMgr::mgr.currentTheme->scopeBackground * 2.0); bgPanel.calcTransform(transform); bgPanel.draw(); - + glLineWidth(1.0); + glEnable(GL_LINE_SMOOTH); glLoadMatrixf(transform); glColor3f(ThemeMgr::mgr.currentTheme->scopeLine.r * 0.35, ThemeMgr::mgr.currentTheme->scopeLine.g * 0.35, ThemeMgr::mgr.currentTheme->scopeLine.b * 0.35); @@ -45,8 +45,10 @@ void ScopePanel::drawPanelContents() { bgPanelStereo[1].calcTransform(transform); bgPanelStereo[1].draw(); + glLineWidth(1.0); glLoadMatrixf(transform); glColor3f(ThemeMgr::mgr.currentTheme->scopeLine.r, ThemeMgr::mgr.currentTheme->scopeLine.g, ThemeMgr::mgr.currentTheme->scopeLine.b); + glEnable(GL_LINE_SMOOTH); glBegin (GL_LINES); glVertex2f(-1.0, 0.0); glVertex2f(1.0, 0.0); diff --git a/src/ui/GLPanel.cpp b/src/ui/GLPanel.cpp index 00fa2be..7d06c81 100644 --- a/src/ui/GLPanel.cpp +++ b/src/ui/GLPanel.cpp @@ -8,6 +8,9 @@ using namespace CubicVR; GLPanel::GLPanel() : fillType(GLPANEL_FILL_SOLID), contentsVisible(true), transform(mat4::identity()) { pos[0] = 0.0f; pos[1] = 0.0f; + rot[0] = 0.0f; + rot[1] = 0.0f; + rot[2] = 0.0f; size[0] = 1.0f; size[1] = 1.0f; fill[0] = RGBA4f(0.5,0.5,0.5); @@ -241,6 +244,11 @@ void GLPanel::drawPanelContents() { void GLPanel::calcTransform(mat4 transform_in) { // compute local transform localTransform = mat4::translate(pos[0], pos[1], 0) * mat4::scale(size[0], size[1], 1); + + if (rot[0] || rot[1] || rot[2]) { + localTransform *= mat4::rotate(rot[0], rot[1], rot[2]); + } + // compute global transform transform = transform_in * localTransform; diff --git a/src/ui/GLPanel.h b/src/ui/GLPanel.h index b87bac2..0f3e3b6 100644 --- a/src/ui/GLPanel.h +++ b/src/ui/GLPanel.h @@ -36,6 +36,7 @@ public: typedef enum GLPanelFillType { GLPANEL_FILL_NONE, GLPANEL_FILL_SOLID, GLPANEL_FILL_GRAD_X, GLPANEL_FILL_GRAD_Y, GLPANEL_FILL_GRAD_BAR_X, GLPANEL_FILL_GRAD_BAR_Y } GLPanelFillType; typedef enum GLPanelCoordinateSystem { GLPANEL_Y_DOWN_ZERO_ONE, GLPANEL_Y_UP_ZERO_ONE, GLPANEL_Y_UP, GLPANEL_Y_DOWN } GLPanelCoordinateSystem; float pos[2]; + float rot[3]; float size[2]; float view[2]; GLPanelFillType fillType; diff --git a/src/util/ThreadQueue.h b/src/util/ThreadQueue.h index 8ba5e0a..a2d693e 100644 --- a/src/util/ThreadQueue.h +++ b/src/util/ThreadQueue.h @@ -49,6 +49,8 @@ public: * \param[in] item An item. */ void set_max_num_items(unsigned int max_num_items) { + std::lock_guard < std::mutex > lock(m_mutex); + m_max_num_items = max_num_items; } diff --git a/src/visual/ScopeCanvas.cpp b/src/visual/ScopeCanvas.cpp index a4cd4be..220997f 100644 --- a/src/visual/ScopeCanvas.cpp +++ b/src/visual/ScopeCanvas.cpp @@ -14,18 +14,28 @@ #include "CubicSDRDefs.h" #include "AppFrame.h" #include +#include wxBEGIN_EVENT_TABLE(ScopeCanvas, wxGLCanvas) EVT_PAINT(ScopeCanvas::OnPaint) EVT_IDLE(ScopeCanvas::OnIdle) +EVT_MOTION(ScopeCanvas::OnMouseMoved) +EVT_LEFT_DOWN(ScopeCanvas::OnMouseDown) +EVT_LEFT_UP(ScopeCanvas::OnMouseReleased) +EVT_RIGHT_DOWN(ScopeCanvas::OnMouseRightDown) +EVT_RIGHT_UP(ScopeCanvas::OnMouseRightReleased) +EVT_LEAVE_WINDOW(ScopeCanvas::OnMouseLeftWindow) +EVT_ENTER_WINDOW(ScopeCanvas::OnMouseEnterWindow) wxEND_EVENT_TABLE() -ScopeCanvas::ScopeCanvas(wxWindow *parent, int *attribList) : - wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, - wxFULL_REPAINT_ON_RESIZE), stereo(false), ppmMode(false) { +ScopeCanvas::ScopeCanvas(wxWindow *parent, int *attribList) : InteractiveCanvas(parent, attribList), stereo(false), ppmMode(false), ctr(0), ctrTarget(0), dragAccel(0) { glContext = new ScopeContext(this, &wxGetApp().GetContext(this)); inputData.set_max_num_items(1); + bgPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_Y); + bgPanel.setSize(1.0, 0.5); + bgPanel.setPosition(0.0, -0.5); + panelSpacing = 0.2; } ScopeCanvas::~ScopeCanvas() { @@ -71,21 +81,74 @@ void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { initGLExtensions(); glViewport(0, 0, ClientSize.x, ClientSize.y); - + glContext->DrawBegin(); + + bgPanel.setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground * 3.0, RGBA4f(0,0,0,0)); + bgPanel.calcTransform(CubicVR::mat4::identity()); + bgPanel.draw(); + scopePanel.setMode(stereo?ScopePanel::SCOPE_MODE_2Y:ScopePanel::SCOPE_MODE_Y); - scopePanel.calcTransform(CubicVR::mat4::identity()); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glLoadMatrixf(CubicVR::mat4::perspective(45.0, 1.0, 1.0, 1000.0)); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + CubicVR::mat4 modelView = CubicVR::mat4::lookat(0, 0, -1.2, 0, 0, 0, 0, -1, 0); + + float panelWidth = 1.0; + float panelInterval = (panelWidth * 2.0 + panelSpacing); + + if (!mouseTracker.mouseDown()) { + if (!dragAccel) { + ctrTarget = round(ctr / panelInterval); + if (ctrTarget < -1.0) { + ctrTarget = -1.0; + } else if (ctrTarget > 0.0) { + ctrTarget = 0.0; + } + ctrTarget *= panelInterval; + if (ctr != ctrTarget) { + ctr += (ctrTarget-ctr)*0.2; + } + } else { + dragAccel -= dragAccel * 0.01; + if (abs(dragAccel) < 0.1 || ctr < ctrTarget-panelInterval/2.0 || ctr > ctrTarget+panelInterval/2.0 ) { + dragAccel = 0; + } else { + ctr += dragAccel; + } + } + } + + scopePanel.setPosition(ctr, 0); + float roty = atan2(scopePanel.pos[0],1.2); + scopePanel.rot[1] = -(roty * (180.0 / M_PI)); + scopePanel.calcTransform(modelView); scopePanel.draw(); + + scopePanel.setPosition(panelInterval+ctr, 0); + roty = atan2(scopePanel.pos[0],1.2); + scopePanel.rot[1] = -(roty * (180.0 / M_PI)); + scopePanel.calcTransform(modelView); + scopePanel.draw(); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); glContext->DrawTunerTitles(ppmMode); if (!deviceName.empty()) { glContext->DrawDeviceName(deviceName); } glContext->DrawEnd(); - SwapBuffers(); } + void ScopeCanvas::OnIdle(wxIdleEvent &event) { Refresh(); event.RequestMore(); @@ -94,3 +157,36 @@ void ScopeCanvas::OnIdle(wxIdleEvent &event) { ScopeRenderDataQueue *ScopeCanvas::getInputQueue() { return &inputData; } + +void ScopeCanvas::OnMouseMoved(wxMouseEvent& event) { + InteractiveCanvas::OnMouseMoved(event); + if (mouseTracker.mouseDown()) { + dragAccel = 4.0*mouseTracker.getDeltaMouseX(); + ctr += dragAccel; + } +} + +void ScopeCanvas::OnMouseWheelMoved(wxMouseEvent& event) { + +} + +void ScopeCanvas::OnMouseDown(wxMouseEvent& event) { + InteractiveCanvas::OnMouseDown(event); + +} + +void ScopeCanvas::OnMouseReleased(wxMouseEvent& event) { + InteractiveCanvas::OnMouseReleased(event); + +} + +void ScopeCanvas::OnMouseEnterWindow(wxMouseEvent& event) { + InteractiveCanvas::OnMouseEnterWindow(event); + +} + +void ScopeCanvas::OnMouseLeftWindow(wxMouseEvent& event) { + InteractiveCanvas::OnMouseLeftWindow(event); + +} + diff --git a/src/visual/ScopeCanvas.h b/src/visual/ScopeCanvas.h index 6cdff03..d5bfba5 100644 --- a/src/visual/ScopeCanvas.h +++ b/src/visual/ScopeCanvas.h @@ -10,8 +10,9 @@ #include "ScopeVisualProcessor.h" #include "ScopePanel.h" #include "fftw3.h" +#include "InteractiveCanvas.h" -class ScopeCanvas: public wxGLCanvas { +class ScopeCanvas: public InteractiveCanvas { public: ScopeCanvas(wxWindow *parent, int *attribList = NULL); ~ScopeCanvas(); @@ -26,13 +27,24 @@ public: private: void OnPaint(wxPaintEvent& event); void OnIdle(wxIdleEvent &event); + void OnMouseMoved(wxMouseEvent& event); + void OnMouseWheelMoved(wxMouseEvent& event); + void OnMouseDown(wxMouseEvent& event); + void OnMouseReleased(wxMouseEvent& event); + void OnMouseEnterWindow(wxMouseEvent& event); + void OnMouseLeftWindow(wxMouseEvent& event); ScopeRenderDataQueue inputData; ScopePanel scopePanel; + GLPanel bgPanel; ScopeContext *glContext; std::string deviceName; bool stereo; bool ppmMode; + float panelSpacing; + float ctr; + float ctrTarget; + float dragAccel; // event table wxDECLARE_EVENT_TABLE(); }; From 13140ec28c9635874e3ffdab970d4c80ebe6cfcc Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Sun, 23 Aug 2015 17:51:20 -0400 Subject: [PATCH 13/19] SpectrumVisualProcessor thread fixes, spectrum label optimize --- src/panel/SpectrumPanel.cpp | 28 ++++++++++++++----------- src/process/SpectrumVisualProcessor.cpp | 27 +++++++++++++++++------- src/process/SpectrumVisualProcessor.h | 5 +++-- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/panel/SpectrumPanel.cpp b/src/panel/SpectrumPanel.cpp index c615490..2b6f65e 100644 --- a/src/panel/SpectrumPanel.cpp +++ b/src/panel/SpectrumPanel.cpp @@ -132,27 +132,31 @@ void SpectrumPanel::drawPanelContents() { long long leftFreq = (double) freq - ((double) bandwidth / 2.0); long long rightFreq = leftFreq + (double) bandwidth; - - long long firstMhz = (leftFreq / 1000000) * 1000000; - long double mhzStart = ((long double) (firstMhz - leftFreq) / (long double) (rightFreq - leftFreq)) * 2.0; + + long long hzStep = 1000000; long double mhzStep = (100000.0 / (long double) (rightFreq - leftFreq)) * 2.0; - double mhzVisualStep = 0.1f; - + double mhzVisualStep = 0.1; + + std::stringstream label; + label.precision(1); + if (mhzStep * 0.5 * viewWidth < 40) { mhzStep = (250000.0 / (long double) (rightFreq - leftFreq)) * 2.0; - mhzVisualStep = 0.25f; + mhzVisualStep = 0.25; + label.precision(2); } - if (mhzStep * 0.5 * viewWidth > 400) { + if (mhzStep * 0.5 * viewWidth > 350) { mhzStep = (10000.0 / (long double) (rightFreq - leftFreq)) * 2.0; - mhzVisualStep = 0.01f; + mhzVisualStep = 0.01; + label.precision(2); } - - long double currentMhz = trunc(floor(firstMhz / 1000000.0)); - std::stringstream label; - label.precision(2); + long long firstMhz = (leftFreq / hzStep) * hzStep; + long double mhzStart = ((long double) (firstMhz - leftFreq) / (long double) (rightFreq - leftFreq)) * 2.0; + long double currentMhz = trunc(floor(firstMhz / (long double)1000000.0)); + double hPos = 1.0 - (16.0 / viewHeight); double lMhzPos = 1.0 - (5.0 / viewHeight); diff --git a/src/process/SpectrumVisualProcessor.cpp b/src/process/SpectrumVisualProcessor.cpp index 9322462..8dc890d 100644 --- a/src/process/SpectrumVisualProcessor.cpp +++ b/src/process/SpectrumVisualProcessor.cpp @@ -14,7 +14,7 @@ SpectrumVisualProcessor::SpectrumVisualProcessor() : lastInputBandwidth(0), last fft_ceil_ma = fft_ceil_maa = 100.0; fft_floor_ma = fft_floor_maa = 0.0; - desiredInputSize = 0; + desiredInputSize.store(0); fft_average_rate = 0.65; } @@ -27,19 +27,25 @@ bool SpectrumVisualProcessor::isView() { } void SpectrumVisualProcessor::setView(bool bView) { + busy_run.lock(); is_view.store(bView); + busy_run.unlock(); } void SpectrumVisualProcessor::setFFTAverageRate(float fftAverageRate) { - this->fft_average_rate = fftAverageRate; + busy_run.lock(); + this->fft_average_rate.store(fftAverageRate); + busy_run.unlock(); } float SpectrumVisualProcessor::getFFTAverageRate() { - return this->fft_average_rate; + return this->fft_average_rate.load(); } void SpectrumVisualProcessor::setCenterFrequency(long long centerFreq_in) { + busy_run.lock(); centerFreq.store(centerFreq_in); + busy_run.unlock(); } long long SpectrumVisualProcessor::getCenterFrequency() { @@ -47,7 +53,9 @@ long long SpectrumVisualProcessor::getCenterFrequency() { } void SpectrumVisualProcessor::setBandwidth(long bandwidth_in) { + busy_run.lock(); bandwidth.store(bandwidth_in); + busy_run.unlock(); } long SpectrumVisualProcessor::getBandwidth() { @@ -55,12 +63,14 @@ long SpectrumVisualProcessor::getBandwidth() { } int SpectrumVisualProcessor::getDesiredInputSize() { - return desiredInputSize; + return desiredInputSize.load(); } void SpectrumVisualProcessor::setup(int fftSize_in) { + busy_run.lock(); + fftSize = fftSize_in; - desiredInputSize = fftSize; + desiredInputSize.store(fftSize); if (fftwInput) { free(fftwInput); @@ -82,7 +92,7 @@ void SpectrumVisualProcessor::setup(int fftSize_in) { fftwf_destroy_plan(fftw_plan); } fftw_plan = fftwf_plan_dft_1d(fftSize, fftwInput, fftwOutput, FFTW_FORWARD, FFTW_ESTIMATE); - + busy_run.unlock(); } void SpectrumVisualProcessor::process() { @@ -102,6 +112,7 @@ void SpectrumVisualProcessor::process() { } iqData->busy_rw.lock(); + busy_run.lock(); std::vector *data = &iqData->data; @@ -118,6 +129,7 @@ void SpectrumVisualProcessor::process() { if (!iqData->frequency || !iqData->sampleRate) { iqData->decRefCount(); iqData->busy_rw.unlock(); + busy_run.unlock(); return; } @@ -125,7 +137,7 @@ void SpectrumVisualProcessor::process() { int desired_input_size = fftSize / resamplerRatio; - this->desiredInputSize = desired_input_size; + this->desiredInputSize.store(desired_input_size); if (iqData->data.size() < desired_input_size) { // std::cout << "fft underflow, desired: " << desired_input_size << " actual:" << input->data.size() << std::endl; @@ -304,5 +316,6 @@ void SpectrumVisualProcessor::process() { iqData->decRefCount(); iqData->busy_rw.unlock(); + busy_run.unlock(); } diff --git a/src/process/SpectrumVisualProcessor.h b/src/process/SpectrumVisualProcessor.h index 8e1e694..149c170 100644 --- a/src/process/SpectrumVisualProcessor.h +++ b/src/process/SpectrumVisualProcessor.h @@ -53,7 +53,7 @@ private: double fft_ceil_ma, fft_ceil_maa; double fft_floor_ma, fft_floor_maa; - float fft_average_rate; + std::atomic fft_average_rate; std::vector fft_result; std::vector fft_result_ma; @@ -66,7 +66,8 @@ private: std::vector shiftBuffer; std::vector resampleBuffer; - int desiredInputSize; + std::atomic_int desiredInputSize; + std::mutex busy_run; }; From c30cce9114b87fbda0423fec73dc94f0d272658a Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Mon, 24 Aug 2015 01:31:37 -0400 Subject: [PATCH 14/19] Add functional Spectrum view to demodulator visuals - Might need to do some renaming from Scope->AVDisplay or something for ScopeCanvas to avoid confusion. --- src/AppFrame.cpp | 7 + src/audio/AudioThread.h | 1 + src/demod/DemodulatorThread.cpp | 12 +- src/demod/DemodulatorThread.h | 2 +- src/panel/SpectrumPanel.cpp | 50 +++++-- src/panel/SpectrumPanel.h | 6 +- src/process/ScopeVisualProcessor.cpp | 204 +++++++++++++++++++++++---- src/process/ScopeVisualProcessor.h | 30 ++++ src/util/ThreadQueue.h | 19 ++- src/visual/ScopeCanvas.cpp | 136 ++++++++++++++---- src/visual/ScopeCanvas.h | 13 ++ 11 files changed, 405 insertions(+), 75 deletions(-) diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index f85d2b2..0a20f77 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -90,7 +90,9 @@ AppFrame::AppFrame() : demodTray->AddSpacer(1); scopeCanvas = new ScopeCanvas(this, attribList); + scopeCanvas->setHelpTip("Audio Visuals, drag left/right to toggle Scope or Spectrum."); demodScopeTray->Add(scopeCanvas, 8, wxEXPAND | wxALL, 0); + wxGetApp().getScopeProcessor()->setup(2048); wxGetApp().getScopeProcessor()->attachOutput(scopeCanvas->getInputQueue()); demodScopeTray->AddSpacer(1); @@ -790,6 +792,11 @@ void AppFrame::OnIdle(wxIdleEvent& event) { scopeCanvas->setPPMMode(demodTuner->isAltDown()); + scopeCanvas->setShowDb(spectrumCanvas->getShowDb()); + wxGetApp().getScopeProcessor()->setScopeEnabled(scopeCanvas->scopeVisible()); + wxGetApp().getScopeProcessor()->setSpectrumEnabled(scopeCanvas->spectrumVisible()); + wxGetApp().getAudioVisualQueue()->set_max_num_items((scopeCanvas->scopeVisible()?1:0) + (scopeCanvas->spectrumVisible()?1:0)); + wxGetApp().getScopeProcessor()->run(); wxGetApp().getSpectrumDistributor()->run(); diff --git a/src/audio/AudioThread.h b/src/audio/AudioThread.h index d5dcb61..01ebda5 100644 --- a/src/audio/AudioThread.h +++ b/src/audio/AudioThread.h @@ -14,6 +14,7 @@ class AudioThreadInput: public ReferenceCounter { public: long long frequency; + int inputRate; int sampleRate; int channels; float peak; diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index 485fd54..2ed31b0 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -310,6 +310,7 @@ void DemodulatorThread::run() { ati = outputBuffers.getBuffer(); ati->sampleRate = audioSampleRate; + ati->inputRate = inp->sampleRate; ati->setRefCount(1); if (demodulatorType == DEMOD_TYPE_RAW) { @@ -359,7 +360,9 @@ void DemodulatorThread::run() { if (ati && audioVisOutputQueue != NULL && audioVisOutputQueue->empty()) { ati_vis->busy_update.lock(); - + ati_vis->sampleRate = inp->sampleRate; + ati_vis->inputRate = inp->sampleRate; + int num_vis = DEMOD_VIS_SIZE; if (demodulatorType == DEMOD_TYPE_RAW || (stereo && inp->sampleRate >= 100000)) { ati_vis->channels = 2; @@ -377,6 +380,8 @@ void DemodulatorThread::run() { } } else { for (int i = 0; i < stereoSize / 2; i++) { + ati_vis->inputRate = audioSampleRate; + ati_vis->sampleRate = 36000; ati_vis->data[i] = ati->data[i * 2]; ati_vis->data[i + stereoSize / 2] = ati->data[i * 2 + 1]; } @@ -384,7 +389,7 @@ void DemodulatorThread::run() { } else { ati_vis->channels = 1; if (numAudioWritten > bufSize) { - + ati_vis->inputRate = audioSampleRate; if (num_vis > numAudioWritten) { num_vis = numAudioWritten; } @@ -399,9 +404,8 @@ void DemodulatorThread::run() { // std::cout << "Signal: " << agc_crcf_get_signal_level(agc) << " -- " << agc_crcf_get_rssi(agc) << "dB " << std::endl; } - audioVisOutputQueue->push(ati_vis); - ati_vis->busy_update.unlock(); + audioVisOutputQueue->push(ati_vis); } if (ati != NULL) { diff --git a/src/demod/DemodulatorThread.h b/src/demod/DemodulatorThread.h index 7f7e417..91d6b0c 100644 --- a/src/demod/DemodulatorThread.h +++ b/src/demod/DemodulatorThread.h @@ -8,7 +8,7 @@ typedef ThreadQueue DemodulatorThreadOutputQueue; -#define DEMOD_VIS_SIZE 1024 +#define DEMOD_VIS_SIZE 2048 class DemodulatorThread : public IOThread { public: diff --git a/src/panel/SpectrumPanel.cpp b/src/panel/SpectrumPanel.cpp index 2b6f65e..142e1a4 100644 --- a/src/panel/SpectrumPanel.cpp +++ b/src/panel/SpectrumPanel.cpp @@ -5,7 +5,7 @@ #include #include "ColorTheme.h" -SpectrumPanel::SpectrumPanel() : floorValue(0), ceilValue(1), showDb(false) { +SpectrumPanel::SpectrumPanel() : floorValue(0), ceilValue(1), showDb(false), fftSize(2048) { setFill(GLPANEL_FILL_GRAD_Y); setFillColor(ThemeMgr::mgr.currentTheme->fftBackground * 2.0, ThemeMgr::mgr.currentTheme->fftBackground); @@ -51,6 +51,14 @@ long long SpectrumPanel::getBandwidth() { return bandwidth; } +void SpectrumPanel::setFFTSize(int fftSize_in) { + this->fftSize = fftSize_in; +} + +int SpectrumPanel::getFFTSize() { + return fftSize; +} + void SpectrumPanel::setShowDb(bool showDb) { this->showDb = showDb; if (showDb) { @@ -133,7 +141,7 @@ void SpectrumPanel::drawPanelContents() { long long leftFreq = (double) freq - ((double) bandwidth / 2.0); long long rightFreq = leftFreq + (double) bandwidth; - long long hzStep = 1000000; + long long hzStep = 100000; long double mhzStep = (100000.0 / (long double) (rightFreq - leftFreq)) * 2.0; double mhzVisualStep = 0.1; @@ -144,10 +152,37 @@ void SpectrumPanel::drawPanelContents() { if (mhzStep * 0.5 * viewWidth < 40) { mhzStep = (250000.0 / (long double) (rightFreq - leftFreq)) * 2.0; mhzVisualStep = 0.25; - label.precision(2); - } - if (mhzStep * 0.5 * viewWidth > 350) { + if (mhzStep * 0.5 * viewWidth < 40) { + mhzStep = (500000.0 / (long double) (rightFreq - leftFreq)) * 2.0; + mhzVisualStep = 0.5; + } + + if (mhzStep * 0.5 * viewWidth < 40) { + mhzStep = (1000000.0 / (long double) (rightFreq - leftFreq)) * 2.0; + mhzVisualStep = 1.0; + } + + if (mhzStep * 0.5 * viewWidth < 40) { + mhzStep = (2500000.0 / (long double) (rightFreq - leftFreq)) * 2.0; + mhzVisualStep = 2.5; + } + + if (mhzStep * 0.5 * viewWidth < 40) { + mhzStep = (5000000.0 / (long double) (rightFreq - leftFreq)) * 2.0; + mhzVisualStep = 5.0; + } + + if (mhzStep * 0.5 * viewWidth < 40) { + mhzStep = (10000000.0 / (long double) (rightFreq - leftFreq)) * 2.0; + mhzVisualStep = 10.0; + } + + if (mhzStep * 0.5 * viewWidth < 40) { + mhzStep = (50000000.0 / (long double) (rightFreq - leftFreq)) * 2.0; + mhzVisualStep = 50.0; + } + } else if (mhzStep * 0.5 * viewWidth > 350) { mhzStep = (10000.0 / (long double) (rightFreq - leftFreq)) * 2.0; mhzVisualStep = 0.01; label.precision(2); @@ -193,21 +228,20 @@ void SpectrumPanel::drawPanelContents() { glLineWidth(1.0); - if (showDb) { float dbPanelWidth = (1.0/viewWidth)*75.0; float dbPanelHeight = (1.0/viewHeight)*14.0; std::stringstream ssLabel; - ssLabel << std::fixed << std::setprecision(1) << (20.0 * log10(2.0*(getCeilValue())/2048.0)) << "dB"; + ssLabel << std::fixed << std::setprecision(1) << (20.0 * log10(2.0*(getCeilValue())/(double)fftSize)) << "dB"; dbPanelCeil.setText(ssLabel.str(), GLFont::GLFONT_ALIGN_RIGHT); dbPanelCeil.setSize(dbPanelWidth, dbPanelHeight); dbPanelCeil.setPosition(-1.0 + dbPanelWidth, 1.0 - dbPanelHeight); ssLabel.str(""); - ssLabel << (20.0 * log10(2.0*(getFloorValue())/2048.0)) << "dB"; + ssLabel << (20.0 * log10(2.0*(getFloorValue())/(double)fftSize)) << "dB"; dbPanelFloor.setText(ssLabel.str(), GLFont::GLFONT_ALIGN_RIGHT); dbPanelFloor.setSize(dbPanelWidth, dbPanelHeight); diff --git a/src/panel/SpectrumPanel.h b/src/panel/SpectrumPanel.h index 6ffa3f8..3ccd747 100644 --- a/src/panel/SpectrumPanel.h +++ b/src/panel/SpectrumPanel.h @@ -19,7 +19,10 @@ public: void setBandwidth(long long bandwidth); long long getBandwidth(); - + + void setFFTSize(int fftSize_in); + int getFFTSize(); + void setShowDb(bool showDb); bool getShowDb(); @@ -28,6 +31,7 @@ protected: private: float floorValue, ceilValue; + int fftSize; long long freq; long long bandwidth; std::vector points; diff --git a/src/process/ScopeVisualProcessor.cpp b/src/process/ScopeVisualProcessor.cpp index 15669da..1cc4d24 100644 --- a/src/process/ScopeVisualProcessor.cpp +++ b/src/process/ScopeVisualProcessor.cpp @@ -1,4 +1,54 @@ #include "ScopeVisualProcessor.h" +#include +#include + +ScopeVisualProcessor::ScopeVisualProcessor(): fftInData(NULL), fftwOutput(NULL), fftw_plan(NULL), maxScopeSamples(1024) { + scopeEnabled.store(true); + spectrumEnabled.store(true); + fft_average_rate = 0.65; +} + +ScopeVisualProcessor::~ScopeVisualProcessor() { + if (fftInData) { + free(fftInData); + } + if (fftwOutput) { + free(fftwOutput); + } + if (fftw_plan) { + fftwf_destroy_plan(fftw_plan); + } +} + + +void ScopeVisualProcessor::setup(int fftSize_in) { + fftSize = fftSize_in; + desiredInputSize = fftSize; + + if (fftInData) { + free(fftInData); + } + fftInData = (float*) fftwf_malloc(sizeof(float) * fftSize); + if (fftwOutput) { + free(fftwOutput); + } + fftwOutput = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize); + if (fftw_plan) { + fftwf_destroy_plan(fftw_plan); + } + fftw_plan = fftwf_plan_dft_r2c_1d(fftSize, fftInData, fftwOutput, FFTW_ESTIMATE); + //(fftSize, fftInData, fftwOutput, 0); + //(fftSize, fftwInput, fftwOutput, FFTW_R2HC, FFTW_ESTIMATE); + +} + +void ScopeVisualProcessor::setScopeEnabled(bool scopeEnable) { + scopeEnabled.store(scopeEnable); +} + +void ScopeVisualProcessor::setSpectrumEnabled(bool spectrumEnable) { + spectrumEnabled.store(spectrumEnable); +} void ScopeVisualProcessor::process() { if (!isOutputEmpty()) { @@ -11,42 +61,144 @@ void ScopeVisualProcessor::process() { if (!audioInputData) { return; } - int iMax = audioInputData->data.size(); + int i, iMax = audioInputData->data.size(); if (!iMax) { audioInputData->decRefCount(); return; } audioInputData->busy_update.lock(); - ScopeRenderData *renderData = outputBuffers.getBuffer(); - renderData->channels = audioInputData->channels; - if (renderData->waveform_points.size() != iMax * 2) { - renderData->waveform_points.resize(iMax * 2); - } + ScopeRenderData *renderData = NULL; + + if (scopeEnabled) { + iMax = audioInputData->data.size(); + if (iMax > maxScopeSamples) { + iMax = maxScopeSamples; + } - float peak = 1.0f; - - for (int i = 0; i < iMax; i++) { - float p = fabs(audioInputData->data[i]); - if (p > peak) { - peak = p; + renderData = outputBuffers.getBuffer(); + renderData->channels = audioInputData->channels; + renderData->inputRate = audioInputData->inputRate; + renderData->sampleRate = audioInputData->sampleRate; + + if (renderData->waveform_points.size() != iMax * 2) { + renderData->waveform_points.resize(iMax * 2); } - } - - if (audioInputData->channels == 2) { - for (int i = 0; i < iMax; i++) { - renderData->waveform_points[i * 2] = (((double) (i % (iMax/2)) / (double) iMax) * 2.0 - 0.5) * 2.0; - renderData->waveform_points[i * 2 + 1] = audioInputData->data[i] / peak; - } - } else { - for (int i = 0; i < iMax; i++) { - renderData->waveform_points[i * 2] = (((double) i / (double) iMax) - 0.5) * 2.0; - renderData->waveform_points[i * 2 + 1] = audioInputData->data[i] / peak; - } - } - distribute(renderData); + float peak = 1.0f; + + for (i = 0; i < iMax; i++) { + float p = fabs(audioInputData->data[i]); + if (p > peak) { + peak = p; + } + } + + if (audioInputData->channels == 2) { + iMax = audioInputData->data.size(); + if (renderData->waveform_points.size() != iMax * 2) { + renderData->waveform_points.resize(iMax * 2); + } + for (i = 0; i < iMax; i++) { + renderData->waveform_points[i * 2] = (((double) (i % (iMax/2)) / (double) iMax) * 2.0 - 0.5) * 2.0; + renderData->waveform_points[i * 2 + 1] = audioInputData->data[i] / peak; + } + } else { + for (i = 0; i < iMax; i++) { + renderData->waveform_points[i * 2] = (((double) i / (double) iMax) - 0.5) * 2.0; + renderData->waveform_points[i * 2 + 1] = audioInputData->data[i] / peak; + } + } + + renderData->spectrum = false; + + distribute(renderData); + } + + if (spectrumEnabled) { + renderData = outputBuffers.getBuffer(); + iMax = audioInputData->data.size(); + + if (audioInputData->channels==1) { + for (i = 0; i < fftSize; i++) { + if (i < iMax) { + fftInData[i] = audioInputData->data[i]; + } else { + fftInData[i] = 0; + } + } + } else if (audioInputData->channels==2) { + iMax = iMax/2; + for (i = 0; i < fftSize; i++) { + if (i < iMax) { + fftInData[i] = audioInputData->data[i] + audioInputData->data[iMax+i]; + } else { + fftInData[i] = 0; + } + } + } + + + fftwf_execute(fftw_plan); + + float fft_ceil = 0, fft_floor = 1; + + if (fft_result.size() < (fftSize/2)) { + fft_result.resize((fftSize/2)); + fft_result_ma.resize((fftSize/2)); + fft_result_maa.resize((fftSize/2)); + } + + for (i = 0; i < (fftSize/2); i++) { + float a = fftwOutput[i][0]; + float b = fftwOutput[i][1]; + fft_result[i] = sqrt( a * a + b * b); + } + + for (i = 0; i < (fftSize/2); i++) { + fft_result_maa[i] += (fft_result_ma[i] - fft_result_maa[i]) * fft_average_rate; + fft_result_ma[i] += (fft_result[i] - fft_result_ma[i]) * fft_average_rate; + + if (fft_result_maa[i] > fft_ceil) { + fft_ceil = fft_result_maa[i]; + } + if (fft_result_maa[i] < fft_floor) { + fft_floor = fft_result_maa[i]; + } + } + + fft_ceil_ma = fft_ceil_ma + (fft_ceil - fft_ceil_ma) * 0.05; + fft_ceil_maa = fft_ceil_maa + (fft_ceil_ma - fft_ceil_maa) * 0.05; + + fft_floor_ma = fft_floor_ma + (fft_floor - fft_floor_ma) * 0.05; + fft_floor_maa = fft_floor_maa + (fft_floor_ma - fft_floor_maa) * 0.05; + + int outSize = fftSize/2; + + if (audioInputData->sampleRate != audioInputData->inputRate) { + outSize = (int)floor((float)outSize * ((float)audioInputData->sampleRate/(float)audioInputData->inputRate)); + } + + if (renderData->waveform_points.size() != outSize*2) { + renderData->waveform_points.resize(outSize*2); + } + + for (i = 0; i < outSize; i++) { + float v = (log10(fft_result_maa[i]+0.25 - (fft_floor_maa-0.75)) / log10((fft_ceil_maa+0.25) - (fft_floor_maa-0.75))); + renderData->waveform_points[i * 2] = ((double) i / (double) (outSize)); + renderData->waveform_points[i * 2 + 1] = v; + } + + renderData->fft_floor = fft_floor_maa; + renderData->fft_ceil = fft_ceil_maa; + renderData->fft_size = fftSize/2; + renderData->inputRate = audioInputData->inputRate; + renderData->sampleRate = audioInputData->sampleRate; + renderData->spectrum = true; + distribute(renderData); + } + audioInputData->busy_update.unlock(); } } diff --git a/src/process/ScopeVisualProcessor.h b/src/process/ScopeVisualProcessor.h index 08ce3bb..98a2398 100644 --- a/src/process/ScopeVisualProcessor.h +++ b/src/process/ScopeVisualProcessor.h @@ -2,17 +2,47 @@ #include "VisualProcessor.h" #include "AudioThread.h" +#include "fftw3.h" class ScopeRenderData: public ReferenceCounter { public: std::vector waveform_points; + int inputRate; + int sampleRate; int channels; + bool spectrum; + int fft_size; + double fft_floor, fft_ceil; }; typedef ThreadQueue ScopeRenderDataQueue; class ScopeVisualProcessor : public VisualProcessor { +public: + ScopeVisualProcessor(); + ~ScopeVisualProcessor(); + void setup(int fftSize_in); + void setScopeEnabled(bool scopeEnable); + void setSpectrumEnabled(bool spectrumEnable); protected: void process(); ReBuffer outputBuffers; + + std::atomic_bool scopeEnabled; + std::atomic_bool spectrumEnabled; + + float *fftInData; + fftwf_complex *fftwOutput; + fftwf_plan fftw_plan; + int fftSize; + int desiredInputSize; + int maxScopeSamples; + + double fft_ceil_ma, fft_ceil_maa; + double fft_floor_ma, fft_floor_maa; + float fft_average_rate; + + std::vector fft_result; + std::vector fft_result_ma; + std::vector fft_result_maa; }; diff --git a/src/util/ThreadQueue.h b/src/util/ThreadQueue.h index a2d693e..2d88b59 100644 --- a/src/util/ThreadQueue.h +++ b/src/util/ThreadQueue.h @@ -30,13 +30,17 @@ class ThreadQueue : public ThreadQueueBase { public: /*! Create safe queue. */ - ThreadQueue() = default; + ThreadQueue() { + m_max_num_items.store(0); + }; ThreadQueue(ThreadQueue&& sq) { m_queue = std::move(sq.m_queue); + m_max_num_items.store(0); } ThreadQueue(const ThreadQueue& sq) { std::lock_guard < std::mutex > lock(sq.m_mutex); m_queue = sq.m_queue; + m_max_num_items.store(0); } /*! Destroy safe queue. */ @@ -50,8 +54,9 @@ public: */ void set_max_num_items(unsigned int max_num_items) { std::lock_guard < std::mutex > lock(m_mutex); - - m_max_num_items = max_num_items; + if (m_max_num_items.load() != max_num_items) { + m_max_num_items.store(max_num_items); + } } /** @@ -62,7 +67,7 @@ public: bool push(const value_type& item) { std::lock_guard < std::mutex > lock(m_mutex); - if (m_max_num_items > 0 && m_queue.size() > m_max_num_items) + if (m_max_num_items.load() > 0 && m_queue.size() > m_max_num_items.load()) return false; m_queue.push(item); @@ -78,7 +83,7 @@ public: bool push(const value_type&& item) { std::lock_guard < std::mutex > lock(m_mutex); - if (m_max_num_items > 0 && m_queue.size() > m_max_num_items) + if (m_max_num_items.load() > 0 && m_queue.size() > m_max_num_items.load()) return false; m_queue.push(item); @@ -219,7 +224,7 @@ public: */ bool full() const { std::lock_guard < std::mutex > lock(m_mutex); - return (m_max_num_items != 0) && (m_queue.size() >= m_max_num_items); + return (m_max_num_items.load() != 0) && (m_queue.size() >= m_max_num_items.load()); } /** @@ -280,7 +285,7 @@ private: std::queue m_queue; mutable std::mutex m_mutex; std::condition_variable m_condition; - unsigned int m_max_num_items = 0; + std::atomic_uint m_max_num_items; }; /*! Swaps the contents of two ThreadQueue objects. */ diff --git a/src/visual/ScopeCanvas.cpp b/src/visual/ScopeCanvas.cpp index 220997f..9dfaced 100644 --- a/src/visual/ScopeCanvas.cpp +++ b/src/visual/ScopeCanvas.cpp @@ -28,20 +28,51 @@ EVT_LEAVE_WINDOW(ScopeCanvas::OnMouseLeftWindow) EVT_ENTER_WINDOW(ScopeCanvas::OnMouseEnterWindow) wxEND_EVENT_TABLE() -ScopeCanvas::ScopeCanvas(wxWindow *parent, int *attribList) : InteractiveCanvas(parent, attribList), stereo(false), ppmMode(false), ctr(0), ctrTarget(0), dragAccel(0) { +ScopeCanvas::ScopeCanvas(wxWindow *parent, int *attribList) : InteractiveCanvas(parent, attribList), stereo(false), ppmMode(false), ctr(0), ctrTarget(0), dragAccel(0), helpTip("") { glContext = new ScopeContext(this, &wxGetApp().GetContext(this)); - inputData.set_max_num_items(1); + inputData.set_max_num_items(2); bgPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_Y); bgPanel.setSize(1.0, 0.5); bgPanel.setPosition(0.0, -0.5); - panelSpacing = 0.2; + panelSpacing = 0.4; + + parentPanel.addChild(&scopePanel); + parentPanel.addChild(&spectrumPanel); + parentPanel.setFill(GLPanel::GLPANEL_FILL_NONE); + scopePanel.setSize(1.0,-1.0); + spectrumPanel.setSize(1.0,-1.0); + spectrumPanel.setShowDb(true); } ScopeCanvas::~ScopeCanvas() { } +bool ScopeCanvas::scopeVisible() { + float panelInterval = (2.0 + panelSpacing); + + ctrTarget = abs(round(ctr / panelInterval)); + + if (ctrTarget == 0 || dragAccel || (ctr != ctrTarget)) { + return true; + } + + return false; +} + +bool ScopeCanvas::spectrumVisible() { + float panelInterval = (2.0 + panelSpacing); + + ctrTarget = abs(round(ctr / panelInterval)); + + if (ctrTarget == 1 || dragAccel || (ctr != ctrTarget)) { + return true; + } + + return false; +} + void ScopeCanvas::setStereo(bool state) { stereo = state; } @@ -59,20 +90,40 @@ bool ScopeCanvas::getPPMMode() { return ppmMode; } +void ScopeCanvas::setShowDb(bool showDb) { + this->showDb = showDb; +} + +bool ScopeCanvas::getShowDb() { + return showDb; +} + void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { wxPaintDC dc(this); const wxSize ClientSize = GetClientSize(); - if (!inputData.empty()) { + while (!inputData.empty()) { ScopeRenderData *avData; inputData.pop(avData); - if (avData) { + if (!avData->spectrum) { if (avData->waveform_points.size()) { scopePanel.setPoints(avData->waveform_points); setStereo(avData->channels == 2); } + avData->decRefCount(); + } else { + if (avData->waveform_points.size()) { + spectrumPanel.setPoints(avData->waveform_points); + spectrumPanel.setFloorValue(avData->fft_floor); + spectrumPanel.setCeilValue(avData->fft_ceil); + spectrumPanel.setBandwidth((avData->sampleRate/2)*1000); + spectrumPanel.setFreq((avData->sampleRate/4)*1000); + spectrumPanel.setFFTSize(avData->fft_size); + spectrumPanel.setShowDb(showDb); + } + avData->decRefCount(); } } @@ -96,53 +147,74 @@ void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - CubicVR::mat4 modelView = CubicVR::mat4::lookat(0, 0, -1.2, 0, 0, 0, 0, -1, 0); + CubicVR::mat4 modelView = CubicVR::mat4::lookat(0, 0, -1.205, 0, 0, 0, 0, -1, 0); float panelWidth = 1.0; float panelInterval = (panelWidth * 2.0 + panelSpacing); if (!mouseTracker.mouseDown()) { + ctrTarget = round(ctr / panelInterval); + if (ctrTarget < -1.0) { + ctrTarget = -1.0; + } else if (ctrTarget > 0.0) { + ctrTarget = 0.0; + } + ctrTarget *= panelInterval; if (!dragAccel) { - ctrTarget = round(ctr / panelInterval); - if (ctrTarget < -1.0) { - ctrTarget = -1.0; - } else if (ctrTarget > 0.0) { - ctrTarget = 0.0; - } - ctrTarget *= panelInterval; if (ctr != ctrTarget) { ctr += (ctrTarget-ctr)*0.2; } + if (abs(ctr - ctrTarget) < 0.001) { + ctr=ctrTarget; + } } else { - dragAccel -= dragAccel * 0.01; - if (abs(dragAccel) < 0.1 || ctr < ctrTarget-panelInterval/2.0 || ctr > ctrTarget+panelInterval/2.0 ) { + dragAccel -= dragAccel * 0.1; + if ((abs(dragAccel) < 0.2) || (ctr < (ctrTarget-panelInterval/2.0)) || (ctr > (ctrTarget+panelInterval/2.0)) ) { dragAccel = 0; } else { ctr += dragAccel; } } } + + float roty = 0; scopePanel.setPosition(ctr, 0); - float roty = atan2(scopePanel.pos[0],1.2); - scopePanel.rot[1] = -(roty * (180.0 / M_PI)); - scopePanel.calcTransform(modelView); - scopePanel.draw(); + if (scopeVisible()) { + scopePanel.contentsVisible = true; + roty = atan2(scopePanel.pos[0],1.2); + scopePanel.rot[1] = -(roty * (180.0 / M_PI)); + } else { + scopePanel.contentsVisible = false; + } + + spectrumPanel.setPosition(panelInterval+ctr, 0); + if (spectrumVisible()) { + spectrumPanel.setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground * 2.0, RGBA4f(0,0,0,0)); + spectrumPanel.contentsVisible = true; + roty = atan2(spectrumPanel.pos[0],1.2); + spectrumPanel.rot[1] = -(roty * (180.0 / M_PI)); + } else { + spectrumPanel.contentsVisible = false; + } - scopePanel.setPosition(panelInterval+ctr, 0); - roty = atan2(scopePanel.pos[0],1.2); - scopePanel.rot[1] = -(roty * (180.0 / M_PI)); - scopePanel.calcTransform(modelView); - scopePanel.draw(); + parentPanel.calcTransform(modelView); + parentPanel.draw(); + + if (spectrumVisible()) { + spectrumPanel.drawChildren(); + } + + glLoadMatrixf(scopePanel.transform); + if (!deviceName.empty()) { + glContext->DrawDeviceName(deviceName); + } glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glContext->DrawTunerTitles(ppmMode); - if (!deviceName.empty()) { - glContext->DrawDeviceName(deviceName); - } glContext->DrawEnd(); SwapBuffers(); @@ -182,7 +254,10 @@ void ScopeCanvas::OnMouseReleased(wxMouseEvent& event) { void ScopeCanvas::OnMouseEnterWindow(wxMouseEvent& event) { InteractiveCanvas::OnMouseEnterWindow(event); - + if (!helpTip.empty()) { + setStatusText(helpTip); + } + SetCursor(wxCURSOR_SIZEWE); } void ScopeCanvas::OnMouseLeftWindow(wxMouseEvent& event) { @@ -190,3 +265,8 @@ void ScopeCanvas::OnMouseLeftWindow(wxMouseEvent& event) { } + +void ScopeCanvas::setHelpTip(std::string tip) { + helpTip = tip; +} + diff --git a/src/visual/ScopeCanvas.h b/src/visual/ScopeCanvas.h index d5bfba5..d868b2e 100644 --- a/src/visual/ScopeCanvas.h +++ b/src/visual/ScopeCanvas.h @@ -9,6 +9,7 @@ #include "ScopeContext.h" #include "ScopeVisualProcessor.h" #include "ScopePanel.h" +#include "SpectrumPanel.h" #include "fftw3.h" #include "InteractiveCanvas.h" @@ -22,6 +23,14 @@ public: void setPPMMode(bool ppmMode); bool getPPMMode(); + void setShowDb(bool showDb); + bool getShowDb(); + + bool scopeVisible(); + bool spectrumVisible(); + + void setHelpTip(std::string tip); + ScopeRenderDataQueue *getInputQueue(); private: @@ -36,15 +45,19 @@ private: ScopeRenderDataQueue inputData; ScopePanel scopePanel; + GLPanel parentPanel; + SpectrumPanel spectrumPanel; GLPanel bgPanel; ScopeContext *glContext; std::string deviceName; bool stereo; bool ppmMode; + bool showDb; float panelSpacing; float ctr; float ctrTarget; float dragAccel; + std::string helpTip; // event table wxDECLARE_EVENT_TABLE(); }; From fe46fb191f0540f836c23d26320cfe82f3723324 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Mon, 24 Aug 2015 07:25:04 -0400 Subject: [PATCH 15/19] Fix demodulator worker thread crash on terminate --- src/demod/DemodulatorPreThread.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/demod/DemodulatorPreThread.cpp b/src/demod/DemodulatorPreThread.cpp index c93df6f..5c3820f 100644 --- a/src/demod/DemodulatorPreThread.cpp +++ b/src/demod/DemodulatorPreThread.cpp @@ -72,9 +72,6 @@ void DemodulatorPreThread::initialize() { } DemodulatorPreThread::~DemodulatorPreThread() { - delete workerThread; - delete workerQueue; - delete workerResults; } void DemodulatorPreThread::run() { @@ -260,7 +257,7 @@ void DemodulatorPreThread::run() { inp->decRefCount(); - if (!workerResults->empty()) { + if (!terminated && !workerResults->empty()) { while (!workerResults->empty()) { DemodulatorWorkerThreadResult result; workerResults->pop(result); @@ -323,7 +320,12 @@ void DemodulatorPreThread::terminate() { terminated = true; DemodulatorThreadIQData *inp = new DemodulatorThreadIQData; // push dummy to nudge queue iqInputQueue->push(inp); + DemodulatorWorkerThreadCommand command(DemodulatorWorkerThreadCommand::DEMOD_WORKER_THREAD_CMD_NULL); + workerQueue->push(command); workerThread->terminate(); - t_Worker->detach(); + t_Worker->join(); delete t_Worker; + delete workerThread; + delete workerResults; + delete workerQueue; } From 233e07164a3a92ddeee639208d841e57f5909828 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Mon, 24 Aug 2015 08:58:08 -0400 Subject: [PATCH 16/19] spectrum label calc error --- src/panel/SpectrumPanel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panel/SpectrumPanel.cpp b/src/panel/SpectrumPanel.cpp index 142e1a4..034d3c5 100644 --- a/src/panel/SpectrumPanel.cpp +++ b/src/panel/SpectrumPanel.cpp @@ -141,7 +141,7 @@ void SpectrumPanel::drawPanelContents() { long long leftFreq = (double) freq - ((double) bandwidth / 2.0); long long rightFreq = leftFreq + (double) bandwidth; - long long hzStep = 100000; + long long hzStep = 1000000; long double mhzStep = (100000.0 / (long double) (rightFreq - leftFreq)) * 2.0; double mhzVisualStep = 0.1; From eae47fdfd7ad660633bff8f6af3649b6d84def5b Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Tue, 25 Aug 2015 21:26:32 -0400 Subject: [PATCH 17/19] Fix IQ swap default --- src/AppConfig.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AppConfig.cpp b/src/AppConfig.cpp index 7510cc6..f1a716d 100644 --- a/src/AppConfig.cpp +++ b/src/AppConfig.cpp @@ -2,6 +2,7 @@ #include "CubicSDR.h" DeviceConfig::DeviceConfig() : deviceId("") { + iqSwap.store(0); ppm.store(0); directSampling.store(false); offset.store(0); From 261afbce8e5e8aca8968021006d9da00b94eaaa3 Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Tue, 25 Aug 2015 21:35:38 -0400 Subject: [PATCH 18/19] Windows fix-up --- external/fftw-3.3.4/64/libfftw3f-3.lib | Bin 246562 -> 0 bytes src/util/ThreadQueue.h | 1 + 2 files changed, 1 insertion(+) delete mode 100644 external/fftw-3.3.4/64/libfftw3f-3.lib diff --git a/external/fftw-3.3.4/64/libfftw3f-3.lib b/external/fftw-3.3.4/64/libfftw3f-3.lib deleted file mode 100644 index 4f2f89db33ecc005567ad0ff351211606e46a81c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 246562 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 #include #include +#include class ThreadQueueBase { From ecda9a8f7652660b20f759c085b9a2606f3c3cbc Mon Sep 17 00:00:00 2001 From: "Charles J. Cliffe" Date: Tue, 25 Aug 2015 22:30:47 -0400 Subject: [PATCH 19/19] Windows exit crash fix.. ? --- src/process/ScopeVisualProcessor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/process/ScopeVisualProcessor.cpp b/src/process/ScopeVisualProcessor.cpp index 1cc4d24..7282ec9 100644 --- a/src/process/ScopeVisualProcessor.cpp +++ b/src/process/ScopeVisualProcessor.cpp @@ -9,12 +9,12 @@ ScopeVisualProcessor::ScopeVisualProcessor(): fftInData(NULL), fftwOutput(NULL), } ScopeVisualProcessor::~ScopeVisualProcessor() { - if (fftInData) { + /*if (fftInData) { free(fftInData); } if (fftwOutput) { free(fftwOutput); - } + }*/ if (fftw_plan) { fftwf_destroy_plan(fftw_plan); }