diff --git a/CMakeLists.txt b/CMakeLists.txt index 3db72f8..d8d9868 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,6 +136,9 @@ SET (cubicsdr_sources src/util/MouseTracker.cpp src/util/GLFont.cpp src/visual/PrimaryGLContext.cpp + src/visual/InteractiveCanvas.cpp + src/visual/MeterCanvas.cpp + src/visual/MeterContext.cpp src/visual/ScopeCanvas.cpp src/visual/ScopeContext.cpp src/visual/SpectrumCanvas.cpp @@ -165,6 +168,9 @@ SET (cubicsdr_headers src/util/MouseTracker.h src/util/GLFont.h src/visual/PrimaryGLContext.h + src/visual/InteractiveCanvas.h + src/visual/MeterCanvas.h + src/visual/MeterContext.h src/visual/ScopeCanvas.h src/visual/ScopeContext.h src/visual/SpectrumCanvas.h diff --git a/README.md b/README.md index 707c176..3772158 100644 --- a/README.md +++ b/README.md @@ -38,20 +38,19 @@ Basic Goals and Status: - [ ] 3D visuals - Demodulation: - [x] Multiple demodulators per IQ stream - - [ ] Audio device selection - - [ ] Modes + - [x] Audio device selection + - [x] Modes - [x] FM - - [x] WFM - - [x] WBFM stereo - - [ ] AM - - [ ] LSB - - [ ] USB + - [x] FM stereo + - [x] AM + - [x] LSB + - [x] USB - [ ] Controls - [ ] Display Frequency and allow manual adjustments - - [ ] Allow selection of demodulation type - - [ ] Display separate zoomed-in view of current waterfall and spectrum, allow adjustments - - [ ] Display signal level and allow squelch control - - [ ] Display audio output selection + - [x] Allow selection of demodulation type + - [x] Display separate zoomed-in view of current waterfall and spectrum, allow adjustments + - [x] Display signal level and allow squelch control + - [x] Display audio output selection - [ ] Volume control - [ ] Basic noise reduction / filter controls? - Basic Input Controls diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp index ce3ad1f..f7dfdf6 100644 --- a/src/AppFrame.cpp +++ b/src/AppFrame.cpp @@ -18,25 +18,62 @@ #include +#include + wxBEGIN_EVENT_TABLE(AppFrame, wxFrame) //EVT_MENU(wxID_NEW, AppFrame::OnNewWindow) EVT_MENU(wxID_CLOSE, AppFrame::OnClose) +EVT_MENU(wxID_ANY, AppFrame::OnMenu) + EVT_COMMAND(wxID_ANY, wxEVT_THREAD, AppFrame::OnThread) EVT_IDLE(AppFrame::OnIdle) wxEND_EVENT_TABLE() AppFrame::AppFrame() : - wxFrame(NULL, wxID_ANY, wxT("CubicSDR")) { + wxFrame(NULL, wxID_ANY, wxT("CubicSDR")), activeDemodulator(NULL) { wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *demodOpts = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *demodVisuals = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *demodTray = new wxBoxSizer(wxHORIZONTAL); + + demodSpectrumCanvas = new SpectrumCanvas(this, NULL); + demodSpectrumCanvas->Setup(1024); + demodSpectrumCanvas->SetView(DEFAULT_FREQ, 300000); + demodVisuals->Add(demodSpectrumCanvas, 1, wxEXPAND | wxALL, 0); + + demodVisuals->AddSpacer(1); + + demodWaterfallCanvas = new WaterfallCanvas(this, NULL); + demodWaterfallCanvas->Setup(1024, 256); + demodWaterfallCanvas->SetView(DEFAULT_FREQ, 300000); + demodWaterfallCanvas->attachSpectrumCanvas(demodSpectrumCanvas); + demodSpectrumCanvas->attachWaterfallCanvas(demodWaterfallCanvas); + demodVisuals->Add(demodWaterfallCanvas, 3, wxEXPAND | wxALL, 0); + + demodTray->Add(demodVisuals, 30, wxEXPAND | wxALL, 0); + + demodTray->AddSpacer(2); + + demodSignalMeter = new MeterCanvas(this, NULL); + demodSignalMeter->setMax(0.5); + demodTray->Add(demodSignalMeter, 1, wxEXPAND | wxALL, 0); + + demodTray->AddSpacer(2); scopeCanvas = new ScopeCanvas(this, NULL); - vbox->Add(scopeCanvas, 1, wxEXPAND | wxALL, 0); + demodTray->Add(scopeCanvas, 30, wxEXPAND | wxALL, 0); + + vbox->Add(demodTray, 2, wxEXPAND | wxALL, 0); vbox->AddSpacer(2); spectrumCanvas = new SpectrumCanvas(this, NULL); + spectrumCanvas->Setup(2048); vbox->Add(spectrumCanvas, 1, wxEXPAND | wxALL, 0); vbox->AddSpacer(2); waterfallCanvas = new WaterfallCanvas(this, NULL); + waterfallCanvas->Setup(2048, 512); + waterfallCanvas->attachSpectrumCanvas(spectrumCanvas); + spectrumCanvas->attachWaterfallCanvas(waterfallCanvas); vbox->Add(waterfallCanvas, 4, wxEXPAND | wxALL, 0); this->SetSizer(vbox); @@ -47,12 +84,52 @@ AppFrame::AppFrame() : // SetIcon(wxICON(sample)); // Make a menubar - wxMenu *menu = new wxMenu; +// wxMenu *menu = new wxMenu; // menu->Append(wxID_NEW); // menu->AppendSeparator(); - menu->Append(wxID_CLOSE); +// menu->Append(wxID_CLOSE); +// wxMenuBar *menuBar = new wxMenuBar; +// menuBar->Append(menu, wxT("&File")); + + wxMenu *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) { + input_devices[i] = *devices_i; + } + if (devices_i->outputChannels) { + output_devices[i] = *devices_i; + } + i++; + } + + i = 0; + + for (mdevices_i = output_devices.begin(); mdevices_i != output_devices.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); + } + output_device_menuitems[mdevices_i->first] = itm; + } + wxMenuBar *menuBar = new wxMenuBar; - menuBar->Append(menu, wxT("&File")); + menuBar->Append(menu, wxT("Active Demodulator &Output")); + + wxMenu *demodMenu = new wxMenu; + demod_menuitems[DEMOD_TYPE_FM] = demodMenu->AppendRadioItem(wxID_DEMOD_TYPE_FM, wxT("FM"), wxT("Description?")); + demod_menuitems[DEMOD_TYPE_AM] = demodMenu->AppendRadioItem(wxID_DEMOD_TYPE_AM, wxT("AM"), wxT("Description?")); + demod_menuitems[DEMOD_TYPE_LSB] = demodMenu->AppendRadioItem(wxID_DEMOD_TYPE_LSB, wxT("LSB"), wxT("Description?")); + demod_menuitems[DEMOD_TYPE_USB] = demodMenu->AppendRadioItem(wxID_DEMOD_TYPE_USB, wxT("USB"), wxT("Description?")); + + menuBar->Append(demodMenu, wxT("Active Demodulator &Type")); SetMenuBar(menuBar); @@ -70,13 +147,34 @@ AppFrame::AppFrame() : AppFrame::~AppFrame() { -// delete t_SDR; +} +void AppFrame::OnMenu(wxCommandEvent& event) { + if (event.GetId() >= wxID_RT_AUDIO_DEVICE && event.GetId() < wxID_RT_AUDIO_DEVICE + devices.size()) { + if (activeDemodulator) { + activeDemodulator->setOutputDevice(event.GetId() - wxID_RT_AUDIO_DEVICE); + activeDemodulator = NULL; + } + } + + if (activeDemodulator) { + if (event.GetId() == wxID_DEMOD_TYPE_FM) { + activeDemodulator->setDemodulatorType(DEMOD_TYPE_FM); + activeDemodulator = NULL; + } else if (event.GetId() == wxID_DEMOD_TYPE_AM) { + activeDemodulator->setDemodulatorType(DEMOD_TYPE_AM); + activeDemodulator = NULL; + } else if (event.GetId() == wxID_DEMOD_TYPE_LSB) { + activeDemodulator->setDemodulatorType(DEMOD_TYPE_LSB); + activeDemodulator = NULL; + } else if (event.GetId() == wxID_DEMOD_TYPE_USB) { + activeDemodulator->setDemodulatorType(DEMOD_TYPE_USB); + activeDemodulator = NULL; + } + } } void AppFrame::OnClose(wxCommandEvent& WXUNUSED(event)) { - - // true is to force the frame to close Close(true); } @@ -95,14 +193,48 @@ void AppFrame::OnIdle(wxIdleEvent& event) { // std::this_thread::sleep_for(std::chrono::milliseconds(4)); // std::this_thread::yield(); //#endif + + DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); + + if (demod) { + if (demod != activeDemodulator) { + demodSignalMeter->setInputValue(demod->getSquelchLevel()); + int outputDevice = demod->getOutputDevice(); + scopeCanvas->setDeviceName(output_devices[outputDevice].name); + output_device_menuitems[outputDevice]->Check(true); + int dType = demod->getDemodulatorType(); + demod_menuitems[dType]->Check(true); + } + if (demodWaterfallCanvas->getDragState() == WaterfallCanvas::WF_DRAG_NONE) { + if (demod->getParams().frequency != demodWaterfallCanvas->GetCenterFrequency()) { + demodWaterfallCanvas->SetCenterFrequency(demod->getParams().frequency); + demodSpectrumCanvas->SetCenterFrequency(demod->getParams().frequency); + } + unsigned int demodBw = (unsigned int) ceil((float) demod->getParams().bandwidth * 2.5); + if (demodBw > SRATE / 2) { + demodBw = SRATE / 2; + } + if (demodBw < 80000) { + demodBw = 80000; + } + demodWaterfallCanvas->SetBandwidth(demodBw); + demodSpectrumCanvas->SetBandwidth(demodBw); + } + demodSignalMeter->setLevel(demod->getSignalLevel()); + if (demodSignalMeter->inputChanged()) { + demod->setSquelchLevel(demodSignalMeter->getInputValue()); + } + activeDemodulator = demod; + } + if (!wxGetApp().getIQVisualQueue()->empty()) { DemodulatorThreadIQData *iqData; wxGetApp().getIQVisualQueue()->pop(iqData); if (iqData && iqData->data.size()) { - spectrumCanvas->setData(&iqData->data); - waterfallCanvas->setData(&iqData->data); - +// spectrumCanvas->setData(iqData); + waterfallCanvas->setData(iqData); + demodWaterfallCanvas->setData(iqData); delete iqData; } else { std::cout << "Incoming IQ data empty?" << std::endl; @@ -114,8 +246,8 @@ void AppFrame::OnIdle(wxIdleEvent& event) { AudioThreadInput *demodAudioData; wxGetApp().getAudioVisualQueue()->pop(demodAudioData); if (demodAudioData && demodAudioData->data.size()) { - if (scopeCanvas->waveform_points.size() != demodAudioData->data.size()*2) { - scopeCanvas->waveform_points.resize(demodAudioData->data.size()*2); + if (scopeCanvas->waveform_points.size() != demodAudioData->data.size() * 2) { + scopeCanvas->waveform_points.resize(demodAudioData->data.size() * 2); } for (int i = 0, iMax = demodAudioData->data.size(); i < iMax; i++) { @@ -123,7 +255,7 @@ void AppFrame::OnIdle(wxIdleEvent& event) { scopeCanvas->waveform_points[i * 2] = ((double) i / (double) iMax); } - scopeCanvas->setDivider(demodAudioData->channels == 2); + scopeCanvas->setStereo(demodAudioData->channels == 2); delete demodAudioData; } else { diff --git a/src/AppFrame.h b/src/AppFrame.h index 15c20a3..d3ff416 100644 --- a/src/AppFrame.h +++ b/src/AppFrame.h @@ -6,6 +6,15 @@ #include "ScopeCanvas.h" #include "SpectrumCanvas.h" #include "WaterfallCanvas.h" +#include "MeterCanvas.h" + +#include + +#define wxID_RT_AUDIO_DEVICE 1000 +#define wxID_DEMOD_TYPE_FM 2000 +#define wxID_DEMOD_TYPE_AM 2001 +#define wxID_DEMOD_TYPE_LSB 2002 +#define wxID_DEMOD_TYPE_USB 2003 // Define a new frame type class AppFrame: public wxFrame { @@ -16,6 +25,7 @@ public: void OnEventInput(wxThreadEvent& event); private: + void OnMenu(wxCommandEvent& event); void OnClose(wxCommandEvent& event); void OnNewWindow(wxCommandEvent& event); void OnIdle(wxIdleEvent& event); @@ -23,7 +33,19 @@ private: ScopeCanvas *scopeCanvas; SpectrumCanvas *spectrumCanvas; WaterfallCanvas *waterfallCanvas; - + SpectrumCanvas *demodSpectrumCanvas; + WaterfallCanvas *demodWaterfallCanvas; + MeterCanvas *demodSignalMeter; // event table -wxDECLARE_EVENT_TABLE(); + + DemodulatorInstance *activeDemodulator; + + std::vector devices; + std::map input_devices; + std::map output_devices; + std::map output_device_menuitems; + + std::map demod_menuitems; + + wxDECLARE_EVENT_TABLE(); }; diff --git a/src/CubicSDR.cpp b/src/CubicSDR.cpp index f2e6e8a..142073d 100644 --- a/src/CubicSDR.cpp +++ b/src/CubicSDR.cpp @@ -28,6 +28,7 @@ bool CubicSDR::OnInit() { sdrThread = new SDRThread(threadCmdQueueSDR); sdrPostThread = new SDRPostThread(); + sdrPostThread->setNumVisSamples(2048); iqPostDataQueue = new SDRThreadIQDataQueue; iqVisualQueue = new DemodulatorThreadInputQueue; diff --git a/src/CubicSDRDefs.h b/src/CubicSDRDefs.h index 7a8bb8e..1a8fa98 100644 --- a/src/CubicSDRDefs.h +++ b/src/CubicSDRDefs.h @@ -4,12 +4,13 @@ #define BUF_SIZE (16384*2) #define SRATE 2000000 #else -#define BUF_SIZE (16384*4) +#define BUF_SIZE (16384*5) #define SRATE 2500000 #endif -#define FFT_SIZE 2048 +#define DEFAULT_FFT_SIZE 2048 -#define DEFAULT_FREQ 98900000 +//#define DEFAULT_FREQ 98900000 +#define DEFAULT_FREQ 132000000 #define AUDIO_FREQUENCY 44100 #include diff --git a/src/audio/AudioThread.cpp b/src/audio/AudioThread.cpp index 346268f..f1b75b6 100644 --- a/src/audio/AudioThread.cpp +++ b/src/audio/AudioThread.cpp @@ -10,7 +10,7 @@ std::map AudioThread::deviceThread; #endif AudioThread::AudioThread(AudioThreadInputQueue *inputQueue, DemodulatorThreadCommandQueue* threadQueueNotify) : - currentInput(NULL), inputQueue(inputQueue), audio_queue_ptr(0), underflow_count(0), terminated(false), active(false), gain(1.0), threadQueueNotify( + currentInput(NULL), inputQueue(inputQueue), audio_queue_ptr(0), underflow_count(0), terminated(false), active(false), output_device(-1), gain(1.0), threadQueueNotify( threadQueueNotify) { #ifdef __APPLE__ boundThreads = new std::vector; @@ -25,7 +25,9 @@ AudioThread::~AudioThread() { #ifdef __APPLE__ void AudioThread::bindThread(AudioThread *other) { - boundThreads.load()->push_back(other); + if (std::find(boundThreads.load()->begin(), boundThreads.load()->end(), other) == boundThreads.load()->end()) { + boundThreads.load()->push_back(other); + } } void AudioThread::removeThread(AudioThread *other) { @@ -211,11 +213,15 @@ static int audioCallback(void *outputBuffer, void *inputBuffer, unsigned int nBu } #endif -void AudioThread::enumerateDevices() { - int numDevices = dac.getDeviceCount(); +void AudioThread::enumerateDevices(std::vector &devs) { + RtAudio endac; + + int numDevices = endac.getDeviceCount(); for (int i = 0; i < numDevices; i++) { - RtAudio::DeviceInfo info = dac.getDeviceInfo(i); + RtAudio::DeviceInfo info = endac.getDeviceInfo(i); + + devs.push_back(info); std::cout << std::endl; @@ -259,6 +265,70 @@ void AudioThread::enumerateDevices() { } } +void AudioThread::setupDevice(int deviceId) { + parameters.deviceId = deviceId; + parameters.nChannels = 2; + parameters.firstChannel = 0; + unsigned int sampleRate = AUDIO_FREQUENCY; + unsigned int bufferFrames = 256; + + RtAudio::StreamOptions opts; + opts.streamName = "CubicSDR Audio Output"; + + try { + +#ifdef __APPLE__ + if (deviceController.find(output_device.load()) != deviceController.end()) { + deviceController[output_device.load()]->removeThread(this); + } + + opts.priority = sched_get_priority_max(SCHED_FIFO); + // opts.flags = RTAUDIO_MINIMIZE_LATENCY; + opts.flags = RTAUDIO_SCHEDULE_REALTIME; + + if (deviceController.find(parameters.deviceId) == deviceController.end()) { + deviceController[parameters.deviceId] = new AudioThread(NULL, NULL); + deviceController[parameters.deviceId]->setInitOutputDevice(parameters.deviceId); + deviceController[parameters.deviceId]->bindThread(this); + deviceThread[parameters.deviceId] = new std::thread(&AudioThread::threadMain, deviceController[parameters.deviceId]); + } else if (deviceController[parameters.deviceId] == this) { + dac.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &audioCallback, (void *) this, &opts); + dac.startStream(); + } else { + deviceController[parameters.deviceId]->bindThread(this); + } + active = true; +#else + if (dac.isStreamOpen()) { + if (dac.isStreamRunning()) { + dac.stopStream(); + } + dac.closeStream(); + } + + dac.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &audioCallback, (void *) this, &opts); + dac.startStream(); + +#endif + } catch (RtAudioError& e) { + e.printMessage(); + return; + } + + output_device = deviceId; +} + +int AudioThread::getOutputDevice() { + if (output_device == -1) { + return dac.getDefaultOutputDevice(); + } + return output_device; +} + +void AudioThread::setInitOutputDevice(int deviceId) { + output_device = deviceId; +} + void AudioThread::threadMain() { #ifdef __APPLE__ pthread_t tID = pthread_self(); // ID of this thread @@ -274,46 +344,17 @@ void AudioThread::threadMain() { return; } - parameters.deviceId = dac.getDefaultOutputDevice(); - parameters.nChannels = 2; - parameters.firstChannel = 0; - unsigned int sampleRate = AUDIO_FREQUENCY; - unsigned int bufferFrames = 256; + setupDevice((output_device.load() == -1)?(dac.getDefaultOutputDevice()):output_device.load()); - RtAudio::StreamOptions opts; - opts.streamName = "CubicSDR Audio Output"; - - try { - -#ifdef __APPLE__ - opts.priority = sched_get_priority_max(SCHED_FIFO); - // opts.flags = RTAUDIO_MINIMIZE_LATENCY; - opts.flags = RTAUDIO_SCHEDULE_REALTIME; - - if (deviceController.find(parameters.deviceId) == deviceController.end()) { - deviceController[parameters.deviceId] = new AudioThread(NULL, NULL); - deviceController[parameters.deviceId]->bindThread(this); - deviceThread[parameters.deviceId] = new std::thread(&AudioThread::threadMain, deviceController[parameters.deviceId]); - } else if (deviceController[parameters.deviceId] == this) { - dac.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &audioCallback, (void *) this, &opts); - dac.startStream(); - } else { - deviceController[parameters.deviceId]->bindThread(this); - } - active = true; -#else - dac.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &audioCallback, (void *) this, &opts); - dac.startStream(); - -#endif - } catch (RtAudioError& e) { - e.printMessage(); - return; - } + std::cout << "Audio thread started." << std::endl; while (!terminated) { AudioThreadCommand command; cmdQueue.pop(command); + + if (command.cmd == AudioThreadCommand::AUDIO_THREAD_CMD_SET_DEVICE) { + setupDevice(command.int_value); + } } #ifdef __APPLE__ @@ -367,7 +408,7 @@ void AudioThread::setActive(bool state) { while (!inputQueue->empty()) { // flush queue inputQueue->pop(dummy); if (dummy) { - delete dummy; + dummy->decRefCount(); } } deviceController[parameters.deviceId]->bindThread(this); @@ -376,10 +417,15 @@ void AudioThread::setActive(bool state) { while (!inputQueue->empty()) { // flush queue inputQueue->pop(dummy); if (dummy) { - delete dummy; + dummy->decRefCount(); } } } #endif active = state; } + + +AudioThreadCommandQueue *AudioThread::getCommandQueue() { + return &cmdQueue; +} diff --git a/src/audio/AudioThread.h b/src/audio/AudioThread.h index 941fdaf..36f53f2 100644 --- a/src/audio/AudioThread.h +++ b/src/audio/AudioThread.h @@ -61,19 +61,25 @@ public: std::atomic underflow_count; std::atomic terminated; std::atomic active; + std::atomic output_device; float gain; AudioThread(AudioThreadInputQueue *inputQueue, DemodulatorThreadCommandQueue* threadQueueNotify); ~AudioThread(); - void enumerateDevices(); + static void enumerateDevices(std::vector &devs); + void setupDevice(int deviceId); + void setInitOutputDevice(int deviceId); + int getOutputDevice(); void threadMain(); void terminate(); bool isActive(); void setActive(bool state); + AudioThreadCommandQueue *getCommandQueue(); + private: RtAudio dac; RtAudio::StreamParameters parameters; diff --git a/src/demod/DemodDefs.h b/src/demod/DemodDefs.h index ac899ba..af0625d 100644 --- a/src/demod/DemodDefs.h +++ b/src/demod/DemodDefs.h @@ -7,9 +7,12 @@ #include #include -enum DemodulatorType { - DEMOD_TYPE_NULL, DEMOD_TYPE_AM, DEMOD_TYPE_FM, DEMOD_TYPE_LSB, DEMOD_TYPE_USB -}; +#define DEMOD_TYPE_NULL 0 +#define DEMOD_TYPE_FM 1 +#define DEMOD_TYPE_AM 2 +#define DEMOD_TYPE_LSB 3 +#define DEMOD_TYPE_USB 4 + class DemodulatorThread; class DemodulatorThreadCommand { @@ -41,14 +44,15 @@ public: class DemodulatorThreadControlCommand { public: enum DemodulatorThreadControlCommandEnum { - DEMOD_THREAD_CMD_CTL_NULL, DEMOD_THREAD_CMD_CTL_SQUELCH_AUTO, DEMOD_THREAD_CMD_CTL_SQUELCH_OFF + DEMOD_THREAD_CMD_CTL_NULL, DEMOD_THREAD_CMD_CTL_SQUELCH_AUTO, DEMOD_THREAD_CMD_CTL_SQUELCH_OFF, DEMOD_THREAD_CMD_CTL_TYPE }; DemodulatorThreadControlCommand() : - cmd(DEMOD_THREAD_CMD_CTL_NULL) { + cmd(DEMOD_THREAD_CMD_CTL_NULL), demodType(DEMOD_TYPE_NULL) { } DemodulatorThreadControlCommandEnum cmd; + int demodType; }; class DemodulatorThreadIQData: public ReferenceCounter { @@ -71,10 +75,10 @@ class DemodulatorThreadPostIQData: public ReferenceCounter { public: std::vector data; int bandwidth; - float audio_resample_ratio; + double audio_resample_ratio; msresamp_rrrf audio_resampler; msresamp_rrrf stereo_resampler; - float resample_ratio; + double resample_ratio; msresamp_crcf resampler; DemodulatorThreadPostIQData() : @@ -122,7 +126,7 @@ public: unsigned int bandwidth; // set equal to disable second stage re-sampling? unsigned int audioSampleRate; - DemodulatorType demodType; + int demodType; DemodulatorThreadParameters() : frequency(0), inputRate(SRATE), bandwidth(200000), audioSampleRate( diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp index 9650f01..12c2692 100644 --- a/src/demod/DemodulatorInstance.cpp +++ b/src/demod/DemodulatorInstance.cpp @@ -19,6 +19,8 @@ DemodulatorInstance::DemodulatorInstance() : audioThread = new AudioThread(audioInputQueue, threadQueueNotify); demodulatorThread->setAudioInputQueue(audioInputQueue); + + currentDemodType = demodulatorThread->getDemodulatorType(); } DemodulatorInstance::~DemodulatorInstance() { @@ -181,3 +183,44 @@ void DemodulatorInstance::setSquelchEnabled(bool state) { squelch = state; } +float DemodulatorInstance::getSignalLevel() { + return demodulatorThread->getSignalLevel(); +} + +void DemodulatorInstance::setSquelchLevel(float signal_level_in) { + demodulatorThread->setSquelchLevel(signal_level_in); +} + + +float DemodulatorInstance::getSquelchLevel() { + return demodulatorThread->getSquelchLevel(); +} + + +void DemodulatorInstance::setOutputDevice(int device_id) { + if (audioThread) { + AudioThreadCommand command; + command.cmd = AudioThreadCommand::AUDIO_THREAD_CMD_SET_DEVICE; + command.int_value = device_id; + audioThread->getCommandQueue()->push(command); + } +} + +int DemodulatorInstance::getOutputDevice() { + return audioThread->getOutputDevice(); +} + +void DemodulatorInstance::setDemodulatorType(int demod_type_in) { + if (demodulatorThread && threadQueueControl) { + DemodulatorThreadControlCommand command; + command.cmd = DemodulatorThreadControlCommand::DEMOD_THREAD_CMD_CTL_TYPE; + currentDemodType = demod_type_in; + command.demodType = demod_type_in; + threadQueueControl->push(command); + } +} + +int DemodulatorInstance::getDemodulatorType() { + return currentDemodType; +} + diff --git a/src/demod/DemodulatorInstance.h b/src/demod/DemodulatorInstance.h index 8d0ee39..5a440b3 100644 --- a/src/demod/DemodulatorInstance.h +++ b/src/demod/DemodulatorInstance.h @@ -52,13 +52,30 @@ public: bool isStereo(); void setStereo(bool state); - void squelchAuto();bool isSquelchEnabled(); + void squelchAuto(); + bool isSquelchEnabled(); void setSquelchEnabled(bool state); + float getSignalLevel(); + void setSquelchLevel(float signal_level_in); + float getSquelchLevel(); + + void setOutputDevice(int device_id); + int getOutputDevice(); + + void setDemodulatorType(int demod_type_in); + int getDemodulatorType(); + private: - std::atomic label;bool terminated;bool demodTerminated;bool audioTerminated;bool preDemodTerminated; + std::atomic label; // + bool terminated; // + bool demodTerminated; // + bool audioTerminated; // + bool preDemodTerminated; std::atomic active; std::atomic squelch; std::atomic stereo; + + int currentDemodType; }; diff --git a/src/demod/DemodulatorPreThread.cpp b/src/demod/DemodulatorPreThread.cpp index 5acf025..8ee7a9f 100644 --- a/src/demod/DemodulatorPreThread.cpp +++ b/src/demod/DemodulatorPreThread.cpp @@ -9,8 +9,8 @@ DemodulatorPreThread::DemodulatorPreThread(DemodulatorThreadInputQueue* pQueueIn, DemodulatorThreadPostInputQueue* pQueueOut, DemodulatorThreadControlCommandQueue *threadQueueControl, DemodulatorThreadCommandQueue* threadQueueNotify) : - inputQueue(pQueueIn), postInputQueue(pQueueOut), terminated(false), initialized(false), audio_resampler(NULL), stereo_resampler(NULL), resample_ratio(1), audio_resample_ratio( - 1), resampler(NULL), commandQueue(NULL), audioInputQueue(NULL), threadQueueNotify(threadQueueNotify), threadQueueControl( + inputQueue(pQueueIn), postInputQueue(pQueueOut), terminated(false), initialized(false), audio_resampler(NULL), stereo_resampler(NULL), resample_ratio( + 1), audio_resample_ratio(1), resampler(NULL), commandQueue(NULL), audioInputQueue(NULL), threadQueueNotify(threadQueueNotify), threadQueueControl( threadQueueControl) { float kf = 0.5; // modulation factor @@ -30,33 +30,17 @@ DemodulatorPreThread::DemodulatorPreThread(DemodulatorThreadInputQueue* pQueueIn void DemodulatorPreThread::initialize() { initialized = false; - resample_ratio = (float) (params.bandwidth) / (float) params.inputRate; - audio_resample_ratio = (float) (params.audioSampleRate) / (float) params.bandwidth; + resample_ratio = (double) (params.bandwidth) / (double) params.inputRate; + audio_resample_ratio = (double) (params.audioSampleRate) / (double) params.bandwidth; float As = 60.0f; // stop-band attenuation [dB] - // create multi-stage arbitrary resampler object - if (resampler) { - msresamp_crcf_destroy(resampler); - } resampler = msresamp_crcf_create(resample_ratio, As); -// msresamp_crcf_print(resampler); - - if (audio_resampler) { - msresamp_rrrf_destroy(audio_resampler); - } audio_resampler = msresamp_rrrf_create(audio_resample_ratio, As); -// msresamp_crcf_print(audio_resampler); - - if (stereo_resampler) { - msresamp_rrrf_destroy(stereo_resampler); - } stereo_resampler = msresamp_rrrf_create(audio_resample_ratio, As); - initialized = true; // std::cout << "inputResampleRate " << params.bandwidth << std::endl; - last_params = params; } @@ -73,7 +57,7 @@ void DemodulatorPreThread::threadMain() { #endif #ifdef __APPLE__ pthread_t tID = pthread_self(); // ID of this thread - int priority = sched_get_priority_max( SCHED_FIFO )-1; + int priority = sched_get_priority_max( SCHED_FIFO) - 1; sched_param prio = {priority}; // scheduling priority of thread pthread_setschedparam(tID, SCHED_FIFO, &prio); #endif @@ -104,8 +88,8 @@ void DemodulatorPreThread::threadMain() { commandQueue->pop(command); switch (command.cmd) { case DemodulatorThreadCommand::DEMOD_THREAD_CMD_SET_BANDWIDTH: - if (command.int_value < 3000) { - command.int_value = 3000; + if (command.int_value < 1500) { + command.int_value = 1500; } if (command.int_value > SRATE) { command.int_value = SRATE; @@ -138,13 +122,13 @@ void DemodulatorPreThread::threadMain() { if (inp->frequency != params.frequency) { if ((params.frequency - inp->frequency) != shift_freq) { shift_freq = params.frequency - inp->frequency; - if (abs(shift_freq) <= (int) ((float) (SRATE / 2) * 1.5)) { - nco_crcf_set_frequency(nco_shift, (2.0 * M_PI) * (((float) abs(shift_freq)) / ((float) SRATE))); + if (abs(shift_freq) <= (int) ((double) (SRATE / 2) * 1.5)) { + nco_crcf_set_frequency(nco_shift, (2.0 * M_PI) * (((double) abs(shift_freq)) / ((double) SRATE))); } } } - if (abs(shift_freq) > (int) ((float) (SRATE / 2) * 1.5)) { + if (abs(shift_freq) > (int) ((double) (SRATE / 2) * 1.5)) { continue; } @@ -162,7 +146,7 @@ void DemodulatorPreThread::threadMain() { out_buf_data.resize(bufSize); } - in_buf_data.assign(inp->data.begin(),inp->data.end()); + in_buf_data.assign(inp->data.begin(), inp->data.end()); liquid_float_complex *in_buf = &in_buf_data[0]; liquid_float_complex *out_buf = &out_buf_data[0]; diff --git a/src/demod/DemodulatorPreThread.h b/src/demod/DemodulatorPreThread.h index 7bd0b23..962b3f9 100644 --- a/src/demod/DemodulatorPreThread.h +++ b/src/demod/DemodulatorPreThread.h @@ -53,11 +53,11 @@ protected: AudioThreadInputQueue *audioInputQueue; msresamp_crcf resampler; - float resample_ratio; + double resample_ratio; msresamp_rrrf audio_resampler; msresamp_rrrf stereo_resampler; - float audio_resample_ratio; + double audio_resample_ratio; DemodulatorThreadParameters params; DemodulatorThreadParameters last_params; diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp index 3f10217..2d81cf2 100644 --- a/src/demod/DemodulatorThread.cpp +++ b/src/demod/DemodulatorThread.cpp @@ -8,12 +8,16 @@ DemodulatorThread::DemodulatorThread(DemodulatorThreadPostInputQueue* pQueue, DemodulatorThreadControlCommandQueue *threadQueueControl, DemodulatorThreadCommandQueue* threadQueueNotify) : - postInputQueue(pQueue), visOutQueue(NULL), audioInputQueue(NULL), agc(NULL), stereo(false), terminated(false), threadQueueNotify( - threadQueueNotify), threadQueueControl(threadQueueControl), squelch_level(0), squelch_tolerance(0), squelch_enabled(false) { + postInputQueue(pQueue), visOutQueue(NULL), audioInputQueue(NULL), agc(NULL), am_max(1), am_max_ma(1), am_max_maa(1), stereo(false), terminated( + false), demodulatorType(DEMOD_TYPE_FM), threadQueueNotify(threadQueueNotify), threadQueueControl(threadQueueControl), squelch_level(0), squelch_tolerance( + 0), signal_level(0), squelch_enabled(false) { + + fdem = freqdem_create(0.5); + ampdem_lsb = ampmodem_create(0.5, 0.0, LIQUID_AMPMODEM_LSB, 1); + ampdem_usb = ampmodem_create(0.5, 0.0, LIQUID_AMPMODEM_USB, 1); + ampdem = ampmodem_create(0.5, 0.0, LIQUID_AMPMODEM_DSB, 0); + ampdem_active = ampdem; - float kf = 0.5; // modulation factor - fdem = freqdem_create(kf); -// freqdem_print(fdem); } DemodulatorThread::~DemodulatorThread() { } @@ -36,7 +40,7 @@ void DemodulatorThread::threadMain() { firfilt_rrrf fir_filter2 = NULL; msresamp_crcf resampler = NULL; - float fc = 0.5 * ((double) 36000 / (double) AUDIO_FREQUENCY); // filter cutoff frequency + double fc = 0.5 * ((double) 36000 / (double) AUDIO_FREQUENCY); // filter cutoff frequency if (fc <= 0) { fc = 0; } @@ -60,24 +64,29 @@ void DemodulatorThread::threadMain() { firhilbf firR2C = firhilbf_create(m, slsl); firhilbf firC2R = firhilbf_create(m, slsl); - nco_crcf nco_shift = nco_crcf_create(LIQUID_NCO); - float shift_freq = 0; + nco_crcf nco_stereo_shift = nco_crcf_create(LIQUID_NCO); + double nco_stereo_shift_freq = 0; + + nco_crcf nco_ssb_shift_up = nco_crcf_create(LIQUID_NCO); + nco_crcf_set_frequency(nco_ssb_shift_up, (2.0 * M_PI) * 0.25); + + + nco_crcf nco_ssb_shift_down = nco_crcf_create(LIQUID_NCO); + nco_crcf_set_frequency(nco_ssb_shift_down, (2.0 * M_PI) * 0.25); + + // estimate required filter length and generate filter + h_len = estimate_req_filter_len(ft,100.0); + float h2[h_len]; + liquid_firdes_kaiser(h_len,0.25,As,0.0,h2); + + firfilt_crcf ssb_fir_filter = firfilt_crcf_create(h2, h_len); + agc = agc_crcf_create(); - agc_crcf_set_bandwidth(agc, 1e-3f); + agc_crcf_set_bandwidth(agc, 0.9); std::cout << "Demodulator thread started.." << std::endl; - std::deque buffers; - std::deque::iterator buffers_i; - - std::vector resampled_data; - std::vector agc_data; - std::vector demod_output; - std::vector demod_output_stereo; - std::vector resampled_audio_output; - std::vector resampled_audio_output_stereo; - double freq_index = 0; while (!terminated) { @@ -103,25 +112,30 @@ void DemodulatorThread::threadMain() { resampler = inp->resampler; audio_resampler = inp->audio_resampler; stereo_resampler = inp->stereo_resampler; + + ampmodem_reset(ampdem_lsb); + ampmodem_reset(ampdem_usb); + ampmodem_reset(ampdem); + freqdem_reset(fdem); } - int out_size = ceil((float) (bufSize) * inp->resample_ratio); + int out_size = ceil((double) (bufSize) * inp->resample_ratio) + 512; if (agc_data.size() != out_size) { if (agc_data.capacity() < out_size) { agc_data.reserve(out_size); + agc_am_data.reserve(out_size); resampled_data.reserve(out_size); } agc_data.resize(out_size); resampled_data.resize(out_size); + agc_am_data.resize(out_size); } unsigned int num_written; msresamp_crcf_execute(resampler, &(inp->data[0]), bufSize, &resampled_data[0], &num_written); - agc_crcf_execute_block(agc, &resampled_data[0], num_written, &agc_data[0]); - - float audio_resample_ratio = inp->audio_resample_ratio; + double audio_resample_ratio = inp->audio_resample_ratio; if (demod_output.size() != num_written) { if (demod_output.capacity() < num_written) { @@ -130,9 +144,64 @@ void DemodulatorThread::threadMain() { demod_output.resize(num_written); } - int audio_out_size = ceil((float) (num_written) * audio_resample_ratio); + int audio_out_size = ceil((double) (num_written) * audio_resample_ratio) + 512; - freqdem_demodulate_block(fdem, &agc_data[0], num_written, &demod_output[0]); + agc_crcf_execute_block(agc, &resampled_data[0], num_written, &agc_data[0]); + + float current_level = 0; + + current_level = ((60.0 / fabs(agc_crcf_get_rssi(agc))) / 15.0 - signal_level); + + if (agc_crcf_get_signal_level(agc) > current_level) { + current_level = agc_crcf_get_signal_level(agc); + } + + if (demodulatorType == DEMOD_TYPE_FM) { + freqdem_demodulate_block(fdem, &agc_data[0], num_written, &demod_output[0]); + } else { + float p; + switch (demodulatorType) { + case DEMOD_TYPE_LSB: + for (int i = 0; i < num_written; i++) { // Reject upper band + nco_crcf_mix_up(nco_ssb_shift_up, resampled_data[i], &x); + nco_crcf_step(nco_ssb_shift_up); + firfilt_crcf_push(ssb_fir_filter, x); + firfilt_crcf_execute(ssb_fir_filter, &x); + nco_crcf_mix_down(nco_ssb_shift_down, x, &resampled_data[i]); + nco_crcf_step(nco_ssb_shift_down); + } + break; + case DEMOD_TYPE_USB: + for (int i = 0; i < num_written; i++) { // Reject lower band + nco_crcf_mix_down(nco_ssb_shift_down, resampled_data[i], &x); + nco_crcf_step(nco_ssb_shift_down); + firfilt_crcf_push(ssb_fir_filter, x); + firfilt_crcf_execute(ssb_fir_filter, &x); + nco_crcf_mix_up(nco_ssb_shift_up, x, &resampled_data[i]); + nco_crcf_step(nco_ssb_shift_up); + } + break; + case DEMOD_TYPE_AM: + break; + } + + am_max = 0; + + for (int i = 0; i < num_written; i++) { + ampmodem_demodulate(ampdem_active, resampled_data[i], &demod_output[i]); + if (demod_output[i] > am_max) { + am_max = demod_output[i]; + } + } + am_max_ma = am_max_ma + (am_max - am_max_ma) * 0.05; + am_max_maa = am_max_maa + (am_max_ma - am_max_maa) * 0.05; + + float gain = 0.95 / am_max_maa; + + for (int i = 0; i < num_written; i++) { + demod_output[i] *= gain; + } + } if (audio_out_size != resampled_audio_output.size()) { if (resampled_audio_output.capacity() < audio_out_size) { @@ -152,17 +221,17 @@ void DemodulatorThread::threadMain() { demod_output_stereo.resize(num_written); } - double freq = (2.0 * M_PI) * (((float) abs(38000)) / ((float) inp->bandwidth)); + double freq = (2.0 * M_PI) * (((double) abs(38000)) / ((double) inp->bandwidth)); - if (shift_freq != freq) { - nco_crcf_set_frequency(nco_shift, freq); - shift_freq = freq; + if (nco_stereo_shift_freq != freq) { + nco_crcf_set_frequency(nco_stereo_shift, freq); + nco_stereo_shift_freq = freq; } for (int i = 0; i < num_written; i++) { firhilbf_r2c_execute(firR2C, demod_output[i], &x); - nco_crcf_mix_down(nco_shift, x, &y); - nco_crcf_step(nco_shift); + nco_crcf_mix_down(nco_stereo_shift, x, &y); + nco_crcf_step(nco_stereo_shift); firhilbf_c2r_execute(firC2R, y, &demod_output_stereo[i]); } @@ -176,10 +245,16 @@ void DemodulatorThread::threadMain() { msresamp_rrrf_execute(stereo_resampler, &demod_output_stereo[0], num_written, &resampled_audio_output_stereo[0], &num_audio_written); } + if (current_level > signal_level) { + signal_level = signal_level + (current_level - signal_level) * 0.5; + } else { + signal_level = signal_level + (current_level - signal_level) * 0.05; + } + AudioThreadInput *ati = NULL; if (audioInputQueue != NULL) { - if (!squelch_enabled || ((agc_crcf_get_signal_level(agc)) >= 0.1)) { + if (!squelch_enabled || (signal_level >= squelch_level)) { for (buffers_i = buffers.begin(); buffers_i != buffers.end(); buffers_i++) { if ((*buffers_i)->getRefCount() <= 0) { @@ -235,8 +310,8 @@ void DemodulatorThread::threadMain() { ati_vis->data.resize(stereoSize); for (int i = 0; i < stereoSize / 2; i++) { - ati_vis->data[i] = ati->data[i*2]; - ati_vis->data[i + stereoSize / 2] = ati->data[i*2+1]; + ati_vis->data[i] = ati->data[i * 2]; + ati_vis->data[i + stereoSize / 2] = ati->data[i * 2 + 1]; } } else { ati_vis->channels = 1; @@ -259,6 +334,8 @@ void DemodulatorThread::threadMain() { visOutQueue->push(ati_vis); } if (!threadQueueControl->empty()) { + int newDemodType = DEMOD_TYPE_NULL; + while (!threadQueueControl->empty()) { DemodulatorThreadControlCommand command; threadQueueControl->pop(command); @@ -274,10 +351,30 @@ void DemodulatorThread::threadMain() { squelch_tolerance = 1; squelch_enabled = false; break; + case DemodulatorThreadControlCommand::DEMOD_THREAD_CMD_CTL_TYPE: + newDemodType = command.demodType; + break; default: break; } } + + if (newDemodType != DEMOD_TYPE_NULL) { + switch (newDemodType) { + case DEMOD_TYPE_FM: + break; + case DEMOD_TYPE_LSB: + ampdem_active = ampdem_lsb; + break; + case DEMOD_TYPE_USB: + ampdem_active = ampdem_usb; + break; + case DEMOD_TYPE_AM: + ampdem_active = ampdem; + break; + } + demodulatorType = newDemodType; + } } inp->decRefCount(); @@ -302,7 +399,11 @@ void DemodulatorThread::threadMain() { agc_crcf_destroy(agc); firhilbf_destroy(firR2C); firhilbf_destroy(firC2R); - nco_crcf_destroy(nco_shift); +// firhilbf_destroy(firR2Cssb); +// firhilbf_destroy(firC2Rssb); + nco_crcf_destroy(nco_stereo_shift); + nco_crcf_destroy(nco_ssb_shift_up); + nco_crcf_destroy(nco_ssb_shift_down); while (!buffers.empty()) { AudioThreadInput *audioDataDel = buffers.front(); @@ -330,3 +431,27 @@ void DemodulatorThread::setStereo(bool state) { bool DemodulatorThread::isStereo() { return stereo; } + +float DemodulatorThread::getSignalLevel() { + return signal_level; +} + +void DemodulatorThread::setSquelchLevel(float signal_level_in) { + if (!squelch_enabled) { + squelch_enabled = true; + } + squelch_level = signal_level_in; +} + +float DemodulatorThread::getSquelchLevel() { + return squelch_level; +} + +void DemodulatorThread::setDemodulatorType(int demod_type_in) { + demodulatorType = demod_type_in; +} + +int DemodulatorThread::getDemodulatorType() { + return demodulatorType; +} + diff --git a/src/demod/DemodulatorThread.h b/src/demod/DemodulatorThread.h index a546e55..0e59333 100644 --- a/src/demod/DemodulatorThread.h +++ b/src/demod/DemodulatorThread.h @@ -8,7 +8,7 @@ typedef ThreadQueue DemodulatorThreadOutputQueue; -#define DEMOD_VIS_SIZE 2048 +#define DEMOD_VIS_SIZE 1024 class DemodulatorThread { public: @@ -32,12 +32,18 @@ public: } void initialize(); - void terminate(); void setStereo(bool state); bool isStereo(); + float getSignalLevel(); + void setSquelchLevel(float signal_level_in); + float getSquelchLevel(); + + void setDemodulatorType(int demod_type_in); + int getDemodulatorType(); + #ifdef __APPLE__ static void *pthread_helper(void *context) { return ((DemodulatorThread *) context)->threadMain(); @@ -45,18 +51,41 @@ public: #endif protected: + std::deque buffers; + std::deque::iterator buffers_i; + + std::vector resampled_data; + std::vector agc_data; + std::vector agc_am_data; + std::vector demod_output; + std::vector demod_output_stereo; + std::vector resampled_audio_output; + std::vector resampled_audio_output_stereo; + DemodulatorThreadPostInputQueue* postInputQueue; DemodulatorThreadOutputQueue* visOutQueue; AudioThreadInputQueue *audioInputQueue; freqdem fdem; + ampmodem ampdem_active; + ampmodem ampdem; + ampmodem ampdem_usb; + ampmodem ampdem_lsb; + agc_crcf agc; + float am_max; + float am_max_ma; + float am_max_maa; + std::atomic stereo; std::atomic terminated; + std::atomic demodulatorType; DemodulatorThreadCommandQueue* threadQueueNotify; DemodulatorThreadControlCommandQueue *threadQueueControl; - float squelch_level; - float squelch_tolerance;bool squelch_enabled; + std::atomic squelch_level; + float squelch_tolerance; + std::atomic signal_level; + bool squelch_enabled; }; diff --git a/src/demod/DemodulatorWorkerThread.cpp b/src/demod/DemodulatorWorkerThread.cpp index 1d79c9f..a57f18f 100644 --- a/src/demod/DemodulatorWorkerThread.cpp +++ b/src/demod/DemodulatorWorkerThread.cpp @@ -34,8 +34,8 @@ void DemodulatorWorkerThread::threadMain() { if (filterChanged) { DemodulatorWorkerThreadResult result(DemodulatorWorkerThreadResult::DEMOD_WORKER_THREAD_RESULT_FILTERS); - result.resample_ratio = (float) (filterCommand.bandwidth) / (float) filterCommand.inputRate; - result.audio_resample_ratio = (float) (filterCommand.audioSampleRate) / (float) filterCommand.bandwidth; + result.resample_ratio = (double) (filterCommand.bandwidth) / (double) filterCommand.inputRate; + result.audio_resample_ratio = (double) (filterCommand.audioSampleRate) / (double) filterCommand.bandwidth; float As = 60.0f; // stop-band attenuation [dB] @@ -46,7 +46,6 @@ void DemodulatorWorkerThread::threadMain() { result.audioSampleRate = filterCommand.audioSampleRate; result.bandwidth = filterCommand.bandwidth; result.inputRate = filterCommand.inputRate; - resultQueue->push(result); } diff --git a/src/demod/DemodulatorWorkerThread.h b/src/demod/DemodulatorWorkerThread.h index a51e88d..a45336c 100644 --- a/src/demod/DemodulatorWorkerThread.h +++ b/src/demod/DemodulatorWorkerThread.h @@ -36,10 +36,10 @@ public: DemodulatorThreadResultEnum cmd; msresamp_crcf resampler; - float resample_ratio; + double resample_ratio; msresamp_rrrf audio_resampler; msresamp_rrrf stereo_resampler; - float audio_resample_ratio; + double audio_resample_ratio; unsigned int inputRate; unsigned int bandwidth; diff --git a/src/sdr/SDRPostThread.cpp b/src/sdr/SDRPostThread.cpp index bb93b6d..b269d42 100644 --- a/src/sdr/SDRPostThread.cpp +++ b/src/sdr/SDRPostThread.cpp @@ -6,7 +6,7 @@ #include SDRPostThread::SDRPostThread() : - sample_rate(SRATE), iqDataOutQueue(NULL), iqDataInQueue(NULL), iqVisualQueue(NULL), terminated(false), dcFilter(NULL) { + sample_rate(SRATE), iqDataOutQueue(NULL), iqDataInQueue(NULL), iqVisualQueue(NULL), terminated(false), dcFilter(NULL), num_vis_samples(2048) { } SDRPostThread::~SDRPostThread() { @@ -34,6 +34,14 @@ void SDRPostThread::setIQVisualQueue(DemodulatorThreadInputQueue *iqVisQueue) { iqVisualQueue = iqVisQueue; } +void SDRPostThread::setNumVisSamples(int num_vis_samples_in) { + num_vis_samples = num_vis_samples_in; +} + +int SDRPostThread::getNumVisSamples() { + return num_vis_samples; +} + void SDRPostThread::threadMain() { int n_read; double seconds = 0.0; @@ -89,7 +97,9 @@ void SDRPostThread::threadMain() { if (iqVisualQueue != NULL && iqVisualQueue.load()->empty()) { DemodulatorThreadIQData *visualDataOut = new DemodulatorThreadIQData; - visualDataOut->data.assign(dataOut.begin(), dataOut.begin() + FFT_SIZE); + visualDataOut->frequency = data_in->frequency; + visualDataOut->bandwidth = data_in->bandwidth; + visualDataOut->data.assign(dataOut.begin(), dataOut.begin() + num_vis_samples); iqVisualQueue.load()->push(visualDataOut); } @@ -122,7 +132,7 @@ void SDRPostThread::threadMain() { DemodulatorInstance *demod = *i; if (demod->getParams().frequency != data_in->frequency - && abs(data_in->frequency - demod->getParams().frequency) > (int) ((float) ((float) SRATE / 2.0))) { + && abs(data_in->frequency - demod->getParams().frequency) > (int) ((double) ((double) SRATE / 2.0))) { continue; } activeDemods++; @@ -156,7 +166,7 @@ void SDRPostThread::threadMain() { DemodulatorThreadInputQueue *demodQueue = demod->threadQueueDemod; if (demod->getParams().frequency != data_in->frequency - && abs(data_in->frequency - demod->getParams().frequency) > (int) ((float) ((float) SRATE / 2.0))) { + && abs(data_in->frequency - demod->getParams().frequency) > (int) ((double) ((double) SRATE / 2.0))) { if (demod->isActive()) { demod->setActive(false); DemodulatorThreadIQData *dummyDataOut = new DemodulatorThreadIQData; diff --git a/src/sdr/SDRPostThread.h b/src/sdr/SDRPostThread.h index e70f082..7b222a7 100644 --- a/src/sdr/SDRPostThread.h +++ b/src/sdr/SDRPostThread.h @@ -15,6 +15,9 @@ public: void setIQDataOutQueue(DemodulatorThreadInputQueue* iqDataQueue); void setIQVisualQueue(DemodulatorThreadInputQueue* iqVisQueue); + void setNumVisSamples(int num_vis_samples_in); + int getNumVisSamples(); + void threadMain(); void terminate(); @@ -30,4 +33,5 @@ protected: std::vector demodulators_remove; std::atomic terminated; iirfilt_crcf dcFilter; + int num_vis_samples; }; diff --git a/src/util/GLFont.cpp b/src/util/GLFont.cpp index 4fa2bed..88c7b64 100644 --- a/src/util/GLFont.cpp +++ b/src/util/GLFont.cpp @@ -382,7 +382,7 @@ void GLFont::drawString(std::string str, float xpos, float ypos, int pxHeight, A glPushMatrix(); glTranslatef(xpos, ypos, 0.0f); - switch (hAlign) { + switch (vAlign) { case GLFONT_ALIGN_TOP: glTranslatef(0.0, -size, 0.0); break; @@ -393,7 +393,7 @@ void GLFont::drawString(std::string str, float xpos, float ypos, int pxHeight, A break; } - switch (vAlign) { + switch (hAlign) { case GLFONT_ALIGN_RIGHT: glTranslatef(-msgWidth, 0.0, 0.0); break; @@ -445,5 +445,6 @@ void GLFont::drawString(std::string str, float xpos, float ypos, int pxHeight, A glPopMatrix(); glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); } diff --git a/src/util/MouseTracker.cpp b/src/util/MouseTracker.cpp index 59819ed..905ca0d 100644 --- a/src/util/MouseTracker.cpp +++ b/src/util/MouseTracker.cpp @@ -9,7 +9,7 @@ void MouseTracker::OnMouseMoved(wxMouseEvent& event) { const wxSize ClientSize = target->GetClientSize(); mouseX = (float) event.m_x / (float) ClientSize.x; - mouseY = (float) event.m_y / (float) ClientSize.y; + mouseY = 1.0 - (float) event.m_y / (float) ClientSize.y; deltaMouseX = mouseX - lastMouseX; deltaMouseY = mouseY - lastMouseY; @@ -17,11 +17,11 @@ void MouseTracker::OnMouseMoved(wxMouseEvent& event) { if (isMouseDown) { #ifndef __APPLE__ if (horizDragLock && vertDragLock) { - target->WarpPointer(originMouseX * ClientSize.x, originMouseY * ClientSize.y); + target->WarpPointer(originMouseX * ClientSize.x, (1.0-originMouseY) * ClientSize.y); mouseX = originMouseX; mouseY = originMouseY; } else if (vertDragLock && mouseY != lastMouseY) { - target->WarpPointer(event.m_x, originMouseY * ClientSize.y); + target->WarpPointer(event.m_x, (1.0-originMouseY) * ClientSize.y); mouseY = originMouseY; } else if (horizDragLock && mouseX != lastMouseX) { target->WarpPointer(originMouseX * ClientSize.x, event.m_y); @@ -42,7 +42,7 @@ void MouseTracker::OnMouseDown(wxMouseEvent& event) { const wxSize ClientSize = target->GetClientSize(); mouseX = lastMouseX = (float) event.m_x / (float) ClientSize.x; - mouseY = lastMouseY = (float) event.m_y / (float) ClientSize.y; + mouseY = lastMouseY = 1.0 - (float) event.m_y / (float) ClientSize.y; originMouseX = mouseX; originMouseY = mouseY; diff --git a/src/visual/InteractiveCanvas.cpp b/src/visual/InteractiveCanvas.cpp new file mode 100644 index 0000000..a68709e --- /dev/null +++ b/src/visual/InteractiveCanvas.cpp @@ -0,0 +1,133 @@ +#include "InteractiveCanvas.h" + +#include "wx/wxprec.h" + +#ifndef WX_PRECOMP +#include "wx/wx.h" +#endif + +#if !wxUSE_GLCANVAS +#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library" +#endif + +#include "CubicSDR.h" +#include "CubicSDRDefs.h" +#include "AppFrame.h" +#include + +#include + +InteractiveCanvas::InteractiveCanvas(wxWindow *parent, int *attribList) : + wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, + wxFULL_REPAINT_ON_RESIZE), parent(parent), shiftDown(false), altDown(false), ctrlDown(false), center_freq(0), bandwidth(0), last_bandwidth(0), isView( + false) { + mTracker.setTarget(this); +} + +InteractiveCanvas::~InteractiveCanvas() { +} + +void InteractiveCanvas::SetView(int center_freq_in, int bandwidth_in) { + isView = true; + center_freq = center_freq_in; + bandwidth = bandwidth_in; + last_bandwidth = 0; +} + +void InteractiveCanvas::DisableView() { + isView = false; + center_freq = wxGetApp().getFrequency(); + bandwidth = SRATE; + last_bandwidth = 0; +} + +int InteractiveCanvas::GetFrequencyAt(float x) { + int iqCenterFreq = GetCenterFrequency(); + int iqBandwidth = GetBandwidth(); + int freq = iqCenterFreq - (int) (0.5 * (float) iqBandwidth) + (int) ((float) x * (float) iqBandwidth); + + return freq; +} + +void InteractiveCanvas::SetCenterFrequency(unsigned int center_freq_in) { + center_freq = center_freq_in; +} + +unsigned int InteractiveCanvas::GetCenterFrequency() { + if (isView) { + return center_freq; + } else { + return (unsigned int) wxGetApp().getFrequency(); + } +} + +void InteractiveCanvas::SetBandwidth(unsigned int bandwidth_in) { + bandwidth = bandwidth_in; +} + +unsigned int InteractiveCanvas::GetBandwidth() { + if (isView) { + return bandwidth; + } else { + return SRATE; + } +} + +void InteractiveCanvas::OnKeyUp(wxKeyEvent& event) { + shiftDown = event.ShiftDown(); + altDown = event.AltDown(); + ctrlDown = event.ControlDown(); +} + +void InteractiveCanvas::OnKeyDown(wxKeyEvent& event) { + float angle = 5.0; + + shiftDown = event.ShiftDown(); + altDown = event.AltDown(); + ctrlDown = event.ControlDown(); +} + +void InteractiveCanvas::mouseMoved(wxMouseEvent& event) { + mTracker.OnMouseMoved(event); + + shiftDown = event.ShiftDown(); + altDown = event.AltDown(); + ctrlDown = event.ControlDown(); +} + +void InteractiveCanvas::mouseDown(wxMouseEvent& event) { + mTracker.OnMouseDown(event); + + shiftDown = event.ShiftDown(); + altDown = event.AltDown(); + ctrlDown = event.ControlDown(); +} + +void InteractiveCanvas::mouseWheelMoved(wxMouseEvent& event) { + mTracker.OnMouseWheelMoved(event); +} + +void InteractiveCanvas::mouseReleased(wxMouseEvent& event) { + mTracker.OnMouseReleased(event); + + shiftDown = event.ShiftDown(); + altDown = event.AltDown(); + ctrlDown = event.ControlDown(); +} + +void InteractiveCanvas::mouseLeftWindow(wxMouseEvent& event) { + mTracker.OnMouseLeftWindow(event); +} + +void InteractiveCanvas::mouseEnterWindow(wxMouseEvent& event) { + mTracker.OnMouseEnterWindow(event); +} + +void InteractiveCanvas::setStatusText(std::string statusText) { + ((wxFrame*) parent)->GetStatusBar()->SetStatusText(statusText); +} + +void InteractiveCanvas::setStatusText(std::string statusText, int value) { + ((wxFrame*) parent)->GetStatusBar()->SetStatusText( + wxString::Format(statusText.c_str(), wxNumberFormatter::ToString((long) value, wxNumberFormatter::Style_WithThousandsSep))); +} diff --git a/src/visual/InteractiveCanvas.h b/src/visual/InteractiveCanvas.h new file mode 100644 index 0000000..ac84802 --- /dev/null +++ b/src/visual/InteractiveCanvas.h @@ -0,0 +1,52 @@ +#pragma once + +#include "wx/glcanvas.h" +#include "wx/timer.h" + +#include "MouseTracker.h" +#include + +class InteractiveCanvas: public wxGLCanvas { +public: + InteractiveCanvas(wxWindow *parent, int *attribList = NULL); + ~InteractiveCanvas(); + + int GetFrequencyAt(float x); + + void SetView(int center_freq_in, int bandwidth_in); + void DisableView(); + + void SetCenterFrequency(unsigned int center_freq_in); + unsigned int GetCenterFrequency(); + + void SetBandwidth(unsigned int bandwidth_in); + unsigned int GetBandwidth(); + +protected: + void OnKeyDown(wxKeyEvent& event); + void OnKeyUp(wxKeyEvent& event); + + void mouseMoved(wxMouseEvent& event); + void mouseDown(wxMouseEvent& event); + void mouseWheelMoved(wxMouseEvent& event); + void mouseReleased(wxMouseEvent& event); + void mouseEnterWindow(wxMouseEvent& event); + void mouseLeftWindow(wxMouseEvent& event); + + void setStatusText(std::string statusText); + void setStatusText(std::string statusText, int value); + + wxWindow *parent; + MouseTracker mTracker; + + bool shiftDown; + bool altDown; + bool ctrlDown; + + unsigned int center_freq; + unsigned int bandwidth; + unsigned int last_bandwidth; + + bool isView; +}; + diff --git a/src/visual/MeterCanvas.cpp b/src/visual/MeterCanvas.cpp new file mode 100644 index 0000000..61989b9 --- /dev/null +++ b/src/visual/MeterCanvas.cpp @@ -0,0 +1,131 @@ +#include "MeterCanvas.h" + +#include "wx/wxprec.h" + +#ifndef WX_PRECOMP +#include "wx/wx.h" +#endif + +#if !wxUSE_GLCANVAS +#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library" +#endif + +#include "CubicSDR.h" +#include "CubicSDRDefs.h" +#include "AppFrame.h" +#include + +wxBEGIN_EVENT_TABLE(MeterCanvas, wxGLCanvas) EVT_PAINT(MeterCanvas::OnPaint) +EVT_IDLE(MeterCanvas::OnIdle) +EVT_MOTION(MeterCanvas::mouseMoved) +EVT_LEFT_DOWN(MeterCanvas::mouseDown) +EVT_LEFT_UP(MeterCanvas::mouseReleased) +EVT_LEAVE_WINDOW(MeterCanvas::mouseLeftWindow) +EVT_ENTER_WINDOW(MeterCanvas::mouseEnterWindow) +wxEND_EVENT_TABLE() + +MeterCanvas::MeterCanvas(wxWindow *parent, int *attribList) : + wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, + wxFULL_REPAINT_ON_RESIZE), parent(parent), level(0), level_max(1), inputValue(0), userInputValue(0), shiftDown(false), altDown(false), ctrlDown(false) { + + glContext = new MeterContext(this, &wxGetApp().GetContext(this)); + mTracker.setTarget(this); +} + +MeterCanvas::~MeterCanvas() { + +} + +void MeterCanvas::setLevel(float level_in) { + level = level_in; +} +float MeterCanvas::getLevel() { + return level; +} + +void MeterCanvas::setMax(float max_in) { + level_max = max_in; +} + +bool MeterCanvas::setInputValue(float slider_in) { + userInputValue = inputValue = slider_in; +} +bool MeterCanvas::inputChanged() { + return (inputValue != userInputValue); +} +float MeterCanvas::getInputValue() { + inputValue = userInputValue; + return userInputValue; +} + +void MeterCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { + wxPaintDC dc(this); + const wxSize ClientSize = GetClientSize(); + + glContext->SetCurrent(*this); + glViewport(0, 0, ClientSize.x, ClientSize.y); + + glContext->DrawBegin(); + if (mTracker.mouseInView()) { + glContext->Draw(0.4, 0.4, 0.4, 0.5, mTracker.getMouseY()); + } + + glContext->Draw(0.1, 0.75, 0.1, 0.5, level/level_max); + + glContext->Draw(0.75, 0.1, 0.1, 0.5, userInputValue/level_max); + + glContext->DrawEnd(); + + SwapBuffers(); +} + +void MeterCanvas::OnIdle(wxIdleEvent &event) { + Refresh(false); +} + +void MeterCanvas::mouseMoved(wxMouseEvent& event) { + mTracker.OnMouseMoved(event); + + shiftDown = event.ShiftDown(); + altDown = event.AltDown(); + ctrlDown = event.ControlDown(); + + if (mTracker.mouseDown()) { + userInputValue = mTracker.getMouseY()*level_max; + } +} + +void MeterCanvas::mouseDown(wxMouseEvent& event) { + mTracker.OnMouseDown(event); + + shiftDown = event.ShiftDown(); + altDown = event.AltDown(); + ctrlDown = event.ControlDown(); + + userInputValue = mTracker.getMouseY()*level_max; + mTracker.setHorizDragLock(true); +} + +void MeterCanvas::mouseWheelMoved(wxMouseEvent& event) { + mTracker.OnMouseWheelMoved(event); +} + +void MeterCanvas::mouseReleased(wxMouseEvent& event) { + mTracker.OnMouseReleased(event); + + shiftDown = event.ShiftDown(); + altDown = event.AltDown(); + ctrlDown = event.ControlDown(); + + userInputValue = mTracker.getMouseY()*level_max; +} + +void MeterCanvas::mouseLeftWindow(wxMouseEvent& event) { + mTracker.OnMouseLeftWindow(event); + SetCursor(wxCURSOR_CROSS); +} + +void MeterCanvas::mouseEnterWindow(wxMouseEvent& event) { + mTracker.OnMouseEnterWindow(event); + SetCursor(wxCURSOR_CROSS); +} diff --git a/src/visual/MeterCanvas.h b/src/visual/MeterCanvas.h new file mode 100644 index 0000000..67da7e5 --- /dev/null +++ b/src/visual/MeterCanvas.h @@ -0,0 +1,57 @@ +#pragma once + +#include "wx/glcanvas.h" +#include "wx/timer.h" + +#include +#include + +#include "MeterContext.h" +#include "MouseTracker.h" + +#include "fftw3.h" +#include "Timer.h" + +class MeterCanvas: public wxGLCanvas { +public: + std::vector waveform_points; + + MeterCanvas(wxWindow *parent, int *attribList = NULL); + ~MeterCanvas(); + + void setLevel(float level_in); + float getLevel(); + + void setMax(float max_in); + + bool setInputValue(float slider_in); + bool inputChanged(); + float getInputValue(); + +private: + void OnPaint(wxPaintEvent& event); + void OnIdle(wxIdleEvent &event); + + void mouseMoved(wxMouseEvent& event); + void mouseDown(wxMouseEvent& event); + void mouseWheelMoved(wxMouseEvent& event); + void mouseReleased(wxMouseEvent& event); + void mouseEnterWindow(wxMouseEvent& event); + void mouseLeftWindow(wxMouseEvent& event); + + MouseTracker mTracker; + wxWindow *parent; + MeterContext *glContext; + + float level; + float level_max; + + float inputValue; + float userInputValue; + + bool shiftDown; + bool altDown; + bool ctrlDown; +wxDECLARE_EVENT_TABLE(); +}; + diff --git a/src/visual/MeterContext.cpp b/src/visual/MeterContext.cpp new file mode 100644 index 0000000..40fe4fd --- /dev/null +++ b/src/visual/MeterContext.cpp @@ -0,0 +1,40 @@ +#include "MeterContext.h" +#include "MeterCanvas.h" + +MeterContext::MeterContext(MeterCanvas *canvas, wxGLContext *sharedContext) : + PrimaryGLContext(canvas, sharedContext) { + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); +} + +void MeterContext::DrawBegin() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glDisable(GL_TEXTURE_2D); +} + +void MeterContext::Draw(float r, float g, float b, float a, float level) { + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + glColor4f(r, g, b, a); + glBegin(GL_QUADS); + glVertex2f(1.0, -1.0 + 2.0 * level); + glVertex2f(-1.0, -1.0 + 2.0 * level); + glVertex2f(-1.0, -1.0); + glVertex2f(1.0, -1.0); + glEnd(); + glDisable(GL_BLEND); +} + +void MeterContext::DrawEnd() { + glFlush(); + + CheckGLError(); +} + diff --git a/src/visual/MeterContext.h b/src/visual/MeterContext.h new file mode 100644 index 0000000..3fac2f2 --- /dev/null +++ b/src/visual/MeterContext.h @@ -0,0 +1,19 @@ +#pragma once + +#include "PrimaryGLContext.h" +#include "Gradient.h" + +#define NUM_WATERFALL_LINES 512 + +class MeterCanvas; + +class MeterContext: public PrimaryGLContext { +public: + MeterContext(MeterCanvas *canvas, wxGLContext *sharedContext); + + void DrawBegin(); + void Draw(float r, float g, float b, float a, float level); + void DrawEnd(); + +private: +}; diff --git a/src/visual/PrimaryGLContext.cpp b/src/visual/PrimaryGLContext.cpp index 88fd3b8..90404cc 100644 --- a/src/visual/PrimaryGLContext.cpp +++ b/src/visual/PrimaryGLContext.cpp @@ -92,7 +92,7 @@ GLFont &PrimaryGLContext::getFont(GLFontSize esize) { return fonts[esize]; } -void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, float r, float g, float b) { +void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, float r, float g, float b, int center_freq, int srate) { if (!demod) { return; } @@ -103,7 +103,11 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, float r, float float viewHeight = (float) vp[3]; float viewWidth = (float) vp[2]; - float uxPos = (float) (demod->getParams().frequency - (wxGetApp().getFrequency() - SRATE / 2)) / (float) SRATE; + if (center_freq == -1) { + center_freq = wxGetApp().getFrequency(); + } + + float uxPos = (float) (demod->getParams().frequency - (center_freq - srate / 2)) / (float) srate; uxPos = (uxPos - 0.5) * 2.0; glDisable(GL_TEXTURE_2D); @@ -112,7 +116,7 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, float r, float glBlendFunc(GL_SRC_ALPHA, GL_DST_COLOR); glColor4f(r, g, b, 0.6); - float ofs = ((float) demod->getParams().bandwidth) / (float) SRATE; + float ofs = ((float) demod->getParams().bandwidth) / (float) srate; glBlendFunc(GL_SRC_ALPHA, GL_DST_COLOR); glColor4f(r, g, b, 0.2); @@ -149,12 +153,16 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, float r, float } -void PrimaryGLContext::DrawDemod(DemodulatorInstance *demod, float r, float g, float b) { +void PrimaryGLContext::DrawDemod(DemodulatorInstance *demod, float r, float g, float b, int center_freq, int srate) { if (!demod) { return; } - float uxPos = (float) (demod->getParams().frequency - (wxGetApp().getFrequency() - SRATE / 2)) / (float) SRATE; + if (center_freq == -1) { + center_freq = wxGetApp().getFrequency(); + } + + float uxPos = (float) (demod->getParams().frequency - (center_freq - srate / 2)) / (float) srate; glDisable(GL_DEPTH_TEST); glDisable(GL_TEXTURE_2D); @@ -167,7 +175,7 @@ void PrimaryGLContext::DrawDemod(DemodulatorInstance *demod, float r, float g, f glVertex3f((uxPos - 0.5) * 2.0, 1.0, 0.0); glVertex3f((uxPos - 0.5) * 2.0, -1.0, 0.0); - float ofs = ((float) demod->getParams().bandwidth) / (float) SRATE; + float ofs = ((float) demod->getParams().bandwidth) / (float) srate; glVertex3f((uxPos - 0.5) * 2.0 - ofs, 1.0, 0.0); glVertex3f((uxPos - 0.5) * 2.0 - ofs, -1.0, 0.0); @@ -191,7 +199,7 @@ void PrimaryGLContext::DrawDemod(DemodulatorInstance *demod, float r, float g, f glEnable(GL_DEPTH_TEST); } -void PrimaryGLContext::DrawFreqSelector(float uxPos, float r, float g, float b, float w) { +void PrimaryGLContext::DrawFreqSelector(float uxPos, float r, float g, float b, float w, int center_freq, int srate) { DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator(); int bw = 0; @@ -218,7 +226,7 @@ void PrimaryGLContext::DrawFreqSelector(float uxPos, float r, float g, float b, if (w) { ofs = w; } else { - ofs = ((float) bw) / (float) SRATE; + ofs = ((float) bw) / (float) srate; } glVertex3f((uxPos - 0.5) * 2.0 - ofs, 1.0, 0.0); diff --git a/src/visual/PrimaryGLContext.h b/src/visual/PrimaryGLContext.h index 43a4167..9635548 100644 --- a/src/visual/PrimaryGLContext.h +++ b/src/visual/PrimaryGLContext.h @@ -23,9 +23,9 @@ public: void BeginDraw(); void EndDraw(); - void DrawFreqSelector(float uxPos, float r = 1, float g = 1, float b = 1, float w = 0); - void DrawDemod(DemodulatorInstance *demod, float r = 1, float g = 1, float b = 1); - void DrawDemodInfo(DemodulatorInstance *demod, float r = 1, float g = 1, float b = 1); + void DrawFreqSelector(float uxPos, float r = 1, float g = 1, float b = 1, float w = 0, int center_freq = -1, int srate = SRATE); + void DrawDemod(DemodulatorInstance *demod, float r = 1, float g = 1, float b = 1, int center_freq = -1, int srate = SRATE); + void DrawDemodInfo(DemodulatorInstance *demod, float r = 1, float g = 1, float b = 1, int center_freq = -1, int srate = SRATE); static GLFont &getFont(GLFontSize esize); diff --git a/src/visual/ScopeCanvas.cpp b/src/visual/ScopeCanvas.cpp index 76071ed..ef04240 100644 --- a/src/visual/ScopeCanvas.cpp +++ b/src/visual/ScopeCanvas.cpp @@ -21,10 +21,9 @@ wxEND_EVENT_TABLE() ScopeCanvas::ScopeCanvas(wxWindow *parent, int *attribList) : wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, - wxFULL_REPAINT_ON_RESIZE), parent(parent), frameTimer(0), divider(false) { + wxFULL_REPAINT_ON_RESIZE), parent(parent), stereo(false) { glContext = new ScopeContext(this, &wxGetApp().GetContext(this)); - timer.start(); } ScopeCanvas::~ScopeCanvas() { @@ -35,8 +34,13 @@ void ScopeCanvas::setWaveformPoints(std::vector &waveform_points_in) { waveform_points = waveform_points_in; } -void ScopeCanvas::setDivider(bool state) { - divider = state; +void ScopeCanvas::setStereo(bool state) { + stereo = state; +} + +void ScopeCanvas::setDeviceName(std::string device_name) { + deviceName = device_name; + deviceName.append(" "); } void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { @@ -47,20 +51,16 @@ void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { glViewport(0, 0, ClientSize.x, ClientSize.y); glContext->DrawBegin(); - glContext->Plot(waveform_points); - if (divider) { - glContext->DrawDivider(); + if (!deviceName.empty()) { + glContext->DrawDeviceName(deviceName); } + glContext->Plot(waveform_points, stereo); glContext->DrawEnd(); + SwapBuffers(); } void ScopeCanvas::OnIdle(wxIdleEvent &event) { -// timer.update(); -// frameTimer += timer.lastUpdateSeconds(); -// if (frameTimer > 1.0/30.0) { Refresh(false); -// frameTimer = 0; -// } } diff --git a/src/visual/ScopeCanvas.h b/src/visual/ScopeCanvas.h index 26ace11..94f3328 100644 --- a/src/visual/ScopeCanvas.h +++ b/src/visual/ScopeCanvas.h @@ -7,9 +7,7 @@ #include #include "ScopeContext.h" - #include "fftw3.h" -#include "Timer.h" class ScopeCanvas: public wxGLCanvas { public: @@ -19,7 +17,8 @@ public: ~ScopeCanvas(); void setWaveformPoints(std::vector &waveform_points_in); - void setDivider(bool state); + void setStereo(bool state); + void setDeviceName(std::string device_name); private: void OnPaint(wxPaintEvent& event); @@ -28,9 +27,8 @@ private: wxWindow *parent; ScopeContext *glContext; - Timer timer; - float frameTimer; - bool divider; + std::string deviceName; + bool stereo; // event table wxDECLARE_EVENT_TABLE(); }; diff --git a/src/visual/ScopeContext.cpp b/src/visual/ScopeContext.cpp index 5c1badd..15d43ef 100644 --- a/src/visual/ScopeContext.cpp +++ b/src/visual/ScopeContext.cpp @@ -4,35 +4,84 @@ ScopeContext::ScopeContext(ScopeCanvas *canvas, wxGLContext *sharedContext) : PrimaryGLContext(canvas, sharedContext) { - glDisable (GL_CULL_FACE); - glDisable (GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); - glMatrixMode (GL_PROJECTION); + glMatrixMode(GL_PROJECTION); glLoadIdentity(); } void ScopeContext::DrawBegin() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glMatrixMode (GL_MODELVIEW); + glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - glDisable (GL_TEXTURE_2D); + glDisable(GL_TEXTURE_2D); } -void ScopeContext::Plot(std::vector &points) { +void ScopeContext::Plot(std::vector &points, bool stereo) { glColor3f(1.0, 1.0, 1.0); - if (points.size()) { - glPushMatrix(); - glTranslatef(-1.0f, 0.0f, 0.0f); - glScalef(2.0f, 2.0f, 1.0f); - glEnableClientState (GL_VERTEX_ARRAY); - glVertexPointer(2, GL_FLOAT, 0, &points[0]); - glDrawArrays(GL_LINE_STRIP, 0, points.size() / 2); - glDisableClientState(GL_VERTEX_ARRAY); - glPopMatrix(); + if (stereo) { + glColor3f(0.7, 0.7, 0.7); + glBegin(GL_LINES); + glVertex2f(-1.0, 0.0); + glVertex2f(1.0, 0.0); + glEnd(); + glColor3f(0.3, 0.3, 0.3); + glBegin(GL_LINES); + glVertex2f(-1.0, 0.5); + glVertex2f(1.0, 0.5); + glVertex2f(-1.0, -0.5); + glVertex2f(1.0, -0.5); + glEnd(); + } else { + glColor3f(0.3, 0.3, 0.3); + glBegin(GL_LINES); + glVertex2f(-1.0, 0.0); + glVertex2f(1.0, 0.0); + glEnd(); } + + glColor3f(0.9, 0.9, 0.9); + if (points.size()) { + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, GL_FLOAT, 0, &points[0]); + if (stereo) { + glPushMatrix(); + glTranslatef(-1.0f, 0.5f, 0.0f); + glScalef(4.0f, 0.92f, 1.0f); + glDrawArrays(GL_LINE_STRIP, 0, points.size() / 4); + glPopMatrix(); + + glPushMatrix(); + glTranslatef(-3.0f, -0.5f, 0.0f); + glPushMatrix(); + glScalef(4.0f, 0.92f, 1.0f); + glDrawArrays(GL_LINE_STRIP, points.size() / 4, points.size() / 4); + glPopMatrix(); + glPopMatrix(); + } else { + glPushMatrix(); + glTranslatef(-1.0f, 0.0f, 0.0f); + glScalef(2.0f, 2.0f, 1.0f); + glDrawArrays(GL_LINE_STRIP, 0, points.size() / 2); + glPopMatrix(); + } + glDisableClientState(GL_VERTEX_ARRAY); + + } +} + +void ScopeContext::DrawDeviceName(std::string deviceName) { + GLint vp[4]; + glGetIntegerv( GL_VIEWPORT, vp); + float viewHeight = (float) vp[3]; + float hPos = (float) (viewHeight - 20) / viewHeight; + + glColor3f(0.65,0.65,0.65); + getFont(PrimaryGLContext::GLFONT_SIZE12).drawString(deviceName.c_str(), 1.0, hPos, 12, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER); } void ScopeContext::DrawEnd() { @@ -44,7 +93,7 @@ void ScopeContext::DrawEnd() { void ScopeContext::DrawDivider() { glColor3f(1.0, 1.0, 1.0); glBegin(GL_LINES); - glVertex2f(0.0,-1.0); - glVertex2f(0.0,1.0); + glVertex2f(0.0, -1.0); + glVertex2f(0.0, 1.0); glEnd(); } diff --git a/src/visual/ScopeContext.h b/src/visual/ScopeContext.h index 6f001b0..d4fa1a0 100644 --- a/src/visual/ScopeContext.h +++ b/src/visual/ScopeContext.h @@ -12,7 +12,8 @@ public: ScopeContext(ScopeCanvas *canvas, wxGLContext *sharedContext); void DrawBegin(); - void Plot(std::vector &points); + void Plot(std::vector &points, bool stereo=false); + void DrawDeviceName(std::string deviceName); void DrawDivider(); void DrawEnd(); diff --git a/src/visual/SpectrumCanvas.cpp b/src/visual/SpectrumCanvas.cpp index fab9c59..be837c2 100644 --- a/src/visual/SpectrumCanvas.cpp +++ b/src/visual/SpectrumCanvas.cpp @@ -15,6 +15,7 @@ #include "AppFrame.h" #include #include +#include "WaterfallCanvas.h" wxBEGIN_EVENT_TABLE(SpectrumCanvas, wxGLCanvas) EVT_PAINT(SpectrumCanvas::OnPaint) EVT_IDLE(SpectrumCanvas::OnIdle) @@ -26,28 +27,40 @@ EVT_MOUSEWHEEL(SpectrumCanvas::mouseWheelMoved) wxEND_EVENT_TABLE() SpectrumCanvas::SpectrumCanvas(wxWindow *parent, int *attribList) : - wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, - wxFULL_REPAINT_ON_RESIZE), parent(parent), frameTimer(0) { - - int in_block_size = FFT_SIZE; - int out_block_size = FFT_SIZE; - - in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * in_block_size); - out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * out_block_size); - plan = fftw_plan_dft_1d(out_block_size, in, out, FFTW_FORWARD, FFTW_MEASURE); - - fft_ceil_ma = fft_ceil_maa = 100.0; - fft_floor_ma = fft_floor_maa = 0.0; + 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) { glContext = new SpectrumContext(this, &wxGetApp().GetContext(this)); - timer.start(); - mTracker.setTarget(this); mTracker.setVertDragLock(true); SetCursor(wxCURSOR_SIZEWE); } +void SpectrumCanvas::Setup(int fft_size_in) { + if (fft_size == fft_size_in) { + return; + } + + fft_size = fft_size_in; + + if (in) { + free(in); + } + in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * fft_size); + if (out) { + free(out); + } + out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * fft_size); + if (plan) { + fftw_destroy_plan(plan); + } + plan = fftw_plan_dft_1d(fft_size, in, out, FFTW_FORWARD, FFTW_MEASURE); + + fft_ceil_ma = fft_ceil_maa = 100.0; + fft_floor_ma = fft_floor_maa = 0.0; +} + SpectrumCanvas::~SpectrumCanvas() { } @@ -60,12 +73,12 @@ void SpectrumCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { glViewport(0, 0, ClientSize.x, ClientSize.y); glContext->BeginDraw(); - glContext->Draw(spectrum_points); + glContext->Draw(spectrum_points, GetCenterFrequency(), GetBandwidth()); std::vector &demods = wxGetApp().getDemodMgr().getDemodulators(); for (int i = 0, iMax = demods.size(); i < iMax; i++) { - glContext->DrawDemodInfo(demods[i]); + glContext->DrawDemodInfo(demods[i], 1, 1, 1, GetCenterFrequency(), GetBandwidth()); } glContext->EndDraw(); @@ -73,14 +86,23 @@ void SpectrumCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { SwapBuffers(); } -void SpectrumCanvas::setData(std::vector *data) { - +void SpectrumCanvas::setData(DemodulatorThreadIQData *input) { + if (!input) { + return; + } + std::vector *data = &input->data; if (data && data->size()) { - if (spectrum_points.size() < FFT_SIZE * 2) { - spectrum_points.resize(FFT_SIZE * 2); + if (fft_size != data->size()) { + Setup(data->size()); + } + if (spectrum_points.size() < fft_size * 2) { + if (spectrum_points.capacity() < fft_size * 2) { + spectrum_points.reserve(fft_size * 2); + } + spectrum_points.resize(fft_size * 2); } - for (int i = 0; i < FFT_SIZE; i++) { + for (int i = 0; i < fft_size; i++) { in[i][0] = (*data)[i].real; in[i][1] = (*data)[i].imag; } @@ -89,31 +111,33 @@ void SpectrumCanvas::setData(std::vector *data) { double fft_ceil = 0, fft_floor = 1; - if (fft_result.size() < FFT_SIZE) { - fft_result.resize(FFT_SIZE); - fft_result_ma.resize(FFT_SIZE); - fft_result_maa.resize(FFT_SIZE); + if (fft_result.size() != fft_size) { + if (fft_result.capacity() < fft_size) { + fft_result.reserve(fft_size); + fft_result_ma.reserve(fft_size); + fft_result_maa.reserve(fft_size); + } + fft_result.resize(fft_size); + fft_result_ma.resize(fft_size); + fft_result_maa.resize(fft_size); } int n; - for (int i = 0, iMax = FFT_SIZE / 2; i < iMax; i++) { + for (int i = 0, iMax = fft_size / 2; i < iMax; i++) { n = (i == 0) ? 1 : i; double a = out[n][0]; double b = out[n][1]; double c = sqrt(a * a + b * b); -// n = (i == FFT_SIZE / 2) ? (FFT_SIZE / 2 + 1) : i; - double x = out[FFT_SIZE / 2 + n][0]; - double y = out[FFT_SIZE / 2 + n][1]; + double x = out[fft_size / 2 + n][0]; + double y = out[fft_size / 2 + n][1]; double z = sqrt(x * x + y * y); fft_result[i] = (z); - fft_result[FFT_SIZE / 2 + i] = (c); + fft_result[fft_size / 2 + i] = (c); } - float time_slice = (float) SRATE / (float) (BUF_SIZE / 2); - - for (int i = 0, iMax = FFT_SIZE; i < iMax; i++) { + for (int i = 0, iMax = fft_size; i < iMax; i++) { fft_result_maa[i] += (fft_result_ma[i] - fft_result_maa[i]) * 0.65; fft_result_ma[i] += (fft_result[i] - fft_result_ma[i]) * 0.65; @@ -136,7 +160,7 @@ void SpectrumCanvas::setData(std::vector *data) { // fftw_execute(plan[1]); - for (int i = 0, iMax = FFT_SIZE; i < iMax; i++) { + for (int i = 0, iMax = fft_size; i < iMax; i++) { float v = (log10(fft_result_maa[i] - fft_floor_maa) / log10(fft_ceil_maa - fft_floor_maa)); spectrum_points[i * 2] = ((float) i / (float) iMax); spectrum_points[i * 2 + 1] = v; @@ -146,51 +170,66 @@ void SpectrumCanvas::setData(std::vector *data) { } void SpectrumCanvas::OnIdle(wxIdleEvent &event) { -// timer.update(); -// frameTimer += timer.lastUpdateSeconds(); -// if (frameTimer > 1.0/30.0) { Refresh(false); -// frameTimer = 0; -// } } void SpectrumCanvas::mouseMoved(wxMouseEvent& event) { - mTracker.OnMouseMoved(event); + InteractiveCanvas::mouseMoved(event); if (mTracker.mouseDown()) { - int freqChange = mTracker.getDeltaMouseX() * SRATE; + int freqChange = mTracker.getDeltaMouseX() * GetBandwidth(); if (freqChange != 0) { int freq = wxGetApp().getFrequency(); - freq -= freqChange; - wxGetApp().setFrequency(freq); - ((wxFrame*) parent)->GetStatusBar()->SetStatusText( - wxString::Format(wxT("Set center frequency: %s"), - wxNumberFormatter::ToString((long) freq, wxNumberFormatter::Style_WithThousandsSep))); + if (isView) { + center_freq = center_freq - freqChange; + if (waterfallCanvas) { + waterfallCanvas->SetCenterFrequency(center_freq); + } + + int bw = (int) bandwidth; + int bwOfs = ((int) center_freq > freq) ? ((int) bandwidth / 2) : (-(int) bandwidth / 2); + int freqEdge = ((int) center_freq + bwOfs); + + if (abs(freq - freqEdge) > (SRATE / 2)) { + freqChange = -(((int) center_freq > freq) ? (freqEdge - freq - (SRATE / 2)) : (freqEdge - freq + (SRATE / 2))); + } else { + freqChange = 0; + } + } + + if (freqChange) { + freq -= freqChange; + wxGetApp().setFrequency(freq); + setStatusText("Set center frequency: %s", freq); + } + } + } else { + setStatusText("Click and drag to adjust center frequency."); } } void SpectrumCanvas::mouseDown(wxMouseEvent& event) { - mTracker.OnMouseDown(event); + InteractiveCanvas::mouseDown(event); SetCursor(wxCURSOR_CROSS); } void SpectrumCanvas::mouseWheelMoved(wxMouseEvent& event) { - mTracker.OnMouseWheelMoved(event); + InteractiveCanvas::mouseWheelMoved(event); } void SpectrumCanvas::mouseReleased(wxMouseEvent& event) { - mTracker.OnMouseReleased(event); + InteractiveCanvas::mouseReleased(event); SetCursor(wxCURSOR_SIZEWE); } void SpectrumCanvas::mouseLeftWindow(wxMouseEvent& event) { - mTracker.OnMouseLeftWindow(event); + InteractiveCanvas::mouseLeftWindow(event); SetCursor(wxCURSOR_SIZEWE); } -//void SpectrumCanvas::rightClick(wxMouseEvent& event) {} -//void SpectrumCanvas::keyPressed(wxKeyEvent& event) {} -//void SpectrumCanvas::keyReleased(wxKeyEvent& event) {} +void SpectrumCanvas::attachWaterfallCanvas(WaterfallCanvas* canvas_in) { + waterfallCanvas = canvas_in; +} diff --git a/src/visual/SpectrumCanvas.h b/src/visual/SpectrumCanvas.h index d02f4fe..6a5cfe8 100644 --- a/src/visual/SpectrumCanvas.h +++ b/src/visual/SpectrumCanvas.h @@ -6,18 +6,25 @@ #include #include +#include "InteractiveCanvas.h" #include "SpectrumContext.h" #include "fftw3.h" -#include "Timer.h" #include "MouseTracker.h" -class SpectrumCanvas: public wxGLCanvas { +class WaterfallCanvas; + +class SpectrumCanvas: public InteractiveCanvas { public: + std::vector spectrum_points; + SpectrumCanvas(wxWindow *parent, int *attribList = NULL); + void Setup(int fft_size_in); ~SpectrumCanvas(); - void setData(std::vector *data); + void setData(DemodulatorThreadIQData *input); + void attachWaterfallCanvas(WaterfallCanvas *canvas_in); + private: void OnPaint(wxPaintEvent& event); @@ -31,24 +38,19 @@ private: // void rightClick(wxMouseEvent& event); void mouseLeftWindow(wxMouseEvent& event); - wxWindow *parent; - std::vector spectrum_points; - fftw_complex *in, *out; fftw_plan plan; - float fft_ceil_ma, fft_ceil_maa; - float fft_floor_ma, fft_floor_maa; + double fft_ceil_ma, fft_ceil_maa; + double fft_floor_ma, fft_floor_maa; - std::vector fft_result; - std::vector fft_result_ma; - std::vector fft_result_maa; + std::vector fft_result; + std::vector fft_result_ma; + std::vector fft_result_maa; SpectrumContext *glContext; - Timer timer; - float frameTimer; - - MouseTracker mTracker; + WaterfallCanvas *waterfallCanvas; + int fft_size; // event table wxDECLARE_EVENT_TABLE(); }; diff --git a/src/visual/SpectrumContext.cpp b/src/visual/SpectrumContext.cpp index 8d4993e..2a5207a 100644 --- a/src/visual/SpectrumContext.cpp +++ b/src/visual/SpectrumContext.cpp @@ -6,7 +6,7 @@ #include SpectrumContext::SpectrumContext(SpectrumCanvas *canvas, wxGLContext *sharedContext) : - PrimaryGLContext(canvas, sharedContext) { + PrimaryGLContext(canvas, sharedContext), fft_size(0) { glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); @@ -15,7 +15,7 @@ SpectrumContext::SpectrumContext(SpectrumCanvas *canvas, wxGLContext *sharedCont } -void SpectrumContext::Draw(std::vector &points) { +void SpectrumContext::Draw(std::vector &points, int freq, int bandwidth) { glDisable(GL_TEXTURE_2D); glColor3f(1.0, 1.0, 1.0); @@ -35,9 +35,10 @@ void SpectrumContext::Draw(std::vector &points) { glGetIntegerv( GL_VIEWPORT, vp); float viewHeight = (float) vp[3]; + float viewWidth = (float) vp[2]; - float leftFreq = (float) wxGetApp().getFrequency() - ((float) SRATE / 2.0); - float rightFreq = leftFreq + (float) SRATE; + float leftFreq = (float) freq - ((float) bandwidth / 2.0); + float rightFreq = leftFreq + (float) bandwidth; float firstMhz = floor(leftFreq / 1000000.0) * 1000000.0; float mhzStart = ((firstMhz - leftFreq) / (rightFreq - leftFreq)) * 2.0; diff --git a/src/visual/SpectrumContext.h b/src/visual/SpectrumContext.h index be5a62f..91c900c 100644 --- a/src/visual/SpectrumContext.h +++ b/src/visual/SpectrumContext.h @@ -11,7 +11,8 @@ class SpectrumContext: public PrimaryGLContext { public: SpectrumContext(SpectrumCanvas *canvas, wxGLContext *sharedContext); - void Draw(std::vector &points); + void Draw(std::vector &points, int freq, int bandwidth); private: + int fft_size; }; diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp index 597ecee..36bcd17 100644 --- a/src/visual/WaterfallCanvas.cpp +++ b/src/visual/WaterfallCanvas.cpp @@ -17,6 +17,8 @@ #include +#define MIN_BANDWIDTH 1500 + wxBEGIN_EVENT_TABLE(WaterfallCanvas, wxGLCanvas) EVT_PAINT(WaterfallCanvas::OnPaint) EVT_KEY_DOWN(WaterfallCanvas::OnKeyDown) EVT_KEY_UP(WaterfallCanvas::OnKeyUp) @@ -29,37 +31,58 @@ EVT_ENTER_WINDOW(WaterfallCanvas::mouseEnterWindow) wxEND_EVENT_TABLE() WaterfallCanvas::WaterfallCanvas(wxWindow *parent, int *attribList) : - wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, - wxFULL_REPAINT_ON_RESIZE), parent(parent), frameTimer(0), activeDemodulatorBandwidth(0), activeDemodulatorFrequency(0), dragState( - WF_DRAG_NONE), nextDragState(WF_DRAG_NONE), shiftDown(false), altDown(false), ctrlDown(false) { + InteractiveCanvas(parent, attribList), spectrumCanvas(NULL), activeDemodulatorBandwidth(0), activeDemodulatorFrequency(0), dragState( + WF_DRAG_NONE), nextDragState(WF_DRAG_NONE), fft_size(0), waterfall_lines(0), plan( + NULL), in(NULL), out(NULL), resampler(NULL), resample_ratio(0), last_input_bandwidth(0), zoom(0) { - int in_block_size = FFT_SIZE; - int out_block_size = FFT_SIZE; + glContext = new WaterfallContext(this, &wxGetApp().GetContext(this)); - in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * in_block_size); - out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * out_block_size); - plan = fftw_plan_dft_1d(out_block_size, in, out, FFTW_FORWARD, FFTW_MEASURE); + nco_shift = nco_crcf_create(LIQUID_NCO); + shift_freq = 0; fft_ceil_ma = fft_ceil_maa = 100.0; fft_floor_ma = fft_floor_maa = 0.0; - glContext = new WaterfallContext(this, &wxGetApp().GetContext(this)); - timer.start(); - - mTracker.setTarget(this); SetCursor(wxCURSOR_CROSS); } WaterfallCanvas::~WaterfallCanvas() { - + nco_crcf_destroy(nco_shift); } -int WaterfallCanvas::GetFrequencyAt(float x) { +void WaterfallCanvas::Setup(int fft_size_in, int waterfall_lines_in) { + if (fft_size == fft_size_in && waterfall_lines_in == waterfall_lines) { + return; + } + fft_size = fft_size_in; + waterfall_lines = waterfall_lines_in; - int center_freq = wxGetApp().getFrequency(); - int freq = center_freq - (int) (0.5 * (float) SRATE) + (int) ((float) x * (float) SRATE); + if (in) { + free(in); + } + in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * fft_size); + if (out) { + free(out); + } + out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * fft_size); + if (plan) { + fftw_destroy_plan(plan); + } + plan = fftw_plan_dft_1d(fft_size, in, out, FFTW_FORWARD, FFTW_ESTIMATE); - return freq; + glContext->Setup(fft_size, waterfall_lines); +} + +WaterfallCanvas::DragState WaterfallCanvas::getDragState() { + return dragState; +} + +WaterfallCanvas::DragState WaterfallCanvas::getNextDragState() { + return nextDragState; +} + +void WaterfallCanvas::attachSpectrumCanvas(SpectrumCanvas *canvas_in) { + spectrumCanvas = canvas_in; } void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { @@ -80,6 +103,9 @@ void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { bool isNew = shiftDown || (wxGetApp().getDemodMgr().getLastActiveDemodulator() && !wxGetApp().getDemodMgr().getLastActiveDemodulator()->isActive()); + int currentBandwidth = GetBandwidth(); + int currentCenterFreq = GetCenterFrequency(); + if (mTracker.mouseInView()) { if (nextDragState == WF_DRAG_RANGE) { if (mTracker.mouseDown()) { @@ -87,47 +113,49 @@ void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { float centerPos = mTracker.getOriginMouseX() + width / 2.0; if (isNew) { - glContext->DrawDemod(lastActiveDemodulator); - glContext->DrawFreqSelector(centerPos, 0, 1, 0, width ? width : (1.0 / (float) ClientSize.x)); + glContext->DrawDemod(lastActiveDemodulator, 1, 1, 1, currentCenterFreq, currentBandwidth); + glContext->DrawFreqSelector(centerPos, 0, 1, 0, width ? width : (1.0 / (float) ClientSize.x), currentCenterFreq, + currentBandwidth); } else { - glContext->DrawDemod(lastActiveDemodulator, 1, 0, 0); - glContext->DrawFreqSelector(centerPos, 1, 1, 0, width ? width : (1.0 / (float) ClientSize.x)); + glContext->DrawDemod(lastActiveDemodulator, 1, 0, 0, currentCenterFreq, currentBandwidth); + glContext->DrawFreqSelector(centerPos, 1, 1, 0, width ? width : (1.0 / (float) ClientSize.x), currentCenterFreq, + currentBandwidth); } } else { if (isNew) { - glContext->DrawDemod(lastActiveDemodulator); - glContext->DrawFreqSelector(mTracker.getMouseX(), 0, 1, 0, 1.0 / (float) ClientSize.x); + glContext->DrawDemod(lastActiveDemodulator, 1, 1, 1, currentCenterFreq, currentBandwidth); + glContext->DrawFreqSelector(mTracker.getMouseX(), 0, 1, 0, 1.0 / (float) ClientSize.x, currentCenterFreq, currentBandwidth); } else { - glContext->DrawDemod(lastActiveDemodulator, 1, 0, 0); - glContext->DrawFreqSelector(mTracker.getMouseX(), 1, 1, 0, 1.0 / (float) ClientSize.x); + glContext->DrawDemod(lastActiveDemodulator, 1, 0, 0, currentCenterFreq, currentBandwidth); + glContext->DrawFreqSelector(mTracker.getMouseX(), 1, 1, 0, 1.0 / (float) ClientSize.x, currentCenterFreq, currentBandwidth); } } } else { if (activeDemodulator == NULL) { if (lastActiveDemodulator) { if (isNew) { - glContext->DrawDemod(lastActiveDemodulator); - glContext->DrawFreqSelector(mTracker.getMouseX(), 0, 1, 0); + glContext->DrawDemod(lastActiveDemodulator, 1, 1, 1, currentCenterFreq, currentBandwidth); + glContext->DrawFreqSelector(mTracker.getMouseX(), 0, 1, 0, 0, currentCenterFreq, currentBandwidth); } else { - glContext->DrawDemod(lastActiveDemodulator, 1, 0, 0); - glContext->DrawFreqSelector(mTracker.getMouseX(), 1, 1, 0); + glContext->DrawDemod(lastActiveDemodulator, 1, 0, 0, currentCenterFreq, currentBandwidth); + glContext->DrawFreqSelector(mTracker.getMouseX(), 1, 1, 0, 0, currentCenterFreq, currentBandwidth); } } else { - glContext->DrawFreqSelector(mTracker.getMouseX(), 1, 1, 0); + glContext->DrawFreqSelector(mTracker.getMouseX(), 1, 1, 0, 0, currentCenterFreq, currentBandwidth); } } else { if (lastActiveDemodulator) { - glContext->DrawDemod(lastActiveDemodulator); + glContext->DrawDemod(lastActiveDemodulator, 1, 1, 1, currentCenterFreq, currentBandwidth); } - glContext->DrawDemod(activeDemodulator, 1, 1, 0); + glContext->DrawDemod(activeDemodulator, 1, 1, 0, currentCenterFreq, currentBandwidth); } } } else { if (activeDemodulator) { - glContext->DrawDemod(activeDemodulator); + glContext->DrawDemod(activeDemodulator, 1, 1, 1, currentCenterFreq, currentBandwidth); } if (lastActiveDemodulator) { - glContext->DrawDemod(lastActiveDemodulator); + glContext->DrawDemod(lastActiveDemodulator, 1, 1, 1, currentCenterFreq, currentBandwidth); } } @@ -135,7 +163,7 @@ void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { if (activeDemodulator == demods[i] || lastActiveDemodulator == demods[i]) { continue; } - glContext->DrawDemod(demods[i]); + glContext->DrawDemod(demods[i], 1, 1, 1, currentCenterFreq, currentBandwidth); } glContext->EndDraw(); @@ -144,43 +172,78 @@ void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { } void WaterfallCanvas::OnKeyUp(wxKeyEvent& event) { + InteractiveCanvas::OnKeyUp(event); shiftDown = event.ShiftDown(); altDown = event.AltDown(); ctrlDown = event.ControlDown(); -// switch (event.GetKeyCode()) { -// } + switch (event.GetKeyCode()) { + case 'A': + zoom = 0; + break; + case 'Z': + zoom = 0; + break; + } } void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) { + InteractiveCanvas::OnKeyDown(event); float angle = 5.0; - shiftDown = event.ShiftDown(); - altDown = event.AltDown(); - ctrlDown = event.ControlDown(); - DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getActiveDemodulator(); unsigned int freq; + unsigned int bw; switch (event.GetKeyCode()) { + case 'A': + zoom = 1; + break; + case 'Z': + zoom = -1; + break; case WXK_RIGHT: freq = wxGetApp().getFrequency(); if (shiftDown) { freq += SRATE * 10; + if (isView) { + SetView(center_freq + SRATE * 10, GetBandwidth()); + if (spectrumCanvas) { + spectrumCanvas->SetView(GetCenterFrequency(), GetBandwidth()); + } + } } else { freq += SRATE / 2; + if (isView) { + SetView(center_freq + SRATE / 2, GetBandwidth()); + if (spectrumCanvas) { + spectrumCanvas->SetView(GetCenterFrequency(), GetBandwidth()); + } + } } wxGetApp().setFrequency(freq); - ((wxFrame*) parent)->GetStatusBar()->SetStatusText(wxString::Format(wxT("Set center frequency: %i"), freq)); + setStatusText("Set center frequency: %s", freq); break; case WXK_LEFT: freq = wxGetApp().getFrequency(); if (shiftDown) { freq -= SRATE * 10; + if (isView) { + SetView(center_freq - SRATE * 10, GetBandwidth()); + if (spectrumCanvas) { + spectrumCanvas->SetView(GetCenterFrequency(), GetBandwidth()); + } + } } else { freq -= SRATE / 2; + if (isView) { + SetView(center_freq - SRATE / 2, GetBandwidth()); + if (spectrumCanvas) { + spectrumCanvas->SetView(GetCenterFrequency(), GetBandwidth()); + } + } } wxGetApp().setFrequency(freq); - ((wxFrame*) parent)->GetStatusBar()->SetStatusText(wxString::Format(wxT("Set center frequency: %i"), freq)); + setStatusText("Set center frequency: %s", freq); break; case 'D': case WXK_DELETE: @@ -201,64 +264,222 @@ void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) { } break; case WXK_SPACE: - if (!activeDemod) { - break; - } - if (activeDemod->isStereo()) { - activeDemod->setStereo(false); - } else { - activeDemod->setStereo(true); - } - break; + if (!activeDemod) { + break; + } + if (activeDemod->isStereo()) { + activeDemod->setStereo(false); + } else { + activeDemod->setStereo(true); + } + break; default: event.Skip(); return; } } -void WaterfallCanvas::setData(std::vector *data) { +void WaterfallCanvas::setData(DemodulatorThreadIQData *input) { + if (!input) { + return; + } + + unsigned int bw; + if (zoom) { + int freq = wxGetApp().getFrequency(); + + if (zoom > 0) { + center_freq = GetCenterFrequency(); + bw = GetBandwidth(); + bw = (unsigned int) ceil((float) bw * 0.95); + if (bw < 80000) { + bw = 80000; + } + if (mTracker.mouseInView()) { + int mfreqA = GetFrequencyAt(mTracker.getMouseX()); + SetBandwidth(bw); + int mfreqB = GetFrequencyAt(mTracker.getMouseX()); + center_freq += mfreqA - mfreqB; + } + + SetView(center_freq, bw); + if (spectrumCanvas) { + spectrumCanvas->SetView(center_freq, bw); + } + } else { + if (isView) { + bw = GetBandwidth(); + bw = (unsigned int) ceil((float) bw * 1.05); + if ((int) bw >= SRATE) { + bw = (unsigned int) SRATE; + DisableView(); + if (spectrumCanvas) { + spectrumCanvas->DisableView(); + } + } else { + if (mTracker.mouseInView()) { + int freq = wxGetApp().getFrequency(); + int mfreqA = GetFrequencyAt(mTracker.getMouseX()); + SetBandwidth(bw); + int mfreqB = GetFrequencyAt(mTracker.getMouseX()); + center_freq += mfreqA - mfreqB; + } + + SetView(GetCenterFrequency(), bw); + if (spectrumCanvas) { + spectrumCanvas->SetView(center_freq, bw); + } + } + } + } + if (center_freq < freq && (center_freq - bandwidth / 2) < (freq - SRATE / 2)) { + center_freq = (freq - SRATE / 2) + bandwidth / 2; + } + if (center_freq > freq && (center_freq + bandwidth / 2) > (freq + SRATE / 2)) { + center_freq = (freq + SRATE / 2) - bandwidth / 2; + } + } + + std::vector *data = &input->data; if (data && data->size()) { - if (spectrum_points.size() < FFT_SIZE * 2) { - spectrum_points.resize(FFT_SIZE * 2); +// if (fft_size != data->size() && !isView) { +// Setup(data->size(), waterfall_lines); +// } + +// if (last_bandwidth != bandwidth && !isView) { +// Setup(bandwidth, waterfall_lines); +// } + + if (spectrum_points.size() < fft_size * 2) { + spectrum_points.resize(fft_size * 2); } - for (int i = 0; i < FFT_SIZE; i++) { - in[i][0] = (*data)[i].real; - in[i][1] = (*data)[i].imag; + if (isView) { + if (!input->frequency || !input->bandwidth) { + return; + } + + if (center_freq != input->frequency) { + if (((int) center_freq - (int) input->frequency) != shift_freq || last_input_bandwidth != input->bandwidth) { + if ((int) input->frequency - abs((int) center_freq) < (int) ((float) ((float) SRATE / 2.0))) { + shift_freq = (int) center_freq - (int) input->frequency; + nco_crcf_reset(nco_shift); + nco_crcf_set_frequency(nco_shift, (2.0 * M_PI) * (((float) abs(shift_freq)) / ((float) input->bandwidth))); + } + } + + if (shift_buffer.size() != input->data.size()) { + if (shift_buffer.capacity() < input->data.size()) { + shift_buffer.reserve(input->data.size()); + } + shift_buffer.resize(input->data.size()); + } + + if (shift_freq < 0) { + nco_crcf_mix_block_up(nco_shift, &input->data[0], &shift_buffer[0], input->data.size()); + } else { + nco_crcf_mix_block_down(nco_shift, &input->data[0], &shift_buffer[0], input->data.size()); + } + } else { + shift_buffer.assign(input->data.begin(), input->data.end()); + } + + if (!resampler || bandwidth != last_bandwidth || last_input_bandwidth != input->bandwidth) { + resample_ratio = (double) (bandwidth) / (double) input->bandwidth; + + float As = 60.0f; + + if (resampler) { + msresamp_crcf_destroy(resampler); + } + resampler = msresamp_crcf_create(resample_ratio, As); + + last_bandwidth = bandwidth; + last_input_bandwidth = input->bandwidth; + } + + int out_size = ceil((double) (input->data.size()) * resample_ratio) + 512; + + if (resampler_buffer.size() != out_size) { + if (resampler_buffer.capacity() < out_size) { + resampler_buffer.reserve(out_size); + } + resampler_buffer.resize(out_size); + } + + unsigned int num_written; + msresamp_crcf_execute(resampler, &shift_buffer[0], input->data.size(), &resampler_buffer[0], &num_written); + + resampler_buffer.resize(fft_size); + + if (num_written < fft_size) { + for (int i = 0; i < num_written; i++) { + in[i][0] = resampler_buffer[i].real; + in[i][1] = resampler_buffer[i].imag; + } + for (int i = num_written; i < fft_size; i++) { + in[i][0] = 0; + in[i][1] = 0; + } + } else { + for (int i = 0; i < fft_size; i++) { + in[i][0] = resampler_buffer[i].real; + in[i][1] = resampler_buffer[i].imag; + } + } + } else { + + if (data->size() < fft_size) { + for (int i = 0, iMax = data->size(); i < iMax; i++) { + in[i][0] = (*data)[i].real; + in[i][1] = (*data)[i].imag; + } + for (int i = data->size(); i < fft_size; i++) { + in[i][0] = 0; + in[i][1] = 0; + } + } else { + for (int i = 0; i < fft_size; i++) { + in[i][0] = (*data)[i].real; + in[i][1] = (*data)[i].imag; + } + } } fftw_execute(plan); double fft_ceil = 0, fft_floor = 1; - if (fft_result.size() < FFT_SIZE) { - fft_result.resize(FFT_SIZE); - fft_result_ma.resize(FFT_SIZE); - fft_result_maa.resize(FFT_SIZE); + if (fft_result.size() < fft_size) { + fft_result.resize(fft_size); + fft_result_ma.resize(fft_size); + fft_result_maa.resize(fft_size); } int n; - for (int i = 0, iMax = FFT_SIZE / 2; i < iMax; i++) { + for (int i = 0, iMax = fft_size / 2; i < iMax; i++) { n = (i == 0) ? 1 : i; double a = out[n][0]; double b = out[n][1]; double c = sqrt(a * a + b * b); -// n = (i == FFT_SIZE / 2) ? (FFT_SIZE / 2 + 1) : i; - double x = out[FFT_SIZE / 2 + n][0]; - double y = out[FFT_SIZE / 2 + n][1]; + double x = out[fft_size / 2 + n][0]; + double y = out[fft_size / 2 + n][1]; double z = sqrt(x * x + y * y); fft_result[i] = (z); - fft_result[FFT_SIZE / 2 + i] = (c); + fft_result[fft_size / 2 + i] = (c); } - float time_slice = (float) SRATE / (float) (BUF_SIZE / 2); - - for (int i = 0, iMax = FFT_SIZE; i < iMax; i++) { - fft_result_maa[i] += (fft_result_ma[i] - fft_result_maa[i]) * 0.65; - fft_result_ma[i] += (fft_result[i] - fft_result_ma[i]) * 0.65; + for (int i = 0, iMax = fft_size; i < iMax; i++) { + if (isView) { + fft_result_maa[i] += (fft_result_ma[i] - fft_result_maa[i]) * 0.85; + fft_result_ma[i] += (fft_result[i] - fft_result_ma[i]) * 0.55; + } else { + fft_result_maa[i] += (fft_result_ma[i] - fft_result_maa[i]) * 0.65; + fft_result_ma[i] += (fft_result[i] - fft_result_ma[i]) * 0.65; + } if (fft_result_maa[i] > fft_ceil) { fft_ceil = fft_result_maa[i]; @@ -271,37 +492,30 @@ void WaterfallCanvas::setData(std::vector *data) { fft_ceil += 1; fft_floor -= 1; - fft_ceil_ma = fft_ceil_ma + (fft_ceil - fft_ceil_ma) * 0.01; - fft_ceil_maa = fft_ceil_maa + (fft_ceil_ma - fft_ceil_maa) * 0.01; + fft_ceil_ma = fft_ceil_ma + (fft_ceil - fft_ceil_ma) * 0.05; + fft_ceil_maa = fft_ceil_maa + (fft_ceil_ma - fft_ceil_maa) * 0.05; - fft_floor_ma = fft_floor_ma + (fft_floor - fft_floor_ma) * 0.01; - fft_floor_maa = fft_floor_maa + (fft_floor_ma - fft_floor_maa) * 0.01; + fft_floor_ma = fft_floor_ma + (fft_floor - fft_floor_ma) * 0.05; + fft_floor_maa = fft_floor_maa + (fft_floor_ma - fft_floor_maa) * 0.05; - for (int i = 0, iMax = FFT_SIZE; i < iMax; i++) { - float v = (log10(fft_result_maa[i] - fft_floor_maa) / log10(fft_ceil_maa - fft_floor_maa)); + for (int i = 0, iMax = fft_size; i < iMax; i++) { + double v = (log10(fft_result_maa[i] - fft_floor_maa) / log10(fft_ceil_maa - fft_floor_maa)); spectrum_points[i * 2] = ((float) i / (float) iMax); spectrum_points[i * 2 + 1] = v; } + if (spectrumCanvas) { + spectrumCanvas->spectrum_points.assign(spectrum_points.begin(), spectrum_points.end()); + } } } void WaterfallCanvas::OnIdle(wxIdleEvent &event) { -// timer.update(); -// frameTimer += timer.lastUpdateSeconds(); -// if (frameTimer > 1.0/30.0) { Refresh(false); -// frameTimer = 0; -// } } void WaterfallCanvas::mouseMoved(wxMouseEvent& event) { - mTracker.OnMouseMoved(event); - - shiftDown = event.ShiftDown(); - altDown = event.AltDown(); - ctrlDown = event.ControlDown(); - + InteractiveCanvas::mouseMoved(event); DemodulatorInstance *demod = wxGetApp().getDemodMgr().getActiveDemodulator(); if (mTracker.mouseDown()) { @@ -310,7 +524,7 @@ void WaterfallCanvas::mouseMoved(wxMouseEvent& event) { } if (dragState == WF_DRAG_BANDWIDTH_LEFT || dragState == WF_DRAG_BANDWIDTH_RIGHT) { - int bwDiff = (int) (mTracker.getDeltaMouseX() * (float) SRATE) * 2; + int bwDiff = (int) (mTracker.getDeltaMouseX() * (float) GetBandwidth()) * 2; if (dragState == WF_DRAG_BANDWIDTH_LEFT) { bwDiff = -bwDiff; @@ -323,19 +537,20 @@ void WaterfallCanvas::mouseMoved(wxMouseEvent& event) { DemodulatorThreadCommand command; command.cmd = DemodulatorThreadCommand::DEMOD_THREAD_CMD_SET_BANDWIDTH; activeDemodulatorBandwidth = activeDemodulatorBandwidth + bwDiff; - if (activeDemodulatorBandwidth < 2000) { - activeDemodulatorBandwidth = 2000; - } if (activeDemodulatorBandwidth > SRATE) { activeDemodulatorBandwidth = SRATE; } + if (activeDemodulatorBandwidth < MIN_BANDWIDTH) { + activeDemodulatorBandwidth = MIN_BANDWIDTH; + } command.int_value = activeDemodulatorBandwidth; demod->getCommandQueue()->push(command); + setStatusText("Set demodulator bandwidth: %s", activeDemodulatorBandwidth); } if (dragState == WF_DRAG_FREQUENCY) { - int bwDiff = (int) (mTracker.getDeltaMouseX() * (float) SRATE); + int bwDiff = (int) (mTracker.getDeltaMouseX() * (float) GetBandwidth()); if (!activeDemodulatorFrequency) { activeDemodulatorFrequency = demod->getParams().frequency; @@ -349,6 +564,8 @@ void WaterfallCanvas::mouseMoved(wxMouseEvent& event) { demod->getCommandQueue()->push(command); demod->updateLabel(activeDemodulatorFrequency); + + setStatusText("Set demodulator frequency: %s", activeDemodulatorFrequency); } } else { int freqPos = GetFrequencyAt(mTracker.getMouseX()); @@ -361,9 +578,14 @@ void WaterfallCanvas::mouseMoved(wxMouseEvent& event) { nextDragState = WF_DRAG_RANGE; mTracker.setVertDragLock(true); mTracker.setHorizDragLock(false); + if (shiftDown) { + setStatusText("Click and drag to create a new demodulator by range."); + } else { + setStatusText("Click and drag to set the current demodulator range."); + } } else if (demodsHover->size()) { int hovered = -1; - int near_dist = SRATE; + int near_dist = GetBandwidth(); DemodulatorInstance *activeDemodulator = NULL; @@ -407,16 +629,23 @@ void WaterfallCanvas::mouseMoved(wxMouseEvent& event) { mTracker.setVertDragLock(true); mTracker.setHorizDragLock(false); + setStatusText("Click and drag to change demodulator bandwidth. D to delete, SPACE for stereo."); } else { SetCursor(wxCURSOR_SIZING); nextDragState = WF_DRAG_FREQUENCY; mTracker.setVertDragLock(true); mTracker.setHorizDragLock(false); + setStatusText("Click and drag to change demodulator frequency. D to delete, SPACE for stereo."); } } else { SetCursor(wxCURSOR_CROSS); nextDragState = WF_DRAG_NONE; + if (shiftDown) { + setStatusText("Click to create a new demodulator or hold ALT to drag range."); + } else { + setStatusText("Click to move active demodulator frequency or hold ALT to drag range; hold SHIFT to create new. A / Z to Zoom. Arrow keys (+SHIFT) to move center frequency."); + } } delete demodsHover; @@ -424,12 +653,9 @@ void WaterfallCanvas::mouseMoved(wxMouseEvent& event) { } void WaterfallCanvas::mouseDown(wxMouseEvent& event) { - mTracker.OnMouseDown(event); - dragState = nextDragState; + InteractiveCanvas::mouseDown(event); - shiftDown = event.ShiftDown(); - altDown = event.AltDown(); - ctrlDown = event.ControlDown(); + dragState = nextDragState; if (dragState && dragState != WF_DRAG_RANGE) { wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getActiveDemodulator(), false); @@ -440,15 +666,11 @@ void WaterfallCanvas::mouseDown(wxMouseEvent& event) { } void WaterfallCanvas::mouseWheelMoved(wxMouseEvent& event) { - mTracker.OnMouseWheelMoved(event); + InteractiveCanvas::mouseWheelMoved(event); } void WaterfallCanvas::mouseReleased(wxMouseEvent& event) { - mTracker.OnMouseReleased(event); - - shiftDown = event.ShiftDown(); - altDown = event.AltDown(); - ctrlDown = event.ControlDown(); + InteractiveCanvas::mouseReleased(event); bool isNew = shiftDown || (wxGetApp().getDemodMgr().getLastActiveDemodulator() && !wxGetApp().getDemodMgr().getLastActiveDemodulator()->isActive()); @@ -460,8 +682,8 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) { if (mTracker.getOriginDeltaMouseX() == 0 && mTracker.getOriginDeltaMouseY() == 0) { float pos = mTracker.getMouseX(); - int center_freq = wxGetApp().getFrequency(); - int freq = center_freq - (int) (0.5 * (float) SRATE) + (int) ((float) pos * (float) SRATE); + int input_center_freq = GetCenterFrequency(); + int freq = input_center_freq - (int) (0.5 * (float) GetBandwidth()) + (int) ((float) pos * (float) GetBandwidth()); if (dragState == WF_DRAG_NONE) { if (!isNew && wxGetApp().getDemodMgr().getDemodulators().size()) { @@ -472,6 +694,10 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) { if (DemodulatorInstance *last = wxGetApp().getDemodMgr().getLastActiveDemodulator()) { demod->getParams().bandwidth = last->getParams().bandwidth; + demod->setDemodulatorType(last->getDemodulatorType()); + demod->setSquelchLevel(last->getSquelchLevel()); + demod->setSquelchEnabled(last->isSquelchEnabled()); + demod->setStereo(last->isStereo()); } demod->run(); @@ -492,9 +718,7 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) { command.int_value = freq; demod->getCommandQueue()->push(command); - ((wxFrame*) parent)->GetStatusBar()->SetStatusText( - wxString::Format(wxT("Set demodulator frequency: %s"), - wxNumberFormatter::ToString((long) freq, wxNumberFormatter::Style_WithThousandsSep))); + setStatusText("New demodulator at frequency: %s", freq); wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getLastActiveDemodulator(), false); SetCursor(wxCURSOR_SIZING); @@ -502,10 +726,6 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) { mTracker.setVertDragLock(true); mTracker.setHorizDragLock(false); } else { - float pos = mTracker.getMouseX(); - int center_freq = wxGetApp().getFrequency(); - int freq = center_freq - (int) (0.5 * (float) SRATE) + (int) ((float) pos * (float) SRATE); - wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getActiveDemodulator(), false); nextDragState = WF_DRAG_FREQUENCY; } @@ -513,15 +733,15 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) { float width = mTracker.getOriginDeltaMouseX(); float pos = mTracker.getOriginMouseX() + width / 2.0; - int center_freq = wxGetApp().getFrequency(); - int freq = center_freq - (int) (0.5 * (float) SRATE) + (int) ((float) pos * (float) SRATE); - int bandwidth = (int) (fabs(width) * (float) SRATE); + int input_center_freq = GetCenterFrequency(); + unsigned int freq = input_center_freq - (int) (0.5 * (float) GetBandwidth()) + (int) ((float) pos * (float) GetBandwidth()); + unsigned int bw = (unsigned int) (fabs(width) * (float) GetBandwidth()); - if (bandwidth < 2000) { - bandwidth = 2000; + if (bw < MIN_BANDWIDTH) { + bw = MIN_BANDWIDTH; } - if (!bandwidth) { + if (!bw) { dragState = WF_DRAG_NONE; return; } @@ -531,8 +751,13 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) { } else { demod = wxGetApp().getDemodMgr().newThread(); demod->getParams().frequency = freq; - demod->getParams().bandwidth = bandwidth; - + demod->getParams().bandwidth = bw; + if (DemodulatorInstance *last = wxGetApp().getDemodMgr().getLastActiveDemodulator()) { + demod->setDemodulatorType(last->getDemodulatorType()); + demod->setSquelchLevel(last->getSquelchLevel()); + demod->setSquelchEnabled(last->isSquelchEnabled()); + demod->setStereo(last->isStereo()); + } demod->run(); wxGetApp().bindDemodulator(demod); @@ -544,9 +769,7 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) { return; } - ((wxFrame*) parent)->GetStatusBar()->SetStatusText( - wxString::Format(wxT("Set demodulator frequency: %s"), - wxNumberFormatter::ToString((long) freq, wxNumberFormatter::Style_WithThousandsSep))); + setStatusText("New demodulator at frequency: %s", freq); wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getLastActiveDemodulator(), false); demod->updateLabel(freq); @@ -556,7 +779,7 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) { command.int_value = freq; demod->getCommandQueue()->push(command); command.cmd = DemodulatorThreadCommand::DEMOD_THREAD_CMD_SET_BANDWIDTH; - command.int_value = bandwidth; + command.int_value = bw; demod->getCommandQueue()->push(command); } @@ -564,12 +787,13 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) { } void WaterfallCanvas::mouseLeftWindow(wxMouseEvent& event) { - mTracker.OnMouseLeftWindow(event); + InteractiveCanvas::mouseLeftWindow(event); SetCursor(wxCURSOR_CROSS); wxGetApp().getDemodMgr().setActiveDemodulator(NULL); } void WaterfallCanvas::mouseEnterWindow(wxMouseEvent& event) { - mTracker.OnMouseEnterWindow(event); + InteractiveCanvas::mouseEnterWindow(event); SetCursor(wxCURSOR_CROSS); } + diff --git a/src/visual/WaterfallCanvas.h b/src/visual/WaterfallCanvas.h index e320a73..1264103 100644 --- a/src/visual/WaterfallCanvas.h +++ b/src/visual/WaterfallCanvas.h @@ -6,23 +6,29 @@ #include #include +#include "InteractiveCanvas.h" #include "WaterfallContext.h" #include "MouseTracker.h" +#include "SpectrumCanvas.h" #include "fftw3.h" -#include "Timer.h" -class WaterfallCanvas: public wxGLCanvas { +class WaterfallCanvas: public InteractiveCanvas { public: enum DragState { WF_DRAG_NONE, WF_DRAG_BANDWIDTH_LEFT, WF_DRAG_BANDWIDTH_RIGHT, WF_DRAG_FREQUENCY, WF_DRAG_RANGE }; WaterfallCanvas(wxWindow *parent, int *attribList = NULL); + void Setup(int fft_size_in, int waterfall_lines_in); ~WaterfallCanvas(); - void setData(std::vector *data); - int GetFrequencyAt(float x); + void setData(DemodulatorThreadIQData *input); + + DragState getDragState(); + DragState getNextDragState(); + + void attachSpectrumCanvas(SpectrumCanvas *canvas_in); private: void OnPaint(wxPaintEvent& event); @@ -38,23 +44,20 @@ private: void mouseEnterWindow(wxMouseEvent& event); void mouseLeftWindow(wxMouseEvent& event); - wxWindow *parent; + SpectrumCanvas *spectrumCanvas; std::vector spectrum_points; fftw_complex *in, *out; fftw_plan plan; - float fft_ceil_ma, fft_ceil_maa; - float fft_floor_ma, fft_floor_maa; + double fft_ceil_ma, fft_ceil_maa; + double fft_floor_ma, fft_floor_maa; - std::vector fft_result; - std::vector fft_result_ma; - std::vector fft_result_maa; + std::vector fft_result; + std::vector fft_result_ma; + std::vector fft_result_maa; WaterfallContext *glContext; - Timer timer; - float frameTimer; - MouseTracker mTracker; int activeDemodulatorBandwidth; int activeDemodulatorFrequency; @@ -62,7 +65,20 @@ private: DragState dragState; DragState nextDragState; - bool shiftDown;bool altDown;bool ctrlDown; + int fft_size; + int waterfall_lines; + + msresamp_crcf resampler; + double resample_ratio; + nco_crcf nco_shift; + int shift_freq; + + int last_input_bandwidth; + int zoom; + + std::vector shift_buffer; + std::vector resampler_buffer; + // event table wxDECLARE_EVENT_TABLE(); }; diff --git a/src/visual/WaterfallContext.cpp b/src/visual/WaterfallContext.cpp index ac90129..91ebb09 100644 --- a/src/visual/WaterfallContext.cpp +++ b/src/visual/WaterfallContext.cpp @@ -3,7 +3,31 @@ #include "CubicSDR.h" WaterfallContext::WaterfallContext(WaterfallCanvas *canvas, wxGLContext *sharedContext) : - PrimaryGLContext(canvas, sharedContext) { + PrimaryGLContext(canvas, sharedContext), waterfall(0), waterfall_tex(NULL) { + grad.addColor(GradientColor(0, 0, 0)); + grad.addColor(GradientColor(0, 0, 1.0)); + grad.addColor(GradientColor(0, 1.0, 0)); + grad.addColor(GradientColor(1.0, 1.0, 0)); + grad.addColor(GradientColor(1.0, 0.2, 0.0)); + + grad.generate(256); +} + +void WaterfallContext::Setup(int fft_size_in, int num_waterfall_lines_in) { + if (waterfall) { + glDeleteTextures(1, &waterfall); + waterfall = 0; + } + if (waterfall_tex) { + delete waterfall_tex; + } + + waterfall_lines = num_waterfall_lines_in; + fft_size = fft_size_in; + + waterfall_tex = new unsigned char[fft_size * waterfall_lines]; + memset(waterfall_tex,0,fft_size * waterfall_lines); + glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); @@ -23,26 +47,19 @@ WaterfallContext::WaterfallContext(WaterfallCanvas *canvas, wxGLContext *sharedC glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); - grad.addColor(GradientColor(0, 0, 0)); - grad.addColor(GradientColor(0, 0, 1.0)); - grad.addColor(GradientColor(0, 1.0, 0)); - grad.addColor(GradientColor(1.0, 1.0, 0)); - grad.addColor(GradientColor(1.0, 0.2, 0.0)); - - grad.generate(256); - glPixelTransferi(GL_MAP_COLOR, GL_TRUE); glPixelMapfv(GL_PIXEL_MAP_I_TO_R, 256, &(grad.getRed())[0]); glPixelMapfv(GL_PIXEL_MAP_I_TO_G, 256, &(grad.getGreen())[0]); glPixelMapfv(GL_PIXEL_MAP_I_TO_B, 256, &(grad.getBlue())[0]); + } void WaterfallContext::Draw(std::vector &points) { if (points.size()) { - memmove(waterfall_tex + FFT_SIZE, waterfall_tex, (NUM_WATERFALL_LINES - 1) * FFT_SIZE); + memmove(waterfall_tex + fft_size, waterfall_tex, (waterfall_lines - 1) * fft_size); - for (int i = 0, iMax = FFT_SIZE; i < iMax; i++) { + for (int i = 0, iMax = fft_size; i < iMax; i++) { float v = points[i * 2 + 1]; float wv = v; @@ -57,7 +74,7 @@ void WaterfallContext::Draw(std::vector &points) { glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, waterfall); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, FFT_SIZE, NUM_WATERFALL_LINES, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, (GLvoid *) waterfall_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, fft_size, waterfall_lines, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, (GLvoid *) waterfall_tex); glColor3f(1.0, 1.0, 1.0); diff --git a/src/visual/WaterfallContext.h b/src/visual/WaterfallContext.h index a488f6d..535b254 100644 --- a/src/visual/WaterfallContext.h +++ b/src/visual/WaterfallContext.h @@ -3,8 +3,6 @@ #include "PrimaryGLContext.h" #include "Gradient.h" -#define NUM_WATERFALL_LINES 512 - class WaterfallCanvas; class WaterfallContext: public PrimaryGLContext { @@ -12,9 +10,12 @@ public: WaterfallContext(WaterfallCanvas *canvas, wxGLContext *sharedContext); void Draw(std::vector &points); + void Setup(int fft_size_in, int num_waterfall_lines_in); private: Gradient grad; GLuint waterfall; - unsigned char waterfall_tex[FFT_SIZE * NUM_WATERFALL_LINES]; + unsigned char *waterfall_tex; + int fft_size; + int waterfall_lines; };