Merge pull request #22 from cjcliffe/ui-demodulator

Demodulator updates and More
This commit is contained in:
Charles J. Cliffe 2015-01-02 22:53:26 -05:00
commit cfe35dc922
41 changed files with 1662 additions and 444 deletions

View File

@ -136,6 +136,9 @@ SET (cubicsdr_sources
src/util/MouseTracker.cpp src/util/MouseTracker.cpp
src/util/GLFont.cpp src/util/GLFont.cpp
src/visual/PrimaryGLContext.cpp src/visual/PrimaryGLContext.cpp
src/visual/InteractiveCanvas.cpp
src/visual/MeterCanvas.cpp
src/visual/MeterContext.cpp
src/visual/ScopeCanvas.cpp src/visual/ScopeCanvas.cpp
src/visual/ScopeContext.cpp src/visual/ScopeContext.cpp
src/visual/SpectrumCanvas.cpp src/visual/SpectrumCanvas.cpp
@ -165,6 +168,9 @@ SET (cubicsdr_headers
src/util/MouseTracker.h src/util/MouseTracker.h
src/util/GLFont.h src/util/GLFont.h
src/visual/PrimaryGLContext.h src/visual/PrimaryGLContext.h
src/visual/InteractiveCanvas.h
src/visual/MeterCanvas.h
src/visual/MeterContext.h
src/visual/ScopeCanvas.h src/visual/ScopeCanvas.h
src/visual/ScopeContext.h src/visual/ScopeContext.h
src/visual/SpectrumCanvas.h src/visual/SpectrumCanvas.h

View File

@ -38,20 +38,19 @@ Basic Goals and Status:
- [ ] 3D visuals - [ ] 3D visuals
- Demodulation: - Demodulation:
- [x] Multiple demodulators per IQ stream - [x] Multiple demodulators per IQ stream
- [ ] Audio device selection - [x] Audio device selection
- [ ] Modes - [x] Modes
- [x] FM - [x] FM
- [x] WFM - [x] FM stereo
- [x] WBFM stereo - [x] AM
- [ ] AM - [x] LSB
- [ ] LSB - [x] USB
- [ ] USB
- [ ] Controls - [ ] Controls
- [ ] Display Frequency and allow manual adjustments - [ ] Display Frequency and allow manual adjustments
- [ ] Allow selection of demodulation type - [x] Allow selection of demodulation type
- [ ] Display separate zoomed-in view of current waterfall and spectrum, allow adjustments - [x] Display separate zoomed-in view of current waterfall and spectrum, allow adjustments
- [ ] Display signal level and allow squelch control - [x] Display signal level and allow squelch control
- [ ] Display audio output selection - [x] Display audio output selection
- [ ] Volume control - [ ] Volume control
- [ ] Basic noise reduction / filter controls? - [ ] Basic noise reduction / filter controls?
- Basic Input Controls - Basic Input Controls

View File

@ -18,25 +18,62 @@
#include <thread> #include <thread>
#include <wx/panel.h>
wxBEGIN_EVENT_TABLE(AppFrame, wxFrame) wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
//EVT_MENU(wxID_NEW, AppFrame::OnNewWindow) //EVT_MENU(wxID_NEW, AppFrame::OnNewWindow)
EVT_MENU(wxID_CLOSE, AppFrame::OnClose) EVT_MENU(wxID_CLOSE, AppFrame::OnClose)
EVT_MENU(wxID_ANY, AppFrame::OnMenu)
EVT_COMMAND(wxID_ANY, wxEVT_THREAD, AppFrame::OnThread) EVT_COMMAND(wxID_ANY, wxEVT_THREAD, AppFrame::OnThread)
EVT_IDLE(AppFrame::OnIdle) EVT_IDLE(AppFrame::OnIdle)
wxEND_EVENT_TABLE() wxEND_EVENT_TABLE()
AppFrame::AppFrame() : AppFrame::AppFrame() :
wxFrame(NULL, wxID_ANY, wxT("CubicSDR")) { wxFrame(NULL, wxID_ANY, wxT("CubicSDR")), activeDemodulator(NULL) {
wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL); 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); 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); vbox->AddSpacer(2);
spectrumCanvas = new SpectrumCanvas(this, NULL); spectrumCanvas = new SpectrumCanvas(this, NULL);
spectrumCanvas->Setup(2048);
vbox->Add(spectrumCanvas, 1, wxEXPAND | wxALL, 0); vbox->Add(spectrumCanvas, 1, wxEXPAND | wxALL, 0);
vbox->AddSpacer(2); vbox->AddSpacer(2);
waterfallCanvas = new WaterfallCanvas(this, NULL); waterfallCanvas = new WaterfallCanvas(this, NULL);
waterfallCanvas->Setup(2048, 512);
waterfallCanvas->attachSpectrumCanvas(spectrumCanvas);
spectrumCanvas->attachWaterfallCanvas(waterfallCanvas);
vbox->Add(waterfallCanvas, 4, wxEXPAND | wxALL, 0); vbox->Add(waterfallCanvas, 4, wxEXPAND | wxALL, 0);
this->SetSizer(vbox); this->SetSizer(vbox);
@ -47,12 +84,52 @@ AppFrame::AppFrame() :
// SetIcon(wxICON(sample)); // SetIcon(wxICON(sample));
// Make a menubar // Make a menubar
wxMenu *menu = new wxMenu; // wxMenu *menu = new wxMenu;
// menu->Append(wxID_NEW); // menu->Append(wxID_NEW);
// menu->AppendSeparator(); // 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<RtAudio::DeviceInfo>::iterator devices_i;
std::map<int, RtAudio::DeviceInfo>::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; 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); SetMenuBar(menuBar);
@ -70,13 +147,34 @@ AppFrame::AppFrame() :
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)) { void AppFrame::OnClose(wxCommandEvent& WXUNUSED(event)) {
// true is to force the frame to close
Close(true); Close(true);
} }
@ -95,14 +193,48 @@ void AppFrame::OnIdle(wxIdleEvent& event) {
// std::this_thread::sleep_for(std::chrono::milliseconds(4)); // std::this_thread::sleep_for(std::chrono::milliseconds(4));
// std::this_thread::yield(); // std::this_thread::yield();
//#endif //#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()) { if (!wxGetApp().getIQVisualQueue()->empty()) {
DemodulatorThreadIQData *iqData; DemodulatorThreadIQData *iqData;
wxGetApp().getIQVisualQueue()->pop(iqData); wxGetApp().getIQVisualQueue()->pop(iqData);
if (iqData && iqData->data.size()) { if (iqData && iqData->data.size()) {
spectrumCanvas->setData(&iqData->data); // spectrumCanvas->setData(iqData);
waterfallCanvas->setData(&iqData->data); waterfallCanvas->setData(iqData);
demodWaterfallCanvas->setData(iqData);
delete iqData; delete iqData;
} else { } else {
std::cout << "Incoming IQ data empty?" << std::endl; std::cout << "Incoming IQ data empty?" << std::endl;
@ -123,7 +255,7 @@ void AppFrame::OnIdle(wxIdleEvent& event) {
scopeCanvas->waveform_points[i * 2] = ((double) i / (double) iMax); scopeCanvas->waveform_points[i * 2] = ((double) i / (double) iMax);
} }
scopeCanvas->setDivider(demodAudioData->channels == 2); scopeCanvas->setStereo(demodAudioData->channels == 2);
delete demodAudioData; delete demodAudioData;
} else { } else {

View File

@ -6,6 +6,15 @@
#include "ScopeCanvas.h" #include "ScopeCanvas.h"
#include "SpectrumCanvas.h" #include "SpectrumCanvas.h"
#include "WaterfallCanvas.h" #include "WaterfallCanvas.h"
#include "MeterCanvas.h"
#include <map>
#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 // Define a new frame type
class AppFrame: public wxFrame { class AppFrame: public wxFrame {
@ -16,6 +25,7 @@ public:
void OnEventInput(wxThreadEvent& event); void OnEventInput(wxThreadEvent& event);
private: private:
void OnMenu(wxCommandEvent& event);
void OnClose(wxCommandEvent& event); void OnClose(wxCommandEvent& event);
void OnNewWindow(wxCommandEvent& event); void OnNewWindow(wxCommandEvent& event);
void OnIdle(wxIdleEvent& event); void OnIdle(wxIdleEvent& event);
@ -23,7 +33,19 @@ private:
ScopeCanvas *scopeCanvas; ScopeCanvas *scopeCanvas;
SpectrumCanvas *spectrumCanvas; SpectrumCanvas *spectrumCanvas;
WaterfallCanvas *waterfallCanvas; WaterfallCanvas *waterfallCanvas;
SpectrumCanvas *demodSpectrumCanvas;
WaterfallCanvas *demodWaterfallCanvas;
MeterCanvas *demodSignalMeter;
// event table // event table
DemodulatorInstance *activeDemodulator;
std::vector<RtAudio::DeviceInfo> devices;
std::map<int,RtAudio::DeviceInfo> input_devices;
std::map<int,RtAudio::DeviceInfo> output_devices;
std::map<int,wxMenuItem *> output_device_menuitems;
std::map<int,wxMenuItem *> demod_menuitems;
wxDECLARE_EVENT_TABLE(); wxDECLARE_EVENT_TABLE();
}; };

View File

@ -28,6 +28,7 @@ bool CubicSDR::OnInit() {
sdrThread = new SDRThread(threadCmdQueueSDR); sdrThread = new SDRThread(threadCmdQueueSDR);
sdrPostThread = new SDRPostThread(); sdrPostThread = new SDRPostThread();
sdrPostThread->setNumVisSamples(2048);
iqPostDataQueue = new SDRThreadIQDataQueue; iqPostDataQueue = new SDRThreadIQDataQueue;
iqVisualQueue = new DemodulatorThreadInputQueue; iqVisualQueue = new DemodulatorThreadInputQueue;

View File

@ -4,12 +4,13 @@
#define BUF_SIZE (16384*2) #define BUF_SIZE (16384*2)
#define SRATE 2000000 #define SRATE 2000000
#else #else
#define BUF_SIZE (16384*4) #define BUF_SIZE (16384*5)
#define SRATE 2500000 #define SRATE 2500000
#endif #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 #define AUDIO_FREQUENCY 44100
#include <mutex> #include <mutex>

View File

@ -10,7 +10,7 @@ std::map<int, std::thread *> AudioThread::deviceThread;
#endif #endif
AudioThread::AudioThread(AudioThreadInputQueue *inputQueue, DemodulatorThreadCommandQueue* threadQueueNotify) : 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) { threadQueueNotify) {
#ifdef __APPLE__ #ifdef __APPLE__
boundThreads = new std::vector<AudioThread *>; boundThreads = new std::vector<AudioThread *>;
@ -25,8 +25,10 @@ AudioThread::~AudioThread() {
#ifdef __APPLE__ #ifdef __APPLE__
void AudioThread::bindThread(AudioThread *other) { void AudioThread::bindThread(AudioThread *other) {
if (std::find(boundThreads.load()->begin(), boundThreads.load()->end(), other) == boundThreads.load()->end()) {
boundThreads.load()->push_back(other); boundThreads.load()->push_back(other);
} }
}
void AudioThread::removeThread(AudioThread *other) { void AudioThread::removeThread(AudioThread *other) {
std::vector<AudioThread *>::iterator i; std::vector<AudioThread *>::iterator i;
@ -211,11 +213,15 @@ static int audioCallback(void *outputBuffer, void *inputBuffer, unsigned int nBu
} }
#endif #endif
void AudioThread::enumerateDevices() { void AudioThread::enumerateDevices(std::vector<RtAudio::DeviceInfo> &devs) {
int numDevices = dac.getDeviceCount(); RtAudio endac;
int numDevices = endac.getDeviceCount();
for (int i = 0; i < numDevices; i++) { 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; 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(&parameters, 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(&parameters, 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() { void AudioThread::threadMain() {
#ifdef __APPLE__ #ifdef __APPLE__
pthread_t tID = pthread_self(); // ID of this thread pthread_t tID = pthread_self(); // ID of this thread
@ -274,46 +344,17 @@ void AudioThread::threadMain() {
return; return;
} }
parameters.deviceId = dac.getDefaultOutputDevice(); setupDevice((output_device.load() == -1)?(dac.getDefaultOutputDevice()):output_device.load());
parameters.nChannels = 2;
parameters.firstChannel = 0;
unsigned int sampleRate = AUDIO_FREQUENCY;
unsigned int bufferFrames = 256;
RtAudio::StreamOptions opts; std::cout << "Audio thread started." << std::endl;
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(&parameters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &audioCallback, (void *) this, &opts);
dac.startStream();
} else {
deviceController[parameters.deviceId]->bindThread(this);
}
active = true;
#else
dac.openStream(&parameters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &audioCallback, (void *) this, &opts);
dac.startStream();
#endif
} catch (RtAudioError& e) {
e.printMessage();
return;
}
while (!terminated) { while (!terminated) {
AudioThreadCommand command; AudioThreadCommand command;
cmdQueue.pop(command); cmdQueue.pop(command);
if (command.cmd == AudioThreadCommand::AUDIO_THREAD_CMD_SET_DEVICE) {
setupDevice(command.int_value);
}
} }
#ifdef __APPLE__ #ifdef __APPLE__
@ -367,7 +408,7 @@ void AudioThread::setActive(bool state) {
while (!inputQueue->empty()) { // flush queue while (!inputQueue->empty()) { // flush queue
inputQueue->pop(dummy); inputQueue->pop(dummy);
if (dummy) { if (dummy) {
delete dummy; dummy->decRefCount();
} }
} }
deviceController[parameters.deviceId]->bindThread(this); deviceController[parameters.deviceId]->bindThread(this);
@ -376,10 +417,15 @@ void AudioThread::setActive(bool state) {
while (!inputQueue->empty()) { // flush queue while (!inputQueue->empty()) { // flush queue
inputQueue->pop(dummy); inputQueue->pop(dummy);
if (dummy) { if (dummy) {
delete dummy; dummy->decRefCount();
} }
} }
} }
#endif #endif
active = state; active = state;
} }
AudioThreadCommandQueue *AudioThread::getCommandQueue() {
return &cmdQueue;
}

View File

@ -61,19 +61,25 @@ public:
std::atomic<unsigned int> underflow_count; std::atomic<unsigned int> underflow_count;
std::atomic<bool> terminated; std::atomic<bool> terminated;
std::atomic<bool> active; std::atomic<bool> active;
std::atomic<int> output_device;
float gain; float gain;
AudioThread(AudioThreadInputQueue *inputQueue, DemodulatorThreadCommandQueue* threadQueueNotify); AudioThread(AudioThreadInputQueue *inputQueue, DemodulatorThreadCommandQueue* threadQueueNotify);
~AudioThread(); ~AudioThread();
void enumerateDevices(); static void enumerateDevices(std::vector<RtAudio::DeviceInfo> &devs);
void setupDevice(int deviceId);
void setInitOutputDevice(int deviceId);
int getOutputDevice();
void threadMain(); void threadMain();
void terminate(); void terminate();
bool isActive(); bool isActive();
void setActive(bool state); void setActive(bool state);
AudioThreadCommandQueue *getCommandQueue();
private: private:
RtAudio dac; RtAudio dac;
RtAudio::StreamParameters parameters; RtAudio::StreamParameters parameters;

View File

@ -7,9 +7,12 @@
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
enum DemodulatorType { #define DEMOD_TYPE_NULL 0
DEMOD_TYPE_NULL, DEMOD_TYPE_AM, DEMOD_TYPE_FM, DEMOD_TYPE_LSB, DEMOD_TYPE_USB #define DEMOD_TYPE_FM 1
}; #define DEMOD_TYPE_AM 2
#define DEMOD_TYPE_LSB 3
#define DEMOD_TYPE_USB 4
class DemodulatorThread; class DemodulatorThread;
class DemodulatorThreadCommand { class DemodulatorThreadCommand {
@ -41,14 +44,15 @@ public:
class DemodulatorThreadControlCommand { class DemodulatorThreadControlCommand {
public: public:
enum DemodulatorThreadControlCommandEnum { 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() : DemodulatorThreadControlCommand() :
cmd(DEMOD_THREAD_CMD_CTL_NULL) { cmd(DEMOD_THREAD_CMD_CTL_NULL), demodType(DEMOD_TYPE_NULL) {
} }
DemodulatorThreadControlCommandEnum cmd; DemodulatorThreadControlCommandEnum cmd;
int demodType;
}; };
class DemodulatorThreadIQData: public ReferenceCounter { class DemodulatorThreadIQData: public ReferenceCounter {
@ -71,10 +75,10 @@ class DemodulatorThreadPostIQData: public ReferenceCounter {
public: public:
std::vector<liquid_float_complex> data; std::vector<liquid_float_complex> data;
int bandwidth; int bandwidth;
float audio_resample_ratio; double audio_resample_ratio;
msresamp_rrrf audio_resampler; msresamp_rrrf audio_resampler;
msresamp_rrrf stereo_resampler; msresamp_rrrf stereo_resampler;
float resample_ratio; double resample_ratio;
msresamp_crcf resampler; msresamp_crcf resampler;
DemodulatorThreadPostIQData() : DemodulatorThreadPostIQData() :
@ -122,7 +126,7 @@ public:
unsigned int bandwidth; // set equal to disable second stage re-sampling? unsigned int bandwidth; // set equal to disable second stage re-sampling?
unsigned int audioSampleRate; unsigned int audioSampleRate;
DemodulatorType demodType; int demodType;
DemodulatorThreadParameters() : DemodulatorThreadParameters() :
frequency(0), inputRate(SRATE), bandwidth(200000), audioSampleRate( frequency(0), inputRate(SRATE), bandwidth(200000), audioSampleRate(

View File

@ -19,6 +19,8 @@ DemodulatorInstance::DemodulatorInstance() :
audioThread = new AudioThread(audioInputQueue, threadQueueNotify); audioThread = new AudioThread(audioInputQueue, threadQueueNotify);
demodulatorThread->setAudioInputQueue(audioInputQueue); demodulatorThread->setAudioInputQueue(audioInputQueue);
currentDemodType = demodulatorThread->getDemodulatorType();
} }
DemodulatorInstance::~DemodulatorInstance() { DemodulatorInstance::~DemodulatorInstance() {
@ -181,3 +183,44 @@ void DemodulatorInstance::setSquelchEnabled(bool state) {
squelch = 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;
}

View File

@ -52,13 +52,30 @@ public:
bool isStereo(); bool isStereo();
void setStereo(bool state); void setStereo(bool state);
void squelchAuto();bool isSquelchEnabled(); void squelchAuto();
bool isSquelchEnabled();
void setSquelchEnabled(bool state); 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: private:
std::atomic<std::string *> label;bool terminated;bool demodTerminated;bool audioTerminated;bool preDemodTerminated; std::atomic<std::string *> label; //
bool terminated; //
bool demodTerminated; //
bool audioTerminated; //
bool preDemodTerminated;
std::atomic<bool> active; std::atomic<bool> active;
std::atomic<bool> squelch; std::atomic<bool> squelch;
std::atomic<bool> stereo; std::atomic<bool> stereo;
int currentDemodType;
}; };

View File

@ -9,8 +9,8 @@
DemodulatorPreThread::DemodulatorPreThread(DemodulatorThreadInputQueue* pQueueIn, DemodulatorThreadPostInputQueue* pQueueOut, DemodulatorPreThread::DemodulatorPreThread(DemodulatorThreadInputQueue* pQueueIn, DemodulatorThreadPostInputQueue* pQueueOut,
DemodulatorThreadControlCommandQueue *threadQueueControl, DemodulatorThreadCommandQueue* threadQueueNotify) : DemodulatorThreadControlCommandQueue *threadQueueControl, DemodulatorThreadCommandQueue* threadQueueNotify) :
inputQueue(pQueueIn), postInputQueue(pQueueOut), terminated(false), initialized(false), audio_resampler(NULL), stereo_resampler(NULL), resample_ratio(1), audio_resample_ratio( inputQueue(pQueueIn), postInputQueue(pQueueOut), terminated(false), initialized(false), audio_resampler(NULL), stereo_resampler(NULL), resample_ratio(
1), resampler(NULL), commandQueue(NULL), audioInputQueue(NULL), threadQueueNotify(threadQueueNotify), threadQueueControl( 1), audio_resample_ratio(1), resampler(NULL), commandQueue(NULL), audioInputQueue(NULL), threadQueueNotify(threadQueueNotify), threadQueueControl(
threadQueueControl) { threadQueueControl) {
float kf = 0.5; // modulation factor float kf = 0.5; // modulation factor
@ -30,33 +30,17 @@ DemodulatorPreThread::DemodulatorPreThread(DemodulatorThreadInputQueue* pQueueIn
void DemodulatorPreThread::initialize() { void DemodulatorPreThread::initialize() {
initialized = false; initialized = false;
resample_ratio = (float) (params.bandwidth) / (float) params.inputRate; resample_ratio = (double) (params.bandwidth) / (double) params.inputRate;
audio_resample_ratio = (float) (params.audioSampleRate) / (float) params.bandwidth; audio_resample_ratio = (double) (params.audioSampleRate) / (double) params.bandwidth;
float As = 60.0f; // stop-band attenuation [dB] 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); 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); 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); stereo_resampler = msresamp_rrrf_create(audio_resample_ratio, As);
initialized = true; initialized = true;
// std::cout << "inputResampleRate " << params.bandwidth << std::endl; // std::cout << "inputResampleRate " << params.bandwidth << std::endl;
last_params = params; last_params = params;
} }
@ -104,8 +88,8 @@ void DemodulatorPreThread::threadMain() {
commandQueue->pop(command); commandQueue->pop(command);
switch (command.cmd) { switch (command.cmd) {
case DemodulatorThreadCommand::DEMOD_THREAD_CMD_SET_BANDWIDTH: case DemodulatorThreadCommand::DEMOD_THREAD_CMD_SET_BANDWIDTH:
if (command.int_value < 3000) { if (command.int_value < 1500) {
command.int_value = 3000; command.int_value = 1500;
} }
if (command.int_value > SRATE) { if (command.int_value > SRATE) {
command.int_value = SRATE; command.int_value = SRATE;
@ -138,13 +122,13 @@ void DemodulatorPreThread::threadMain() {
if (inp->frequency != params.frequency) { if (inp->frequency != params.frequency) {
if ((params.frequency - inp->frequency) != shift_freq) { if ((params.frequency - inp->frequency) != shift_freq) {
shift_freq = params.frequency - inp->frequency; shift_freq = params.frequency - inp->frequency;
if (abs(shift_freq) <= (int) ((float) (SRATE / 2) * 1.5)) { if (abs(shift_freq) <= (int) ((double) (SRATE / 2) * 1.5)) {
nco_crcf_set_frequency(nco_shift, (2.0 * M_PI) * (((float) abs(shift_freq)) / ((float) SRATE))); 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; continue;
} }

View File

@ -53,11 +53,11 @@ protected:
AudioThreadInputQueue *audioInputQueue; AudioThreadInputQueue *audioInputQueue;
msresamp_crcf resampler; msresamp_crcf resampler;
float resample_ratio; double resample_ratio;
msresamp_rrrf audio_resampler; msresamp_rrrf audio_resampler;
msresamp_rrrf stereo_resampler; msresamp_rrrf stereo_resampler;
float audio_resample_ratio; double audio_resample_ratio;
DemodulatorThreadParameters params; DemodulatorThreadParameters params;
DemodulatorThreadParameters last_params; DemodulatorThreadParameters last_params;

View File

@ -8,12 +8,16 @@
DemodulatorThread::DemodulatorThread(DemodulatorThreadPostInputQueue* pQueue, DemodulatorThreadControlCommandQueue *threadQueueControl, DemodulatorThread::DemodulatorThread(DemodulatorThreadPostInputQueue* pQueue, DemodulatorThreadControlCommandQueue *threadQueueControl,
DemodulatorThreadCommandQueue* threadQueueNotify) : DemodulatorThreadCommandQueue* threadQueueNotify) :
postInputQueue(pQueue), visOutQueue(NULL), audioInputQueue(NULL), agc(NULL), stereo(false), terminated(false), threadQueueNotify( postInputQueue(pQueue), visOutQueue(NULL), audioInputQueue(NULL), agc(NULL), am_max(1), am_max_ma(1), am_max_maa(1), stereo(false), terminated(
threadQueueNotify), threadQueueControl(threadQueueControl), squelch_level(0), squelch_tolerance(0), squelch_enabled(false) { 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() { DemodulatorThread::~DemodulatorThread() {
} }
@ -36,7 +40,7 @@ void DemodulatorThread::threadMain() {
firfilt_rrrf fir_filter2 = NULL; firfilt_rrrf fir_filter2 = NULL;
msresamp_crcf resampler = 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) { if (fc <= 0) {
fc = 0; fc = 0;
} }
@ -60,24 +64,29 @@ void DemodulatorThread::threadMain() {
firhilbf firR2C = firhilbf_create(m, slsl); firhilbf firR2C = firhilbf_create(m, slsl);
firhilbf firC2R = firhilbf_create(m, slsl); firhilbf firC2R = firhilbf_create(m, slsl);
nco_crcf nco_shift = nco_crcf_create(LIQUID_NCO); nco_crcf nco_stereo_shift = nco_crcf_create(LIQUID_NCO);
float shift_freq = 0; 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 = 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::cout << "Demodulator thread started.." << std::endl;
std::deque<AudioThreadInput *> buffers;
std::deque<AudioThreadInput *>::iterator buffers_i;
std::vector<liquid_float_complex> resampled_data;
std::vector<liquid_float_complex> agc_data;
std::vector<float> demod_output;
std::vector<float> demod_output_stereo;
std::vector<float> resampled_audio_output;
std::vector<float> resampled_audio_output_stereo;
double freq_index = 0; double freq_index = 0;
while (!terminated) { while (!terminated) {
@ -103,25 +112,30 @@ void DemodulatorThread::threadMain() {
resampler = inp->resampler; resampler = inp->resampler;
audio_resampler = inp->audio_resampler; audio_resampler = inp->audio_resampler;
stereo_resampler = inp->stereo_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.size() != out_size) {
if (agc_data.capacity() < out_size) { if (agc_data.capacity() < out_size) {
agc_data.reserve(out_size); agc_data.reserve(out_size);
agc_am_data.reserve(out_size);
resampled_data.reserve(out_size); resampled_data.reserve(out_size);
} }
agc_data.resize(out_size); agc_data.resize(out_size);
resampled_data.resize(out_size); resampled_data.resize(out_size);
agc_am_data.resize(out_size);
} }
unsigned int num_written; unsigned int num_written;
msresamp_crcf_execute(resampler, &(inp->data[0]), bufSize, &resampled_data[0], &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]); double audio_resample_ratio = inp->audio_resample_ratio;
float audio_resample_ratio = inp->audio_resample_ratio;
if (demod_output.size() != num_written) { if (demod_output.size() != num_written) {
if (demod_output.capacity() < num_written) { if (demod_output.capacity() < num_written) {
@ -130,9 +144,64 @@ void DemodulatorThread::threadMain() {
demod_output.resize(num_written); 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;
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]); 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 (audio_out_size != resampled_audio_output.size()) {
if (resampled_audio_output.capacity() < audio_out_size) { if (resampled_audio_output.capacity() < audio_out_size) {
@ -152,17 +221,17 @@ void DemodulatorThread::threadMain() {
demod_output_stereo.resize(num_written); 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) { if (nco_stereo_shift_freq != freq) {
nco_crcf_set_frequency(nco_shift, freq); nco_crcf_set_frequency(nco_stereo_shift, freq);
shift_freq = freq; nco_stereo_shift_freq = freq;
} }
for (int i = 0; i < num_written; i++) { for (int i = 0; i < num_written; i++) {
firhilbf_r2c_execute(firR2C, demod_output[i], &x); firhilbf_r2c_execute(firR2C, demod_output[i], &x);
nco_crcf_mix_down(nco_shift, x, &y); nco_crcf_mix_down(nco_stereo_shift, x, &y);
nco_crcf_step(nco_shift); nco_crcf_step(nco_stereo_shift);
firhilbf_c2r_execute(firC2R, y, &demod_output_stereo[i]); 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); 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; AudioThreadInput *ati = NULL;
if (audioInputQueue != 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++) { for (buffers_i = buffers.begin(); buffers_i != buffers.end(); buffers_i++) {
if ((*buffers_i)->getRefCount() <= 0) { if ((*buffers_i)->getRefCount() <= 0) {
@ -259,6 +334,8 @@ void DemodulatorThread::threadMain() {
visOutQueue->push(ati_vis); visOutQueue->push(ati_vis);
} }
if (!threadQueueControl->empty()) { if (!threadQueueControl->empty()) {
int newDemodType = DEMOD_TYPE_NULL;
while (!threadQueueControl->empty()) { while (!threadQueueControl->empty()) {
DemodulatorThreadControlCommand command; DemodulatorThreadControlCommand command;
threadQueueControl->pop(command); threadQueueControl->pop(command);
@ -274,10 +351,30 @@ void DemodulatorThread::threadMain() {
squelch_tolerance = 1; squelch_tolerance = 1;
squelch_enabled = false; squelch_enabled = false;
break; break;
case DemodulatorThreadControlCommand::DEMOD_THREAD_CMD_CTL_TYPE:
newDemodType = command.demodType;
break;
default: default:
break; 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(); inp->decRefCount();
@ -302,7 +399,11 @@ void DemodulatorThread::threadMain() {
agc_crcf_destroy(agc); agc_crcf_destroy(agc);
firhilbf_destroy(firR2C); firhilbf_destroy(firR2C);
firhilbf_destroy(firC2R); 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()) { while (!buffers.empty()) {
AudioThreadInput *audioDataDel = buffers.front(); AudioThreadInput *audioDataDel = buffers.front();
@ -330,3 +431,27 @@ void DemodulatorThread::setStereo(bool state) {
bool DemodulatorThread::isStereo() { bool DemodulatorThread::isStereo() {
return stereo; 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;
}

View File

@ -8,7 +8,7 @@
typedef ThreadQueue<AudioThreadInput *> DemodulatorThreadOutputQueue; typedef ThreadQueue<AudioThreadInput *> DemodulatorThreadOutputQueue;
#define DEMOD_VIS_SIZE 2048 #define DEMOD_VIS_SIZE 1024
class DemodulatorThread { class DemodulatorThread {
public: public:
@ -32,12 +32,18 @@ public:
} }
void initialize(); void initialize();
void terminate(); void terminate();
void setStereo(bool state); void setStereo(bool state);
bool isStereo(); bool isStereo();
float getSignalLevel();
void setSquelchLevel(float signal_level_in);
float getSquelchLevel();
void setDemodulatorType(int demod_type_in);
int getDemodulatorType();
#ifdef __APPLE__ #ifdef __APPLE__
static void *pthread_helper(void *context) { static void *pthread_helper(void *context) {
return ((DemodulatorThread *) context)->threadMain(); return ((DemodulatorThread *) context)->threadMain();
@ -45,18 +51,41 @@ public:
#endif #endif
protected: protected:
std::deque<AudioThreadInput *> buffers;
std::deque<AudioThreadInput *>::iterator buffers_i;
std::vector<liquid_float_complex> resampled_data;
std::vector<liquid_float_complex> agc_data;
std::vector<float> agc_am_data;
std::vector<float> demod_output;
std::vector<float> demod_output_stereo;
std::vector<float> resampled_audio_output;
std::vector<float> resampled_audio_output_stereo;
DemodulatorThreadPostInputQueue* postInputQueue; DemodulatorThreadPostInputQueue* postInputQueue;
DemodulatorThreadOutputQueue* visOutQueue; DemodulatorThreadOutputQueue* visOutQueue;
AudioThreadInputQueue *audioInputQueue; AudioThreadInputQueue *audioInputQueue;
freqdem fdem; freqdem fdem;
ampmodem ampdem_active;
ampmodem ampdem;
ampmodem ampdem_usb;
ampmodem ampdem_lsb;
agc_crcf agc; agc_crcf agc;
float am_max;
float am_max_ma;
float am_max_maa;
std::atomic<bool> stereo; std::atomic<bool> stereo;
std::atomic<bool> terminated; std::atomic<bool> terminated;
std::atomic<int> demodulatorType;
DemodulatorThreadCommandQueue* threadQueueNotify; DemodulatorThreadCommandQueue* threadQueueNotify;
DemodulatorThreadControlCommandQueue *threadQueueControl; DemodulatorThreadControlCommandQueue *threadQueueControl;
float squelch_level; std::atomic<float> squelch_level;
float squelch_tolerance;bool squelch_enabled; float squelch_tolerance;
std::atomic<float> signal_level;
bool squelch_enabled;
}; };

View File

@ -34,8 +34,8 @@ void DemodulatorWorkerThread::threadMain() {
if (filterChanged) { if (filterChanged) {
DemodulatorWorkerThreadResult result(DemodulatorWorkerThreadResult::DEMOD_WORKER_THREAD_RESULT_FILTERS); DemodulatorWorkerThreadResult result(DemodulatorWorkerThreadResult::DEMOD_WORKER_THREAD_RESULT_FILTERS);
result.resample_ratio = (float) (filterCommand.bandwidth) / (float) filterCommand.inputRate; result.resample_ratio = (double) (filterCommand.bandwidth) / (double) filterCommand.inputRate;
result.audio_resample_ratio = (float) (filterCommand.audioSampleRate) / (float) filterCommand.bandwidth; result.audio_resample_ratio = (double) (filterCommand.audioSampleRate) / (double) filterCommand.bandwidth;
float As = 60.0f; // stop-band attenuation [dB] float As = 60.0f; // stop-band attenuation [dB]
@ -46,7 +46,6 @@ void DemodulatorWorkerThread::threadMain() {
result.audioSampleRate = filterCommand.audioSampleRate; result.audioSampleRate = filterCommand.audioSampleRate;
result.bandwidth = filterCommand.bandwidth; result.bandwidth = filterCommand.bandwidth;
result.inputRate = filterCommand.inputRate; result.inputRate = filterCommand.inputRate;
resultQueue->push(result); resultQueue->push(result);
} }

View File

@ -36,10 +36,10 @@ public:
DemodulatorThreadResultEnum cmd; DemodulatorThreadResultEnum cmd;
msresamp_crcf resampler; msresamp_crcf resampler;
float resample_ratio; double resample_ratio;
msresamp_rrrf audio_resampler; msresamp_rrrf audio_resampler;
msresamp_rrrf stereo_resampler; msresamp_rrrf stereo_resampler;
float audio_resample_ratio; double audio_resample_ratio;
unsigned int inputRate; unsigned int inputRate;
unsigned int bandwidth; unsigned int bandwidth;

View File

@ -6,7 +6,7 @@
#include <deque> #include <deque>
SDRPostThread::SDRPostThread() : 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() { SDRPostThread::~SDRPostThread() {
@ -34,6 +34,14 @@ void SDRPostThread::setIQVisualQueue(DemodulatorThreadInputQueue *iqVisQueue) {
iqVisualQueue = 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() { void SDRPostThread::threadMain() {
int n_read; int n_read;
double seconds = 0.0; double seconds = 0.0;
@ -89,7 +97,9 @@ void SDRPostThread::threadMain() {
if (iqVisualQueue != NULL && iqVisualQueue.load()->empty()) { if (iqVisualQueue != NULL && iqVisualQueue.load()->empty()) {
DemodulatorThreadIQData *visualDataOut = new DemodulatorThreadIQData; 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); iqVisualQueue.load()->push(visualDataOut);
} }
@ -122,7 +132,7 @@ void SDRPostThread::threadMain() {
DemodulatorInstance *demod = *i; DemodulatorInstance *demod = *i;
if (demod->getParams().frequency != data_in->frequency 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; continue;
} }
activeDemods++; activeDemods++;
@ -156,7 +166,7 @@ void SDRPostThread::threadMain() {
DemodulatorThreadInputQueue *demodQueue = demod->threadQueueDemod; DemodulatorThreadInputQueue *demodQueue = demod->threadQueueDemod;
if (demod->getParams().frequency != data_in->frequency 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()) { if (demod->isActive()) {
demod->setActive(false); demod->setActive(false);
DemodulatorThreadIQData *dummyDataOut = new DemodulatorThreadIQData; DemodulatorThreadIQData *dummyDataOut = new DemodulatorThreadIQData;

View File

@ -15,6 +15,9 @@ public:
void setIQDataOutQueue(DemodulatorThreadInputQueue* iqDataQueue); void setIQDataOutQueue(DemodulatorThreadInputQueue* iqDataQueue);
void setIQVisualQueue(DemodulatorThreadInputQueue* iqVisQueue); void setIQVisualQueue(DemodulatorThreadInputQueue* iqVisQueue);
void setNumVisSamples(int num_vis_samples_in);
int getNumVisSamples();
void threadMain(); void threadMain();
void terminate(); void terminate();
@ -30,4 +33,5 @@ protected:
std::vector<DemodulatorInstance *> demodulators_remove; std::vector<DemodulatorInstance *> demodulators_remove;
std::atomic<bool> terminated; std::atomic<bool> terminated;
iirfilt_crcf dcFilter; iirfilt_crcf dcFilter;
int num_vis_samples;
}; };

View File

@ -382,7 +382,7 @@ void GLFont::drawString(std::string str, float xpos, float ypos, int pxHeight, A
glPushMatrix(); glPushMatrix();
glTranslatef(xpos, ypos, 0.0f); glTranslatef(xpos, ypos, 0.0f);
switch (hAlign) { switch (vAlign) {
case GLFONT_ALIGN_TOP: case GLFONT_ALIGN_TOP:
glTranslatef(0.0, -size, 0.0); glTranslatef(0.0, -size, 0.0);
break; break;
@ -393,7 +393,7 @@ void GLFont::drawString(std::string str, float xpos, float ypos, int pxHeight, A
break; break;
} }
switch (vAlign) { switch (hAlign) {
case GLFONT_ALIGN_RIGHT: case GLFONT_ALIGN_RIGHT:
glTranslatef(-msgWidth, 0.0, 0.0); glTranslatef(-msgWidth, 0.0, 0.0);
break; break;
@ -445,5 +445,6 @@ void GLFont::drawString(std::string str, float xpos, float ypos, int pxHeight, A
glPopMatrix(); glPopMatrix();
glDisable(GL_BLEND); glDisable(GL_BLEND);
glDisable(GL_TEXTURE_2D);
} }

View File

@ -9,7 +9,7 @@ void MouseTracker::OnMouseMoved(wxMouseEvent& event) {
const wxSize ClientSize = target->GetClientSize(); const wxSize ClientSize = target->GetClientSize();
mouseX = (float) event.m_x / (float) ClientSize.x; 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; deltaMouseX = mouseX - lastMouseX;
deltaMouseY = mouseY - lastMouseY; deltaMouseY = mouseY - lastMouseY;
@ -17,11 +17,11 @@ void MouseTracker::OnMouseMoved(wxMouseEvent& event) {
if (isMouseDown) { if (isMouseDown) {
#ifndef __APPLE__ #ifndef __APPLE__
if (horizDragLock && vertDragLock) { if (horizDragLock && vertDragLock) {
target->WarpPointer(originMouseX * ClientSize.x, originMouseY * ClientSize.y); target->WarpPointer(originMouseX * ClientSize.x, (1.0-originMouseY) * ClientSize.y);
mouseX = originMouseX; mouseX = originMouseX;
mouseY = originMouseY; mouseY = originMouseY;
} else if (vertDragLock && mouseY != lastMouseY) { } 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; mouseY = originMouseY;
} else if (horizDragLock && mouseX != lastMouseX) { } else if (horizDragLock && mouseX != lastMouseX) {
target->WarpPointer(originMouseX * ClientSize.x, event.m_y); target->WarpPointer(originMouseX * ClientSize.x, event.m_y);
@ -42,7 +42,7 @@ void MouseTracker::OnMouseDown(wxMouseEvent& event) {
const wxSize ClientSize = target->GetClientSize(); const wxSize ClientSize = target->GetClientSize();
mouseX = lastMouseX = (float) event.m_x / (float) ClientSize.x; 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; originMouseX = mouseX;
originMouseY = mouseY; originMouseY = mouseY;

View File

@ -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 <algorithm>
#include <wx/numformatter.h>
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)));
}

View File

@ -0,0 +1,52 @@
#pragma once
#include "wx/glcanvas.h"
#include "wx/timer.h"
#include "MouseTracker.h"
#include <string>
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;
};

131
src/visual/MeterCanvas.cpp Normal file
View File

@ -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 <algorithm>
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);
}

57
src/visual/MeterCanvas.h Normal file
View File

@ -0,0 +1,57 @@
#pragma once
#include "wx/glcanvas.h"
#include "wx/timer.h"
#include <vector>
#include <queue>
#include "MeterContext.h"
#include "MouseTracker.h"
#include "fftw3.h"
#include "Timer.h"
class MeterCanvas: public wxGLCanvas {
public:
std::vector<float> 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();
};

View File

@ -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();
}

19
src/visual/MeterContext.h Normal file
View File

@ -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:
};

View File

@ -92,7 +92,7 @@ GLFont &PrimaryGLContext::getFont(GLFontSize esize) {
return fonts[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) { if (!demod) {
return; return;
} }
@ -103,7 +103,11 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, float r, float
float viewHeight = (float) vp[3]; float viewHeight = (float) vp[3];
float viewWidth = (float) vp[2]; 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; uxPos = (uxPos - 0.5) * 2.0;
glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_2D);
@ -112,7 +116,7 @@ void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, float r, float
glBlendFunc(GL_SRC_ALPHA, GL_DST_COLOR); glBlendFunc(GL_SRC_ALPHA, GL_DST_COLOR);
glColor4f(r, g, b, 0.6); 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); glBlendFunc(GL_SRC_ALPHA, GL_DST_COLOR);
glColor4f(r, g, b, 0.2); 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) { if (!demod) {
return; 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_DEPTH_TEST);
glDisable(GL_TEXTURE_2D); 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);
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);
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); 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(); DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
int bw = 0; int bw = 0;
@ -218,7 +226,7 @@ void PrimaryGLContext::DrawFreqSelector(float uxPos, float r, float g, float b,
if (w) { if (w) {
ofs = w; ofs = w;
} else { } else {
ofs = ((float) bw) / (float) SRATE; ofs = ((float) bw) / (float) srate;
} }
glVertex3f((uxPos - 0.5) * 2.0 - ofs, 1.0, 0.0); glVertex3f((uxPos - 0.5) * 2.0 - ofs, 1.0, 0.0);

View File

@ -23,9 +23,9 @@ public:
void BeginDraw(); void BeginDraw();
void EndDraw(); void EndDraw();
void DrawFreqSelector(float uxPos, float r = 1, float g = 1, float b = 1, float w = 0); 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); 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); 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); static GLFont &getFont(GLFontSize esize);

View File

@ -21,10 +21,9 @@ wxEND_EVENT_TABLE()
ScopeCanvas::ScopeCanvas(wxWindow *parent, int *attribList) : ScopeCanvas::ScopeCanvas(wxWindow *parent, int *attribList) :
wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, 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)); glContext = new ScopeContext(this, &wxGetApp().GetContext(this));
timer.start();
} }
ScopeCanvas::~ScopeCanvas() { ScopeCanvas::~ScopeCanvas() {
@ -35,8 +34,13 @@ void ScopeCanvas::setWaveformPoints(std::vector<float> &waveform_points_in) {
waveform_points = waveform_points_in; waveform_points = waveform_points_in;
} }
void ScopeCanvas::setDivider(bool state) { void ScopeCanvas::setStereo(bool state) {
divider = state; stereo = state;
}
void ScopeCanvas::setDeviceName(std::string device_name) {
deviceName = device_name;
deviceName.append(" ");
} }
void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
@ -47,20 +51,16 @@ void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
glViewport(0, 0, ClientSize.x, ClientSize.y); glViewport(0, 0, ClientSize.x, ClientSize.y);
glContext->DrawBegin(); glContext->DrawBegin();
glContext->Plot(waveform_points); if (!deviceName.empty()) {
if (divider) { glContext->DrawDeviceName(deviceName);
glContext->DrawDivider();
} }
glContext->Plot(waveform_points, stereo);
glContext->DrawEnd(); glContext->DrawEnd();
SwapBuffers(); SwapBuffers();
} }
void ScopeCanvas::OnIdle(wxIdleEvent &event) { void ScopeCanvas::OnIdle(wxIdleEvent &event) {
// timer.update();
// frameTimer += timer.lastUpdateSeconds();
// if (frameTimer > 1.0/30.0) {
Refresh(false); Refresh(false);
// frameTimer = 0;
// }
} }

View File

@ -7,9 +7,7 @@
#include <queue> #include <queue>
#include "ScopeContext.h" #include "ScopeContext.h"
#include "fftw3.h" #include "fftw3.h"
#include "Timer.h"
class ScopeCanvas: public wxGLCanvas { class ScopeCanvas: public wxGLCanvas {
public: public:
@ -19,7 +17,8 @@ public:
~ScopeCanvas(); ~ScopeCanvas();
void setWaveformPoints(std::vector<float> &waveform_points_in); void setWaveformPoints(std::vector<float> &waveform_points_in);
void setDivider(bool state); void setStereo(bool state);
void setDeviceName(std::string device_name);
private: private:
void OnPaint(wxPaintEvent& event); void OnPaint(wxPaintEvent& event);
@ -28,9 +27,8 @@ private:
wxWindow *parent; wxWindow *parent;
ScopeContext *glContext; ScopeContext *glContext;
Timer timer; std::string deviceName;
float frameTimer; bool stereo;
bool divider;
// event table // event table
wxDECLARE_EVENT_TABLE(); wxDECLARE_EVENT_TABLE();
}; };

View File

@ -20,19 +20,68 @@ void ScopeContext::DrawBegin() {
glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_2D);
} }
void ScopeContext::Plot(std::vector<float> &points) { void ScopeContext::Plot(std::vector<float> &points, bool stereo) {
glColor3f(1.0, 1.0, 1.0); glColor3f(1.0, 1.0, 1.0);
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()) { 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(); glPushMatrix();
glTranslatef(-1.0f, 0.0f, 0.0f); glTranslatef(-1.0f, 0.0f, 0.0f);
glScalef(2.0f, 2.0f, 1.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); glDrawArrays(GL_LINE_STRIP, 0, points.size() / 2);
glDisableClientState(GL_VERTEX_ARRAY);
glPopMatrix(); 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() { void ScopeContext::DrawEnd() {

View File

@ -12,7 +12,8 @@ public:
ScopeContext(ScopeCanvas *canvas, wxGLContext *sharedContext); ScopeContext(ScopeCanvas *canvas, wxGLContext *sharedContext);
void DrawBegin(); void DrawBegin();
void Plot(std::vector<float> &points); void Plot(std::vector<float> &points, bool stereo=false);
void DrawDeviceName(std::string deviceName);
void DrawDivider(); void DrawDivider();
void DrawEnd(); void DrawEnd();

View File

@ -15,6 +15,7 @@
#include "AppFrame.h" #include "AppFrame.h"
#include <algorithm> #include <algorithm>
#include <wx/numformatter.h> #include <wx/numformatter.h>
#include "WaterfallCanvas.h"
wxBEGIN_EVENT_TABLE(SpectrumCanvas, wxGLCanvas) EVT_PAINT(SpectrumCanvas::OnPaint) wxBEGIN_EVENT_TABLE(SpectrumCanvas, wxGLCanvas) EVT_PAINT(SpectrumCanvas::OnPaint)
EVT_IDLE(SpectrumCanvas::OnIdle) EVT_IDLE(SpectrumCanvas::OnIdle)
@ -26,28 +27,40 @@ EVT_MOUSEWHEEL(SpectrumCanvas::mouseWheelMoved)
wxEND_EVENT_TABLE() wxEND_EVENT_TABLE()
SpectrumCanvas::SpectrumCanvas(wxWindow *parent, int *attribList) : SpectrumCanvas::SpectrumCanvas(wxWindow *parent, int *attribList) :
wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, 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(
wxFULL_REPAINT_ON_RESIZE), parent(parent), frameTimer(0) { 0), waterfallCanvas(NULL) {
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;
glContext = new SpectrumContext(this, &wxGetApp().GetContext(this)); glContext = new SpectrumContext(this, &wxGetApp().GetContext(this));
timer.start();
mTracker.setTarget(this);
mTracker.setVertDragLock(true); mTracker.setVertDragLock(true);
SetCursor(wxCURSOR_SIZEWE); 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() { SpectrumCanvas::~SpectrumCanvas() {
} }
@ -60,12 +73,12 @@ void SpectrumCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
glViewport(0, 0, ClientSize.x, ClientSize.y); glViewport(0, 0, ClientSize.x, ClientSize.y);
glContext->BeginDraw(); glContext->BeginDraw();
glContext->Draw(spectrum_points); glContext->Draw(spectrum_points, GetCenterFrequency(), GetBandwidth());
std::vector<DemodulatorInstance *> &demods = wxGetApp().getDemodMgr().getDemodulators(); std::vector<DemodulatorInstance *> &demods = wxGetApp().getDemodMgr().getDemodulators();
for (int i = 0, iMax = demods.size(); i < iMax; i++) { 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(); glContext->EndDraw();
@ -73,14 +86,23 @@ void SpectrumCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
SwapBuffers(); SwapBuffers();
} }
void SpectrumCanvas::setData(std::vector<liquid_float_complex> *data) { void SpectrumCanvas::setData(DemodulatorThreadIQData *input) {
if (!input) {
return;
}
std::vector<liquid_float_complex> *data = &input->data;
if (data && data->size()) { if (data && data->size()) {
if (spectrum_points.size() < FFT_SIZE * 2) { if (fft_size != data->size()) {
spectrum_points.resize(FFT_SIZE * 2); 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][0] = (*data)[i].real;
in[i][1] = (*data)[i].imag; in[i][1] = (*data)[i].imag;
} }
@ -89,31 +111,33 @@ void SpectrumCanvas::setData(std::vector<liquid_float_complex> *data) {
double fft_ceil = 0, fft_floor = 1; double fft_ceil = 0, fft_floor = 1;
if (fft_result.size() < FFT_SIZE) { if (fft_result.size() != fft_size) {
fft_result.resize(FFT_SIZE); if (fft_result.capacity() < fft_size) {
fft_result_ma.resize(FFT_SIZE); fft_result.reserve(fft_size);
fft_result_maa.resize(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; 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; n = (i == 0) ? 1 : i;
double a = out[n][0]; double a = out[n][0];
double b = out[n][1]; double b = out[n][1];
double c = sqrt(a * a + b * b); 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 x = out[FFT_SIZE / 2 + n][0]; double y = out[fft_size / 2 + n][1];
double y = out[FFT_SIZE / 2 + n][1];
double z = sqrt(x * x + y * y); double z = sqrt(x * x + y * y);
fft_result[i] = (z); 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_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; fft_result_ma[i] += (fft_result[i] - fft_result_ma[i]) * 0.65;
@ -136,7 +160,7 @@ void SpectrumCanvas::setData(std::vector<liquid_float_complex> *data) {
// fftw_execute(plan[1]); // 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)); 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] = ((float) i / (float) iMax);
spectrum_points[i * 2 + 1] = v; spectrum_points[i * 2 + 1] = v;
@ -146,51 +170,66 @@ void SpectrumCanvas::setData(std::vector<liquid_float_complex> *data) {
} }
void SpectrumCanvas::OnIdle(wxIdleEvent &event) { void SpectrumCanvas::OnIdle(wxIdleEvent &event) {
// timer.update();
// frameTimer += timer.lastUpdateSeconds();
// if (frameTimer > 1.0/30.0) {
Refresh(false); Refresh(false);
// frameTimer = 0;
// }
} }
void SpectrumCanvas::mouseMoved(wxMouseEvent& event) { void SpectrumCanvas::mouseMoved(wxMouseEvent& event) {
mTracker.OnMouseMoved(event); InteractiveCanvas::mouseMoved(event);
if (mTracker.mouseDown()) { if (mTracker.mouseDown()) {
int freqChange = mTracker.getDeltaMouseX() * SRATE; int freqChange = mTracker.getDeltaMouseX() * GetBandwidth();
if (freqChange != 0) { if (freqChange != 0) {
int freq = wxGetApp().getFrequency(); int freq = wxGetApp().getFrequency();
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; freq -= freqChange;
wxGetApp().setFrequency(freq); wxGetApp().setFrequency(freq);
setStatusText("Set center frequency: %s", freq);
((wxFrame*) parent)->GetStatusBar()->SetStatusText(
wxString::Format(wxT("Set center frequency: %s"),
wxNumberFormatter::ToString((long) freq, wxNumberFormatter::Style_WithThousandsSep)));
} }
}
} else {
setStatusText("Click and drag to adjust center frequency.");
} }
} }
void SpectrumCanvas::mouseDown(wxMouseEvent& event) { void SpectrumCanvas::mouseDown(wxMouseEvent& event) {
mTracker.OnMouseDown(event); InteractiveCanvas::mouseDown(event);
SetCursor(wxCURSOR_CROSS); SetCursor(wxCURSOR_CROSS);
} }
void SpectrumCanvas::mouseWheelMoved(wxMouseEvent& event) { void SpectrumCanvas::mouseWheelMoved(wxMouseEvent& event) {
mTracker.OnMouseWheelMoved(event); InteractiveCanvas::mouseWheelMoved(event);
} }
void SpectrumCanvas::mouseReleased(wxMouseEvent& event) { void SpectrumCanvas::mouseReleased(wxMouseEvent& event) {
mTracker.OnMouseReleased(event); InteractiveCanvas::mouseReleased(event);
SetCursor(wxCURSOR_SIZEWE); SetCursor(wxCURSOR_SIZEWE);
} }
void SpectrumCanvas::mouseLeftWindow(wxMouseEvent& event) { void SpectrumCanvas::mouseLeftWindow(wxMouseEvent& event) {
mTracker.OnMouseLeftWindow(event); InteractiveCanvas::mouseLeftWindow(event);
SetCursor(wxCURSOR_SIZEWE); SetCursor(wxCURSOR_SIZEWE);
} }
//void SpectrumCanvas::rightClick(wxMouseEvent& event) {} void SpectrumCanvas::attachWaterfallCanvas(WaterfallCanvas* canvas_in) {
//void SpectrumCanvas::keyPressed(wxKeyEvent& event) {} waterfallCanvas = canvas_in;
//void SpectrumCanvas::keyReleased(wxKeyEvent& event) {} }

View File

@ -6,18 +6,25 @@
#include <vector> #include <vector>
#include <queue> #include <queue>
#include "InteractiveCanvas.h"
#include "SpectrumContext.h" #include "SpectrumContext.h"
#include "fftw3.h" #include "fftw3.h"
#include "Timer.h"
#include "MouseTracker.h" #include "MouseTracker.h"
class SpectrumCanvas: public wxGLCanvas { class WaterfallCanvas;
class SpectrumCanvas: public InteractiveCanvas {
public: public:
std::vector<float> spectrum_points;
SpectrumCanvas(wxWindow *parent, int *attribList = NULL); SpectrumCanvas(wxWindow *parent, int *attribList = NULL);
void Setup(int fft_size_in);
~SpectrumCanvas(); ~SpectrumCanvas();
void setData(std::vector<liquid_float_complex> *data); void setData(DemodulatorThreadIQData *input);
void attachWaterfallCanvas(WaterfallCanvas *canvas_in);
private: private:
void OnPaint(wxPaintEvent& event); void OnPaint(wxPaintEvent& event);
@ -31,24 +38,19 @@ private:
// void rightClick(wxMouseEvent& event); // void rightClick(wxMouseEvent& event);
void mouseLeftWindow(wxMouseEvent& event); void mouseLeftWindow(wxMouseEvent& event);
wxWindow *parent;
std::vector<float> spectrum_points;
fftw_complex *in, *out; fftw_complex *in, *out;
fftw_plan plan; fftw_plan plan;
float fft_ceil_ma, fft_ceil_maa; double fft_ceil_ma, fft_ceil_maa;
float fft_floor_ma, fft_floor_maa; double fft_floor_ma, fft_floor_maa;
std::vector<float> fft_result; std::vector<double> fft_result;
std::vector<float> fft_result_ma; std::vector<double> fft_result_ma;
std::vector<float> fft_result_maa; std::vector<double> fft_result_maa;
SpectrumContext *glContext; SpectrumContext *glContext;
Timer timer; WaterfallCanvas *waterfallCanvas;
float frameTimer; int fft_size;
MouseTracker mTracker;
// event table // event table
wxDECLARE_EVENT_TABLE(); wxDECLARE_EVENT_TABLE();
}; };

View File

@ -6,7 +6,7 @@
#include <iostream> #include <iostream>
SpectrumContext::SpectrumContext(SpectrumCanvas *canvas, wxGLContext *sharedContext) : SpectrumContext::SpectrumContext(SpectrumCanvas *canvas, wxGLContext *sharedContext) :
PrimaryGLContext(canvas, sharedContext) { PrimaryGLContext(canvas, sharedContext), fft_size(0) {
glDisable(GL_CULL_FACE); glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
@ -15,7 +15,7 @@ SpectrumContext::SpectrumContext(SpectrumCanvas *canvas, wxGLContext *sharedCont
} }
void SpectrumContext::Draw(std::vector<float> &points) { void SpectrumContext::Draw(std::vector<float> &points, int freq, int bandwidth) {
glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_2D);
glColor3f(1.0, 1.0, 1.0); glColor3f(1.0, 1.0, 1.0);
@ -35,9 +35,10 @@ void SpectrumContext::Draw(std::vector<float> &points) {
glGetIntegerv( GL_VIEWPORT, vp); glGetIntegerv( GL_VIEWPORT, vp);
float viewHeight = (float) vp[3]; float viewHeight = (float) vp[3];
float viewWidth = (float) vp[2];
float leftFreq = (float) wxGetApp().getFrequency() - ((float) SRATE / 2.0); float leftFreq = (float) freq - ((float) bandwidth / 2.0);
float rightFreq = leftFreq + (float) SRATE; float rightFreq = leftFreq + (float) bandwidth;
float firstMhz = floor(leftFreq / 1000000.0) * 1000000.0; float firstMhz = floor(leftFreq / 1000000.0) * 1000000.0;
float mhzStart = ((firstMhz - leftFreq) / (rightFreq - leftFreq)) * 2.0; float mhzStart = ((firstMhz - leftFreq) / (rightFreq - leftFreq)) * 2.0;

View File

@ -11,7 +11,8 @@ class SpectrumContext: public PrimaryGLContext {
public: public:
SpectrumContext(SpectrumCanvas *canvas, wxGLContext *sharedContext); SpectrumContext(SpectrumCanvas *canvas, wxGLContext *sharedContext);
void Draw(std::vector<float> &points); void Draw(std::vector<float> &points, int freq, int bandwidth);
private: private:
int fft_size;
}; };

View File

@ -17,6 +17,8 @@
#include <wx/numformatter.h> #include <wx/numformatter.h>
#define MIN_BANDWIDTH 1500
wxBEGIN_EVENT_TABLE(WaterfallCanvas, wxGLCanvas) EVT_PAINT(WaterfallCanvas::OnPaint) wxBEGIN_EVENT_TABLE(WaterfallCanvas, wxGLCanvas) EVT_PAINT(WaterfallCanvas::OnPaint)
EVT_KEY_DOWN(WaterfallCanvas::OnKeyDown) EVT_KEY_DOWN(WaterfallCanvas::OnKeyDown)
EVT_KEY_UP(WaterfallCanvas::OnKeyUp) EVT_KEY_UP(WaterfallCanvas::OnKeyUp)
@ -29,37 +31,58 @@ EVT_ENTER_WINDOW(WaterfallCanvas::mouseEnterWindow)
wxEND_EVENT_TABLE() wxEND_EVENT_TABLE()
WaterfallCanvas::WaterfallCanvas(wxWindow *parent, int *attribList) : WaterfallCanvas::WaterfallCanvas(wxWindow *parent, int *attribList) :
wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, InteractiveCanvas(parent, attribList), spectrumCanvas(NULL), activeDemodulatorBandwidth(0), activeDemodulatorFrequency(0), dragState(
wxFULL_REPAINT_ON_RESIZE), parent(parent), frameTimer(0), activeDemodulatorBandwidth(0), activeDemodulatorFrequency(0), dragState( WF_DRAG_NONE), nextDragState(WF_DRAG_NONE), fft_size(0), waterfall_lines(0), plan(
WF_DRAG_NONE), nextDragState(WF_DRAG_NONE), shiftDown(false), altDown(false), ctrlDown(false) { NULL), in(NULL), out(NULL), resampler(NULL), resample_ratio(0), last_input_bandwidth(0), zoom(0) {
int in_block_size = FFT_SIZE; glContext = new WaterfallContext(this, &wxGetApp().GetContext(this));
int out_block_size = FFT_SIZE;
in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * in_block_size); nco_shift = nco_crcf_create(LIQUID_NCO);
out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * out_block_size); shift_freq = 0;
plan = fftw_plan_dft_1d(out_block_size, in, out, FFTW_FORWARD, FFTW_MEASURE);
fft_ceil_ma = fft_ceil_maa = 100.0; fft_ceil_ma = fft_ceil_maa = 100.0;
fft_floor_ma = fft_floor_maa = 0.0; fft_floor_ma = fft_floor_maa = 0.0;
glContext = new WaterfallContext(this, &wxGetApp().GetContext(this));
timer.start();
mTracker.setTarget(this);
SetCursor(wxCURSOR_CROSS); SetCursor(wxCURSOR_CROSS);
} }
WaterfallCanvas::~WaterfallCanvas() { 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(); if (in) {
int freq = center_freq - (int) (0.5 * (float) SRATE) + (int) ((float) x * (float) SRATE); 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)) { void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
@ -80,6 +103,9 @@ void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
bool isNew = shiftDown bool isNew = shiftDown
|| (wxGetApp().getDemodMgr().getLastActiveDemodulator() && !wxGetApp().getDemodMgr().getLastActiveDemodulator()->isActive()); || (wxGetApp().getDemodMgr().getLastActiveDemodulator() && !wxGetApp().getDemodMgr().getLastActiveDemodulator()->isActive());
int currentBandwidth = GetBandwidth();
int currentCenterFreq = GetCenterFrequency();
if (mTracker.mouseInView()) { if (mTracker.mouseInView()) {
if (nextDragState == WF_DRAG_RANGE) { if (nextDragState == WF_DRAG_RANGE) {
if (mTracker.mouseDown()) { if (mTracker.mouseDown()) {
@ -87,47 +113,49 @@ void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
float centerPos = mTracker.getOriginMouseX() + width / 2.0; float centerPos = mTracker.getOriginMouseX() + width / 2.0;
if (isNew) { if (isNew) {
glContext->DrawDemod(lastActiveDemodulator); glContext->DrawDemod(lastActiveDemodulator, 1, 1, 1, currentCenterFreq, currentBandwidth);
glContext->DrawFreqSelector(centerPos, 0, 1, 0, width ? width : (1.0 / (float) ClientSize.x)); glContext->DrawFreqSelector(centerPos, 0, 1, 0, width ? width : (1.0 / (float) ClientSize.x), currentCenterFreq,
currentBandwidth);
} else { } else {
glContext->DrawDemod(lastActiveDemodulator, 1, 0, 0); glContext->DrawDemod(lastActiveDemodulator, 1, 0, 0, currentCenterFreq, currentBandwidth);
glContext->DrawFreqSelector(centerPos, 1, 1, 0, width ? width : (1.0 / (float) ClientSize.x)); glContext->DrawFreqSelector(centerPos, 1, 1, 0, width ? width : (1.0 / (float) ClientSize.x), currentCenterFreq,
currentBandwidth);
} }
} else { } else {
if (isNew) { if (isNew) {
glContext->DrawDemod(lastActiveDemodulator); glContext->DrawDemod(lastActiveDemodulator, 1, 1, 1, currentCenterFreq, currentBandwidth);
glContext->DrawFreqSelector(mTracker.getMouseX(), 0, 1, 0, 1.0 / (float) ClientSize.x); glContext->DrawFreqSelector(mTracker.getMouseX(), 0, 1, 0, 1.0 / (float) ClientSize.x, currentCenterFreq, currentBandwidth);
} else { } else {
glContext->DrawDemod(lastActiveDemodulator, 1, 0, 0); glContext->DrawDemod(lastActiveDemodulator, 1, 0, 0, currentCenterFreq, currentBandwidth);
glContext->DrawFreqSelector(mTracker.getMouseX(), 1, 1, 0, 1.0 / (float) ClientSize.x); glContext->DrawFreqSelector(mTracker.getMouseX(), 1, 1, 0, 1.0 / (float) ClientSize.x, currentCenterFreq, currentBandwidth);
} }
} }
} else { } else {
if (activeDemodulator == NULL) { if (activeDemodulator == NULL) {
if (lastActiveDemodulator) { if (lastActiveDemodulator) {
if (isNew) { if (isNew) {
glContext->DrawDemod(lastActiveDemodulator); glContext->DrawDemod(lastActiveDemodulator, 1, 1, 1, currentCenterFreq, currentBandwidth);
glContext->DrawFreqSelector(mTracker.getMouseX(), 0, 1, 0); glContext->DrawFreqSelector(mTracker.getMouseX(), 0, 1, 0, 0, currentCenterFreq, currentBandwidth);
} else { } else {
glContext->DrawDemod(lastActiveDemodulator, 1, 0, 0); glContext->DrawDemod(lastActiveDemodulator, 1, 0, 0, currentCenterFreq, currentBandwidth);
glContext->DrawFreqSelector(mTracker.getMouseX(), 1, 1, 0); glContext->DrawFreqSelector(mTracker.getMouseX(), 1, 1, 0, 0, currentCenterFreq, currentBandwidth);
} }
} else { } else {
glContext->DrawFreqSelector(mTracker.getMouseX(), 1, 1, 0); glContext->DrawFreqSelector(mTracker.getMouseX(), 1, 1, 0, 0, currentCenterFreq, currentBandwidth);
} }
} else { } else {
if (lastActiveDemodulator) { 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 { } else {
if (activeDemodulator) { if (activeDemodulator) {
glContext->DrawDemod(activeDemodulator); glContext->DrawDemod(activeDemodulator, 1, 1, 1, currentCenterFreq, currentBandwidth);
} }
if (lastActiveDemodulator) { 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]) { if (activeDemodulator == demods[i] || lastActiveDemodulator == demods[i]) {
continue; continue;
} }
glContext->DrawDemod(demods[i]); glContext->DrawDemod(demods[i], 1, 1, 1, currentCenterFreq, currentBandwidth);
} }
glContext->EndDraw(); glContext->EndDraw();
@ -144,43 +172,78 @@ void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
} }
void WaterfallCanvas::OnKeyUp(wxKeyEvent& event) { void WaterfallCanvas::OnKeyUp(wxKeyEvent& event) {
InteractiveCanvas::OnKeyUp(event);
shiftDown = event.ShiftDown(); shiftDown = event.ShiftDown();
altDown = event.AltDown(); altDown = event.AltDown();
ctrlDown = event.ControlDown(); ctrlDown = event.ControlDown();
// switch (event.GetKeyCode()) { switch (event.GetKeyCode()) {
// } case 'A':
zoom = 0;
break;
case 'Z':
zoom = 0;
break;
}
} }
void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) { void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) {
InteractiveCanvas::OnKeyDown(event);
float angle = 5.0; float angle = 5.0;
shiftDown = event.ShiftDown();
altDown = event.AltDown();
ctrlDown = event.ControlDown();
DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getActiveDemodulator(); DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getActiveDemodulator();
unsigned int freq; unsigned int freq;
unsigned int bw;
switch (event.GetKeyCode()) { switch (event.GetKeyCode()) {
case 'A':
zoom = 1;
break;
case 'Z':
zoom = -1;
break;
case WXK_RIGHT: case WXK_RIGHT:
freq = wxGetApp().getFrequency(); freq = wxGetApp().getFrequency();
if (shiftDown) { if (shiftDown) {
freq += SRATE * 10; freq += SRATE * 10;
if (isView) {
SetView(center_freq + SRATE * 10, GetBandwidth());
if (spectrumCanvas) {
spectrumCanvas->SetView(GetCenterFrequency(), GetBandwidth());
}
}
} else { } else {
freq += SRATE / 2; freq += SRATE / 2;
if (isView) {
SetView(center_freq + SRATE / 2, GetBandwidth());
if (spectrumCanvas) {
spectrumCanvas->SetView(GetCenterFrequency(), GetBandwidth());
}
}
} }
wxGetApp().setFrequency(freq); wxGetApp().setFrequency(freq);
((wxFrame*) parent)->GetStatusBar()->SetStatusText(wxString::Format(wxT("Set center frequency: %i"), freq)); setStatusText("Set center frequency: %s", freq);
break; break;
case WXK_LEFT: case WXK_LEFT:
freq = wxGetApp().getFrequency(); freq = wxGetApp().getFrequency();
if (shiftDown) { if (shiftDown) {
freq -= SRATE * 10; freq -= SRATE * 10;
if (isView) {
SetView(center_freq - SRATE * 10, GetBandwidth());
if (spectrumCanvas) {
spectrumCanvas->SetView(GetCenterFrequency(), GetBandwidth());
}
}
} else { } else {
freq -= SRATE / 2; freq -= SRATE / 2;
if (isView) {
SetView(center_freq - SRATE / 2, GetBandwidth());
if (spectrumCanvas) {
spectrumCanvas->SetView(GetCenterFrequency(), GetBandwidth());
}
}
} }
wxGetApp().setFrequency(freq); wxGetApp().setFrequency(freq);
((wxFrame*) parent)->GetStatusBar()->SetStatusText(wxString::Format(wxT("Set center frequency: %i"), freq)); setStatusText("Set center frequency: %s", freq);
break; break;
case 'D': case 'D':
case WXK_DELETE: case WXK_DELETE:
@ -216,49 +279,207 @@ void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) {
} }
} }
void WaterfallCanvas::setData(std::vector<liquid_float_complex> *data) { void WaterfallCanvas::setData(DemodulatorThreadIQData *input) {
if (!input) {
if (data && data->size()) { return;
if (spectrum_points.size() < FFT_SIZE * 2) {
spectrum_points.resize(FFT_SIZE * 2);
} }
for (int i = 0; i < FFT_SIZE; i++) { 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<liquid_float_complex> *data = &input->data;
if (data && data->size()) {
// 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);
}
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][0] = (*data)[i].real;
in[i][1] = (*data)[i].imag; 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); fftw_execute(plan);
double fft_ceil = 0, fft_floor = 1; double fft_ceil = 0, fft_floor = 1;
if (fft_result.size() < FFT_SIZE) { if (fft_result.size() < fft_size) {
fft_result.resize(FFT_SIZE); fft_result.resize(fft_size);
fft_result_ma.resize(FFT_SIZE); fft_result_ma.resize(fft_size);
fft_result_maa.resize(FFT_SIZE); fft_result_maa.resize(fft_size);
} }
int n; 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; n = (i == 0) ? 1 : i;
double a = out[n][0]; double a = out[n][0];
double b = out[n][1]; double b = out[n][1];
double c = sqrt(a * a + b * b); 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 x = out[FFT_SIZE / 2 + n][0]; double y = out[fft_size / 2 + n][1];
double y = out[FFT_SIZE / 2 + n][1];
double z = sqrt(x * x + y * y); double z = sqrt(x * x + y * y);
fft_result[i] = (z); 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++) {
if (isView) {
for (int i = 0, iMax = FFT_SIZE; i < iMax; i++) { 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_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; fft_result_ma[i] += (fft_result[i] - fft_result_ma[i]) * 0.65;
}
if (fft_result_maa[i] > fft_ceil) { if (fft_result_maa[i] > fft_ceil) {
fft_ceil = fft_result_maa[i]; fft_ceil = fft_result_maa[i];
@ -271,37 +492,30 @@ void WaterfallCanvas::setData(std::vector<liquid_float_complex> *data) {
fft_ceil += 1; fft_ceil += 1;
fft_floor -= 1; fft_floor -= 1;
fft_ceil_ma = fft_ceil_ma + (fft_ceil - fft_ceil_ma) * 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.01; 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_ma = fft_floor_ma + (fft_floor - fft_floor_ma) * 0.05;
fft_floor_maa = fft_floor_maa + (fft_floor_ma - fft_floor_maa) * 0.01; fft_floor_maa = fft_floor_maa + (fft_floor_ma - fft_floor_maa) * 0.05;
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)); 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] = ((float) i / (float) iMax);
spectrum_points[i * 2 + 1] = v; spectrum_points[i * 2 + 1] = v;
} }
if (spectrumCanvas) {
spectrumCanvas->spectrum_points.assign(spectrum_points.begin(), spectrum_points.end());
}
} }
} }
void WaterfallCanvas::OnIdle(wxIdleEvent &event) { void WaterfallCanvas::OnIdle(wxIdleEvent &event) {
// timer.update();
// frameTimer += timer.lastUpdateSeconds();
// if (frameTimer > 1.0/30.0) {
Refresh(false); Refresh(false);
// frameTimer = 0;
// }
} }
void WaterfallCanvas::mouseMoved(wxMouseEvent& event) { void WaterfallCanvas::mouseMoved(wxMouseEvent& event) {
mTracker.OnMouseMoved(event); InteractiveCanvas::mouseMoved(event);
shiftDown = event.ShiftDown();
altDown = event.AltDown();
ctrlDown = event.ControlDown();
DemodulatorInstance *demod = wxGetApp().getDemodMgr().getActiveDemodulator(); DemodulatorInstance *demod = wxGetApp().getDemodMgr().getActiveDemodulator();
if (mTracker.mouseDown()) { if (mTracker.mouseDown()) {
@ -310,7 +524,7 @@ void WaterfallCanvas::mouseMoved(wxMouseEvent& event) {
} }
if (dragState == WF_DRAG_BANDWIDTH_LEFT || dragState == WF_DRAG_BANDWIDTH_RIGHT) { 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) { if (dragState == WF_DRAG_BANDWIDTH_LEFT) {
bwDiff = -bwDiff; bwDiff = -bwDiff;
@ -323,19 +537,20 @@ void WaterfallCanvas::mouseMoved(wxMouseEvent& event) {
DemodulatorThreadCommand command; DemodulatorThreadCommand command;
command.cmd = DemodulatorThreadCommand::DEMOD_THREAD_CMD_SET_BANDWIDTH; command.cmd = DemodulatorThreadCommand::DEMOD_THREAD_CMD_SET_BANDWIDTH;
activeDemodulatorBandwidth = activeDemodulatorBandwidth + bwDiff; activeDemodulatorBandwidth = activeDemodulatorBandwidth + bwDiff;
if (activeDemodulatorBandwidth < 2000) {
activeDemodulatorBandwidth = 2000;
}
if (activeDemodulatorBandwidth > SRATE) { if (activeDemodulatorBandwidth > SRATE) {
activeDemodulatorBandwidth = SRATE; activeDemodulatorBandwidth = SRATE;
} }
if (activeDemodulatorBandwidth < MIN_BANDWIDTH) {
activeDemodulatorBandwidth = MIN_BANDWIDTH;
}
command.int_value = activeDemodulatorBandwidth; command.int_value = activeDemodulatorBandwidth;
demod->getCommandQueue()->push(command); demod->getCommandQueue()->push(command);
setStatusText("Set demodulator bandwidth: %s", activeDemodulatorBandwidth);
} }
if (dragState == WF_DRAG_FREQUENCY) { if (dragState == WF_DRAG_FREQUENCY) {
int bwDiff = (int) (mTracker.getDeltaMouseX() * (float) SRATE); int bwDiff = (int) (mTracker.getDeltaMouseX() * (float) GetBandwidth());
if (!activeDemodulatorFrequency) { if (!activeDemodulatorFrequency) {
activeDemodulatorFrequency = demod->getParams().frequency; activeDemodulatorFrequency = demod->getParams().frequency;
@ -349,6 +564,8 @@ void WaterfallCanvas::mouseMoved(wxMouseEvent& event) {
demod->getCommandQueue()->push(command); demod->getCommandQueue()->push(command);
demod->updateLabel(activeDemodulatorFrequency); demod->updateLabel(activeDemodulatorFrequency);
setStatusText("Set demodulator frequency: %s", activeDemodulatorFrequency);
} }
} else { } else {
int freqPos = GetFrequencyAt(mTracker.getMouseX()); int freqPos = GetFrequencyAt(mTracker.getMouseX());
@ -361,9 +578,14 @@ void WaterfallCanvas::mouseMoved(wxMouseEvent& event) {
nextDragState = WF_DRAG_RANGE; nextDragState = WF_DRAG_RANGE;
mTracker.setVertDragLock(true); mTracker.setVertDragLock(true);
mTracker.setHorizDragLock(false); 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()) { } else if (demodsHover->size()) {
int hovered = -1; int hovered = -1;
int near_dist = SRATE; int near_dist = GetBandwidth();
DemodulatorInstance *activeDemodulator = NULL; DemodulatorInstance *activeDemodulator = NULL;
@ -407,16 +629,23 @@ void WaterfallCanvas::mouseMoved(wxMouseEvent& event) {
mTracker.setVertDragLock(true); mTracker.setVertDragLock(true);
mTracker.setHorizDragLock(false); mTracker.setHorizDragLock(false);
setStatusText("Click and drag to change demodulator bandwidth. D to delete, SPACE for stereo.");
} else { } else {
SetCursor(wxCURSOR_SIZING); SetCursor(wxCURSOR_SIZING);
nextDragState = WF_DRAG_FREQUENCY; nextDragState = WF_DRAG_FREQUENCY;
mTracker.setVertDragLock(true); mTracker.setVertDragLock(true);
mTracker.setHorizDragLock(false); mTracker.setHorizDragLock(false);
setStatusText("Click and drag to change demodulator frequency. D to delete, SPACE for stereo.");
} }
} else { } else {
SetCursor(wxCURSOR_CROSS); SetCursor(wxCURSOR_CROSS);
nextDragState = WF_DRAG_NONE; 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; delete demodsHover;
@ -424,12 +653,9 @@ void WaterfallCanvas::mouseMoved(wxMouseEvent& event) {
} }
void WaterfallCanvas::mouseDown(wxMouseEvent& event) { void WaterfallCanvas::mouseDown(wxMouseEvent& event) {
mTracker.OnMouseDown(event); InteractiveCanvas::mouseDown(event);
dragState = nextDragState;
shiftDown = event.ShiftDown(); dragState = nextDragState;
altDown = event.AltDown();
ctrlDown = event.ControlDown();
if (dragState && dragState != WF_DRAG_RANGE) { if (dragState && dragState != WF_DRAG_RANGE) {
wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getActiveDemodulator(), false); wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getActiveDemodulator(), false);
@ -440,15 +666,11 @@ void WaterfallCanvas::mouseDown(wxMouseEvent& event) {
} }
void WaterfallCanvas::mouseWheelMoved(wxMouseEvent& event) { void WaterfallCanvas::mouseWheelMoved(wxMouseEvent& event) {
mTracker.OnMouseWheelMoved(event); InteractiveCanvas::mouseWheelMoved(event);
} }
void WaterfallCanvas::mouseReleased(wxMouseEvent& event) { void WaterfallCanvas::mouseReleased(wxMouseEvent& event) {
mTracker.OnMouseReleased(event); InteractiveCanvas::mouseReleased(event);
shiftDown = event.ShiftDown();
altDown = event.AltDown();
ctrlDown = event.ControlDown();
bool isNew = shiftDown bool isNew = shiftDown
|| (wxGetApp().getDemodMgr().getLastActiveDemodulator() && !wxGetApp().getDemodMgr().getLastActiveDemodulator()->isActive()); || (wxGetApp().getDemodMgr().getLastActiveDemodulator() && !wxGetApp().getDemodMgr().getLastActiveDemodulator()->isActive());
@ -460,8 +682,8 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) {
if (mTracker.getOriginDeltaMouseX() == 0 && mTracker.getOriginDeltaMouseY() == 0) { if (mTracker.getOriginDeltaMouseX() == 0 && mTracker.getOriginDeltaMouseY() == 0) {
float pos = mTracker.getMouseX(); float pos = mTracker.getMouseX();
int center_freq = wxGetApp().getFrequency(); int input_center_freq = GetCenterFrequency();
int freq = center_freq - (int) (0.5 * (float) SRATE) + (int) ((float) pos * (float) SRATE); int freq = input_center_freq - (int) (0.5 * (float) GetBandwidth()) + (int) ((float) pos * (float) GetBandwidth());
if (dragState == WF_DRAG_NONE) { if (dragState == WF_DRAG_NONE) {
if (!isNew && wxGetApp().getDemodMgr().getDemodulators().size()) { if (!isNew && wxGetApp().getDemodMgr().getDemodulators().size()) {
@ -472,6 +694,10 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) {
if (DemodulatorInstance *last = wxGetApp().getDemodMgr().getLastActiveDemodulator()) { if (DemodulatorInstance *last = wxGetApp().getDemodMgr().getLastActiveDemodulator()) {
demod->getParams().bandwidth = last->getParams().bandwidth; demod->getParams().bandwidth = last->getParams().bandwidth;
demod->setDemodulatorType(last->getDemodulatorType());
demod->setSquelchLevel(last->getSquelchLevel());
demod->setSquelchEnabled(last->isSquelchEnabled());
demod->setStereo(last->isStereo());
} }
demod->run(); demod->run();
@ -492,9 +718,7 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) {
command.int_value = freq; command.int_value = freq;
demod->getCommandQueue()->push(command); demod->getCommandQueue()->push(command);
((wxFrame*) parent)->GetStatusBar()->SetStatusText( setStatusText("New demodulator at frequency: %s", freq);
wxString::Format(wxT("Set demodulator frequency: %s"),
wxNumberFormatter::ToString((long) freq, wxNumberFormatter::Style_WithThousandsSep)));
wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getLastActiveDemodulator(), false); wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getLastActiveDemodulator(), false);
SetCursor(wxCURSOR_SIZING); SetCursor(wxCURSOR_SIZING);
@ -502,10 +726,6 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) {
mTracker.setVertDragLock(true); mTracker.setVertDragLock(true);
mTracker.setHorizDragLock(false); mTracker.setHorizDragLock(false);
} else { } 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); wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getActiveDemodulator(), false);
nextDragState = WF_DRAG_FREQUENCY; nextDragState = WF_DRAG_FREQUENCY;
} }
@ -513,15 +733,15 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) {
float width = mTracker.getOriginDeltaMouseX(); float width = mTracker.getOriginDeltaMouseX();
float pos = mTracker.getOriginMouseX() + width / 2.0; float pos = mTracker.getOriginMouseX() + width / 2.0;
int center_freq = wxGetApp().getFrequency(); int input_center_freq = GetCenterFrequency();
int freq = center_freq - (int) (0.5 * (float) SRATE) + (int) ((float) pos * (float) SRATE); unsigned int freq = input_center_freq - (int) (0.5 * (float) GetBandwidth()) + (int) ((float) pos * (float) GetBandwidth());
int bandwidth = (int) (fabs(width) * (float) SRATE); unsigned int bw = (unsigned int) (fabs(width) * (float) GetBandwidth());
if (bandwidth < 2000) { if (bw < MIN_BANDWIDTH) {
bandwidth = 2000; bw = MIN_BANDWIDTH;
} }
if (!bandwidth) { if (!bw) {
dragState = WF_DRAG_NONE; dragState = WF_DRAG_NONE;
return; return;
} }
@ -531,8 +751,13 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) {
} else { } else {
demod = wxGetApp().getDemodMgr().newThread(); demod = wxGetApp().getDemodMgr().newThread();
demod->getParams().frequency = freq; 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(); demod->run();
wxGetApp().bindDemodulator(demod); wxGetApp().bindDemodulator(demod);
@ -544,9 +769,7 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) {
return; return;
} }
((wxFrame*) parent)->GetStatusBar()->SetStatusText( setStatusText("New demodulator at frequency: %s", freq);
wxString::Format(wxT("Set demodulator frequency: %s"),
wxNumberFormatter::ToString((long) freq, wxNumberFormatter::Style_WithThousandsSep)));
wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getLastActiveDemodulator(), false); wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getLastActiveDemodulator(), false);
demod->updateLabel(freq); demod->updateLabel(freq);
@ -556,7 +779,7 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) {
command.int_value = freq; command.int_value = freq;
demod->getCommandQueue()->push(command); demod->getCommandQueue()->push(command);
command.cmd = DemodulatorThreadCommand::DEMOD_THREAD_CMD_SET_BANDWIDTH; command.cmd = DemodulatorThreadCommand::DEMOD_THREAD_CMD_SET_BANDWIDTH;
command.int_value = bandwidth; command.int_value = bw;
demod->getCommandQueue()->push(command); demod->getCommandQueue()->push(command);
} }
@ -564,12 +787,13 @@ void WaterfallCanvas::mouseReleased(wxMouseEvent& event) {
} }
void WaterfallCanvas::mouseLeftWindow(wxMouseEvent& event) { void WaterfallCanvas::mouseLeftWindow(wxMouseEvent& event) {
mTracker.OnMouseLeftWindow(event); InteractiveCanvas::mouseLeftWindow(event);
SetCursor(wxCURSOR_CROSS); SetCursor(wxCURSOR_CROSS);
wxGetApp().getDemodMgr().setActiveDemodulator(NULL); wxGetApp().getDemodMgr().setActiveDemodulator(NULL);
} }
void WaterfallCanvas::mouseEnterWindow(wxMouseEvent& event) { void WaterfallCanvas::mouseEnterWindow(wxMouseEvent& event) {
mTracker.OnMouseEnterWindow(event); InteractiveCanvas::mouseEnterWindow(event);
SetCursor(wxCURSOR_CROSS); SetCursor(wxCURSOR_CROSS);
} }

View File

@ -6,23 +6,29 @@
#include <vector> #include <vector>
#include <queue> #include <queue>
#include "InteractiveCanvas.h"
#include "WaterfallContext.h" #include "WaterfallContext.h"
#include "MouseTracker.h" #include "MouseTracker.h"
#include "SpectrumCanvas.h"
#include "fftw3.h" #include "fftw3.h"
#include "Timer.h"
class WaterfallCanvas: public wxGLCanvas { class WaterfallCanvas: public InteractiveCanvas {
public: public:
enum DragState { enum DragState {
WF_DRAG_NONE, WF_DRAG_BANDWIDTH_LEFT, WF_DRAG_BANDWIDTH_RIGHT, WF_DRAG_FREQUENCY, WF_DRAG_RANGE WF_DRAG_NONE, WF_DRAG_BANDWIDTH_LEFT, WF_DRAG_BANDWIDTH_RIGHT, WF_DRAG_FREQUENCY, WF_DRAG_RANGE
}; };
WaterfallCanvas(wxWindow *parent, int *attribList = NULL); WaterfallCanvas(wxWindow *parent, int *attribList = NULL);
void Setup(int fft_size_in, int waterfall_lines_in);
~WaterfallCanvas(); ~WaterfallCanvas();
void setData(std::vector<liquid_float_complex> *data); void setData(DemodulatorThreadIQData *input);
int GetFrequencyAt(float x);
DragState getDragState();
DragState getNextDragState();
void attachSpectrumCanvas(SpectrumCanvas *canvas_in);
private: private:
void OnPaint(wxPaintEvent& event); void OnPaint(wxPaintEvent& event);
@ -38,23 +44,20 @@ private:
void mouseEnterWindow(wxMouseEvent& event); void mouseEnterWindow(wxMouseEvent& event);
void mouseLeftWindow(wxMouseEvent& event); void mouseLeftWindow(wxMouseEvent& event);
wxWindow *parent; SpectrumCanvas *spectrumCanvas;
std::vector<float> spectrum_points; std::vector<float> spectrum_points;
fftw_complex *in, *out; fftw_complex *in, *out;
fftw_plan plan; fftw_plan plan;
float fft_ceil_ma, fft_ceil_maa; double fft_ceil_ma, fft_ceil_maa;
float fft_floor_ma, fft_floor_maa; double fft_floor_ma, fft_floor_maa;
std::vector<float> fft_result; std::vector<double> fft_result;
std::vector<float> fft_result_ma; std::vector<double> fft_result_ma;
std::vector<float> fft_result_maa; std::vector<double> fft_result_maa;
WaterfallContext *glContext; WaterfallContext *glContext;
Timer timer;
float frameTimer;
MouseTracker mTracker;
int activeDemodulatorBandwidth; int activeDemodulatorBandwidth;
int activeDemodulatorFrequency; int activeDemodulatorFrequency;
@ -62,7 +65,20 @@ private:
DragState dragState; DragState dragState;
DragState nextDragState; 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<liquid_float_complex> shift_buffer;
std::vector<liquid_float_complex> resampler_buffer;
// event table // event table
wxDECLARE_EVENT_TABLE(); wxDECLARE_EVENT_TABLE();
}; };

View File

@ -3,7 +3,31 @@
#include "CubicSDR.h" #include "CubicSDR.h"
WaterfallContext::WaterfallContext(WaterfallCanvas *canvas, wxGLContext *sharedContext) : 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_CULL_FACE);
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
@ -23,26 +47,19 @@ WaterfallContext::WaterfallContext(WaterfallCanvas *canvas, wxGLContext *sharedC
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); 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); glPixelTransferi(GL_MAP_COLOR, GL_TRUE);
glPixelMapfv(GL_PIXEL_MAP_I_TO_R, 256, &(grad.getRed())[0]); 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_G, 256, &(grad.getGreen())[0]);
glPixelMapfv(GL_PIXEL_MAP_I_TO_B, 256, &(grad.getBlue())[0]); glPixelMapfv(GL_PIXEL_MAP_I_TO_B, 256, &(grad.getBlue())[0]);
} }
void WaterfallContext::Draw(std::vector<float> &points) { void WaterfallContext::Draw(std::vector<float> &points) {
if (points.size()) { 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 v = points[i * 2 + 1];
float wv = v; float wv = v;
@ -57,7 +74,7 @@ void WaterfallContext::Draw(std::vector<float> &points) {
glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, waterfall); 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); glColor3f(1.0, 1.0, 1.0);

View File

@ -3,8 +3,6 @@
#include "PrimaryGLContext.h" #include "PrimaryGLContext.h"
#include "Gradient.h" #include "Gradient.h"
#define NUM_WATERFALL_LINES 512
class WaterfallCanvas; class WaterfallCanvas;
class WaterfallContext: public PrimaryGLContext { class WaterfallContext: public PrimaryGLContext {
@ -12,9 +10,12 @@ public:
WaterfallContext(WaterfallCanvas *canvas, wxGLContext *sharedContext); WaterfallContext(WaterfallCanvas *canvas, wxGLContext *sharedContext);
void Draw(std::vector<float> &points); void Draw(std::vector<float> &points);
void Setup(int fft_size_in, int num_waterfall_lines_in);
private: private:
Gradient grad; Gradient grad;
GLuint waterfall; GLuint waterfall;
unsigned char waterfall_tex[FFT_SIZE * NUM_WATERFALL_LINES]; unsigned char *waterfall_tex;
int fft_size;
int waterfall_lines;
}; };