// Copyright (c) Charles J. Cliffe // SPDX-License-Identifier: GPL-2.0+ #include "AppFrame.h" #include "wx/wxprec.h" #ifndef WX_PRECOMP #include "wx/wx.h" #endif #include "wx/numdlg.h" #include "wx/filedlg.h" #if !wxUSE_GLCANVAS #error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library" #endif #include #include #include "AudioThread.h" #include "CubicSDR.h" #include "DataTree.h" #include "ColorTheme.h" #include "DemodulatorMgr.h" #include #include #ifdef __linux__ #include "CubicSDR.xpm" #endif wxBEGIN_EVENT_TABLE(AppFrame, wxFrame) //EVT_MENU(wxID_NEW, AppFrame::OnNewWindow) EVT_CLOSE(AppFrame::OnClose) EVT_MENU(wxID_ANY, AppFrame::OnMenu) EVT_COMMAND(wxID_ANY, wxEVT_THREAD, AppFrame::OnThread) EVT_IDLE(AppFrame::OnIdle) EVT_SPLITTER_DCLICK(wxID_ANY, AppFrame::OnDoubleClickSash) EVT_SPLITTER_UNSPLIT(wxID_ANY, AppFrame::OnUnSplit) wxEND_EVENT_TABLE() #ifdef USE_HAMLIB #include "RigThread.h" #endif #define APPFRAME_MODEMPROPS_MINSIZE 20 #define APPFRAME_MODEMPROPS_MAXSIZE 240 AppFrame::AppFrame() : wxFrame(NULL, wxID_ANY, CUBICSDR_TITLE), activeDemodulator(NULL) { #ifdef __linux__ SetIcon(wxICON(cubicsdr)); #endif wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL); wxBoxSizer *demodVisuals = new wxBoxSizer(wxVERTICAL); demodTray = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer *demodScopeTray = new wxBoxSizer(wxVERTICAL); wxBoxSizer *demodTunerTray = new wxBoxSizer(wxHORIZONTAL); int attribList[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, 0 }; //wxGLAttributes attribList; //attribList.PlatformDefaults().RGBA().DoubleBuffer().EndList(); //attribList.PlatformDefaults().MinRGBA(8, 8, 8, 8).DoubleBuffer().Depth(16).EndList(); mainSplitter = new wxSplitterWindow( this, wxID_MAIN_SPLITTER, wxDefaultPosition, wxDefaultSize, wxSP_3DSASH | wxSP_LIVE_UPDATE ); mainSplitter->SetSashGravity(10.0/37.0); mainSplitter->SetMinimumPaneSize(1); wxPanel *demodPanel = new wxPanel(mainSplitter, wxID_ANY); gainCanvas = new GainCanvas(demodPanel, attribList); gainSizerItem = demodTray->Add(gainCanvas, 0, wxEXPAND | wxALL, 0); gainSizerItem->Show(false); gainSpacerItem = demodTray->AddSpacer(1); gainSpacerItem->Show(false); demodModeSelector = new ModeSelectorCanvas(demodPanel, attribList); demodModeSelector->addChoice("FM"); demodModeSelector->addChoice("FMS"); demodModeSelector->addChoice("NBFM"); demodModeSelector->addChoice("AM"); demodModeSelector->addChoice("LSB"); demodModeSelector->addChoice("USB"); demodModeSelector->addChoice("DSB"); demodModeSelector->addChoice("I/Q"); demodModeSelector->setSelection("FM"); demodModeSelector->setHelpTip("Choose modulation type: Frequency Modulation (Hotkey F), Amplitude Modulation (A) and Lower (L), Upper (U), Double Side-Band and more."); demodModeSelector->SetMinSize(wxSize(50,-1)); demodModeSelector->SetMaxSize(wxSize(50,-1)); demodTray->Add(demodModeSelector, 2, wxEXPAND | wxALL, 0); #ifdef ENABLE_DIGITAL_LAB demodModeSelectorAdv = new ModeSelectorCanvas(demodPanel, attribList); demodModeSelectorAdv->addChoice("ASK"); demodModeSelectorAdv->addChoice("APSK"); demodModeSelectorAdv->addChoice("BPSK"); demodModeSelectorAdv->addChoice("DPSK"); demodModeSelectorAdv->addChoice("PSK"); demodModeSelectorAdv->addChoice("FSK"); demodModeSelectorAdv->addChoice("GMSK"); demodModeSelectorAdv->addChoice("OOK"); demodModeSelectorAdv->addChoice("ST"); demodModeSelectorAdv->addChoice("SQAM"); demodModeSelectorAdv->addChoice("QAM"); demodModeSelectorAdv->addChoice("QPSK"); demodModeSelectorAdv->setHelpTip("Choose advanced modulation types."); demodModeSelectorAdv->SetMinSize(wxSize(50,-1)); demodModeSelectorAdv->SetMaxSize(wxSize(50,-1)); demodTray->Add(demodModeSelectorAdv, 3, wxEXPAND | wxALL, 0); #endif modemPropertiesUpdated.store(false); modemProps = new ModemProperties(demodPanel, wxID_ANY); modemProps->SetMinSize(wxSize(APPFRAME_MODEMPROPS_MAXSIZE,-1)); modemProps->SetMaxSize(wxSize(APPFRAME_MODEMPROPS_MAXSIZE,-1)); modemProps->Hide(); demodTray->Add(modemProps, 15, wxEXPAND | wxALL, 0); #ifndef __APPLE__ demodTray->AddSpacer(1); #endif wxGetApp().getDemodSpectrumProcessor()->setup(1024); demodSpectrumCanvas = new SpectrumCanvas(demodPanel, attribList); demodSpectrumCanvas->setView(wxGetApp().getConfig()->getCenterFreq(), 300000); demodVisuals->Add(demodSpectrumCanvas, 3, wxEXPAND | wxALL, 0); wxGetApp().getDemodSpectrumProcessor()->attachOutput(demodSpectrumCanvas->getVisualDataQueue()); demodVisuals->AddSpacer(1); demodWaterfallCanvas = new WaterfallCanvas(demodPanel, attribList); demodWaterfallCanvas->setup(1024, 128); demodWaterfallCanvas->setView(wxGetApp().getConfig()->getCenterFreq(), 300000); demodWaterfallCanvas->attachSpectrumCanvas(demodSpectrumCanvas); demodWaterfallCanvas->setMinBandwidth(8000); demodSpectrumCanvas->attachWaterfallCanvas(demodWaterfallCanvas); demodVisuals->Add(demodWaterfallCanvas, 6, wxEXPAND | wxALL, 0); wxGetApp().getDemodSpectrumProcessor()->attachOutput(demodWaterfallCanvas->getVisualDataQueue()); demodWaterfallCanvas->getVisualDataQueue()->set_max_num_items(3); demodVisuals->SetMinSize(wxSize(128,-1)); demodTray->Add(demodVisuals, 30, wxEXPAND | wxALL, 0); demodTray->AddSpacer(1); demodSignalMeter = new MeterCanvas(demodPanel, attribList); demodSignalMeter->setMax(DEMOD_SIGNAL_MAX); demodSignalMeter->setMin(DEMOD_SIGNAL_MIN); demodSignalMeter->setLevel(DEMOD_SIGNAL_MIN); demodSignalMeter->setInputValue(DEMOD_SIGNAL_MIN); demodSignalMeter->setHelpTip("Current Signal Level. Click / Drag to set Squelch level. Right-Click to Auto-Zero Squelch"); demodSignalMeter->SetMinSize(wxSize(12,24)); demodTray->Add(demodSignalMeter, 1, wxEXPAND | wxALL, 0); demodTray->AddSpacer(1); scopeCanvas = new ScopeCanvas(demodPanel, attribList); scopeCanvas->setHelpTip("Audio Visuals, drag left/right to toggle Scope or Spectrum."); scopeCanvas->SetMinSize(wxSize(128,-1)); demodScopeTray->Add(scopeCanvas, 8, wxEXPAND | wxALL, 0); wxGetApp().getScopeProcessor()->setup(1024); wxGetApp().getScopeProcessor()->attachOutput(scopeCanvas->getInputQueue()); demodScopeTray->AddSpacer(1); deltaLockButton = new ModeSelectorCanvas(demodPanel, attribList); deltaLockButton->addChoice(1, "V"); deltaLockButton->setPadding(-1,-1); deltaLockButton->setHighlightColor(RGBA4f(0.8f,0.8f,0.2f)); deltaLockButton->setHelpTip("Delta Lock Toggle (V) - Enable to lock modem relative to center frequency."); deltaLockButton->setToggleMode(true); deltaLockButton->setSelection(-1); deltaLockButton->SetMinSize(wxSize(20,28)); demodTunerTray->Add(deltaLockButton, 0, wxEXPAND | wxALL, 0); demodTunerTray->AddSpacer(1); demodTuner = new TuningCanvas(demodPanel, attribList); demodTuner->SetMinClientSize(wxSize(200,28)); demodTunerTray->Add(demodTuner, 1, wxEXPAND | wxALL, 0); demodScopeTray->Add(demodTunerTray, 1, wxEXPAND | wxALL, 0); demodTray->Add(demodScopeTray, 30, wxEXPAND | wxALL, 0); demodTray->AddSpacer(1); wxBoxSizer *demodGainTray = new wxBoxSizer(wxVERTICAL); demodGainMeter = new MeterCanvas(demodPanel, attribList); demodGainMeter->setMax(2.0); demodGainMeter->setHelpTip("Current Demodulator Gain Level. Click / Drag to set Gain level."); demodGainMeter->setShowUserInput(false); demodGainMeter->SetMinSize(wxSize(13,24)); demodGainTray->Add(demodGainMeter, 8, wxEXPAND | wxALL, 0); demodGainTray->AddSpacer(1); soloModeButton = new ModeSelectorCanvas(demodPanel, attribList); soloModeButton->addChoice(1, "S"); soloModeButton->setPadding(-1,-1); soloModeButton->setHighlightColor(RGBA4f(0.8f,0.8f,0.2f)); soloModeButton->setHelpTip("Solo Mode Toggle"); soloModeButton->setToggleMode(true); soloModeButton->setSelection(-1); soloModeButton->SetMinSize(wxSize(12,28)); demodGainTray->Add(soloModeButton, 1, wxEXPAND | wxALL, 0); demodGainTray->AddSpacer(1); demodMuteButton = new ModeSelectorCanvas(demodPanel, attribList); demodMuteButton->addChoice(1, "M"); demodMuteButton->setPadding(-1,-1); demodMuteButton->setHighlightColor(RGBA4f(0.8f,0.2f,0.2f)); demodMuteButton->setHelpTip("Demodulator Mute Toggle"); demodMuteButton->setToggleMode(true); demodMuteButton->setSelection(-1); demodMuteButton->SetMinSize(wxSize(12,28)); demodGainTray->Add(demodMuteButton, 1, wxEXPAND | wxALL, 0); demodTray->Add(demodGainTray, 1, wxEXPAND | wxALL, 0); demodPanel->SetSizer(demodTray); // vbox->Add(demodTray, 12, wxEXPAND | wxALL, 0); // vbox->AddSpacer(1); mainVisSplitter = new wxSplitterWindow( mainSplitter, wxID_VIS_SPLITTER, wxDefaultPosition, wxDefaultSize, wxSP_3DSASH | wxSP_LIVE_UPDATE ); mainVisSplitter->SetSashGravity(6.0/25.0); mainVisSplitter->SetMinimumPaneSize(1); // mainVisSplitter->Connect( wxEVT_IDLE, wxIdleEventHandler( AppFrame::mainVisSplitterIdle ), NULL, this ); wxPanel *spectrumPanel = new wxPanel(mainVisSplitter, wxID_ANY); wxBoxSizer *spectrumSizer = new wxBoxSizer(wxHORIZONTAL); wxGetApp().getSpectrumProcessor()->setup(2048); spectrumCanvas = new SpectrumCanvas(spectrumPanel, attribList); spectrumCanvas->setShowDb(true); spectrumCanvas->setScaleFactorEnabled(true); wxGetApp().getSpectrumProcessor()->attachOutput(spectrumCanvas->getVisualDataQueue()); wxBoxSizer *spectrumCtlTray = new wxBoxSizer(wxVERTICAL); peakHoldButton = new ModeSelectorCanvas(spectrumPanel, attribList); peakHoldButton->addChoice(1, "P"); peakHoldButton->setPadding(-1,-1); peakHoldButton->setHighlightColor(RGBA4f(0.2f,0.8f,0.2f)); peakHoldButton->setHelpTip("Peak Hold Toggle"); peakHoldButton->setToggleMode(true); peakHoldButton->setSelection(-1); peakHoldButton->SetMinSize(wxSize(12,24)); spectrumCtlTray->Add(peakHoldButton, 1, wxEXPAND | wxALL, 0); spectrumCtlTray->AddSpacer(1); spectrumAvgMeter = new MeterCanvas(spectrumPanel, attribList); spectrumAvgMeter->setHelpTip("Spectrum averaging speed, click or drag to adjust."); spectrumAvgMeter->setMax(1.0); spectrumAvgMeter->setLevel(0.65f); spectrumAvgMeter->setShowUserInput(false); spectrumAvgMeter->SetMinSize(wxSize(12,24)); spectrumCtlTray->Add(spectrumAvgMeter, 8, wxEXPAND | wxALL, 0); spectrumSizer->Add(spectrumCanvas, 63, wxEXPAND | wxALL, 0); spectrumSizer->AddSpacer(1); spectrumSizer->Add(spectrumCtlTray, 1, wxEXPAND | wxALL, 0); spectrumPanel->SetSizer(spectrumSizer); // vbox->Add(spectrumSizer, 5, wxEXPAND | wxALL, 0); // vbox->AddSpacer(1); wxPanel *waterfallPanel = new wxPanel(mainVisSplitter, wxID_ANY); wxBoxSizer *wfSizer = new wxBoxSizer(wxHORIZONTAL); waterfallCanvas = new WaterfallCanvas(waterfallPanel, attribList); waterfallCanvas->setup(2048, 512); waterfallDataThread = new FFTVisualDataThread(); waterfallDataThread->setInputQueue("IQDataInput", wxGetApp().getWaterfallVisualQueue()); waterfallDataThread->setOutputQueue("FFTDataOutput", waterfallCanvas->getVisualDataQueue()); waterfallDataThread->getProcessor()->setHideDC(true); t_FFTData = new std::thread(&FFTVisualDataThread::threadMain, waterfallDataThread); waterfallSpeedMeter = new MeterCanvas(waterfallPanel, attribList); waterfallSpeedMeter->setHelpTip("Waterfall speed, click or drag to adjust (max 1024 lines per second)"); waterfallSpeedMeter->setMax(sqrt(1024)); waterfallSpeedMeter->setLevel(sqrt(DEFAULT_WATERFALL_LPS)); waterfallSpeedMeter->setShowUserInput(false); waterfallSpeedMeter->SetMinSize(wxSize(12,24)); wfSizer->Add(waterfallCanvas, 63, wxEXPAND | wxALL, 0); wfSizer->AddSpacer(1); wfSizer->Add(waterfallSpeedMeter, 1, wxEXPAND | wxALL, 0); waterfallPanel->SetSizer(wfSizer); // vbox->Add(wfSizer, 20, wxEXPAND | wxALL, 0); mainVisSplitter->SplitHorizontally( spectrumPanel, waterfallPanel, 0 ); mainSplitter->SplitHorizontally( demodPanel, mainVisSplitter ); vbox->Add(mainSplitter, 1, wxEXPAND | wxALL, 0); // TODO: refactor these.. waterfallCanvas->attachSpectrumCanvas(spectrumCanvas); spectrumCanvas->attachWaterfallCanvas(waterfallCanvas); /* * / vbox->AddSpacer(1); testCanvas = new UITestCanvas(this, attribList); vbox->Add(testCanvas, 20, wxEXPAND | wxALL, 0); // */ this->SetSizer(vbox); // SetIcon(wxICON(sample)); // Make a menubar menuBar = new wxMenuBar; wxMenu *menu = new wxMenu; menu->Append(wxID_SDR_DEVICES, "SDR Devices"); menu->AppendSeparator(); menu->Append(wxID_SDR_START_STOP, "Stop / Start Device"); menu->AppendSeparator(); menu->Append(wxID_OPEN, "&Open Session"); menu->Append(wxID_SAVE, "&Save Session"); menu->Append(wxID_SAVEAS, "Save Session &As.."); menu->AppendSeparator(); menu->Append(wxID_RESET, "&Reset Session"); #ifndef __APPLE__ menu->AppendSeparator(); menu->Append(wxID_CLOSE); #else if ( wxApp::s_macAboutMenuItemId != wxID_NONE ) { wxString aboutLabel; aboutLabel.Printf(_("About %s"), wxTheApp->GetAppDisplayName()); menu->Append( wxApp::s_macAboutMenuItemId, aboutLabel); } #endif menuBar->Append(menu, wxT("&File")); settingsMenu = new wxMenu; menuBar->Append(settingsMenu, wxT("&Settings")); menu = new wxMenu; std::vector::iterator devices_i; std::map::iterator mdevices_i; AudioThread::enumerateDevices(devices); int i = 0; for (devices_i = devices.begin(); devices_i != devices.end(); devices_i++) { if (devices_i->inputChannels) { inputDevices[i] = *devices_i; } if (devices_i->outputChannels) { outputDevices[i] = *devices_i; } i++; } // // for (mdevices_i = outputDevices.begin(); mdevices_i != outputDevices.end(); mdevices_i++) { // wxMenuItem *itm = menu->AppendRadioItem(wxID_RT_AUDIO_DEVICE + mdevices_i->first, mdevices_i->second.name, wxT("Description?")); // itm->SetId(wxID_RT_AUDIO_DEVICE + mdevices_i->first); // if (mdevices_i->second.isDefaultOutput) { // itm->Check(true); // } // outputDeviceMenuItems[mdevices_i->first] = itm; // } // // menuBar->Append(menu, wxT("Audio &Output")); sampleRateMenu = new wxMenu; menuBar->Append(sampleRateMenu, wxT("Sample &Rate")); // Audio Sample Rates menu = new wxMenu; #define NUM_RATES_DEFAULT 4 unsigned int desired_rates[NUM_RATES_DEFAULT] = { 48000, 44100, 96000, 192000 }; for (mdevices_i = outputDevices.begin(); mdevices_i != outputDevices.end(); mdevices_i++) { unsigned int desired_rate = 0; unsigned int desired_rank = NUM_RATES_DEFAULT + 1; for (std::vector::iterator srate = mdevices_i->second.sampleRates.begin(); srate != mdevices_i->second.sampleRates.end(); srate++) { for (unsigned int i = 0; i < NUM_RATES_DEFAULT; i++) { if (desired_rates[i] == (*srate)) { if (desired_rank > i) { desired_rank = i; desired_rate = (*srate); } } } } if (desired_rank > NUM_RATES_DEFAULT) { desired_rate = mdevices_i->second.sampleRates.back(); } AudioThread::deviceSampleRate[mdevices_i->first] = desired_rate; } for (mdevices_i = outputDevices.begin(); mdevices_i != outputDevices.end(); mdevices_i++) { int menu_id = wxID_AUDIO_BANDWIDTH_BASE + wxID_AUDIO_DEVICE_MULTIPLIER * mdevices_i->first; wxMenu *subMenu = new wxMenu; menu->AppendSubMenu(subMenu, mdevices_i->second.name, wxT("Description?")); int j = 0; for (std::vector::iterator srate = mdevices_i->second.sampleRates.begin(); srate != mdevices_i->second.sampleRates.end(); srate++) { std::stringstream srateName; srateName << ((float) (*srate) / 1000.0f) << "kHz"; wxMenuItem *itm = subMenu->AppendRadioItem(menu_id + j, srateName.str(), wxT("Description?")); if ((int)(*srate) == AudioThread::deviceSampleRate[mdevices_i->first]) { itm->Check(true); } audioSampleRateMenuItems[menu_id + j] = itm; j++; } } menuBar->Append(menu, wxT("Audio &Sample Rate")); //Add Display menu displayMenu = new wxMenu; wxMenu *fontMenu = new wxMenu; int fontScale = wxGetApp().getConfig()->getFontScale(); fontMenu->AppendRadioItem(wxID_DISPLAY_BASE, "Normal")->Check(GLFont::GLFONT_SCALE_NORMAL == fontScale); fontMenu->AppendRadioItem(wxID_DISPLAY_BASE + 1, "1.5x")->Check(GLFont::GLFONT_SCALE_MEDIUM == fontScale); fontMenu->AppendRadioItem(wxID_DISPLAY_BASE + 2, "2.0x")->Check(GLFont::GLFONT_SCALE_LARGE == fontScale); displayMenu->AppendSubMenu(fontMenu, "&Text Size"); wxMenu *themeMenu = new wxMenu; int themeId = wxGetApp().getConfig()->getTheme(); themeMenu->AppendRadioItem(wxID_THEME_DEFAULT, "Default")->Check(themeId==COLOR_THEME_DEFAULT); themeMenu->AppendRadioItem(wxID_THEME_RADAR, "RADAR")->Check(themeId==COLOR_THEME_RADAR); themeMenu->AppendRadioItem(wxID_THEME_BW, "Black & White")->Check(themeId==COLOR_THEME_BW); themeMenu->AppendRadioItem(wxID_THEME_SHARP, "Sharp")->Check(themeId==COLOR_THEME_SHARP); themeMenu->AppendRadioItem(wxID_THEME_RAD, "Rad")->Check(themeId==COLOR_THEME_RAD); themeMenu->AppendRadioItem(wxID_THEME_TOUCH, "Touch")->Check(themeId==COLOR_THEME_TOUCH); themeMenu->AppendRadioItem(wxID_THEME_HD, "HD")->Check(themeId==COLOR_THEME_HD); displayMenu->AppendSubMenu(themeMenu, wxT("&Color Scheme")); GLFont::setScale((GLFont::GLFontScale)fontScale); #ifdef USE_HAMLIB rigModel = wxGetApp().getConfig()->getRigModel(); rigSerialRate = wxGetApp().getConfig()->getRigRate(); rigPort = wxGetApp().getConfig()->getRigPort(); rigMenu = new wxMenu; rigEnableMenuItem = rigMenu->AppendCheckItem(wxID_RIG_TOGGLE, wxT("Enable Rig")); rigMenu->Append(wxID_RIG_SDR_IF, wxT("SDR-IF")); rigControlMenuItem = rigMenu->AppendCheckItem(wxID_RIG_CONTROL, wxT("Control Rig")); rigControlMenuItem->Check(wxGetApp().getConfig()->getRigControlMode()); rigFollowMenuItem = rigMenu->AppendCheckItem(wxID_RIG_FOLLOW, wxT("Follow Rig")); rigFollowMenuItem->Check(wxGetApp().getConfig()->getRigFollowMode()); rigCenterLockMenuItem = rigMenu->AppendCheckItem(wxID_RIG_CENTERLOCK, wxT("Floating Center")); rigCenterLockMenuItem->Check(wxGetApp().getConfig()->getRigCenterLock()); rigFollowModemMenuItem = rigMenu->AppendCheckItem(wxID_RIG_FOLLOW_MODEM, wxT("Track Modem")); rigFollowModemMenuItem->Check(wxGetApp().getConfig()->getRigFollowModem()); wxMenu *rigModelMenu = new wxMenu; RigList &rl = RigThread::enumerate(); numRigs = rl.size(); int modelMenuId = wxID_RIG_MODEL_BASE; for (RigList::const_iterator ri = rl.begin(); ri != rl.end(); ri++) { std::string modelString((*ri)->mfg_name); modelString.append(" "); modelString.append((*ri)->model_name); rigModelMenuItems[(*ri)->rig_model] = rigModelMenu->AppendRadioItem(modelMenuId, modelString, wxT("Description?")); if (rigModel == (*ri)->rig_model) { rigModelMenuItems[(*ri)->rig_model]->Check(true); } modelMenuId++; } rigMenu->AppendSubMenu(rigModelMenu, wxT("Model")); wxMenu *rigSerialMenu = new wxMenu; rigSerialRates.push_back(1200); rigSerialRates.push_back(2400); rigSerialRates.push_back(4800); rigSerialRates.push_back(9600); rigSerialRates.push_back(19200); rigSerialRates.push_back(38400); rigSerialRates.push_back(57600); rigSerialRates.push_back(115200); rigSerialRates.push_back(128000); rigSerialRates.push_back(256000); int rateMenuId = wxID_RIG_SERIAL_BASE; for (std::vector::const_iterator rate_i = rigSerialRates.begin(); rate_i != rigSerialRates.end(); rate_i++) { std::string rateString; rateString.append(std::to_string((*rate_i))); rateString.append(" baud"); rigSerialMenuItems[(*rate_i)] = rigSerialMenu->AppendRadioItem(rateMenuId, rateString, wxT("Description?")); if (rigSerialRate == (*rate_i)) { rigSerialMenuItems[(*rate_i)]->Check(true); } rateMenuId++; } rigMenu->AppendSubMenu(rigSerialMenu, wxT("Serial Rate")); rigPortMenuItem = rigMenu->Append(wxID_RIG_PORT, wxT("Control Port")); menuBar->Append(rigMenu, wxT("&Rig Control")); #endif menuBar->Append(displayMenu, wxT("&Display")); SetMenuBar(menuBar); CreateStatusBar(); wxRect *win = wxGetApp().getConfig()->getWindow(); if (win) { this->SetPosition(win->GetPosition()); this->SetClientSize(win->GetSize()); } else { SetClientSize(1280, 600); Centre(); } bool max = wxGetApp().getConfig()->getWindowMaximized(); if (max) { this->Maximize(); } long long freqSnap = wxGetApp().getConfig()->getSnap(); wxGetApp().setFrequencySnap(freqSnap); float spectrumAvg = wxGetApp().getConfig()->getSpectrumAvgSpeed(); spectrumAvgMeter->setLevel(spectrumAvg); wxGetApp().getSpectrumProcessor()->setFFTAverageRate(spectrumAvg); int wflps =wxGetApp().getConfig()->getWaterfallLinesPerSec(); waterfallSpeedMeter->setLevel(sqrt(wflps)); waterfallDataThread->setLinesPerSecond(wflps); waterfallCanvas->setLinesPerSecond(wflps); ThemeMgr::mgr.setTheme(wxGetApp().getConfig()->getTheme()); int mpc =wxGetApp().getConfig()->getModemPropsCollapsed(); if (mpc) { modemProps->setCollapsed(true); } Show(); #ifdef _WIN32 SetIcon(wxICON(frame_icon)); #endif wxAcceleratorEntry entries[3]; entries[0].Set(wxACCEL_CTRL, (int) 'O', wxID_OPEN); entries[1].Set(wxACCEL_CTRL, (int) 'S', wxID_SAVE); entries[2].Set(wxACCEL_CTRL, (int) 'A', wxID_SAVEAS); wxAcceleratorTable accel(3, entries); SetAcceleratorTable(accel); deviceChanged.store(false); devInfo = NULL; wxGetApp().deviceSelector(); // static const int attribs[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, 0 }; // wxLogStatus("Double-buffered display %s supported", wxGLCanvas::IsDisplaySupported(attribs) ? "is" : "not"); // ShowFullScreen(true); //Force refresh of all Refresh(); } AppFrame::~AppFrame() { waterfallDataThread->terminate(); t_FFTData->join(); } void AppFrame::initDeviceParams(SDRDeviceInfo *devInfo) { this->devInfo = devInfo; deviceChanged.store(true); } void AppFrame::updateDeviceParams() { if (!deviceChanged.load()) { return; } int i = 0; SoapySDR::Device *soapyDev = devInfo->getSoapyDevice(); // Build settings menu wxMenu *newSettingsMenu = new wxMenu; showTipMenuItem = newSettingsMenu->AppendCheckItem(wxID_SET_TIPS, "Show Hover Tips"); showTipMenuItem->Check(wxGetApp().getConfig()->getShowTips()); lowPerfMode = wxGetApp().getConfig()->getLowPerfMode(); lowPerfMenuItem = newSettingsMenu->AppendCheckItem(wxID_LOW_PERF, "Reduce CPU Usage"); if (lowPerfMode) { lowPerfMenuItem->Check(true); } newSettingsMenu->AppendSeparator(); newSettingsMenu->Append(wxID_SET_FREQ_OFFSET, "Frequency Offset"); if (devInfo->hasCORR(SOAPY_SDR_RX, 0)) { newSettingsMenu->Append(wxID_SET_PPM, "Device PPM"); } if (devInfo->getDriver() != "rtlsdr") { iqSwapMenuItem = newSettingsMenu->AppendCheckItem(wxID_SET_IQSWAP, "I/Q Swap"); iqSwapMenuItem->Check(wxGetApp().getSDRThread()->getIQSwap()); } agcMenuItem = nullptr; if (soapyDev->listGains(SOAPY_SDR_RX, 0).size()) { agcMenuItem = newSettingsMenu->AppendCheckItem(wxID_AGC_CONTROL, "Automatic Gain"); agcMenuItem->Check(wxGetApp().getAGCMode()); } else if (!wxGetApp().getAGCMode()) { wxGetApp().setAGCMode(true); } SoapySDR::ArgInfoList::const_iterator args_i; settingArgs = soapyDev->getSettingInfo(); if (settingArgs.size()) { newSettingsMenu->AppendSeparator(); } for (args_i = settingArgs.begin(); args_i != settingArgs.end(); args_i++) { SoapySDR::ArgInfo arg = (*args_i); std::string currentVal = soapyDev->readSetting(arg.key); if (arg.type == SoapySDR::ArgInfo::BOOL) { wxMenuItem *item = newSettingsMenu->AppendCheckItem(wxID_SETTINGS_BASE+i, arg.name, arg.description); item->Check(currentVal=="true"); i++; } else if (arg.type == SoapySDR::ArgInfo::INT) { newSettingsMenu->Append(wxID_SETTINGS_BASE+i, arg.name, arg.description); i++; } else if (arg.type == SoapySDR::ArgInfo::FLOAT) { newSettingsMenu->Append(wxID_SETTINGS_BASE+i, arg.name, arg.description); i++; } else if (arg.type == SoapySDR::ArgInfo::STRING) { if (arg.options.size()) { wxMenu *subMenu = new wxMenu; int j = 0; for (std::vector::iterator str_i = arg.options.begin(); str_i != arg.options.end(); str_i++) { std::string optName = (*str_i); std::string displayName = optName; if (arg.optionNames.size()) { displayName = arg.optionNames[j]; } wxMenuItem *item = subMenu->AppendRadioItem(wxID_SETTINGS_BASE+i, displayName); if (currentVal == (*str_i)) { item->Check(true); } j++; i++; } newSettingsMenu->AppendSubMenu(subMenu, arg.name, arg.description); } else { newSettingsMenu->Append(wxID_SETTINGS_BASE+i, arg.name, arg.description); i++; } } } settingsIdMax = wxID_SETTINGS_BASE+i; menuBar->Replace(1, newSettingsMenu, wxT("&Settings")); settingsMenu = newSettingsMenu; // Build sample rate menu sampleRates = devInfo->getSampleRates(SOAPY_SDR_RX, 0); sampleRateMenuItems.erase(sampleRateMenuItems.begin(),sampleRateMenuItems.end()); wxMenu *newSampleRateMenu = new wxMenu; int ofs = 0; long sampleRate = wxGetApp().getSampleRate(); bool checked = false; for (vector::iterator i = sampleRates.begin(); i != sampleRates.end(); i++) { sampleRateMenuItems[wxID_BANDWIDTH_BASE+ofs] = newSampleRateMenu->AppendRadioItem(wxID_BANDWIDTH_BASE+ofs, frequencyToStr(*i)); if (sampleRate == (*i)) { sampleRateMenuItems[wxID_BANDWIDTH_BASE+ofs]->Check(true); checked = true; } ofs++; } sampleRateMenuItems[wxID_BANDWIDTH_MANUAL] = newSampleRateMenu->AppendRadioItem(wxID_BANDWIDTH_MANUAL, wxT("Manual Entry")); if (!checked) { sampleRateMenuItems[wxID_BANDWIDTH_MANUAL]->Check(true); } menuBar->Replace(2, newSampleRateMenu, wxT("Sample &Rate")); sampleRateMenu = newSampleRateMenu; if (!wxGetApp().getAGCMode()) { gainSpacerItem->Show(true); gainSizerItem->Show(true); gainSizerItem->SetMinSize(devInfo->getSoapyDevice()->listGains(SOAPY_SDR_RX,0).size()*50,0); demodTray->Layout(); gainCanvas->updateGainUI(); gainCanvas->Refresh(); gainCanvas->Refresh(); } else { gainSpacerItem->Show(false); gainSizerItem->Show(false); demodTray->Layout(); } #if USE_HAMLIB if (wxGetApp().getConfig()->getRigEnabled() && !wxGetApp().rigIsActive()) { enableRig(); rigEnableMenuItem->Check(true); } std::string deviceId = devInfo->getDeviceId(); DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(deviceId); if (wxGetApp().rigIsActive()) { rigSDRIF = devConfig->getRigIF(rigModel); if (rigSDRIF) { wxGetApp().lockFrequency(rigSDRIF); } else { wxGetApp().unlockFrequency(); } } #endif deviceChanged.store(false); } #ifdef USE_HAMLIB void AppFrame::enableRig() { wxGetApp().stopRig(); wxGetApp().initRig(rigModel, rigPort, rigSerialRate); if (devInfo != nullptr) { std::string deviceId = devInfo->getDeviceId(); DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(deviceId); rigSDRIF = devConfig->getRigIF(rigModel); if (rigSDRIF) { wxGetApp().lockFrequency(rigSDRIF); } else { wxGetApp().unlockFrequency(); } } else { wxGetApp().unlockFrequency(); } wxGetApp().getConfig()->setRigEnabled(true); } void AppFrame::disableRig() { wxGetApp().stopRig(); wxGetApp().unlockFrequency(); wxGetApp().getConfig()->setRigEnabled(false); } #endif void AppFrame::OnMenu(wxCommandEvent& event) { // if (event.GetId() >= wxID_RT_AUDIO_DEVICE && event.GetId() < wxID_RT_AUDIO_DEVICE + (int)devices.size()) { // if (activeDemodulator) { // activeDemodulator->setOutputDevice(event.GetId() - wxID_RT_AUDIO_DEVICE); // activeDemodulator = NULL; // } // return; // } #ifdef __APPLE__ if (event.GetId() == wxApp::s_macAboutMenuItemId) { wxMessageDialog *aboutDlg = new wxMessageDialog(NULL, wxT("CubicSDR v" CUBICSDR_VERSION "\nby Charles J. Cliffe (@ccliffe)\nwww.cubicsdr.com"), wxT("CubicSDR v" CUBICSDR_VERSION), wxOK); aboutDlg->ShowModal(); return; } #endif if (event.GetId() == wxID_SDR_START_STOP) { if (!wxGetApp().getSDRThread()->isTerminated()) { wxGetApp().stopDevice(true, 2000); } else { SDRDeviceInfo *dev = wxGetApp().getDevice(); if (dev != nullptr) { wxGetApp().setDevice(dev, 0); } } } else if (event.GetId() == wxID_LOW_PERF) { lowPerfMode = lowPerfMenuItem->IsChecked(); wxGetApp().getConfig()->setLowPerfMode(lowPerfMode); // long srate = wxGetApp().getSampleRate(); // if (srate > CHANNELIZER_RATE_MAX && lowPerfMode) { // if (wxGetApp().getSpectrumProcessor()->getFFTSize() != 1024) { // setMainWaterfallFFTSize(1024); // } // } else if (srate > CHANNELIZER_RATE_MAX) { // if (wxGetApp().getSpectrumProcessor()->getFFTSize() != 2048) { // setMainWaterfallFFTSize(2048); // } // } } else if (event.GetId() == wxID_SET_TIPS ) { if (wxGetApp().getConfig()->getShowTips()) { wxGetApp().getConfig()->setShowTips(false); } else { wxGetApp().getConfig()->setShowTips(true); } } else if (event.GetId() == wxID_SET_IQSWAP) { wxGetApp().getSDRThread()->setIQSwap(!wxGetApp().getSDRThread()->getIQSwap()); } 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", wxGetApp().getOffset(), -2000000000, 2000000000, this); if (ofs != -1) { wxGetApp().setOffset(ofs); } } else if (event.GetId() == wxID_AGC_CONTROL) { if (wxGetApp().getDevice() == NULL) { agcMenuItem->Check(true); return; } if (!wxGetApp().getAGCMode()) { wxGetApp().setAGCMode(true); gainSpacerItem->Show(false); gainSizerItem->Show(false); demodTray->Layout(); } else { wxGetApp().setAGCMode(false); gainSpacerItem->Show(true); gainSizerItem->Show(true); gainSizerItem->SetMinSize(wxGetApp().getDevice()->getSoapyDevice()->listGains(SOAPY_SDR_RX, 0).size()*40,0); demodTray->Layout(); gainCanvas->updateGainUI(); gainCanvas->Refresh(); gainCanvas->Refresh(); } } else if (event.GetId() == wxID_SDR_DEVICES) { wxGetApp().deviceSelector(); } 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); } else if (event.GetId() == wxID_SAVE) { if (!currentSessionFile.empty()) { saveSession(currentSessionFile); } else { wxFileDialog saveFileDialog(this, _("Save XML Session file"), "", "", "XML files (*.xml)|*.xml", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (saveFileDialog.ShowModal() == wxID_CANCEL) { return; } saveSession(saveFileDialog.GetPath().ToStdString()); } } else if (event.GetId() == wxID_OPEN) { wxFileDialog openFileDialog(this, _("Open XML Session file"), "", "", "XML files (*.xml)|*.xml", wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (openFileDialog.ShowModal() == wxID_CANCEL) { return; } loadSession(openFileDialog.GetPath().ToStdString()); } else if (event.GetId() == wxID_SAVEAS) { wxFileDialog saveFileDialog(this, _("Save XML Session file"), "", "", "XML files (*.xml)|*.xml", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (saveFileDialog.ShowModal() == wxID_CANCEL) { return; } saveSession(saveFileDialog.GetPath().ToStdString()); } else if (event.GetId() == wxID_RESET) { wxGetApp().getDemodMgr().terminateAll(); wxGetApp().setFrequency(100000000); wxGetApp().getDemodMgr().setLastDemodulatorType("FM"); demodModeSelector->setSelection(1); wxGetApp().getDemodMgr().setLastMuted(false); wxGetApp().getDemodMgr().setLastBandwidth(DEFAULT_DEMOD_BW); wxGetApp().getDemodMgr().setLastGain(1.0); wxGetApp().getDemodMgr().setLastSquelchLevel(-100); waterfallCanvas->setBandwidth(wxGetApp().getSampleRate()); waterfallCanvas->setCenterFrequency(wxGetApp().getFrequency()); spectrumCanvas->setBandwidth(wxGetApp().getSampleRate()); spectrumCanvas->setCenterFrequency(wxGetApp().getFrequency()); waterfallDataThread->setLinesPerSecond(DEFAULT_WATERFALL_LPS); waterfallCanvas->setLinesPerSecond(DEFAULT_WATERFALL_LPS); waterfallSpeedMeter->setLevel(sqrt(DEFAULT_WATERFALL_LPS)); wxGetApp().getSpectrumProcessor()->setFFTAverageRate(0.65f); spectrumAvgMeter->setLevel(0.65f); demodModeSelector->Refresh(); demodTuner->Refresh(); SetTitle(CUBICSDR_TITLE); currentSessionFile = ""; } else if (event.GetId() == wxID_CLOSE || event.GetId() == wxID_EXIT) { Close(false); } else if (event.GetId() == wxID_THEME_DEFAULT) { ThemeMgr::mgr.setTheme(COLOR_THEME_DEFAULT); } else if (event.GetId() == wxID_THEME_SHARP) { ThemeMgr::mgr.setTheme(COLOR_THEME_SHARP); } else if (event.GetId() == wxID_THEME_BW) { ThemeMgr::mgr.setTheme(COLOR_THEME_BW); } else if (event.GetId() == wxID_THEME_RAD) { ThemeMgr::mgr.setTheme(COLOR_THEME_RAD); } else if (event.GetId() == wxID_THEME_TOUCH) { ThemeMgr::mgr.setTheme(COLOR_THEME_TOUCH); } else if (event.GetId() == wxID_THEME_HD) { ThemeMgr::mgr.setTheme(COLOR_THEME_HD); } else if (event.GetId() == wxID_THEME_RADAR) { ThemeMgr::mgr.setTheme(COLOR_THEME_RADAR); } //Display : font sizes else if (event.GetId() == wxID_DISPLAY_BASE) { GLFont::setScale(GLFont::GLFONT_SCALE_NORMAL); //force all windows refresh Refresh(); } else if (event.GetId() == wxID_DISPLAY_BASE + 1) { GLFont::setScale(GLFont::GLFONT_SCALE_MEDIUM); //force all windows refresh Refresh(); } else if (event.GetId() == wxID_DISPLAY_BASE + 2) { GLFont::setScale(GLFont::GLFONT_SCALE_LARGE); //force all windows refresh Refresh(); } if (event.GetId() >= wxID_SETTINGS_BASE && event.GetId() < settingsIdMax) { int setIdx = event.GetId()-wxID_SETTINGS_BASE; int menuIdx = 0; for (std::vector::iterator arg_i = settingArgs.begin(); arg_i != settingArgs.end(); arg_i++) { SoapySDR::ArgInfo &arg = (*arg_i); if (arg.type == SoapySDR::ArgInfo::STRING && arg.options.size() && setIdx >= menuIdx && setIdx < menuIdx+(int)arg.options.size()) { int optIdx = setIdx-menuIdx; wxGetApp().getSDRThread()->writeSetting(arg.key, arg.options[optIdx]); break; } else if (arg.type == SoapySDR::ArgInfo::STRING && arg.options.size()) { menuIdx += arg.options.size(); } else if (menuIdx == setIdx) { if (arg.type == SoapySDR::ArgInfo::BOOL) { wxGetApp().getSDRThread()->writeSetting(arg.key, (wxGetApp().getSDRThread()->readSetting(arg.key)=="true")?"false":"true"); break; } else if (arg.type == SoapySDR::ArgInfo::STRING) { wxString stringVal = wxGetTextFromUser(arg.description, arg.name, wxGetApp().getSDRThread()->readSetting(arg.key)); if (stringVal.ToStdString() != "") { wxGetApp().getSDRThread()->writeSetting(arg.key, stringVal.ToStdString()); } break; } else if (arg.type == SoapySDR::ArgInfo::INT) { int currentVal; try { currentVal = std::stoi(wxGetApp().getSDRThread()->readSetting(arg.key)); } catch (std::invalid_argument e) { currentVal = 0; } int intVal = wxGetNumberFromUser(arg.description, arg.units, arg.name, currentVal, arg.range.minimum(), arg.range.maximum(), this); if (intVal != -1) { wxGetApp().getSDRThread()->writeSetting(arg.key, std::to_string(intVal)); } break; } else if (arg.type == SoapySDR::ArgInfo::FLOAT) { wxString floatVal = wxGetTextFromUser(arg.description, arg.name, wxGetApp().getSDRThread()->readSetting(arg.key)); try { wxGetApp().getSDRThread()->writeSetting(arg.key, floatVal.ToStdString()); } catch (std::invalid_argument e) { // ... } break; } else { menuIdx++; } } else { menuIdx++; } } } if (event.GetId() >= wxID_THEME_DEFAULT && event.GetId() <= wxID_THEME_RADAR) { demodTuner->Refresh(); demodModeSelector->Refresh(); waterfallSpeedMeter->Refresh(); spectrumAvgMeter->Refresh(); gainCanvas->setThemeColors(); modemProps->updateTheme(); } switch (event.GetId()) { case wxID_BANDWIDTH_MANUAL: int rateHigh, rateLow; SDRDeviceInfo *dev = wxGetApp().getDevice(); if (dev == NULL) { break; } std::vector sampleRates = dev->getSampleRates(SOAPY_SDR_RX, 0); rateLow = 2000000; rateHigh = 30000000; if (sampleRates.size()) { rateLow = sampleRates[0]; rateHigh = sampleRates[sampleRates.size()-1]; } long bw = wxGetNumberFromUser("\n" + dev->getName() + "\n\n " + "min: " + std::to_string(rateLow) + " Hz" + ", max: " + std::to_string(rateHigh) + " Hz\n", "Sample Rate in Hz", "Manual Sample Rate Entry", wxGetApp().getSampleRate(), rateLow, rateHigh, this); if (bw != -1) { wxGetApp().setSampleRate(bw); } break; } if (event.GetId() >= wxID_BANDWIDTH_BASE && event.GetId() < wxID_BANDWIDTH_BASE + (int)sampleRates.size()) { wxGetApp().setSampleRate(sampleRates[event.GetId()-wxID_BANDWIDTH_BASE]); } if (event.GetId() >= wxID_AUDIO_BANDWIDTH_BASE) { int evId = event.GetId(); std::vector::iterator devices_i; std::map::iterator mdevices_i; int i = 0; for (mdevices_i = outputDevices.begin(); mdevices_i != outputDevices.end(); mdevices_i++) { int menu_id = wxID_AUDIO_BANDWIDTH_BASE + wxID_AUDIO_DEVICE_MULTIPLIER * mdevices_i->first; int j = 0; for (std::vector::iterator srate = mdevices_i->second.sampleRates.begin(); srate != mdevices_i->second.sampleRates.end(); srate++) { if (evId == menu_id + j) { //audioSampleRateMenuItems[menu_id+j]; //std::cout << "Would set audio sample rate on device " << mdevices_i->second.name << " (" << mdevices_i->first << ") to " << (*srate) << "Hz" << std::endl; AudioThread::setDeviceSampleRate(mdevices_i->first, *srate); } j++; } i++; } } #ifdef USE_HAMLIB bool resetRig = false; if (event.GetId() >= wxID_RIG_MODEL_BASE && event.GetId() < wxID_RIG_MODEL_BASE+numRigs) { int rigIdx = event.GetId()-wxID_RIG_MODEL_BASE; RigList &rl = RigThread::enumerate(); rigModel = rl[rigIdx]->rig_model; if (devInfo != nullptr) { std::string deviceId = devInfo->getDeviceId(); DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(deviceId); rigSDRIF = devConfig->getRigIF(rigModel); if (rigSDRIF) { wxGetApp().lockFrequency(rigSDRIF); } else { wxGetApp().unlockFrequency(); } } else { wxGetApp().unlockFrequency(); } resetRig = true; } if (event.GetId() >= wxID_RIG_SERIAL_BASE && event.GetId() < wxID_RIG_SERIAL_BASE+rigSerialRates.size()) { int serialIdx = event.GetId()-wxID_RIG_SERIAL_BASE; rigSerialRate = rigSerialRates[serialIdx]; resetRig = true; } if (event.GetId() == wxID_RIG_PORT) { wxString stringVal = wxGetTextFromUser("Rig Serial / COM / Address", "Rig Control Port", rigPort); std::string rigPortStr = stringVal.ToStdString(); if (rigPortStr != "") { rigPort = rigPortStr; resetRig = true; } } if (event.GetId() == wxID_RIG_TOGGLE) { resetRig = false; if (!wxGetApp().rigIsActive()) { enableRig(); } else { disableRig(); } } if (event.GetId() == wxID_RIG_SDR_IF) { if (devInfo != nullptr) { std::string deviceId = devInfo->getDeviceId(); DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(deviceId); long long freqRigIF = wxGetNumberFromUser("Rig SDR-IF Frequency", "Frequency (Hz)", "Frequency", devConfig->getRigIF(rigModel), 0, 2000000000); if (freqRigIF != -1) { rigSDRIF = freqRigIF; devConfig->setRigIF(rigModel, rigSDRIF); } if (rigSDRIF && wxGetApp().rigIsActive()) { wxGetApp().lockFrequency(rigSDRIF); } else { wxGetApp().unlockFrequency(); } } } if (event.GetId() == wxID_RIG_CONTROL) { if (wxGetApp().rigIsActive()) { RigThread *rt = wxGetApp().getRigThread(); rt->setControlMode(!rt->getControlMode()); rigControlMenuItem->Check(rt->getControlMode()); wxGetApp().getConfig()->setRigControlMode(rt->getControlMode()); } else { wxGetApp().getConfig()->setRigControlMode(rigControlMenuItem->IsChecked()); } } if (event.GetId() == wxID_RIG_FOLLOW) { if (wxGetApp().rigIsActive()) { RigThread *rt = wxGetApp().getRigThread(); rt->setFollowMode(!rt->getFollowMode()); rigFollowMenuItem->Check(rt->getFollowMode()); wxGetApp().getConfig()->setRigFollowMode(rt->getFollowMode()); } else { wxGetApp().getConfig()->setRigFollowMode(rigFollowMenuItem->IsChecked()); } } if (event.GetId() == wxID_RIG_CENTERLOCK) { if (wxGetApp().rigIsActive()) { RigThread *rt = wxGetApp().getRigThread(); rt->setCenterLock(!rt->getCenterLock()); rigCenterLockMenuItem->Check(rt->getCenterLock()); wxGetApp().getConfig()->setRigCenterLock(rt->getCenterLock()); } else { wxGetApp().getConfig()->setRigCenterLock(rigCenterLockMenuItem->IsChecked()); } } if (event.GetId() == wxID_RIG_FOLLOW_MODEM) { if (wxGetApp().rigIsActive()) { RigThread *rt = wxGetApp().getRigThread(); rt->setFollowModem(!rt->getFollowModem()); rigFollowModemMenuItem->Check(rt->getFollowModem()); wxGetApp().getConfig()->setRigFollowModem(rt->getFollowModem()); } else { wxGetApp().getConfig()->setRigFollowModem(rigFollowModemMenuItem->IsChecked()); } } if (wxGetApp().rigIsActive() && resetRig) { wxGetApp().stopRig(); wxGetApp().initRig(rigModel, rigPort, rigSerialRate); } #endif } void AppFrame::OnClose(wxCloseEvent& event) { wxGetApp().closeDeviceSelector(); wxGetApp().getDemodSpectrumProcessor()->removeOutput(demodSpectrumCanvas->getVisualDataQueue()); wxGetApp().getDemodSpectrumProcessor()->removeOutput(demodWaterfallCanvas->getVisualDataQueue()); wxGetApp().getSpectrumProcessor()->removeOutput(spectrumCanvas->getVisualDataQueue()); wxGetApp().getConfig()->setWindow(this->GetPosition(), this->GetClientSize()); wxGetApp().getConfig()->setWindowMaximized(this->IsMaximized()); wxGetApp().getConfig()->setTheme(ThemeMgr::mgr.getTheme()); wxGetApp().getConfig()->setFontScale(GLFont::getScale()); wxGetApp().getConfig()->setSnap(wxGetApp().getFrequencySnap()); wxGetApp().getConfig()->setCenterFreq(wxGetApp().getFrequency()); wxGetApp().getConfig()->setSpectrumAvgSpeed(wxGetApp().getSpectrumProcessor()->getFFTAverageRate()); wxGetApp().getConfig()->setWaterfallLinesPerSec(waterfallDataThread->getLinesPerSecond()); wxGetApp().getConfig()->setManualDevices(SDREnumerator::getManuals()); wxGetApp().getConfig()->setModemPropsCollapsed(modemProps->isCollapsed()); #ifdef USE_HAMLIB wxGetApp().getConfig()->setRigEnabled(rigEnableMenuItem->IsChecked()); wxGetApp().getConfig()->setRigModel(rigModel); wxGetApp().getConfig()->setRigRate(rigSerialRate); wxGetApp().getConfig()->setRigPort(rigPort); wxGetApp().getConfig()->setRigFollowMode(rigFollowMenuItem->IsChecked()); wxGetApp().getConfig()->setRigControlMode(rigControlMenuItem->IsChecked()); wxGetApp().getConfig()->setRigCenterLock(rigCenterLockMenuItem->IsChecked()); wxGetApp().getConfig()->setRigFollowModem(rigFollowModemMenuItem->IsChecked()); #endif wxGetApp().getConfig()->save(); event.Skip(); } void AppFrame::OnNewWindow(wxCommandEvent& WXUNUSED(event)) { new AppFrame(); } void AppFrame::OnThread(wxCommandEvent& event) { event.Skip(); } void AppFrame::OnIdle(wxIdleEvent& event) { if (deviceChanged.load()) { updateDeviceParams(); } DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); if (demod && demod->isModemInitialized()) { 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() > (int)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->getBandwidth() != wxGetApp().getDemodMgr().getLastBandwidth()) { wxGetApp().getDemodMgr().setLastBandwidth(demod->getBandwidth()); } if (demod != activeDemodulator) { demodSignalMeter->setInputValue(demod->getSquelchLevel()); demodGainMeter->setInputValue(demod->getGain()); wxGetApp().getDemodMgr().setLastGain(demod->getGain()); int outputDevice = demod->getOutputDevice(); scopeCanvas->setDeviceName(outputDevices[outputDevice].name); // outputDeviceMenuItems[outputDevice]->Check(true); std::string dType = demod->getDemodulatorType(); demodModeSelector->setSelection(dType); #ifdef ENABLE_DIGITAL_LAB demodModeSelectorAdv->setSelection(dType); #endif deltaLockButton->setSelection(demod->isDeltaLock()?1:-1); demodMuteButton->setSelection(demod->isMuted()?1:-1); modemPropertiesUpdated.store(true); demodTuner->setHalfBand(dType=="USB" || dType=="LSB"); } if (demodWaterfallCanvas->getDragState() == WaterfallCanvas::WF_DRAG_NONE) { long long centerFreq = demod->getFrequency(); unsigned int demodBw = (unsigned int) ceil((float) demod->getBandwidth() * 2.25); if (demod->getDemodulatorType() == "USB") { demodBw /= 2; centerFreq += demod->getBandwidth() / 4; } if (demod->getDemodulatorType() == "LSB") { demodBw /= 2; centerFreq -= demod->getBandwidth() / 4; } if (demodBw > wxGetApp().getSampleRate() / 2) { demodBw = wxGetApp().getSampleRate() / 2; } if (demodBw < 20000) { demodBw = 20000; } if (centerFreq != demodWaterfallCanvas->getCenterFrequency()) { demodWaterfallCanvas->setCenterFrequency(centerFreq); demodSpectrumCanvas->setCenterFrequency(centerFreq); } std::string dSelection = demodModeSelector->getSelectionLabel(); #ifdef ENABLE_DIGITAL_LAB std::string dSelectionadv = demodModeSelectorAdv->getSelectionLabel(); // basic demodulators if (dSelection != "" && dSelection != demod->getDemodulatorType()) { demod->setDemodulatorType(dSelection); demodTuner->setHalfBand(dSelection=="USB" || dSelection=="LSB"); demodModeSelectorAdv->setSelection(-1); } // advanced demodulators else if (dSelectionadv != "" && dSelectionadv != demod->getDemodulatorType()) { demod->setDemodulatorType(dSelectionadv); demodTuner->setHalfBand(false); demodModeSelector->setSelection(-1); } #else // basic demodulators if (dSelection != "" && dSelection != demod->getDemodulatorType()) { demod->setDemodulatorType(dSelection); demodTuner->setHalfBand(dSelection=="USB" || dSelection=="LSB"); } #endif int muteMode = demodMuteButton->getSelection(); if (demodMuteButton->modeChanged()) { if (demod->isMuted() && muteMode == -1) { demod->setMuted(false); } 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()); demodMuteButton->Refresh(); } else if (!demod->isMuted() && muteMode == 1) { demodMuteButton->setSelection(-1); wxGetApp().getDemodMgr().setLastMuted(demod->isMuted()); demodMuteButton->Refresh(); } } int deltaMode = deltaLockButton->getSelection(); if (deltaLockButton->modeChanged()) { if (demod->isDeltaLock() && deltaMode == -1) { demod->setDeltaLock(false); } else if (!demod->isDeltaLock() && deltaMode == 1) { demod->setDeltaLockOfs(demod->getFrequency()-wxGetApp().getFrequency()); demod->setDeltaLock(true); } wxGetApp().getDemodMgr().setLastDeltaLock(demod->isDeltaLock()); deltaLockButton->clearModeChanged(); } else { if (demod->isDeltaLock() && deltaMode == -1) { deltaLockButton->setSelection(1); wxGetApp().getDemodMgr().setLastDeltaLock(true); deltaLockButton->Refresh(); } else if (!demod->isDeltaLock() && deltaMode == 1) { deltaLockButton->setSelection(-1); wxGetApp().getDemodMgr().setLastDeltaLock(false); deltaLockButton->Refresh(); } } int soloMode = soloModeButton->getSelection(); if (soloModeButton->modeChanged()) { if (soloMode == 1) { wxGetApp().setSoloMode(true); } else { wxGetApp().setSoloMode(false); } soloModeButton->clearModeChanged(); } else { if (wxGetApp().getSoloMode() != (soloMode==1)) { soloModeButton->setSelection(wxGetApp().getSoloMode()?1:-1); soloModeButton->Refresh(); } } demodWaterfallCanvas->setBandwidth(demodBw); demodSpectrumCanvas->setBandwidth(demodBw); } demodSignalMeter->setLevel(demod->getSignalLevel()); demodSignalMeter->setMin(demod->getSignalFloor()); demodSignalMeter->setMax(demod->getSignalCeil()); demodGainMeter->setLevel(demod->getGain()); if (demodSignalMeter->inputChanged()) { demod->setSquelchLevel(demodSignalMeter->getInputValue()); } if (demodGainMeter->inputChanged()) { demod->setGain(demodGainMeter->getInputValue()); demodGainMeter->setLevel(demodGainMeter->getInputValue()); } activeDemodulator = demod; } else if (demod) { // Wait state for current demodulator modem to activate.. } else { DemodulatorMgr *mgr = &wxGetApp().getDemodMgr(); std::string dSelection = demodModeSelector->getSelectionLabel(); #ifdef ENABLE_DIGITAL_LAB std::string dSelectionadv = demodModeSelectorAdv->getSelectionLabel(); // basic demodulators if (dSelection != "" && dSelection != mgr->getLastDemodulatorType()) { mgr->setLastDemodulatorType(dSelection); mgr->setLastBandwidth(Modem::getModemDefaultSampleRate(dSelection)); demodTuner->setHalfBand(dSelection=="USB" || dSelection=="LSB"); demodModeSelectorAdv->setSelection(-1); } // advanced demodulators else if(dSelectionadv != "" && dSelectionadv != mgr->getLastDemodulatorType()) { mgr->setLastDemodulatorType(dSelectionadv); mgr->setLastBandwidth(Modem::getModemDefaultSampleRate(dSelectionadv)); demodTuner->setHalfBand(false); demodModeSelector->setSelection(-1); } #else // basic demodulators if (dSelection != "" && dSelection != mgr->getLastDemodulatorType()) { mgr->setLastDemodulatorType(dSelection); mgr->setLastBandwidth(Modem::getModemDefaultSampleRate(dSelection)); demodTuner->setHalfBand(dSelection=="USB" || dSelection=="LSB"); } #endif demodGainMeter->setLevel(mgr->getLastGain()); if (demodSignalMeter->inputChanged()) { mgr->setLastSquelchLevel(demodSignalMeter->getInputValue()); } if (demodGainMeter->inputChanged()) { mgr->setLastGain(demodGainMeter->getInputValue()); demodGainMeter->setLevel(demodGainMeter->getInputValue()); } if (wxGetApp().getFrequency() != demodWaterfallCanvas->getCenterFrequency()) { demodWaterfallCanvas->setCenterFrequency(wxGetApp().getFrequency()); demodSpectrumCanvas->setCenterFrequency(wxGetApp().getFrequency()); } if (spectrumCanvas->getViewState() && abs(wxGetApp().getFrequency()-spectrumCanvas->getCenterFrequency()) > (wxGetApp().getSampleRate()/2)) { 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(); } } 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(); SpectrumVisualProcessor *proc = wxGetApp().getSpectrumProcessor(); if (spectrumAvgMeter->inputChanged()) { float val = spectrumAvgMeter->getInputValue(); if (val < 0.01) { val = 0.01f; } if (val > 0.99) { val = 0.99f; } spectrumAvgMeter->setLevel(val); proc->setFFTAverageRate(val); GetStatusBar()->SetStatusText(wxString::Format(wxT("Spectrum averaging speed changed to %0.2f%%."),val*100.0)); } SpectrumVisualProcessor *dproc = wxGetApp().getDemodSpectrumProcessor(); dproc->setView(demodWaterfallCanvas->getViewState(), demodWaterfallCanvas->getCenterFrequency(),demodWaterfallCanvas->getBandwidth()); SpectrumVisualProcessor *wproc = waterfallDataThread->getProcessor(); if (waterfallSpeedMeter->inputChanged()) { float val = waterfallSpeedMeter->getInputValue(); waterfallSpeedMeter->setLevel(val); waterfallDataThread->setLinesPerSecond((int)ceil(val*val)); waterfallCanvas->setLinesPerSecond((int)ceil(val*val)); GetStatusBar()->SetStatusText(wxString::Format(wxT("Waterfall max speed changed to %d lines per second."),(int)ceil(val*val))); } wproc->setView(waterfallCanvas->getViewState(), waterfallCanvas->getCenterFrequency(), waterfallCanvas->getBandwidth()); wxGetApp().getSDRPostThread()->setIQVisualRange(waterfallCanvas->getCenterFrequency(), waterfallCanvas->getBandwidth()); proc->setView(wproc->isView(), wproc->getCenterFrequency(), wproc->getBandwidth()); demod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); if (modemPropertiesUpdated.load() && demod && demod->isModemInitialized()) { //reset notification flag modemPropertiesUpdated.store(false); modemProps->initProperties(demod->getModemArgs(), demod); modemProps->updateTheme(); demodTray->Layout(); modemProps->fitColumns(); #if ENABLE_DIGITAL_LAB if (demod->getModemType() == "digital") { ModemDigitalOutputConsole *outp = (ModemDigitalOutputConsole *)demod->getOutput(); if (!outp->getDialog()) { outp->setTitle(demod->getDemodulatorType() + ": " + frequencyToStr(demod->getFrequency())); outp->setDialog(new DigitalConsole(this, outp)); } demod->showOutput(); } #endif } else if (!demod) { modemProps->Hide(); demodTray->Layout(); } if (modemProps->IsShown() && modemProps->isCollapsed() && modemProps->GetMinWidth() > 22) { modemProps->SetMinSize(wxSize(APPFRAME_MODEMPROPS_MINSIZE,-1)); modemProps->SetMaxSize(wxSize(APPFRAME_MODEMPROPS_MINSIZE,-1)); demodTray->Layout(); modemProps->fitColumns(); } else if (modemProps->IsShown() && !modemProps->isCollapsed() && modemProps->GetMinWidth() < 200) { modemProps->SetMinSize(wxSize(APPFRAME_MODEMPROPS_MAXSIZE,-1)); modemProps->SetMaxSize(wxSize(APPFRAME_MODEMPROPS_MAXSIZE,-1)); demodTray->Layout(); modemProps->fitColumns(); } int peakHoldMode = peakHoldButton->getSelection(); if (peakHoldButton->modeChanged()) { wxGetApp().getSpectrumProcessor()->setPeakHold(peakHoldMode == 1); //make the peak hold act on the current dmod also, like a zoomed-in version. wxGetApp().getDemodSpectrumProcessor()->setPeakHold(peakHoldMode == 1); peakHoldButton->clearModeChanged(); } #if USE_HAMLIB if (rigEnableMenuItem->IsChecked()) { if (!wxGetApp().rigIsActive()) { rigEnableMenuItem->Check(false); wxGetApp().getConfig()->setRigEnabled(false); } } #endif #ifdef _WIN32 if (scopeCanvas->HasFocus()) { waterfallCanvas->SetFocus(); } #endif if (!this->IsActive()) { std::this_thread::sleep_for(std::chrono::milliseconds(30)); } else { if (lowPerfMode) { std::this_thread::sleep_for(std::chrono::milliseconds(30)); } else { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } event.RequestMore(); } void AppFrame::OnDoubleClickSash(wxSplitterEvent& event) { wxWindow *a, *b; wxSplitterWindow *w = NULL; float g = 0.5; if (event.GetId() == wxID_MAIN_SPLITTER) { w = mainSplitter; g = 10.0f/37.0f; } else if (event.GetId() == wxID_VIS_SPLITTER) { w = mainVisSplitter; g = 6.0f/25.0f; } if (w != NULL) { a = w->GetWindow1(); b = w->GetWindow2(); w->Unsplit(); w->SetSashGravity(g); wxSize s = w->GetSize(); w->SplitHorizontally(a, b, int(float(s.GetHeight()) * g)); } event.Veto(); } void AppFrame::OnUnSplit(wxSplitterEvent& event) { event.Veto(); } void AppFrame::saveSession(std::string fileName) { DataTree s("cubicsdr_session"); DataNode *header = s.rootNode()->newChild("header"); //save as wstring to prevent problems header->newChild("version")->element()->set(wxString(CUBICSDR_VERSION).ToStdWstring()); *header->newChild("center_freq") = wxGetApp().getFrequency(); *header->newChild("sample_rate") = wxGetApp().getSampleRate(); if (waterfallCanvas->getViewState()) { DataNode *viewState = header->newChild("view_state"); *viewState->newChild("center_freq") = waterfallCanvas->getCenterFrequency(); *viewState->newChild("bandwidth") = waterfallCanvas->getBandwidth(); } DataNode *demods = s.rootNode()->newChild("demodulators"); std::vector &instances = wxGetApp().getDemodMgr().getDemodulators(); std::vector::iterator instance_i; for (instance_i = instances.begin(); instance_i != instances.end(); instance_i++) { DataNode *demod = demods->newChild("demodulator"); *demod->newChild("bandwidth") = (*instance_i)->getBandwidth(); *demod->newChild("frequency") = (*instance_i)->getFrequency(); *demod->newChild("type") = (*instance_i)->getDemodulatorType(); demod->newChild("user_label")->element()->set((*instance_i)->getDemodulatorUserLabel()); *demod->newChild("squelch_level") = (*instance_i)->getSquelchLevel(); *demod->newChild("squelch_enabled") = (*instance_i)->isSquelchEnabled() ? 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; if ((*instance_i)->isDeltaLock()) { *demod->newChild("delta_lock") = (*instance_i)->isDeltaLock() ? 1 : 0; *demod->newChild("delta_ofs") = (*instance_i)->getDeltaLockOfs(); } if ((*instance_i) == wxGetApp().getDemodMgr().getLastActiveDemodulator()) { *demod->newChild("active") = 1; } ModemSettings saveSettings = (*instance_i)->readModemSettings(); if (saveSettings.size()) { DataNode *settingsNode = demod->newChild("settings"); for (ModemSettings::const_iterator msi = saveSettings.begin(); msi != saveSettings.end(); msi++) { *settingsNode->newChild(msi->first.c_str()) = msi->second; } } } //end for demodulators // Make sure the file name actually ends in .xml std::string lcFileName = fileName; std::transform(lcFileName.begin(), lcFileName.end(), lcFileName.begin(), ::tolower); if (lcFileName.find_last_of(".xml") != lcFileName.length()-1) { fileName.append(".xml"); } s.SaveToFileXML(fileName); currentSessionFile = fileName; std::string filePart = fileName.substr(fileName.find_last_of(filePathSeparator) + 1); GetStatusBar()->SetStatusText(wxString::Format(wxT("Saved session: %s"), currentSessionFile.c_str())); SetTitle(wxString::Format(wxT("%s: %s"), CUBICSDR_TITLE, filePart.c_str())); } bool AppFrame::loadSession(std::string fileName) { DataTree l; if (!l.LoadFromFileXML(fileName)) { return false; } wxGetApp().getDemodMgr().setActiveDemodulator(nullptr, false); wxGetApp().getDemodMgr().terminateAll(); try { if (!l.rootNode()->hasAnother("header")) { return false; } DataNode *header = l.rootNode()->getNext("header"); if (header->hasAnother("version")) { //"Force" the retreiving of the value as string, even if its look like a number internally ! (ex: "0.2.0") DataNode *versionNode = header->getNext("version"); std::wstring version; try { versionNode->element()->get(version); std::cout << "Loading session file version: '" << version << "'..." << std::endl; } catch (DataTypeMismatchException* e) { //this is for managing the old session format NOT encoded as std:wstring, //force current version std::cout << "Warning while Loading session file version, probably old format :'" << e->what() << "' please consider re-saving the current session..." << std::endl; version = wxString(CUBICSDR_VERSION).ToStdWstring(); } } if (header->hasAnother("sample_rate")) { int sample_rate = *header->getNext("sample_rate"); SDRDeviceInfo *dev = wxGetApp().getSDRThread()->getDevice(); if (dev) { // Try for a reasonable default sample rate. sample_rate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, sample_rate); wxGetApp().setSampleRate(sample_rate); deviceChanged.store(true); } else { wxGetApp().setSampleRate(sample_rate); } } DemodulatorInstance *loadedActiveDemod = nullptr; DemodulatorInstance *newDemod = nullptr; if (l.rootNode()->hasAnother("demodulators")) { DataNode *demodulators = l.rootNode()->getNext("demodulators"); int numDemodulators = 0; std::vector demodsLoaded; while (demodulators->hasAnother("demodulator")) { DataNode *demod = demodulators->getNext("demodulator"); if (!demod->hasAnother("bandwidth") || !demod->hasAnother("frequency")) { continue; } long bandwidth = *demod->getNext("bandwidth"); long long freq = *demod->getNext("frequency"); 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 muted = demod->hasAnother("muted") ? (int) *demod->getNext("muted") : 0; int delta_locked = demod->hasAnother("delta_lock") ? (int) *demod->getNext("delta_lock") : 0; int delta_ofs = demod->hasAnother("delta_ofs") ? (int) *demod->getNext("delta_ofs") : 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; std::string type = "FM"; DataNode *demodTypeNode = demod->hasAnother("type")?demod->getNext("type"):nullptr; if (demodTypeNode && demodTypeNode->element()->getDataType() == DATA_INT) { int legacyType = *demodTypeNode; int legacyStereo = demod->hasAnother("stereo") ? (int) *demod->getNext("stereo") : 0; switch (legacyType) { // legacy demod ID case 1: type = legacyStereo?"FMS":"FM"; break; case 2: type = "AM"; break; case 3: type = "LSB"; break; case 4: type = "USB"; break; case 5: type = "DSB"; break; case 6: type = "ASK"; break; case 7: type = "APSK"; break; case 8: type = "BPSK"; break; case 9: type = "DPSK"; break; case 10: type = "PSK"; break; case 11: type = "OOK"; break; case 12: type = "ST"; break; case 13: type = "SQAM"; break; case 14: type = "QAM"; break; case 15: type = "QPSK"; break; case 16: type = "I/Q"; break; default: type = "FM"; break; } } else if (demodTypeNode && demodTypeNode->element()->getDataType() == DATA_STRING) { demodTypeNode->element()->get(type); } //read the user label associated with the demodulator std::wstring user_label = L""; DataNode *demodUserLabel = demod->hasAnother("user_label") ? demod->getNext("user_label") : nullptr; if (demodUserLabel) { demodUserLabel->element()->get(user_label); } ModemSettings mSettings; if (demod->hasAnother("settings")) { DataNode *modemSettings = demod->getNext("settings"); for (int msi = 0, numSettings = modemSettings->numChildren(); msi < numSettings; msi++) { DataNode *settingNode = modemSettings->child(msi); std::string keyName = settingNode->getName(); std::string strSettingValue = settingNode->element()->toString(); if (keyName != "" && strSettingValue != "") { mSettings[keyName] = strSettingValue; } } } newDemod = wxGetApp().getDemodMgr().newThread(); if (demod->hasAnother("active")) { loadedActiveDemod = newDemod; } numDemodulators++; newDemod->setDemodulatorType(type); newDemod->setDemodulatorUserLabel(user_label); newDemod->writeModemSettings(mSettings); newDemod->setBandwidth(bandwidth); newDemod->setFrequency(freq); newDemod->setGain(gain); newDemod->updateLabel(freq); newDemod->setMuted(muted?true:false); if (delta_locked) { newDemod->setDeltaLock(true); newDemod->setDeltaLockOfs(delta_ofs); } if (squelch_enabled) { newDemod->setSquelchEnabled(true); newDemod->setSquelchLevel(squelch_level); } bool found_device = false; std::map::iterator i; for (i = outputDevices.begin(); i != outputDevices.end(); i++) { if (i->second.name == output_device) { newDemod->setOutputDevice(i->first); found_device = true; } } // if (!found_device) { // std::cout << "\tWarning: named output device '" << output_device << "' was not found. Using default output."; // } newDemod->run(); newDemod->setActive(true); demodsLoaded.push_back(newDemod); // wxGetApp().bindDemodulator(newDemod); std::cout << "\tAdded demodulator at frequency " << newDemod->getFrequency() << " type " << type << std::endl; // std::cout << "\t\tBandwidth: " << bandwidth << std::endl; // std::cout << "\t\tSquelch Level: " << squelch_level << std::endl; // std::cout << "\t\tSquelch Enabled: " << (squelch_enabled ? "true" : "false") << std::endl; // std::cout << "\t\tOutput Device: " << output_device << std::endl; } if (demodsLoaded.size()) { wxGetApp().bindDemodulators(&demodsLoaded); } } // if l.rootNode()->hasAnother("demodulators") if (header->hasAnother("center_freq")) { long long center_freq = *header->getNext("center_freq"); wxGetApp().setFrequency(center_freq); // std::cout << "\tCenter Frequency: " << center_freq << std::endl; } if (header->hasAnother("view_state")) { DataNode *viewState = header->getNext("view_state"); if (viewState->hasAnother("center_freq") && viewState->hasAnother("bandwidth")) { long long center_freq = *viewState->getNext("center_freq"); int bandwidth = *viewState->getNext("bandwidth"); spectrumCanvas->setView(center_freq, bandwidth); waterfallCanvas->setView(center_freq, bandwidth); } } else { spectrumCanvas->disableView(); waterfallCanvas->disableView(); spectrumCanvas->setCenterFrequency(wxGetApp().getFrequency()); waterfallCanvas->setCenterFrequency(wxGetApp().getFrequency()); } if (loadedActiveDemod || newDemod) { wxGetApp().getDemodMgr().setActiveDemodulator(loadedActiveDemod?loadedActiveDemod:newDemod, false); } } catch (DataTypeMismatchException &e) { std::cout << e.what() << std::endl; return false; } currentSessionFile = fileName; std::string filePart = fileName.substr(fileName.find_last_of(filePathSeparator) + 1); GetStatusBar()->SetStatusText(wxString::Format(wxT("Loaded session file: %s"), currentSessionFile.c_str())); SetTitle(wxString::Format(wxT("%s: %s"), CUBICSDR_TITLE, filePart.c_str())); return true; } FFTVisualDataThread *AppFrame::getWaterfallDataThread() { return waterfallDataThread; } void AppFrame::notifyUpdateModemProperties() { modemPropertiesUpdated.store(true); } void AppFrame::setMainWaterfallFFTSize(int fftSize) { wxGetApp().getSpectrumProcessor()->setFFTSize(fftSize); spectrumCanvas->setFFTSize(fftSize); waterfallDataThread->getProcessor()->setFFTSize(fftSize); waterfallCanvas->setFFTSize(fftSize); } void AppFrame::setScopeDeviceName(std::string deviceName) { scopeCanvas->setDeviceName(deviceName); } void AppFrame::refreshGainUI() { gainCanvas->updateGainUI(); gainCanvas->Refresh(); } bool AppFrame::isUserDemodBusy() { return (modemProps && modemProps->isMouseInView()) || (waterfallCanvas->isMouseInView() && waterfallCanvas->isMouseDown()) || (demodWaterfallCanvas->isMouseInView() && demodWaterfallCanvas->isMouseDown()) || (wxGetApp().getDemodMgr().getLastActiveDemodulator() && wxGetApp().getDemodMgr().getActiveDemodulator() && wxGetApp().getDemodMgr().getLastActiveDemodulator() != wxGetApp().getDemodMgr().getActiveDemodulator()); } #ifdef _WIN32 bool AppFrame::canFocus() { return (!wxGetApp().isDeviceSelectorOpen() && (!modemProps || !modemProps->isMouseInView())); } #endif FrequencyDialog::FrequencyDialogTarget AppFrame::getFrequencyDialogTarget() { FrequencyDialog::FrequencyDialogTarget target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_DEFAULT; if (waterfallSpeedMeter->getMouseTracker()->mouseInView()) { target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_WATERFALL_LPS; } else if (spectrumAvgMeter->getMouseTracker()->mouseInView()) { target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_SPECTRUM_AVG; } else if (demodTuner->getMouseTracker()->mouseInView()) { switch (demodTuner->getHoverState()) { case TuningCanvas::ActiveState::TUNING_HOVER_BW: target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_BANDWIDTH; break; case TuningCanvas::ActiveState::TUNING_HOVER_FREQ: target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_FREQ; break; case TuningCanvas::ActiveState::TUNING_HOVER_CENTER: default: target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_DEFAULT; break; } } else if (gainCanvas->getMouseTracker()->mouseInView()) { target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_GAIN; } return target; } void AppFrame::gkNudgeLeft(DemodulatorInstance *demod, int snap) { if (demod) { demod->setFrequency(demod->getFrequency()-snap); demod->updateLabel(demod->getFrequency()); } } void AppFrame::gkNudgeRight(DemodulatorInstance *demod, int snap) { if (demod) { demod->setFrequency(demod->getFrequency()+snap); demod->updateLabel(demod->getFrequency()); } } int AppFrame::OnGlobalKeyDown(wxKeyEvent &event) { if (!this->IsActive()) { return -1; } if (modemProps && (modemProps->HasFocus() || modemProps->isMouseInView())) { return -1; } DemodulatorInstance *demod = nullptr, *lastDemod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); int snap = wxGetApp().getFrequencySnap(); if (event.ControlDown()) { return 1; } if (event.ShiftDown()) { if (snap != 1) { snap /= 2; } } #ifdef wxHAS_RAW_KEY_CODES switch (event.GetRawKeyCode()) { case 30: gkNudgeRight(lastDemod, snap); return 1; case 33: gkNudgeLeft(lastDemod, snap); return 1; } #endif switch (event.GetKeyCode()) { case WXK_UP: case WXK_NUMPAD_UP: case WXK_DOWN: case WXK_NUMPAD_DOWN: case WXK_LEFT: case WXK_NUMPAD_LEFT: case WXK_RIGHT: case WXK_NUMPAD_RIGHT: waterfallCanvas->OnKeyDown(event); // TODO: Move the stuff from there to here return 1; case 'V': return 1; case ']': gkNudgeRight(lastDemod, snap); return 1; case '[': gkNudgeLeft(lastDemod, snap); return 1; case 'A': case 'F': case 'L': case 'U': case 'S': case 'P': case 'M': return 1; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': wxGetApp().showFrequencyInput(getFrequencyDialogTarget(), std::to_string(event.GetKeyCode() - '0')); return 1; break; case WXK_TAB: lastDemod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); if (!lastDemod) { break; } if (event.ShiftDown()) { demod = wxGetApp().getDemodMgr().getPreviousDemodulator(lastDemod); } else { demod = wxGetApp().getDemodMgr().getNextDemodulator(lastDemod); } if (demod) { wxGetApp().getDemodMgr().setActiveDemodulator(nullptr); wxGetApp().getDemodMgr().setActiveDemodulator(demod, false); } return 1; default: break; } if (demodTuner->getMouseTracker()->mouseInView()) { demodTuner->OnKeyDown(event); } else if (waterfallCanvas->getMouseTracker()->mouseInView()) { waterfallCanvas->OnKeyDown(event); } return 1; } int AppFrame::OnGlobalKeyUp(wxKeyEvent &event) { if (!this->IsActive()) { return -1; } if (modemProps && (modemProps->HasFocus() || modemProps->isMouseInView())) { return -1; } if (event.ControlDown()) { return 1; } DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getActiveDemodulator(); DemodulatorInstance *lastDemod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); #ifdef wxHAS_RAW_KEY_CODES switch (event.GetRawKeyCode()) { case 30: return 1; case 33: return 1; } #endif switch (event.GetKeyCode()) { case WXK_SPACE: if (!demodTuner->getMouseTracker()->mouseInView()) { wxGetApp().showFrequencyInput(getFrequencyDialogTarget()); return 1; } break; case WXK_UP: case WXK_NUMPAD_UP: case WXK_DOWN: case WXK_NUMPAD_DOWN: case WXK_LEFT: case WXK_NUMPAD_LEFT: case WXK_RIGHT: case WXK_NUMPAD_RIGHT: waterfallCanvas->OnKeyUp(event); return 1; case 'V': if (activeDemod) { lastDemod = activeDemod; } if (lastDemod && lastDemod->isDeltaLock()) { lastDemod->setDeltaLock(false); } else if (lastDemod) { lastDemod->setDeltaLockOfs(lastDemod->getFrequency() - wxGetApp().getFrequency()); lastDemod->setDeltaLock(true); } break; case 'A': demodModeSelector->setSelection("AM"); return 1; break; case 'F': if (demodModeSelector->getSelectionLabel() == "FM") { demodModeSelector->setSelection("FMS"); } else if (demodModeSelector->getSelectionLabel() == "FMS") { demodModeSelector->setSelection("NBFM"); } else if (demodModeSelector->getSelectionLabel() == "NBFM") { demodModeSelector->setSelection("FM"); } return 1; break; case 'L': demodModeSelector->setSelection("LSB"); return 1; break; case 'U': demodModeSelector->setSelection("USB"); return 1; break; case 'S': wxGetApp().setSoloMode(!wxGetApp().getSoloMode()); return 1; break; case 'P': wxGetApp().getSpectrumProcessor()->setPeakHold(!wxGetApp().getSpectrumProcessor()->getPeakHold()); wxGetApp().getDemodSpectrumProcessor()->setPeakHold(wxGetApp().getSpectrumProcessor()->getPeakHold()); peakHoldButton->setSelection(wxGetApp().getSpectrumProcessor()->getPeakHold()?1:0); peakHoldButton->clearModeChanged(); break; case ']': case '[': return 1; case 'M': if (activeDemod) { lastDemod = activeDemod; } if (lastDemod) { lastDemod->setMuted(!lastDemod->isMuted()); } break; default: break; } if (demodTuner->getMouseTracker()->mouseInView()) { demodTuner->OnKeyUp(event); } else if (waterfallCanvas->getMouseTracker()->mouseInView()) { waterfallCanvas->OnKeyUp(event); } // TODO: Catch key-ups outside of original target return 1; } void AppFrame::setWaterfallLinesPerSecond(int lps) { waterfallSpeedMeter->setUserInputValue(sqrt(lps)); } void AppFrame::setSpectrumAvgSpeed(double avg) { spectrumAvgMeter->setUserInputValue(avg); } void AppFrame::setViewState(long long center_freq, int bandwidth) { spectrumCanvas->setView(center_freq, bandwidth); waterfallCanvas->setView(center_freq, bandwidth); } void AppFrame::setViewState(long long center_freq) { spectrumCanvas->setCenterFrequency(wxGetApp().getFrequency()); waterfallCanvas->setCenterFrequency(wxGetApp().getFrequency()); spectrumCanvas->disableView(); waterfallCanvas->disableView(); }