diff --git a/CMakeLists.txt b/CMakeLists.txt index 2eb316e0..cd4d4dd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,7 @@ ENDIF (APPLE) SET (cubicsdr_sources src/CubicSDR.cpp src/AppFrame.cpp + src/AppConfig.cpp src/sdr/SDRThread.cpp src/sdr/SDRPostThread.cpp src/demod/DemodulatorPreThread.cpp @@ -267,6 +268,7 @@ SET (cubicsdr_headers src/CubicSDRDefs.h src/CubicSDR.h src/AppFrame.h + src/AppConfig.h src/sdr/SDRThread.h src/sdr/SDRPostThread.h src/demod/DemodulatorPreThread.h diff --git a/src/AppConfig.cpp b/src/AppConfig.cpp new file mode 100644 index 00000000..88df6761 --- /dev/null +++ b/src/AppConfig.cpp @@ -0,0 +1,133 @@ +#include "AppConfig.h" + +DeviceConfig::DeviceConfig() : ppm(0), deviceId("") { + +} + +DeviceConfig::DeviceConfig(std::string deviceId) : ppm(0) { + this->deviceId = deviceId; +} + +void DeviceConfig::setPPM(int ppm) { + this->ppm = ppm; +} + +int DeviceConfig::getPPM() { + return ppm; +} + +void DeviceConfig::setDeviceId(std::string deviceId) { + this->deviceId = deviceId; +} + +std::string DeviceConfig::getDeviceId() { + return deviceId; +} + +void DeviceConfig::save(DataNode *node) { + node->newChild("id")->element()->set(deviceId); + DataNode *ppm_node = node->newChild("ppm"); + ppm_node->element()->set((int)ppm); +} + +void DeviceConfig::load(DataNode *node) { + if (node->hasAnother("ppm")) { + DataNode *ppm_node = node->getNext("ppm"); + int ppmValue = 0; + ppm_node->element()->get(ppmValue); + setPPM(ppmValue); + std::cout << "Loaded PPM for device '" << deviceId << "' at " << ppmValue << "ppm" << std::endl; + } +} + + +DeviceConfig *AppConfig::getDevice(std::string deviceId) { + DeviceConfig *conf = &deviceConfig[deviceId]; + conf->setDeviceId(deviceId); + return conf; +} + +std::string AppConfig::getConfigDir() { + std::string dataDir = wxStandardPaths::Get().GetUserDataDir().ToStdString(); + + bool mkStatus = false; + + if (!wxDir::Exists(dataDir)) { + mkStatus = wxDir::Make(dataDir); + } else { + mkStatus = true; + } + + if (!mkStatus) { + std::cout << "Warning, unable to initialize user data directory." << std::endl; + } + + return dataDir; +} + +bool AppConfig::save() { + DataTree cfg; + + cfg.rootNode()->setName("cubicsdr_config"); + DataNode *devices_node = cfg.rootNode()->newChild("devices"); + + std::map::iterator device_config_i; + for (device_config_i = deviceConfig.begin(); device_config_i != deviceConfig.end(); device_config_i++) { + DataNode *device_node = devices_node->newChild("device"); + device_config_i->second.save(device_node); + } + + std::string cfgFileDir = getConfigDir(); + + wxFileName cfgFile = wxFileName(cfgFileDir, "config.xml"); + std::string cfgFileName = cfgFile.GetFullPath(wxPATH_NATIVE).ToStdString(); + + if (!cfg.SaveToFileXML(cfgFileName)) { + std::cout << "Error saving :: configuration file '" << cfgFileName << "' is not writable!" << std::endl; + return false; + } + + return true; +} + +bool AppConfig::load() { + DataTree cfg; + std::string cfgFileDir = getConfigDir(); + + wxFileName cfgFile = wxFileName(cfgFileDir, "config.xml"); + std::string cfgFileName = cfgFile.GetFullPath(wxPATH_NATIVE).ToStdString(); + + if (!cfgFile.Exists()) { + return true; + } + + if (cfgFile.IsFileReadable()) { + std::cout << "Loading:: configuration file '" << cfgFileName << "'" << std::endl; + + cfg.LoadFromFileXML(cfgFileName); + } else { + std::cout << "Error loading:: configuration file '" << cfgFileName << "' is not readable!" << std::endl; + return false; + } + + if (cfg.rootNode()->hasAnother("devices")) { + DataNode *devices_node = cfg.rootNode()->getNext("devices"); + + while (devices_node->hasAnother("device")) { + DataNode *device_node = devices_node->getNext("device"); + if (device_node->hasAnother("id")) { + std::string deviceId; + device_node->getNext("id")->element()->get(deviceId); + + getDevice(deviceId)->load(device_node); + } + } + } + + return true; +} + +bool AppConfig::reset() { + + return true; +} diff --git a/src/AppConfig.h b/src/AppConfig.h new file mode 100644 index 00000000..13e5ce15 --- /dev/null +++ b/src/AppConfig.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +#include "DataTree.h" + + +class DeviceConfig { +public: + DeviceConfig(); + DeviceConfig(std::string deviceId); + + void setPPM(int ppm); + int getPPM(); + + void setDeviceId(std::string deviceId); + std::string getDeviceId(); + + void save(DataNode *node); + void load(DataNode *node); + +private: + std::string deviceId; + int ppm; +}; + +class AppConfig { +public: + std::string getConfigDir(); + DeviceConfig *getDevice(std::string deviceId); + + bool save(); + bool load(); + bool reset(); + +private: + std::map deviceConfig; +}; diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index 26ce8ff6..b783f325 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -132,6 +132,7 @@ AppFrame::AppFrame() : wxMenu *menu = new wxMenu; // menu->Append(wxID_NEW); menu->Append(wxID_SET_FREQ_OFFSET, "Set Frequency Offset"); + menu->Append(wxID_SET_PPM, "Set Device PPM"); menu->Append(wxID_OPEN, "&Open Session"); menu->Append(wxID_SAVE, "&Save Session"); menu->Append(wxID_SAVEAS, "Save Session &As.."); @@ -318,6 +319,11 @@ void AppFrame::OnMenu(wxCommandEvent& event) { if (ofs != -1) { wxGetApp().setOffset(ofs); } + } else if (event.GetId() == wxID_SET_PPM) { + long ofs = wxGetNumberFromUser("Frequency correction for device in PPM.\ni.e. -51 for -51 PPM\n\nNote: you can adjust PPM interactively\nby holding ALT over the frequency tuning bar.\n", "Parts per million (PPM)", + "Frequency Correction", wxGetApp().getPPM(), -1000, 1000, this); + wxGetApp().setPPM(ofs); + wxGetApp().saveConfig(); } else if (event.GetId() == wxID_SAVE) { if (!currentSessionFile.empty()) { saveSession(currentSessionFile); @@ -440,6 +446,26 @@ void AppFrame::OnIdle(wxIdleEvent& event) { DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); if (demod) { + DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); + + if (demod->isTracking()) { + if (spectrumCanvas->getViewState()) { + long long diff = abs(demod->getFrequency() - spectrumCanvas->getCenterFrequency()) + (demod->getBandwidth()/2) + (demod->getBandwidth()/4); + + if (diff > spectrumCanvas->getBandwidth()/2) { + if (demod->getBandwidth() > spectrumCanvas->getBandwidth()) { + diff = abs(demod->getFrequency() - spectrumCanvas->getCenterFrequency()); + } else { + diff = diff - spectrumCanvas->getBandwidth()/2; + } + spectrumCanvas->moveCenterFrequency((demod->getFrequency() < spectrumCanvas->getCenterFrequency())?diff:-diff); + demod->setTracking(false); + } + } else { + demod->setTracking(false); + } + } + if (demod != activeDemodulator) { demodSignalMeter->setInputValue(demod->getSquelchLevel()); demodGainMeter->setInputValue(demod->getGain()); @@ -526,6 +552,8 @@ void AppFrame::OnIdle(wxIdleEvent& event) { waterfallCanvas->SetFocus(); } + scopeCanvas->setPPMMode(demodTuner->isAltDown()); + event.Skip(); } diff --git a/src/AppFrame.h b/src/AppFrame.h index 1863dc20..5a7967f9 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -15,6 +15,7 @@ #define wxID_RT_AUDIO_DEVICE 1000 #define wxID_SET_FREQ_OFFSET 2001 #define wxID_RESET 2002 +#define wxID_SET_PPM 2003 #define wxID_THEME_DEFAULT 2100 #define wxID_THEME_SHARP 2101 diff --git a/src/CubicSDR.cpp b/src/CubicSDR.cpp index 0c50fe92..be24cd5e 100644 --- a/src/CubicSDR.cpp +++ b/src/CubicSDR.cpp @@ -33,12 +33,18 @@ bool CubicSDR::OnInit() { CFRelease(resourcesURL); chdir(path); #endif - - if (!wxApp::OnInit()) + + if (!wxApp::OnInit()) { return false; + } + + wxApp::SetAppName("CubicSDR"); + + config.load(); frequency = DEFAULT_FREQ; offset = 0; + ppm = 0; audioVisualQueue = new DemodulatorThreadOutputQueue(); audioVisualQueue->set_max_num_items(1); @@ -47,7 +53,7 @@ bool CubicSDR::OnInit() { sdrThread = new SDRThread(threadCmdQueueSDR); sdrPostThread = new SDRPostThread(); - sdrPostThread->setNumVisSamples(16384*2); + sdrPostThread->setNumVisSamples(16384 * 2); iqPostDataQueue = new SDRThreadIQDataQueue; iqVisualQueue = new DemodulatorThreadInputQueue; @@ -77,7 +83,7 @@ bool CubicSDR::OnInit() { choices.Add(devName); } - int devId = wxGetSingleChoiceIndex(wxT("Devices"),wxT("Choose Input Device"),choices); + int devId = wxGetSingleChoiceIndex(wxT("Devices"), wxT("Choose Input Device"), choices); std::cout << "Chosen: " << devId << std::endl; sdrThread->setDeviceId(devId); @@ -216,8 +222,52 @@ void CubicSDR::setDevice(int deviceId) { SDRThreadCommand command(SDRThreadCommand::SDR_THREAD_CMD_SET_DEVICE); command.llong_value = deviceId; threadCmdQueueSDR->push(command); + + SDRDeviceInfo *dev = (*getDevices())[deviceId]; + + SDRThreadCommand command_ppm(SDRThreadCommand::SDR_THREAD_CMD_SET_PPM); + ppm = config.getDevice(dev->getDeviceId())->getPPM(); + command_ppm.llong_value = ppm; + threadCmdQueueSDR->push(command_ppm); } int CubicSDR::getDevice() { return sdrThread->getDeviceId(); } + +AppConfig *CubicSDR::getConfig() { + return &config; +} + +void CubicSDR::saveConfig() { + config.save(); +} + +void CubicSDR::setPPM(int ppm_in) { + if (sdrThread->getDeviceId() < 0) { + return; + } + ppm = ppm_in; + + SDRThreadCommand command(SDRThreadCommand::SDR_THREAD_CMD_SET_PPM); + command.llong_value = ppm; + threadCmdQueueSDR->push(command); + + SDRDeviceInfo *dev = (*getDevices())[getDevice()]; + + config.getDevice(dev->getDeviceId())->setPPM(ppm_in); + config.save(); +} + +int CubicSDR::getPPM() { + if (sdrThread->getDeviceId() < 0) { + return 0; + } + SDRDeviceInfo *dev = (*getDevices())[getDevice()]; + + SDRThreadCommand command_ppm(SDRThreadCommand::SDR_THREAD_CMD_SET_PPM); + ppm = config.getDevice(dev->getDeviceId())->getPPM(); + + return ppm; +} + diff --git a/src/CubicSDR.h b/src/CubicSDR.h index d841ad7e..38975c3a 100644 --- a/src/CubicSDR.h +++ b/src/CubicSDR.h @@ -14,6 +14,7 @@ #include "SDRPostThread.h" #include "AudioThread.h" #include "DemodulatorMgr.h" +#include "AppConfig.h" #define NUM_DEMODULATORS 1 @@ -49,7 +50,14 @@ class CubicSDR: public wxApp { void bindDemodulator(DemodulatorInstance *demod); void removeDemodulator(DemodulatorInstance *demod); + AppConfig *getConfig(); + void saveConfig(); + + void setPPM(int ppm_in); + int getPPM(); + private: + AppConfig config; PrimaryGLContext *m_glContext; std::vector devs; @@ -57,6 +65,7 @@ class CubicSDR: public wxApp { long long frequency; long long offset; + int ppm; long long sampleRate; SDRThread *sdrThread; diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index 52b40474..0f16c686 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -164,6 +164,9 @@ void DemodulatorInstance::setActive(bool state) { } else if (!active && state) { audioThread->setActive(state); } + if (!state) { + tracking = false; + } active = state; } @@ -343,3 +346,11 @@ bool DemodulatorInstance::isFollow() { void DemodulatorInstance::setFollow(bool follow) { this->follow = follow; } + +bool DemodulatorInstance::isTracking() { + return tracking; +} + +void DemodulatorInstance::setTracking(bool tracking) { + this->tracking = tracking; +} diff --git a/src/demod/DemodulatorInstance.h b/src/demod/DemodulatorInstance.h index 5f53a382..bbb48419 100644 --- a/src/demod/DemodulatorInstance.h +++ b/src/demod/DemodulatorInstance.h @@ -80,6 +80,8 @@ class DemodulatorInstance { bool isFollow(); void setFollow(bool follow); + bool isTracking(); + void setTracking(bool tracking); private: void checkBandwidth(); @@ -98,5 +100,5 @@ class DemodulatorInstance { int currentDemodType; int currentOutputDevice; int currentAudioSampleRate; - bool follow; + bool follow, tracking; }; diff --git a/src/sdr/SDRPostThread.cpp b/src/sdr/SDRPostThread.cpp index f07b2b28..43839cbb 100644 --- a/src/sdr/SDRPostThread.cpp +++ b/src/sdr/SDRPostThread.cpp @@ -186,7 +186,7 @@ void SDRPostThread::threadMain() { DemodulatorThreadInputQueue *demodQueue = demod->threadQueueDemod; if (abs(data_in->frequency - demod->getFrequency()) > (wxGetApp().getSampleRate() / 2)) { - if (demod->isActive() && !demod->isFollow()) { + if (demod->isActive() && !demod->isFollow() && !demod->isTracking()) { demod->setActive(false); DemodulatorThreadIQData *dummyDataOut = new DemodulatorThreadIQData; dummyDataOut->frequency = data_in->frequency; diff --git a/src/sdr/SDRThread.cpp b/src/sdr/SDRThread.cpp index 405ed0fe..4254d1ce 100644 --- a/src/sdr/SDRThread.cpp +++ b/src/sdr/SDRThread.cpp @@ -122,8 +122,11 @@ void SDRThread::threadMain() { std::cout << "SDR thread initializing.." << std::endl; int devCount = rtlsdr_get_device_count(); + std::vector devs; if (deviceId == -1) { - deviceId = enumerate_rtl(NULL); + deviceId = enumerate_rtl(&devs); + } else { + enumerate_rtl(&devs); } if (deviceId == -1) { @@ -136,10 +139,12 @@ void SDRThread::threadMain() { signed char buf[BUF_SIZE]; long long frequency = DEFAULT_FREQ; + int ppm = wxGetApp().getConfig()->getDevice(devs[deviceId]->getDeviceId())->getPPM(); rtlsdr_open(&dev, deviceId); rtlsdr_set_sample_rate(dev, sampleRate); rtlsdr_set_center_freq(dev, frequency - offset); + rtlsdr_set_freq_correction(dev, ppm); rtlsdr_set_agc_mode(dev, 1); rtlsdr_set_offset_tuning(dev, 0); rtlsdr_reset_buffer(dev); @@ -164,10 +169,12 @@ void SDRThread::threadMain() { bool offset_changed = false; bool rate_changed = false; bool device_changed = false; - long long new_freq; - long long new_offset; - long long new_rate; - int new_device; + bool ppm_changed = false; + long long new_freq = frequency; + long long new_offset = offset; + long long new_rate = sampleRate; + int new_device = deviceId; + int new_ppm = ppm; while (!cmdQueue->empty()) { SDRThreadCommand command; @@ -197,6 +204,11 @@ void SDRThread::threadMain() { new_device = (int) command.llong_value; std::cout << "Set device: " << new_device << std::endl; break; + case SDRThreadCommand::SDR_THREAD_CMD_SET_PPM: + ppm_changed = true; + new_ppm = (int) command.llong_value; + std::cout << "Set PPM: " << new_ppm << std::endl; + break; default: break; } @@ -207,6 +219,7 @@ void SDRThread::threadMain() { rtlsdr_open(&dev, new_device); rtlsdr_set_sample_rate(dev, sampleRate); rtlsdr_set_center_freq(dev, frequency - offset); + rtlsdr_set_freq_correction(dev, ppm); rtlsdr_set_agc_mode(dev, 1); rtlsdr_set_offset_tuning(dev, 0); rtlsdr_reset_buffer(dev); @@ -224,6 +237,10 @@ void SDRThread::threadMain() { frequency = new_freq; rtlsdr_set_center_freq(dev, frequency - offset); } + if (ppm_changed) { + ppm = new_ppm; + rtlsdr_set_freq_correction(dev, ppm); + } } rtlsdr_read_sync(dev, buf, BUF_SIZE, &n_read); diff --git a/src/sdr/SDRThread.h b/src/sdr/SDRThread.h index 47c00625..57e85234 100644 --- a/src/sdr/SDRThread.h +++ b/src/sdr/SDRThread.h @@ -11,6 +11,16 @@ class SDRDeviceInfo { public: SDRDeviceInfo() : name(""), serial(""), available(false) { } + std::string getDeviceId() { + std::string deviceId; + + deviceId.append(getName()); + deviceId.append(" :: "); + deviceId.append(getSerial()); + + return deviceId; + } + bool isAvailable() const { return available; } @@ -71,7 +81,7 @@ class SDRDeviceInfo { class SDRThreadCommand { public: enum SDRThreadCommandEnum { - SDR_THREAD_CMD_NULL, SDR_THREAD_CMD_TUNE, SDR_THREAD_CMD_SET_OFFSET, SDR_THREAD_CMD_SET_SAMPLERATE, SDR_THREAD_CMD_SET_DEVICE + SDR_THREAD_CMD_NULL, SDR_THREAD_CMD_TUNE, SDR_THREAD_CMD_SET_OFFSET, SDR_THREAD_CMD_SET_SAMPLERATE, SDR_THREAD_CMD_SET_PPM, SDR_THREAD_CMD_SET_DEVICE }; SDRThreadCommand() : diff --git a/src/visual/InteractiveCanvas.cpp b/src/visual/InteractiveCanvas.cpp index 9a24165e..19cc0bc7 100644 --- a/src/visual/InteractiveCanvas.cpp +++ b/src/visual/InteractiveCanvas.cpp @@ -81,6 +81,18 @@ MouseTracker *InteractiveCanvas::getMouseTracker() { return &mouseTracker; } +bool InteractiveCanvas::isAltDown() { + return altDown; +} + +bool InteractiveCanvas::isCtrlDown() { + return ctrlDown; +} + +bool InteractiveCanvas::isShiftDown() { + return shiftDown; +} + void InteractiveCanvas::OnKeyUp(wxKeyEvent& event) { shiftDown = event.ShiftDown(); altDown = event.AltDown(); @@ -88,8 +100,6 @@ void InteractiveCanvas::OnKeyUp(wxKeyEvent& event) { } void InteractiveCanvas::OnKeyDown(wxKeyEvent& event) { - float angle = 5.0; - shiftDown = event.ShiftDown(); altDown = event.AltDown(); ctrlDown = event.ControlDown(); @@ -125,10 +135,18 @@ void InteractiveCanvas::OnMouseReleased(wxMouseEvent& event) { void InteractiveCanvas::OnMouseLeftWindow(wxMouseEvent& event) { mouseTracker.OnMouseLeftWindow(event); + + shiftDown = false; + altDown = false; + ctrlDown = false; } void InteractiveCanvas::OnMouseEnterWindow(wxMouseEvent& event) { mouseTracker.OnMouseEnterWindow(event); + + shiftDown = event.ShiftDown(); + altDown = event.AltDown(); + ctrlDown = event.ControlDown(); } void InteractiveCanvas::setStatusText(std::string statusText) { diff --git a/src/visual/InteractiveCanvas.h b/src/visual/InteractiveCanvas.h index 055dcc92..bbc4024a 100644 --- a/src/visual/InteractiveCanvas.h +++ b/src/visual/InteractiveCanvas.h @@ -25,6 +25,10 @@ class InteractiveCanvas: public wxGLCanvas { MouseTracker *getMouseTracker(); + bool isAltDown(); + bool isCtrlDown(); + bool isShiftDown(); + protected: void OnKeyDown(wxKeyEvent& event); void OnKeyUp(wxKeyEvent& event); diff --git a/src/visual/ScopeCanvas.cpp b/src/visual/ScopeCanvas.cpp index cc8c000d..2b01de8f 100644 --- a/src/visual/ScopeCanvas.cpp +++ b/src/visual/ScopeCanvas.cpp @@ -21,7 +21,7 @@ wxEND_EVENT_TABLE() ScopeCanvas::ScopeCanvas(wxWindow *parent, int *attribList) : wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, - wxFULL_REPAINT_ON_RESIZE), parent(parent), stereo(false) { + wxFULL_REPAINT_ON_RESIZE), parent(parent), stereo(false), ppmMode(false) { glContext = new ScopeContext(this, &wxGetApp().GetContext(this)); } @@ -43,6 +43,14 @@ void ScopeCanvas::setDeviceName(std::string device_name) { deviceName.append(" "); } +void ScopeCanvas::setPPMMode(bool ppmMode) { + this->ppmMode = ppmMode; +} + +bool ScopeCanvas::getPPMMode() { + return ppmMode; +} + void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { wxPaintDC dc(this); #ifdef __APPLE__ // force half-rate? @@ -75,7 +83,7 @@ void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { glViewport(0, 0, ClientSize.x, ClientSize.y); glContext->DrawBegin(); - glContext->Plot(waveform_points, stereo); + glContext->Plot(waveform_points, stereo, ppmMode); if (!deviceName.empty()) { glContext->DrawDeviceName(deviceName); } diff --git a/src/visual/ScopeCanvas.h b/src/visual/ScopeCanvas.h index 94f33284..cb24feef 100644 --- a/src/visual/ScopeCanvas.h +++ b/src/visual/ScopeCanvas.h @@ -19,6 +19,9 @@ class ScopeCanvas: public wxGLCanvas { void setWaveformPoints(std::vector &waveform_points_in); void setStereo(bool state); void setDeviceName(std::string device_name); + void setPPMMode(bool ppmMode); + bool getPPMMode(); + private: void OnPaint(wxPaintEvent& event); @@ -29,6 +32,7 @@ class ScopeCanvas: public wxGLCanvas { ScopeContext *glContext; std::string deviceName; bool stereo; + bool ppmMode; // event table wxDECLARE_EVENT_TABLE(); }; diff --git a/src/visual/ScopeContext.cpp b/src/visual/ScopeContext.cpp index 6b72c4a8..b90f19ea 100644 --- a/src/visual/ScopeContext.cpp +++ b/src/visual/ScopeContext.cpp @@ -23,7 +23,7 @@ void ScopeContext::DrawBegin() { glDisable (GL_TEXTURE_2D); } -void ScopeContext::Plot(std::vector &points, bool stereo) { +void ScopeContext::Plot(std::vector &points, bool stereo, bool ppmMode) { if (stereo) { glBegin(GL_QUADS); glColor3f(ThemeMgr::mgr.currentTheme->scopeBackground.r, ThemeMgr::mgr.currentTheme->scopeBackground.g, ThemeMgr::mgr.currentTheme->scopeBackground.b); @@ -81,7 +81,7 @@ void ScopeContext::Plot(std::vector &points, bool stereo) { glColor3f(0.65, 0.65, 0.65); - getFont(PrimaryGLContext::GLFONT_SIZE12).drawString("Frequency", -0.66, -1.0+hPos, 12, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER); + getFont(PrimaryGLContext::GLFONT_SIZE12).drawString(ppmMode?"Device PPM":"Frequency", -0.66, -1.0+hPos, 12, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER); getFont(PrimaryGLContext::GLFONT_SIZE12).drawString("Bandwidth", 0.0, -1.0+hPos, 12, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER); getFont(PrimaryGLContext::GLFONT_SIZE12).drawString("Center Frequency", 0.66, -1.0+hPos, 12, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER); diff --git a/src/visual/ScopeContext.h b/src/visual/ScopeContext.h index d4fa1a04..4afc6adb 100644 --- a/src/visual/ScopeContext.h +++ b/src/visual/ScopeContext.h @@ -12,7 +12,7 @@ class ScopeContext: public PrimaryGLContext { ScopeContext(ScopeCanvas *canvas, wxGLContext *sharedContext); void DrawBegin(); - void Plot(std::vector &points, bool stereo=false); + void Plot(std::vector &points, bool stereo=false, bool ppmMode=false); void DrawDeviceName(std::string deviceName); void DrawDivider(); void DrawEnd(); diff --git a/src/visual/SpectrumCanvas.cpp b/src/visual/SpectrumCanvas.cpp index f22403d6..776b89a7 100644 --- a/src/visual/SpectrumCanvas.cpp +++ b/src/visual/SpectrumCanvas.cpp @@ -28,7 +28,7 @@ wxEND_EVENT_TABLE() SpectrumCanvas::SpectrumCanvas(wxWindow *parent, int *attribList) : InteractiveCanvas(parent, attribList), fft_size(0), in(NULL), out(NULL), plan(NULL), fft_ceil_ma(1), fft_ceil_maa(1), fft_floor_ma(0), fft_floor_maa( - 0), waterfallCanvas(NULL) { + 0), waterfallCanvas(NULL), trackingRate(0) { glContext = new SpectrumContext(this, &wxGetApp().GetContext(this)); @@ -175,46 +175,49 @@ void SpectrumCanvas::OnIdle(wxIdleEvent &event) { Refresh(false); } + +void SpectrumCanvas::moveCenterFrequency(long long freqChange) { + long long freq = wxGetApp().getFrequency(); + + if (isView) { + if (centerFreq - freqChange < bandwidth/2) { + centerFreq = bandwidth/2; + } else { + centerFreq -= freqChange; + } + + if (waterfallCanvas) { + waterfallCanvas->setCenterFrequency(centerFreq); + } + + long long bwOfs = (centerFreq > freq) ? ((long long) bandwidth / 2) : (-(long long) bandwidth / 2); + long long freqEdge = centerFreq + bwOfs; + + if (abs(freq - freqEdge) > (wxGetApp().getSampleRate() / 2)) { + freqChange = -((centerFreq > freq) ? (freqEdge - freq - (wxGetApp().getSampleRate() / 2)) : (freqEdge - freq + (wxGetApp().getSampleRate() / 2))); + } else { + freqChange = 0; + } + } + + if (freqChange) { + if (freq - freqChange < wxGetApp().getSampleRate()/2) { + freq = wxGetApp().getSampleRate()/2; + } else { + freq -= freqChange; + } + wxGetApp().setFrequency(freq); + setStatusText("Set center frequency: %s", freq); + } +} + void SpectrumCanvas::OnMouseMoved(wxMouseEvent& event) { InteractiveCanvas::OnMouseMoved(event); if (mouseTracker.mouseDown()) { int freqChange = mouseTracker.getDeltaMouseX() * getBandwidth(); if (freqChange != 0) { - long long freq = wxGetApp().getFrequency(); - - if (isView) { - if (isView) { - if (centerFreq - freqChange < bandwidth/2) { - centerFreq = bandwidth/2; - } else { - centerFreq -= freqChange; - } - } - if (waterfallCanvas) { - waterfallCanvas->setCenterFrequency(centerFreq); - } - - long long bwOfs = (centerFreq > freq) ? ((long long) bandwidth / 2) : (-(long long) bandwidth / 2); - long long freqEdge = centerFreq + bwOfs; - - if (abs(freq - freqEdge) > (wxGetApp().getSampleRate() / 2)) { - freqChange = -((centerFreq > freq) ? (freqEdge - freq - (wxGetApp().getSampleRate() / 2)) : (freqEdge - freq + (wxGetApp().getSampleRate() / 2))); - } else { - freqChange = 0; - } - } - - if (freqChange) { - if (freq - freqChange < wxGetApp().getSampleRate()/2) { - freq = wxGetApp().getSampleRate()/2; - } else { - freq -= freqChange; - } - wxGetApp().setFrequency(freq); - setStatusText("Set center frequency: %s", freq); - } - + moveCenterFrequency(freqChange); } } else { setStatusText("Click and drag to adjust center frequency."); diff --git a/src/visual/SpectrumCanvas.h b/src/visual/SpectrumCanvas.h index 74756ce7..f1469a25 100644 --- a/src/visual/SpectrumCanvas.h +++ b/src/visual/SpectrumCanvas.h @@ -24,6 +24,7 @@ class SpectrumCanvas: public InteractiveCanvas { void setData(DemodulatorThreadIQData *input); void attachWaterfallCanvas(WaterfallCanvas *canvas_in); + void moveCenterFrequency(long long freqChange); SpectrumContext* getSpectrumContext(); @@ -51,6 +52,7 @@ class SpectrumCanvas: public InteractiveCanvas { SpectrumContext *glContext; WaterfallCanvas *waterfallCanvas; int fft_size; + int trackingRate; // event table wxDECLARE_EVENT_TABLE(); }; diff --git a/src/visual/TuningCanvas.cpp b/src/visual/TuningCanvas.cpp index 30e84797..d65bbaaa 100644 --- a/src/visual/TuningCanvas.cpp +++ b/src/visual/TuningCanvas.cpp @@ -23,6 +23,8 @@ EVT_LEFT_UP(TuningCanvas::OnMouseReleased) EVT_LEAVE_WINDOW(TuningCanvas::OnMouseLeftWindow) EVT_ENTER_WINDOW(TuningCanvas::OnMouseEnterWindow) EVT_MOUSEWHEEL(TuningCanvas::OnMouseWheelMoved) +EVT_KEY_DOWN(TuningCanvas::OnKeyDown) +EVT_KEY_UP(TuningCanvas::OnKeyUp) wxEND_EVENT_TABLE() TuningCanvas::TuningCanvas(wxWindow *parent, int *attribList) : @@ -44,6 +46,8 @@ TuningCanvas::TuningCanvas(wxWindow *parent, int *attribList) : centerDP = -1.0 + (2.0 / 3.0) * 2.0; centerW = (1.0 / 3.0) * 2.0; + + currentPPM = lastPPM = 0; } TuningCanvas::~TuningCanvas() { @@ -110,10 +114,17 @@ void TuningCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { break; case TUNING_HOVER_NONE: break; - } + case TUNING_HOVER_PPM: + glContext->DrawTunerBarIndexed(hoverIndex, hoverIndex, 11, freqDP, freqW, clr, 0.25, top, bottom); // freq + break; + } } - glContext->DrawTuner(freq, 11, freqDP, freqW); + if (altDown) { + glContext->DrawTuner(currentPPM, 11, freqDP, freqW); + } else { + glContext->DrawTuner(freq, 11, freqDP, freqW); + } glContext->DrawTuner(bw, 7, bwDP, bwW); glContext->DrawTuner(center, 11, centerDP, centerW); @@ -142,9 +153,10 @@ void TuningCanvas::StepTuner(ActiveState state, int exponent, bool up) { wxGetApp().setFrequency(freq); } + activeDemod->setTracking(true); + activeDemod->setFollow(true); activeDemod->setFrequency(freq); activeDemod->updateLabel(freq); - activeDemod->setFollow(true); } if (state == TUNING_HOVER_BW) { @@ -179,6 +191,25 @@ void TuningCanvas::StepTuner(ActiveState state, int exponent, bool up) { wxGetApp().setFrequency(ctr); } + + if (state == TUNING_HOVER_PPM) { + if (shiftDown) { + bool carried = (long long)((currentPPM) / (exp * 10)) != (long long)((currentPPM + amount) / (exp * 10)) || (bottom && currentPPM < exp); + currentPPM += carried?(9*-amount):amount; + } else { + currentPPM += amount; + } + + if (currentPPM > 2000) { + currentPPM = 2000; + } + + if (currentPPM < -2000) { + currentPPM = -2000; + } + + wxGetApp().setPPM(currentPPM); + } } void TuningCanvas::OnIdle(wxIdleEvent &event) { @@ -215,7 +246,7 @@ void TuningCanvas::OnMouseMoved(wxMouseEvent& event) { index = glContext->GetTunerDigitIndex(mouseTracker.getMouseX(), 11, freqDP, freqW); // freq if (index > 0) { hoverIndex = index; - hoverState = TUNING_HOVER_FREQ; + hoverState = altDown?TUNING_HOVER_PPM:TUNING_HOVER_FREQ; } if (!index) { @@ -240,15 +271,18 @@ void TuningCanvas::OnMouseMoved(wxMouseEvent& event) { } else { switch (hoverState) { case TUNING_HOVER_FREQ: - setStatusText("Click or drag a digit to change frequency. Hold shift to disable carry."); + setStatusText("Click, wheel or drag a digit to change frequency. Hold ALT to change PPM. Hold SHIFT to disable carry."); break; case TUNING_HOVER_BW: - setStatusText("Click or drag a digit to change bandwidth. Hold shift to disable carry."); + setStatusText("Click, wheel or drag a digit to change bandwidth. Hold SHIFT to disable carry."); break; case TUNING_HOVER_CENTER: - setStatusText("Click or drag a digit to change center frequency. Hold shift to disable carry."); + setStatusText("Click, wheel or drag a digit to change center frequency. Hold SHIFT to disable carry."); break; - } + case TUNING_HOVER_PPM: + setStatusText("Click, wheel or drag a digit to change device PPM offset. Hold SHIFT to disable carry."); + break; + } } @@ -305,6 +339,10 @@ void TuningCanvas::OnMouseLeftWindow(wxMouseEvent& event) { SetCursor(wxCURSOR_CROSS); hoverIndex = 0; hoverState = TUNING_HOVER_NONE; + + if (currentPPM != lastPPM) { + wxGetApp().saveConfig(); + } } void TuningCanvas::OnMouseEnterWindow(wxMouseEvent& event) { @@ -312,8 +350,17 @@ void TuningCanvas::OnMouseEnterWindow(wxMouseEvent& event) { SetCursor(wxCURSOR_ARROW); hoverIndex = 0; hoverState = TUNING_HOVER_NONE; + lastPPM = currentPPM = wxGetApp().getPPM(); } void TuningCanvas::setHelpTip(std::string tip) { helpTip = tip; } + +void TuningCanvas::OnKeyDown(wxKeyEvent& event) { + InteractiveCanvas::OnKeyDown(event); +} + +void TuningCanvas::OnKeyUp(wxKeyEvent& event) { + InteractiveCanvas::OnKeyUp(event); +} diff --git a/src/visual/TuningCanvas.h b/src/visual/TuningCanvas.h index 21b22c58..d6e2cee3 100644 --- a/src/visual/TuningCanvas.h +++ b/src/visual/TuningCanvas.h @@ -16,7 +16,7 @@ class TuningCanvas: public InteractiveCanvas { public: enum ActiveState { - TUNING_HOVER_NONE, TUNING_HOVER_FREQ, TUNING_HOVER_BW, TUNING_HOVER_CENTER + TUNING_HOVER_NONE, TUNING_HOVER_FREQ, TUNING_HOVER_BW, TUNING_HOVER_PPM, TUNING_HOVER_CENTER }; TuningCanvas(wxWindow *parent, int *attribList = NULL); ~TuningCanvas(); @@ -33,6 +33,9 @@ class TuningCanvas: public InteractiveCanvas { void OnMouseReleased(wxMouseEvent& event); void OnMouseEnterWindow(wxMouseEvent& event); void OnMouseLeftWindow(wxMouseEvent& event); + void OnKeyDown(wxKeyEvent& event); + void OnKeyUp(wxKeyEvent& event); + void StepTuner(ActiveState state, int factor, bool up = true); TuningContext *glContext; @@ -58,6 +61,9 @@ class TuningCanvas: public InteractiveCanvas { bool top; bool bottom; + int currentPPM; + int lastPPM; + // wxDECLARE_EVENT_TABLE(); }; diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index 424041a9..77cbdb94 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -820,6 +820,7 @@ void WaterfallCanvas::OnMouseReleased(wxMouseEvent& event) { mouseTracker.setHorizDragLock(false); } else { wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getActiveDemodulator(), false); + wxGetApp().getDemodMgr().getActiveDemodulator()->setTracking(true); nextDragState = WF_DRAG_FREQUENCY; } } else if (dragState == WF_DRAG_RANGE) {