diff --git a/CMakeLists.txt b/CMakeLists.txt index 72f8ca4..c6131f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,8 @@ SET (cubicsdr_sources src/visual/MeterContext.cpp src/visual/TuningCanvas.cpp src/visual/TuningContext.cpp + src/visual/ModeSelectorCanvas.cpp + src/visual/ModeSelectorContext.cpp src/visual/ScopeCanvas.cpp src/visual/ScopeContext.cpp src/visual/SpectrumCanvas.cpp @@ -175,6 +177,8 @@ SET (cubicsdr_headers src/visual/MeterContext.h src/visual/TuningCanvas.h src/visual/TuningContext.h + src/visual/ModeSelectorCanvas.h + src/visual/ModeSelectorContext.h src/visual/ScopeCanvas.h src/visual/ScopeContext.h src/visual/SpectrumCanvas.h diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index e1c5325..bd049e9 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -6,6 +6,8 @@ #include "wx/wx.h" #endif +#include "wx/numdlg.h" + #if !wxUSE_GLCANVAS #error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library" #endif @@ -38,6 +40,16 @@ AppFrame::AppFrame() : wxBoxSizer *demodTray = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer *demodScopeTray = new wxBoxSizer(wxVERTICAL); + + demodModeSelector = new ModeSelectorCanvas(this, NULL); + demodModeSelector->addChoice(DEMOD_TYPE_FM,"FM"); + demodModeSelector->addChoice(DEMOD_TYPE_AM,"AM"); + demodModeSelector->addChoice(DEMOD_TYPE_LSB,"LSB"); + demodModeSelector->addChoice(DEMOD_TYPE_USB,"USB"); + demodTray->Add(demodModeSelector, 2, wxEXPAND | wxALL, 0); + +// demodTray->AddSpacer(2); + demodSpectrumCanvas = new SpectrumCanvas(this, NULL); demodSpectrumCanvas->setup(1024); demodSpectrumCanvas->setView(DEFAULT_FREQ, 300000); @@ -88,20 +100,22 @@ AppFrame::AppFrame() : this->SetSizer(vbox); - waterfallCanvas->SetFocusFromKbd(); +// waterfallCanvas->SetFocusFromKbd(); waterfallCanvas->SetFocus(); // SetIcon(wxICON(sample)); // Make a menubar -// wxMenu *menu = new wxMenu; -// menu->Append(wxID_NEW); -// menu->AppendSeparator(); -// menu->Append(wxID_CLOSE); -// wxMenuBar *menuBar = new wxMenuBar; -// menuBar->Append(menu, wxT("&File")); - + wxMenuBar *menuBar = new wxMenuBar; wxMenu *menu = new wxMenu; +// menu->Append(wxID_NEW); + menu->Append(wxID_SET_FREQ_OFFSET, "Set Frequency Offset"); + menu->AppendSeparator(); + menu->Append(wxID_CLOSE); + + menuBar->Append(menu, wxT("&File")); + + menu = new wxMenu; std::vector::iterator devices_i; std::map::iterator mdevices_i; @@ -130,17 +144,8 @@ AppFrame::AppFrame() : outputDeviceMenuItems[mdevices_i->first] = itm; } - wxMenuBar *menuBar = new wxMenuBar; menuBar->Append(menu, wxT("Active Demodulator &Output")); - wxMenu *demodMenu = new wxMenu; - demodMenuItems[DEMOD_TYPE_FM] = demodMenu->AppendRadioItem(wxID_DEMOD_TYPE_FM, wxT("FM"), wxT("Description?")); - demodMenuItems[DEMOD_TYPE_AM] = demodMenu->AppendRadioItem(wxID_DEMOD_TYPE_AM, wxT("AM"), wxT("Description?")); - demodMenuItems[DEMOD_TYPE_LSB] = demodMenu->AppendRadioItem(wxID_DEMOD_TYPE_LSB, wxT("LSB"), wxT("Description?")); - demodMenuItems[DEMOD_TYPE_USB] = demodMenu->AppendRadioItem(wxID_DEMOD_TYPE_USB, wxT("USB"), wxT("Description?")); - - menuBar->Append(demodMenu, wxT("Active Demodulator &Type")); - SetMenuBar(menuBar); CreateStatusBar(); @@ -165,22 +170,9 @@ void AppFrame::OnMenu(wxCommandEvent& event) { activeDemodulator->setOutputDevice(event.GetId() - wxID_RT_AUDIO_DEVICE); activeDemodulator = NULL; } - } - - if (activeDemodulator) { - if (event.GetId() == wxID_DEMOD_TYPE_FM) { - activeDemodulator->setDemodulatorType(DEMOD_TYPE_FM); - activeDemodulator = NULL; - } else if (event.GetId() == wxID_DEMOD_TYPE_AM) { - activeDemodulator->setDemodulatorType(DEMOD_TYPE_AM); - activeDemodulator = NULL; - } else if (event.GetId() == wxID_DEMOD_TYPE_LSB) { - activeDemodulator->setDemodulatorType(DEMOD_TYPE_LSB); - activeDemodulator = NULL; - } else if (event.GetId() == wxID_DEMOD_TYPE_USB) { - activeDemodulator->setDemodulatorType(DEMOD_TYPE_USB); - activeDemodulator = NULL; - } + } else if (event.GetId() == wxID_SET_FREQ_OFFSET) { + long ofs = wxGetNumberFromUser ("Shift the displayed frequency by this amount.\ni.e. -125000000 for -125 MHz", "Frequency (Hz)", "Frequency Offset", 0, -2000000000, 2000000000, this); + wxGetApp().setOffset(ofs); } } @@ -213,13 +205,18 @@ void AppFrame::OnIdle(wxIdleEvent& event) { scopeCanvas->setDeviceName(outputDevices[outputDevice].name); outputDeviceMenuItems[outputDevice]->Check(true); int dType = demod->getDemodulatorType(); - demodMenuItems[dType]->Check(true); + demodModeSelector->setSelection(dType); } if (demodWaterfallCanvas->getDragState() == WaterfallCanvas::WF_DRAG_NONE) { if (demod->getParams().frequency != demodWaterfallCanvas->getCenterFrequency()) { demodWaterfallCanvas->setCenterFrequency(demod->getFrequency()); demodSpectrumCanvas->setCenterFrequency(demod->getFrequency()); } + int dSelection = demodModeSelector->getSelection(); + if (dSelection != -1 && dSelection != demod->getDemodulatorType()) { + demod->setDemodulatorType(dSelection); + } + unsigned int demodBw = (unsigned int) ceil((float) demod->getParams().bandwidth * 2.5); if (demodBw > SRATE / 2) { demodBw = SRATE / 2; @@ -274,6 +271,10 @@ void AppFrame::OnIdle(wxIdleEvent& event) { work_done = true; } + if (!waterfallCanvas->HasFocus()) { + waterfallCanvas->SetFocus(); + } + if (!work_done) { event.Skip(); } diff --git a/src/AppFrame.h b/src/AppFrame.h index 4140676..4df734d 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -8,14 +8,12 @@ #include "WaterfallCanvas.h" #include "MeterCanvas.h" #include "TuningCanvas.h" +#include "ModeSelectorCanvas.h" #include #define wxID_RT_AUDIO_DEVICE 1000 -#define wxID_DEMOD_TYPE_FM 2000 -#define wxID_DEMOD_TYPE_AM 2001 -#define wxID_DEMOD_TYPE_LSB 2002 -#define wxID_DEMOD_TYPE_USB 2003 +#define wxID_SET_FREQ_OFFSET 2001 // Define a new frame type class AppFrame: public wxFrame { @@ -34,6 +32,7 @@ private: ScopeCanvas *scopeCanvas; SpectrumCanvas *spectrumCanvas; WaterfallCanvas *waterfallCanvas; + ModeSelectorCanvas *demodModeSelector; SpectrumCanvas *demodSpectrumCanvas; WaterfallCanvas *demodWaterfallCanvas; MeterCanvas *demodSignalMeter; @@ -47,7 +46,5 @@ private: std::map outputDevices; std::map outputDeviceMenuItems; - std::map demodMenuItems; - wxDECLARE_EVENT_TABLE(); }; diff --git a/src/CubicSDR.cpp b/src/CubicSDR.cpp index dacf40c..396b9dc 100644 --- a/src/CubicSDR.cpp +++ b/src/CubicSDR.cpp @@ -20,6 +20,7 @@ bool CubicSDR::OnInit() { return false; frequency = DEFAULT_FREQ; + offset = 0; audioVisualQueue = new DemodulatorThreadOutputQueue(); audioVisualQueue->set_max_num_items(1); @@ -110,6 +111,17 @@ void CubicSDR::setFrequency(long long freq) { threadCmdQueueSDR->push(command); } +long long CubicSDR::getOffset() { + return offset; +} + +void CubicSDR::setOffset(long long ofs) { + offset = ofs; + SDRThreadCommand command(SDRThreadCommand::SDR_THREAD_CMD_SET_OFFSET); + command.llong_value = ofs; + threadCmdQueueSDR->push(command); +} + long long CubicSDR::getFrequency() { return frequency; } diff --git a/src/CubicSDR.h b/src/CubicSDR.h index 1355dcd..6317273 100644 --- a/src/CubicSDR.h +++ b/src/CubicSDR.h @@ -32,6 +32,9 @@ public: void setFrequency(long long freq); long long getFrequency(); + void setOffset(long long ofs); + long long getOffset(); + DemodulatorThreadOutputQueue* getAudioVisualQueue(); DemodulatorThreadInputQueue* getIQVisualQueue(); DemodulatorMgr &getDemodMgr(); @@ -45,6 +48,7 @@ private: DemodulatorMgr demodMgr; long long frequency; + long long offset; SDRThread *sdrThread; SDRPostThread *sdrPostThread; diff --git a/src/sdr/SDRThread.cpp b/src/sdr/SDRThread.cpp index 8f4e97c..528b2a2 100644 --- a/src/sdr/SDRThread.cpp +++ b/src/sdr/SDRThread.cpp @@ -4,7 +4,7 @@ #include "CubicSDR.h" SDRThread::SDRThread(SDRThreadCommandQueue* pQueue) : - commandQueue(pQueue), iqDataOutQueue(NULL), terminated(false) { + commandQueue(pQueue), iqDataOutQueue(NULL), terminated(false), offset(0) { dev = NULL; sampleRate = SRATE; } @@ -137,7 +137,9 @@ void SDRThread::threadMain() { if (!cmdQueue->empty()) { bool freq_changed = false; + bool offset_changed = false; long long new_freq; + long long new_offset; while (!cmdQueue->empty()) { SDRThreadCommand command; @@ -152,14 +154,24 @@ void SDRThread::threadMain() { } std::cout << "Set frequency: " << new_freq << std::endl; break; + case SDRThreadCommand::SDR_THREAD_CMD_SET_OFFSET: + offset_changed = true; + new_offset = command.llong_value; + std::cout << "Set offset: " << new_offset << std::endl; + break; default: break; } } + if (offset_changed && !freq_changed) { + new_freq = frequency; + freq_changed = true; + offset = new_offset; + } if (freq_changed) { frequency = new_freq; - rtlsdr_set_center_freq(dev, frequency); + rtlsdr_set_center_freq(dev, frequency-offset); } } diff --git a/src/sdr/SDRThread.h b/src/sdr/SDRThread.h index 3c83870..3143d4c 100644 --- a/src/sdr/SDRThread.h +++ b/src/sdr/SDRThread.h @@ -17,7 +17,7 @@ class SDRThreadCommand { public: enum SDRThreadCommandEnum { - SDR_THREAD_CMD_NULL, SDR_THREAD_CMD_TUNE + SDR_THREAD_CMD_NULL, SDR_THREAD_CMD_TUNE, SDR_THREAD_CMD_SET_OFFSET }; SDRThreadCommand() : @@ -76,6 +76,7 @@ public: void terminate(); protected: uint32_t sampleRate; + long long offset; std::atomic commandQueue; std::atomic iqDataOutQueue; diff --git a/src/util/MouseTracker.cpp b/src/util/MouseTracker.cpp index 1447121..e9a7dad 100644 --- a/src/util/MouseTracker.cpp +++ b/src/util/MouseTracker.cpp @@ -149,6 +149,14 @@ void MouseTracker::setHorizDragLock(bool dragLock) { horizDragLock = dragLock; } +bool MouseTracker::getVertDragLock() { + return vertDragLock; +} + +bool MouseTracker::getHorizDragLock() { + return horizDragLock; +} + bool MouseTracker::mouseDown() { return isMouseDown; } diff --git a/src/util/MouseTracker.h b/src/util/MouseTracker.h index f577ded..33640ff 100644 --- a/src/util/MouseTracker.h +++ b/src/util/MouseTracker.h @@ -29,6 +29,8 @@ public: void setVertDragLock(bool dragLock); void setHorizDragLock(bool dragLock); + bool getVertDragLock(); + bool getHorizDragLock(); bool mouseDown(); bool mouseRightDown(); bool mouseInView(); diff --git a/src/visual/ModeSelectorCanvas.cpp b/src/visual/ModeSelectorCanvas.cpp new file mode 100644 index 0000000..3696186 --- /dev/null +++ b/src/visual/ModeSelectorCanvas.cpp @@ -0,0 +1,147 @@ +#include "ModeSelectorCanvas.h" + +#include "wx/wxprec.h" + +#ifndef WX_PRECOMP +#include "wx/wx.h" +#endif + +#if !wxUSE_GLCANVAS +#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library" +#endif + +#include "CubicSDR.h" +#include "CubicSDRDefs.h" +#include "AppFrame.h" +#include + +wxBEGIN_EVENT_TABLE(ModeSelectorCanvas, wxGLCanvas) EVT_PAINT(ModeSelectorCanvas::OnPaint) +EVT_IDLE(ModeSelectorCanvas::OnIdle) +EVT_MOTION(ModeSelectorCanvas::OnMouseMoved) +EVT_LEFT_DOWN(ModeSelectorCanvas::OnMouseDown) +EVT_LEFT_UP(ModeSelectorCanvas::OnMouseReleased) +EVT_LEAVE_WINDOW(ModeSelectorCanvas::OnMouseLeftWindow) +EVT_ENTER_WINDOW(ModeSelectorCanvas::OnMouseEnterWindow) +wxEND_EVENT_TABLE() + +ModeSelectorCanvas::ModeSelectorCanvas(wxWindow *parent, int *attribList) : +InteractiveCanvas(parent, attribList), currentSelection(-1), numChoices(0) { + + glContext = new ModeSelectorContext(this, &wxGetApp().GetContext(this)); +} + +ModeSelectorCanvas::~ModeSelectorCanvas() { + +} + +int ModeSelectorCanvas::getHoveredSelection() { + if (!mouseTracker.mouseInView()) { + return -1; + } + + float ypos = 1.0 - (mouseTracker.getMouseY() * 2.0); + float yval = (int) (((ypos + 1.0) / 2.0) * (float) numChoices); + + return yval; +} + +void ModeSelectorCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { + wxPaintDC dc(this); + const wxSize ClientSize = GetClientSize(); + + glContext->SetCurrent(*this); + glViewport(0, 0, ClientSize.x, ClientSize.y); + + glContext->DrawBegin(); + + DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); + + int demodType = 0; + if (demod) { + demodType = demod->getDemodulatorType(); + } + + int yval = getHoveredSelection(); + + for (int i = 0; i < numChoices; i++) { + glContext->DrawSelector(selections[i].label, i, numChoices, i == currentSelection || yval == i, (yval == i)?0.9:0.75, (yval == i)?0.9:0.75, (yval == i)?0.2:0.75, 1.0); + } + + glContext->DrawEnd(); + + SwapBuffers(); +} + +void ModeSelectorCanvas::OnIdle(wxIdleEvent &event) { + Refresh(false); +} + +void ModeSelectorCanvas::OnMouseMoved(wxMouseEvent& event) { + InteractiveCanvas::OnMouseMoved(event); +} + +void ModeSelectorCanvas::OnMouseDown(wxMouseEvent& event) { + InteractiveCanvas::OnMouseDown(event); + mouseTracker.setHorizDragLock(true); + mouseTracker.setVertDragLock(true); +} + +void ModeSelectorCanvas::OnMouseWheelMoved(wxMouseEvent& event) { + InteractiveCanvas::OnMouseWheelMoved(event); +} + +void ModeSelectorCanvas::OnMouseReleased(wxMouseEvent& event) { + InteractiveCanvas::OnMouseReleased(event); + mouseTracker.setHorizDragLock(false); + mouseTracker.setVertDragLock(false); + + const wxSize ClientSize = GetClientSize(); + + if (mouseTracker.getOriginDeltaMouseX() < 2.0 / ClientSize.y) { + currentSelection = getHoveredSelection(); + } + + SetCursor (wxCURSOR_ARROW); +} + +void ModeSelectorCanvas::OnMouseLeftWindow(wxMouseEvent& event) { + InteractiveCanvas::OnMouseLeftWindow(event); + SetCursor (wxCURSOR_CROSS); +} + +void ModeSelectorCanvas::OnMouseEnterWindow(wxMouseEvent& event) { + InteractiveCanvas::mouseTracker.OnMouseEnterWindow(event); + SetCursor (wxCURSOR_ARROW); +} + +void ModeSelectorCanvas::setHelpTip(std::string tip) { + helpTip = tip; +} + +void ModeSelectorCanvas::setNumChoices(int numChoices_in) { + numChoices = numChoices_in; +} + +void ModeSelectorCanvas::addChoice(int value, std::string label) { + selections.push_back(ModeSelectorMode(value, label)); + numChoices = selections.size(); +} + +void ModeSelectorCanvas::setSelection(int value) { + for (int i = 0; i < numChoices; i++) { + if (selections[i].value == value) { + currentSelection = i; + return; + } + } + currentSelection = -1; +} + +int ModeSelectorCanvas::getSelection() { + if (currentSelection == -1) { + return -1; + } + return selections[currentSelection].value; +} + + diff --git a/src/visual/ModeSelectorCanvas.h b/src/visual/ModeSelectorCanvas.h new file mode 100644 index 0000000..7dcf12b --- /dev/null +++ b/src/visual/ModeSelectorCanvas.h @@ -0,0 +1,60 @@ +#pragma once + +#include "wx/glcanvas.h" +#include "wx/timer.h" + +#include +#include + +#include "InteractiveCanvas.h" +#include "ModeSelectorContext.h" +#include "MouseTracker.h" + +#include "fftw3.h" +#include "Timer.h" + +class ModeSelectorMode { +public: + int value; + std::string label; + + ModeSelectorMode(int value, std::string label) : value(value), label(label) { + + } +}; + +class ModeSelectorCanvas: public InteractiveCanvas { +public: + ModeSelectorCanvas(wxWindow *parent, int *attribList = NULL); + ~ModeSelectorCanvas(); + + int getHoveredSelection(); + void setHelpTip(std::string tip); + + void addChoice(int value, std::string label); + void setSelection(int value); + int getSelection(); + +private: + void setNumChoices(int numChoices_in); + + void OnPaint(wxPaintEvent& event); + void OnIdle(wxIdleEvent &event); + + void OnMouseMoved(wxMouseEvent& event); + void OnMouseDown(wxMouseEvent& event); + void OnMouseWheelMoved(wxMouseEvent& event); + void OnMouseReleased(wxMouseEvent& event); + void OnMouseEnterWindow(wxMouseEvent& event); + void OnMouseLeftWindow(wxMouseEvent& event); + + ModeSelectorContext *glContext; + + std::string helpTip; + int numChoices; + int currentSelection; + std::vector selections; + // +wxDECLARE_EVENT_TABLE(); +}; + diff --git a/src/visual/ModeSelectorContext.cpp b/src/visual/ModeSelectorContext.cpp new file mode 100644 index 0000000..7643f57 --- /dev/null +++ b/src/visual/ModeSelectorContext.cpp @@ -0,0 +1,64 @@ +#include "ModeSelectorContext.h" +#include "ModeSelectorCanvas.h" + +ModeSelectorContext::ModeSelectorContext(ModeSelectorCanvas *canvas, wxGLContext *sharedContext) : + PrimaryGLContext(canvas, sharedContext) { + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); +} + +void ModeSelectorContext::DrawBegin() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glDisable(GL_TEXTURE_2D); +} + +void ModeSelectorContext::DrawSelector(std::string label, int c, int cMax, bool on, float r, float g, float b, float a) { + GLint vp[4]; + glGetIntegerv( GL_VIEWPORT, vp); + + float viewHeight = (float) vp[3]; + float viewWidth = (float) vp[2]; + + PrimaryGLContext::GLFontSize fontSize = GLFONT_SIZE16; + + int fontHeight = 16; + + if (viewWidth < 30) { + fontSize = GLFONT_SIZE12; + fontHeight = 12; + } + + glColor4f(r, g, b, a); + + 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); + + 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 (on) { + glColor4f(1.0-r, 1.0-g, 1.0-b, a); + } + + getFont(fontSize).drawString(label, 0.0, y + height / 2.0, fontHeight, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER); +} + +void ModeSelectorContext::DrawEnd() { + glFlush(); + + CheckGLError(); +} + diff --git a/src/visual/ModeSelectorContext.h b/src/visual/ModeSelectorContext.h new file mode 100644 index 0000000..0e66e44 --- /dev/null +++ b/src/visual/ModeSelectorContext.h @@ -0,0 +1,17 @@ +#pragma once + +#include "PrimaryGLContext.h" +#include "Gradient.h" + +#define NUM_WATERFALL_LINES 512 + +class ModeSelectorCanvas; + +class ModeSelectorContext: public PrimaryGLContext { +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 DrawEnd(); +};