diff --git a/core/src/dsp/convertion.h b/core/src/dsp/convertion.h new file mode 100644 index 00000000..705f9d26 --- /dev/null +++ b/core/src/dsp/convertion.h @@ -0,0 +1,138 @@ +#pragma once +#include + +namespace dsp { + class ComplexToStereo : public generic_block { + public: + ComplexToStereo() {} + + ComplexToStereo(stream* in) { init(in); } + + ~ComplexToStereo() { generic_block::stop(); } + + static_assert(sizeof(complex_t) == sizeof(stereo_t)); + + void init(stream* in) { + _in = in; + generic_block::registerInput(_in); + generic_block::registerOutput(&out); + } + + void setInput(stream* in) { + std::lock_guard lck(generic_block::ctrlMtx); + generic_block::tempStop(); + generic_block::unregisterInput(_in); + _in = in; + generic_block::registerInput(_in); + generic_block::tempStart(); + } + + int run() { + count = _in->read(); + if (count < 0) { return -1; } + + if (out.aquire() < 0) { return -1; } + memcpy(out.data, _in->data, count * sizeof(complex_t)); + + _in->flush(); + out.write(count); + return count; + } + + stream out; + + private: + float avg; + int count; + stream* _in; + + }; + + class ComplexToReal : public generic_block { + public: + ComplexToReal() {} + + ComplexToReal(stream* in) { init(in); } + + ~ComplexToReal() { generic_block::stop(); } + + void init(stream* in) { + _in = in; + generic_block::registerInput(_in); + generic_block::registerOutput(&out); + } + + void setInput(stream* in) { + std::lock_guard lck(generic_block::ctrlMtx); + generic_block::tempStop(); + generic_block::unregisterInput(_in); + _in = in; + generic_block::registerInput(_in); + generic_block::tempStart(); + } + + int run() { + count = _in->read(); + if (count < 0) { return -1; } + + if (out.aquire() < 0) { return -1; } + volk_32fc_deinterleave_real_32f(out.data, (lv_32fc_t*)_in->data, count); + + _in->flush(); + out.write(count); + return count; + } + + stream out; + + private: + float avg; + int count; + stream* _in; + + }; + + class ComplexToImag : public generic_block { + public: + ComplexToImag() {} + + ComplexToImag(stream* in) { init(in); } + + ~ComplexToImag() { generic_block::stop(); } + + void init(stream* in) { + _in = in; + generic_block::registerInput(_in); + generic_block::registerOutput(&out); + } + + void setInput(stream* in) { + std::lock_guard lck(generic_block::ctrlMtx); + generic_block::tempStop(); + generic_block::unregisterInput(_in); + _in = in; + generic_block::registerInput(_in); + generic_block::tempStart(); + } + + int run() { + count = _in->read(); + if (count < 0) { return -1; } + + if (out.aquire() < 0) { return -1; } + volk_32fc_deinterleave_imag_32f(out.data, (lv_32fc_t*)_in->data, count); + + _in->flush(); + out.write(count); + return count; + } + + stream out; + + private: + float avg; + int count; + stream* _in; + + }; +} \ No newline at end of file diff --git a/core/src/dsp/processing.h b/core/src/dsp/processing.h index e9126b23..0dcbaf00 100644 --- a/core/src/dsp/processing.h +++ b/core/src/dsp/processing.h @@ -5,13 +5,16 @@ #include namespace dsp { - class FrequencyXlator : public generic_block { + template + class FrequencyXlator : public generic_block> { public: FrequencyXlator() {} FrequencyXlator(stream* in, float sampleRate, float freq) { init(in, sampleRate, freq); } - ~FrequencyXlator() { generic_block::stop(); } + ~FrequencyXlator() { + generic_block>::stop(); + } void init(stream* in, float sampleRate, float freq) { _in = in; @@ -19,17 +22,17 @@ namespace dsp { _freq = freq; phase = lv_cmake(1.0f, 0.0f); phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI)); - generic_block::registerInput(_in); - generic_block::registerOutput(&out); + generic_block>::registerInput(_in); + generic_block>::registerOutput(&out); } void setInputSize(stream* in) { - std::lock_guard lck(generic_block::ctrlMtx); - generic_block::tempStop(); - generic_block::unregisterInput(_in); + std::lock_guard lck(generic_block>::ctrlMtx); + generic_block>::tempStop(); + generic_block>::unregisterInput(_in); _in = in; - generic_block::registerInput(_in); - generic_block::tempStart(); + generic_block>::registerInput(_in); + generic_block>::tempStart(); } void setSampleRate(float sampleRate) { @@ -58,7 +61,13 @@ namespace dsp { if (out.aquire() < 0) { return -1; } - volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.data, (lv_32fc_t*)_in->data, phaseDelta, &phase, count); + // TODO: Do float xlation + if constexpr (std::is_same_v) { + spdlog::error("XLATOR NOT IMPLEMENTED FOR FLOAT"); + } + if constexpr (std::is_same_v) { + volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.data, (lv_32fc_t*)_in->data, phaseDelta, &phase, count); + } _in->flush(); out.write(count); diff --git a/core/src/dsp/vfo.h b/core/src/dsp/vfo.h index 78b596df..251bc3d9 100644 --- a/core/src/dsp/vfo.h +++ b/core/src/dsp/vfo.h @@ -105,7 +105,7 @@ namespace dsp { float _offset, _inSampleRate, _outSampleRate, _bandWidth; filter_window::BlackmanWindow win; stream* _in; - FrequencyXlator xlator; + FrequencyXlator xlator; PolyphaseResampler resamp; }; diff --git a/core/src/duktape/duk_config.h b/core/src/duktape/duk_config.h index c79a05cd..7a1b6b89 100644 --- a/core/src/duktape/duk_config.h +++ b/core/src/duktape/duk_config.h @@ -62,7 +62,9 @@ #if !defined(DUK_CONFIG_H_INCLUDED) #define DUK_CONFIG_H_INCLUDED +#ifdef _WIN32 #define DUK_USE_DATE_NOW_WINDOWS +#endif /* * Intermediate helper defines diff --git a/core/src/gui/main_window.cpp b/core/src/gui/main_window.cpp index e1645a45..cdc7d262 100644 --- a/core/src/gui/main_window.cpp +++ b/core/src/gui/main_window.cpp @@ -125,13 +125,14 @@ void windowInit() { // Add "select folder" option for the file source // Fix SSB demod // FIX AUDIO ISSUE ON BOTH LINUX AND SOMETIMES WINDOWS (probly the ring buffer, though double buffering could help) - // Rewrite radio module with CW and RAW modes + // Add CW mode to radio module // Add default main config to avoid having to ship one // Have a good directory system on both linux and windows // Switch to double buffering // TODO for 0.2.6 - // And a module add/remove/change order menu + // Add a module add/remove/change order menu + // Change the way fft samples are stored to make it less CPU intensive // Update UI settings LoadingScreen::show("Loading configuration"); diff --git a/radio/src/am_demod.h b/radio/src/am_demod.h new file mode 100644 index 00000000..10ff1e11 --- /dev/null +++ b/radio/src/am_demod.h @@ -0,0 +1,140 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +class AMDemodulator : public Demodulator { +public: + AMDemodulator() {} + AMDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + init(prefix, vfo, audioSampleRate, bandWidth); + } + + void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + uiPrefix = prefix; + _vfo = vfo; + audioSampRate = audioSampleRate; + bw = bandWidth; + + demod.init(_vfo->output); + + agc.init(&demod.out, 1.0f / 125.0f); + + float audioBW = std::min(audioSampRate / 2.0f, bw / 2.0f); + win.init(audioBW, audioBW, bbSampRate); + resamp.init(&agc.out, &win, bbSampRate, audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + resamp.updateWindow(&win); + + m2s.init(&resamp.out); + } + + void start() { + demod.start(); + agc.start(); + resamp.start(); + m2s.start(); + running = true; + } + + void stop() { + demod.stop(); + agc.stop(); + resamp.stop(); + m2s.stop(); + running = false; + } + + bool isRunning() { + return running; + } + + void select() { + _vfo->setSampleRate(bbSampRate, bw); + _vfo->setSnapInterval(snapInterval); + _vfo->setReference(ImGui::WaterfallVFO::REF_CENTER); + } + + void setVFO(VFOManager::VFO* vfo) { + _vfo = vfo; + demod.setInput(_vfo->output); + } + + VFOManager::VFO* getVFO() { + return _vfo; + } + + void setAudioSampleRate(float sampleRate) { + if (running) { + resamp.stop(); + } + audioSampRate = sampleRate; + float audioBW = std::min(audioSampRate / 2.0f, bw / 2.0f); + resamp.setOutSampleRate(audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + win.setCutoff(audioBW); + win.setTransWidth(audioBW); + resamp.updateWindow(&win); + if (running) { + resamp.start(); + } + } + + float getAudioSampleRate() { + return audioSampRate; + } + + dsp::stream* getOutput() { + return &m2s.out; + } + + void showMenu() { + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + ImGui::SetNextItemWidth(menuWidth); + if (ImGui::InputFloat(("##_radio_am_bw_" + uiPrefix).c_str(), &bw, 1, 100, 0)) { + bw = std::clamp(bw, bwMin, bwMax); + setBandwidth(bw); + } + + ImGui::SetNextItemWidth(menuWidth - ImGui::CalcTextSize("Snap Interval").x - 8); + ImGui::Text("Snap Interval"); + ImGui::SameLine(); + if (ImGui::InputFloat(("##_radio_am_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, 0)) { + setSnapInterval(snapInterval); + } + } + +private: + void setBandwidth(float bandWidth) { + bw = bandWidth; + _vfo->setBandwidth(bw); + } + + void setSnapInterval(float snapInt) { + snapInterval = snapInt; + _vfo->setSnapInterval(snapInterval); + } + + const float bwMax = 15000; + const float bwMin = 6000; + const float bbSampRate = 15000; + + std::string uiPrefix; + float snapInterval = 1000; + float audioSampRate = 48000; + float bw = 12500; + bool running = false; + + VFOManager::VFO* _vfo; + dsp::AMDemod demod; + dsp::AGC agc; + dsp::filter_window::BlackmanWindow win; + dsp::PolyphaseResampler resamp; + dsp::MonoToStereo m2s; + +}; \ No newline at end of file diff --git a/radio/src/cw_demod.h b/radio/src/cw_demod.h new file mode 100644 index 00000000..0b76a61c --- /dev/null +++ b/radio/src/cw_demod.h @@ -0,0 +1,151 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CWDemodulator : public Demodulator { +public: + CWDemodulator() {} + CWDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + init(prefix, vfo, audioSampleRate, bandWidth); + } + + void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + uiPrefix = prefix; + _vfo = vfo; + audioSampRate = audioSampleRate; + bw = bandWidth; + + float audioBW = std::min(audioSampRate / 2.0f, bw / 2.0f); + win.init(audioBW, audioBW, bbSampRate); + resamp.init(vfo->output, &win, bbSampRate, audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + resamp.updateWindow(&win); + + xlator.init(&resamp.out, audioSampleRate, 1000); + + c2r.init(&xlator.out); + + agc.init(&c2r.out, 1.0f / 125.0f); + + m2s.init(&agc.out); + } + + void start() { + resamp.start(); + xlator.start(); + c2r.start(); + agc.start(); + m2s.start(); + running = true; + } + + void stop() { + resamp.stop(); + xlator.stop(); + c2r.stop(); + agc.stop(); + m2s.stop(); + running = false; + } + + bool isRunning() { + return running; + } + + void select() { + _vfo->setSampleRate(bbSampRate, bw); + _vfo->setSnapInterval(snapInterval); + _vfo->setReference(ImGui::WaterfallVFO::REF_CENTER); + } + + void setVFO(VFOManager::VFO* vfo) { + _vfo = vfo; + resamp.setInput(_vfo->output); + } + + VFOManager::VFO* getVFO() { + return _vfo; + } + + void setAudioSampleRate(float sampleRate) { + if (running) { + resamp.stop(); + xlator.stop(); + } + audioSampRate = sampleRate; + float audioBW = std::min(audioSampRate / 2.0f, bw / 2.0f); + resamp.setOutSampleRate(audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + win.setCutoff(audioBW); + win.setTransWidth(audioBW); + resamp.updateWindow(&win); + xlator.setSampleRate(audioSampRate); + if (running) { + resamp.start(); + xlator.start(); + } + } + + float getAudioSampleRate() { + return audioSampRate; + } + + dsp::stream* getOutput() { + return &m2s.out; + } + + void showMenu() { + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + ImGui::SetNextItemWidth(menuWidth); + if (ImGui::InputFloat(("##_radio_cw_bw_" + uiPrefix).c_str(), &bw, 1, 100, 0)) { + bw = std::clamp(bw, bwMin, bwMax); + setBandwidth(bw); + } + + ImGui::SetNextItemWidth(menuWidth - ImGui::CalcTextSize("Snap Interval").x - 8); + ImGui::Text("Snap Interval"); + ImGui::SameLine(); + if (ImGui::InputFloat(("##_radio_cw_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, 0)) { + setSnapInterval(snapInterval); + } + } + +private: + void setBandwidth(float bandWidth) { + bw = bandWidth; + _vfo->setBandwidth(bw); + } + + void setSnapInterval(float snapInt) { + snapInterval = snapInt; + _vfo->setSnapInterval(snapInterval); + } + + const float bwMax = 500; + const float bwMin = 100; + const float bbSampRate = 500; + + std::string uiPrefix; + float snapInterval = 10; + float audioSampRate = 48000; + float bw = 200; + bool running = false; + + VFOManager::VFO* _vfo; + dsp::filter_window::BlackmanWindow win; + dsp::PolyphaseResampler resamp; + dsp::FrequencyXlator xlator; + dsp::ComplexToReal c2r; + dsp::AGC agc; + dsp::MonoToStereo m2s; + +}; \ No newline at end of file diff --git a/radio/src/dsb_demod.h b/radio/src/dsb_demod.h new file mode 100644 index 00000000..e4157e86 --- /dev/null +++ b/radio/src/dsb_demod.h @@ -0,0 +1,140 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +class DSBDemodulator : public Demodulator { +public: + DSBDemodulator() {} + DSBDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + init(prefix, vfo, audioSampleRate, bandWidth); + } + + void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + uiPrefix = prefix; + _vfo = vfo; + audioSampRate = audioSampleRate; + bw = bandWidth; + + demod.init(_vfo->output, bbSampRate, bandWidth, dsp::SSBDemod::MODE_DSB); + + agc.init(&demod.out, 1.0f / 125.0f); + + float audioBW = std::min(audioSampRate / 2.0f, bw / 2.0f); + win.init(audioBW, audioBW, bbSampRate); + resamp.init(&agc.out, &win, bbSampRate, audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + resamp.updateWindow(&win); + + m2s.init(&resamp.out); + } + + void start() { + demod.start(); + agc.start(); + resamp.start(); + m2s.start(); + running = true; + } + + void stop() { + demod.stop(); + agc.stop(); + resamp.stop(); + m2s.stop(); + running = false; + } + + bool isRunning() { + return running; + } + + void select() { + _vfo->setSampleRate(bbSampRate, bw); + _vfo->setSnapInterval(snapInterval); + _vfo->setReference(ImGui::WaterfallVFO::REF_CENTER); + } + + void setVFO(VFOManager::VFO* vfo) { + _vfo = vfo; + demod.setInput(_vfo->output); + } + + VFOManager::VFO* getVFO() { + return _vfo; + } + + void setAudioSampleRate(float sampleRate) { + if (running) { + resamp.stop(); + } + audioSampRate = sampleRate; + float audioBW = std::min(audioSampRate / 2.0f, bw / 2.0f); + resamp.setOutSampleRate(audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + win.setCutoff(audioBW); + win.setTransWidth(audioBW); + resamp.updateWindow(&win); + if (running) { + resamp.start(); + } + } + + float getAudioSampleRate() { + return audioSampRate; + } + + dsp::stream* getOutput() { + return &m2s.out; + } + + void showMenu() { + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + ImGui::SetNextItemWidth(menuWidth); + if (ImGui::InputFloat(("##_radio_dsb_bw_" + uiPrefix).c_str(), &bw, 1, 100, 0)) { + bw = std::clamp(bw, bwMin, bwMax); + setBandwidth(bw); + } + + ImGui::SetNextItemWidth(menuWidth - ImGui::CalcTextSize("Snap Interval").x - 8); + ImGui::Text("Snap Interval"); + ImGui::SameLine(); + if (ImGui::InputFloat(("##_radio_dsb_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, 0)) { + setSnapInterval(snapInterval); + } + } + +private: + void setBandwidth(float bandWidth) { + bw = bandWidth; + _vfo->setBandwidth(bw); + } + + void setSnapInterval(float snapInt) { + snapInterval = snapInt; + _vfo->setSnapInterval(snapInterval); + } + + const float bwMax = 12000; + const float bwMin = 1000; + const float bbSampRate = 12000; + + std::string uiPrefix; + float snapInterval = 100; + float audioSampRate = 48000; + float bw = 6000; + bool running = false; + + VFOManager::VFO* _vfo; + dsp::SSBDemod demod; + dsp::AGC agc; + dsp::filter_window::BlackmanWindow win; + dsp::PolyphaseResampler resamp; + dsp::MonoToStereo m2s; + +}; \ No newline at end of file diff --git a/radio/src/fm_demod.h b/radio/src/fm_demod.h new file mode 100644 index 00000000..f078eb91 --- /dev/null +++ b/radio/src/fm_demod.h @@ -0,0 +1,136 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +class FMDemodulator : public Demodulator { +public: + FMDemodulator() {} + FMDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + init(prefix, vfo, audioSampleRate, bandWidth); + } + + void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + uiPrefix = prefix; + _vfo = vfo; + audioSampRate = audioSampleRate; + bw = bandWidth; + + demod.init(_vfo->output, bbSampRate, bandWidth / 2.0f); + + float audioBW = std::min(audioSampleRate / 2.0f, bw / 2.0f); + win.init(audioBW, audioBW, bbSampRate); + resamp.init(&demod.out, &win, bbSampRate, audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + resamp.updateWindow(&win); + + m2s.init(&resamp.out); + } + + void start() { + demod.start(); + resamp.start(); + m2s.start(); + running = true; + } + + void stop() { + demod.stop(); + resamp.stop(); + m2s.stop(); + running = false; + } + + bool isRunning() { + return running; + } + + void select() { + _vfo->setSampleRate(bbSampRate, bw); + _vfo->setSnapInterval(snapInterval); + _vfo->setReference(ImGui::WaterfallVFO::REF_CENTER); + } + + void setVFO(VFOManager::VFO* vfo) { + _vfo = vfo; + demod.setInput(_vfo->output); + } + + VFOManager::VFO* getVFO() { + return _vfo; + } + + void setAudioSampleRate(float sampleRate) { + if (running) { + resamp.stop(); + } + audioSampRate = sampleRate; + float audioBW = std::min(audioSampRate / 2.0f, 16000.0f); + resamp.setOutSampleRate(audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + win.setCutoff(audioBW); + win.setTransWidth(audioBW); + resamp.updateWindow(&win); + if (running) { + resamp.start(); + } + } + + float getAudioSampleRate() { + return audioSampRate; + } + + dsp::stream* getOutput() { + return &m2s.out; + } + + void showMenu() { + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + ImGui::SetNextItemWidth(menuWidth); + if (ImGui::InputFloat(("##_radio_fm_bw_" + uiPrefix).c_str(), &bw, 1, 100, 0)) { + bw = std::clamp(bw, bwMin, bwMax); + setBandwidth(bw); + } + + ImGui::SetNextItemWidth(menuWidth - ImGui::CalcTextSize("Snap Interval").x - 8); + ImGui::Text("Snap Interval"); + ImGui::SameLine(); + if (ImGui::InputFloat(("##_radio_fm_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, 0)) { + setSnapInterval(snapInterval); + } + } + +private: + void setBandwidth(float bandWidth) { + bw = bandWidth; + _vfo->setBandwidth(bw); + demod.setDeviation(bw / 2.0f); + } + + void setSnapInterval(float snapInt) { + snapInterval = snapInt; + _vfo->setSnapInterval(snapInterval); + } + + const float bwMax = 15000; + const float bwMin = 6000; + const float bbSampRate = 12500; + + std::string uiPrefix; + float snapInterval = 10000; + float audioSampRate = 48000; + float bw = 12500; + bool running = false; + + VFOManager::VFO* _vfo; + dsp::FMDemod demod; + dsp::filter_window::BlackmanWindow win; + dsp::PolyphaseResampler resamp; + dsp::MonoToStereo m2s; + +}; \ No newline at end of file diff --git a/radio/src/lsb_demod.h b/radio/src/lsb_demod.h new file mode 100644 index 00000000..014fc6b9 --- /dev/null +++ b/radio/src/lsb_demod.h @@ -0,0 +1,140 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +class LSBDemodulator : public Demodulator { +public: + LSBDemodulator() {} + LSBDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + init(prefix, vfo, audioSampleRate, bandWidth); + } + + void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + uiPrefix = prefix; + _vfo = vfo; + audioSampRate = audioSampleRate; + bw = bandWidth; + + demod.init(_vfo->output, bbSampRate, bandWidth, dsp::SSBDemod::MODE_LSB); + + agc.init(&demod.out, 1.0f / 125.0f); + + float audioBW = std::min(audioSampRate / 2.0f, bw / 2.0f); + win.init(audioBW, audioBW, bbSampRate); + resamp.init(&agc.out, &win, bbSampRate, audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + resamp.updateWindow(&win); + + m2s.init(&resamp.out); + } + + void start() { + demod.start(); + agc.start(); + resamp.start(); + m2s.start(); + running = true; + } + + void stop() { + demod.stop(); + agc.stop(); + resamp.stop(); + m2s.stop(); + running = false; + } + + bool isRunning() { + return running; + } + + void select() { + _vfo->setSampleRate(bbSampRate, bw); + _vfo->setSnapInterval(snapInterval); + _vfo->setReference(ImGui::WaterfallVFO::REF_UPPER); + } + + void setVFO(VFOManager::VFO* vfo) { + _vfo = vfo; + demod.setInput(_vfo->output); + } + + VFOManager::VFO* getVFO() { + return _vfo; + } + + void setAudioSampleRate(float sampleRate) { + if (running) { + resamp.stop(); + } + audioSampRate = sampleRate; + float audioBW = std::min(audioSampRate / 2.0f, bw / 2.0f); + resamp.setOutSampleRate(audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + win.setCutoff(audioBW); + win.setTransWidth(audioBW); + resamp.updateWindow(&win); + if (running) { + resamp.start(); + } + } + + float getAudioSampleRate() { + return audioSampRate; + } + + dsp::stream* getOutput() { + return &m2s.out; + } + + void showMenu() { + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + ImGui::SetNextItemWidth(menuWidth); + if (ImGui::InputFloat(("##_radio_lsb_bw_" + uiPrefix).c_str(), &bw, 1, 100, 0)) { + bw = std::clamp(bw, bwMin, bwMax); + setBandwidth(bw); + } + + ImGui::SetNextItemWidth(menuWidth - ImGui::CalcTextSize("Snap Interval").x - 8); + ImGui::Text("Snap Interval"); + ImGui::SameLine(); + if (ImGui::InputFloat(("##_radio_lsb_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, 0)) { + setSnapInterval(snapInterval); + } + } + +private: + void setBandwidth(float bandWidth) { + bw = bandWidth; + _vfo->setBandwidth(bw); + } + + void setSnapInterval(float snapInt) { + snapInterval = snapInt; + _vfo->setSnapInterval(snapInterval); + } + + const float bwMax = 3000; + const float bwMin = 500; + const float bbSampRate = 6000; + + std::string uiPrefix; + float snapInterval = 100; + float audioSampRate = 48000; + float bw = 3000; + bool running = false; + + VFOManager::VFO* _vfo; + dsp::SSBDemod demod; + dsp::AGC agc; + dsp::filter_window::BlackmanWindow win; + dsp::PolyphaseResampler resamp; + dsp::MonoToStereo m2s; + +}; \ No newline at end of file diff --git a/radio/src/main.cpp b/radio/src/main.cpp index 00d19ddc..8f4b8b3f 100644 --- a/radio/src/main.cpp +++ b/radio/src/main.cpp @@ -1,336 +1,149 @@ #include #include -#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #define CONCAT(a, b) ((std::string(a) + b).c_str()) -#define DEEMP_LIST "50µS\00075µS\000none\000" MOD_INFO { /* Name: */ "radio", /* Description: */ "Radio module for SDR++", /* Author: */ "Ryzerth", - /* Version: */ "0.2.5" + /* Version: */ "0.3.0" }; class RadioModule { public: RadioModule(std::string name) { this->name = name; - demod = 1; - bandWidth = 200000; - bandWidthMin = 100000; - bandWidthMax = 200000; - sigPath.init(name); - sigPath.start(); - sigPath.setDemodulator(SigPath::DEMOD_FM, bandWidth); - sigPath.vfo->setSnapInterval(100000.0); - gui::menu.registerEntry(name, menuHandler, this); - ScriptManager::ScriptRunHandler_t handler; - handler.ctx = this; - handler.handler = scriptCreateHandler; - core::scriptManager.bindScriptRunHandler(name, handler); + vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 200000, 200000, 1); + + wfmDemod.init(name, vfo, audioSampRate, 200000); + fmDemod.init(name, vfo, audioSampRate, 12500); + amDemod.init(name, vfo, audioSampRate, 12500); + usbDemod.init(name, vfo, audioSampRate, 3000); + lsbDemod.init(name, vfo, audioSampRate, 3000); + dsbDemod.init(name, vfo, audioSampRate, 6000); + rawDemod.init(name, vfo, audioSampRate, audioSampRate); + cwDemod.init(name, vfo, audioSampRate, 200); + + srChangeHandler.ctx = this; + srChangeHandler.handler = sampleRateChangeHandler; + stream.init(wfmDemod.getOutput(), srChangeHandler, audioSampRate); + sigpath::sinkManager.registerStream(name, &stream); + + // TODO: Replace with config load + demodId = 1; + selectDemod(&wfmDemod); + + stream.start(); + + gui::menu.registerEntry(name, menuHandler, this); } ~RadioModule() { - // TODO: Implement destructor + } private: static void menuHandler(void* ctx) { RadioModule* _this = (RadioModule*)ctx; - float menuColumnWidth = ImGui::GetContentRegionAvailWidth(); - + float menuWidth = ImGui::GetContentRegionAvailWidth(); ImGui::BeginGroup(); // TODO: Change VFO ref in signal path ImGui::Columns(4, CONCAT("RadioModeColumns##_", _this->name), false); - if (ImGui::RadioButton(CONCAT("NFM##_", _this->name), _this->demod == 0) && _this->demod != 0) { - _this->demod = 0; - _this->bandWidth = 16000; - _this->bandWidthMin = 8000; - _this->bandWidthMax = 16000; - _this->snapInterval = 10000; - _this->sigPath.setDemodulator(SigPath::DEMOD_NFM, _this->bandWidth); + if (ImGui::RadioButton(CONCAT("NFM##_", _this->name), _this->demodId == 0) && _this->demodId != 0) { + _this->demodId = 0; + _this->selectDemod(&_this->fmDemod); } - if (ImGui::RadioButton(CONCAT("WFM##_", _this->name), _this->demod == 1) && _this->demod != 1) { - _this->demod = 1; - _this->bandWidth = 200000; - _this->bandWidthMin = 100000; - _this->bandWidthMax = 200000; - _this->snapInterval = 100000; - _this->sigPath.setDemodulator(SigPath::DEMOD_FM, _this->bandWidth); + if (ImGui::RadioButton(CONCAT("WFM##_", _this->name), _this->demodId == 1) && _this->demodId != 1) { + _this->demodId = 1; + _this->selectDemod(&_this->wfmDemod); } ImGui::NextColumn(); - if (ImGui::RadioButton(CONCAT("AM##_", _this->name), _this->demod == 2) && _this->demod != 2) { - _this->demod = 2; - _this->bandWidth = 12500; - _this->bandWidthMin = 1500; - _this->bandWidthMax = 12500; - _this->snapInterval = 1000; - _this->sigPath.setDemodulator(SigPath::DEMOD_AM, _this->bandWidth); + if (ImGui::RadioButton(CONCAT("AM##_", _this->name), _this->demodId == 2) && _this->demodId != 2) { + _this->demodId = 2; + _this->selectDemod(&_this->amDemod); } - if (ImGui::RadioButton(CONCAT("DSB##_", _this->name), _this->demod == 3) && _this->demod != 3) { - _this->demod = 3; - _this->bandWidth = 6000; - _this->bandWidthMin = 3000; - _this->bandWidthMax = 6000; - _this->snapInterval = 1000; - _this->sigPath.setDemodulator(SigPath::DEMOD_DSB, _this->bandWidth); + if (ImGui::RadioButton(CONCAT("DSB##_", _this->name), _this->demodId == 3) && _this->demodId != 3) { + _this->demodId = 3; + _this->selectDemod(&_this->dsbDemod); } ImGui::NextColumn(); - if (ImGui::RadioButton(CONCAT("USB##_", _this->name), _this->demod == 4) && _this->demod != 4) { - _this->demod = 4; - _this->bandWidth = 3000; - _this->bandWidthMin = 1500; - _this->bandWidthMax = 3000; - _this->snapInterval = 1000; - _this->sigPath.setDemodulator(SigPath::DEMOD_USB, _this->bandWidth); + if (ImGui::RadioButton(CONCAT("USB##_", _this->name), _this->demodId == 4) && _this->demodId != 4) { + _this->demodId = 4; + _this->selectDemod(&_this->usbDemod); } - if (ImGui::RadioButton(CONCAT("CW##_", _this->name), _this->demod == 5) && _this->demod != 5) { _this->demod = 5; }; + if (ImGui::RadioButton(CONCAT("CW##_", _this->name), _this->demodId == 5) && _this->demodId != 5) { + _this->demodId = 5; + _this->selectDemod(&_this->cwDemod); + }; ImGui::NextColumn(); - if (ImGui::RadioButton(CONCAT("LSB##_", _this->name), _this->demod == 6) && _this->demod != 6) { - _this->demod = 6; - _this->bandWidth = 3000; - _this->bandWidthMin = 1500; - _this->bandWidthMax = 3000; - _this->snapInterval = 1000; - _this->sigPath.setDemodulator(SigPath::DEMOD_LSB, _this->bandWidth); + if (ImGui::RadioButton(CONCAT("LSB##_", _this->name), _this->demodId == 6) && _this->demodId != 6) { + _this->demodId = 6; + _this->selectDemod(&_this->lsbDemod); } - if (ImGui::RadioButton(CONCAT("RAW##_", _this->name), _this->demod == 7) && _this->demod != 7) { - _this->demod = 7; - _this->bandWidth = 10000; - _this->bandWidthMin = 3000; - _this->bandWidthMax = 10000; - _this->snapInterval = 1; - _this->sigPath.setDemodulator(SigPath::DEMOD_RAW, _this->bandWidth); + if (ImGui::RadioButton(CONCAT("RAW##_", _this->name), _this->demodId == 7) && _this->demodId != 7) { + _this->demodId = 7; + _this->selectDemod(&_this->rawDemod); }; ImGui::Columns(1, CONCAT("EndRadioModeColumns##_", _this->name), false); - _this->sigPath.vfo->setSnapInterval(_this->snapInterval); ImGui::EndGroup(); - if (_this->demod == 1) { - ImGui::Text("WFM Deemphasis"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(menuColumnWidth - ImGui::GetCursorPosX()); - if (ImGui::Combo(CONCAT("##_deemp_select_", _this->name), &_this->deemp, DEEMP_LIST)) { - _this->sigPath.setDeemphasis(_this->deemp); - } - } - + _this->currentDemod->showMenu(); + } - ImGui::Text("Bandwidth"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(menuColumnWidth - ImGui::GetCursorPosX()); - if (ImGui::InputInt(CONCAT("##_bw_select_", _this->name), &_this->bandWidth, 100, 1000)) { - _this->bandWidth = std::clamp(_this->bandWidth, _this->bandWidthMin, _this->bandWidthMax); - _this->sigPath.setBandwidth(_this->bandWidth); - } - - ImGui::Text("Snap Interval"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(menuColumnWidth - ImGui::GetCursorPosX()); - if (ImGui::InputDouble(CONCAT("##_snap_select_", _this->name), &_this->snapInterval)) { - _this->sigPath.vfo->setSnapInterval(_this->snapInterval); + static void sampleRateChangeHandler(float sampleRate, void* ctx) { + RadioModule* _this = (RadioModule*)ctx; + // TODO: If too slow, change all demods here and not when setting + _this->audioSampRate = sampleRate; + if (_this->currentDemod != NULL) { + _this->currentDemod->setAudioSampleRate(_this->audioSampRate); } } - static void scriptCreateHandler(void* ctx, duk_context* dukCtx, duk_idx_t objId) { - duk_push_string(dukCtx, "Hello from modules ;)"); - duk_put_prop_string(dukCtx, objId, "test"); - - duk_push_c_function(dukCtx, duk_setDemodulator, 1); - duk_put_prop_string(dukCtx, objId, "setDemodulator"); - - duk_push_c_function(dukCtx, duk_getDemodulator, 0); - duk_put_prop_string(dukCtx, objId, "getDemodulator"); - - duk_push_c_function(dukCtx, duk_setBandwidth, 1); - duk_put_prop_string(dukCtx, objId, "setBandwidth"); - - duk_push_c_function(dukCtx, duk_getBandwidth, 0); - duk_put_prop_string(dukCtx, objId, "getBandwidth"); - - duk_push_c_function(dukCtx, duk_getMaxBandwidth, 0); - duk_put_prop_string(dukCtx, objId, "getMaxBandwidth"); - - duk_push_c_function(dukCtx, duk_getMinBandwidth, 0); - duk_put_prop_string(dukCtx, objId, "getMinBandwidth"); - - duk_push_pointer(dukCtx, ctx); - duk_put_prop_string(dukCtx, objId, DUK_HIDDEN_SYMBOL("radio_ctx")); - } - - static duk_ret_t duk_setDemodulator(duk_context* dukCtx) { - const char* str = duk_require_string(dukCtx, -1); - std::string modName = str; - - duk_push_this(dukCtx); - if (!duk_get_prop_literal(dukCtx, -1, DUK_HIDDEN_SYMBOL("radio_ctx"))) { - printf("COULD NOT RETRIEVE POINTER\n"); - } - - RadioModule* _this = (RadioModule*)duk_require_pointer(dukCtx, -1); - - duk_pop_n(dukCtx, 3); // Pop demod name, this and context - - if (modName == "NFM") { - _this->demod = 0; - _this->bandWidth = 16000; - _this->bandWidthMin = 8000; - _this->bandWidthMax = 16000; - _this->sigPath.setDemodulator(SigPath::DEMOD_NFM, _this->bandWidth); - } - else if (modName == "WFM") { - _this->demod = 1; - _this->bandWidth = 200000; - _this->bandWidthMin = 100000; - _this->bandWidthMax = 200000; - _this->sigPath.setDemodulator(SigPath::DEMOD_FM, _this->bandWidth); - } - else if (modName == "AM") { - _this->demod = 2; - _this->bandWidth = 12500; - _this->bandWidthMin = 6250; - _this->bandWidthMax = 12500; - _this->sigPath.setDemodulator(SigPath::DEMOD_AM, _this->bandWidth); - } - else if (modName == "DSB") { - _this->demod = 3; - _this->bandWidth = 6000; - _this->bandWidthMin = 3000; - _this->bandWidthMax = 6000; - _this->sigPath.setDemodulator(SigPath::DEMOD_DSB, _this->bandWidth); - } - else if (modName == "USB") { - _this->demod = 4; - _this->bandWidth = 3000; - _this->bandWidthMin = 1500; - _this->bandWidthMax = 3000; - _this->sigPath.setDemodulator(SigPath::DEMOD_USB, _this->bandWidth); - } - else if (modName == "CW") { _this->demod = 5; } - else if (modName == "LSB") { - _this->demod = 6; - _this->bandWidth = 3000; - _this->bandWidthMin = 1500; - _this->bandWidthMax = 3000; - _this->sigPath.setDemodulator(SigPath::DEMOD_LSB, _this->bandWidth); - } - else if (modName == "RAW") { - _this->demod = 7; - _this->bandWidth = 10000; - _this->bandWidthMin = 3000; - _this->bandWidthMax = 10000; - _this->sigPath.setDemodulator(SigPath::DEMOD_RAW, _this->bandWidth); - } - else { - duk_push_string(dukCtx, "Invalid demodulator name"); - duk_throw(dukCtx); - } - - return 0; - } - - static duk_ret_t duk_getDemodulator(duk_context* dukCtx) { - duk_push_this(dukCtx); - if (!duk_get_prop_literal(dukCtx, -1, DUK_HIDDEN_SYMBOL("radio_ctx"))) { - printf("COULD NOT RETRIEVE POINTER\n"); - } - - RadioModule* _this = (RadioModule*)duk_require_pointer(dukCtx, -1); - - duk_pop_n(dukCtx, 2); // Pop demod name, this and context - - const char* demodNames[] = {"NFM", "WFM", "AM", "DSB", "USB", "CW", "LSB", "RAW"}; - - duk_push_string(dukCtx, demodNames[_this->demod]); - - return 1; - } - - static duk_ret_t duk_setBandwidth(duk_context* dukCtx) { - double bandwidth = duk_require_number(dukCtx, -1); - - duk_push_this(dukCtx); - if (!duk_get_prop_literal(dukCtx, -1, DUK_HIDDEN_SYMBOL("radio_ctx"))) { - printf("COULD NOT RETRIEVE POINTER\n"); - } - - RadioModule* _this = (RadioModule*)duk_require_pointer(dukCtx, -1); - - duk_pop_n(dukCtx, 3); // Pop demod name, this and context - - if (bandwidth > _this->bandWidthMax || bandwidth < _this->bandWidthMin) { - duk_push_string(dukCtx, "Bandwidth out of range"); - duk_throw(dukCtx); - } - - _this->bandWidth = bandwidth; - _this->bandWidth = std::clamp(_this->bandWidth, _this->bandWidthMin, _this->bandWidthMax); - _this->sigPath.setBandwidth(_this->bandWidth); - - return 0; - } - - static duk_ret_t duk_getBandwidth(duk_context* dukCtx) { - duk_push_this(dukCtx); - if (!duk_get_prop_literal(dukCtx, -1, DUK_HIDDEN_SYMBOL("radio_ctx"))) { - printf("COULD NOT RETRIEVE POINTER\n"); - } - - RadioModule* _this = (RadioModule*)duk_require_pointer(dukCtx, -1); - - duk_pop_n(dukCtx, 2); // Pop demod name, this and context - - duk_push_number(dukCtx, _this->bandWidth); - - return 1; - } - - static duk_ret_t duk_getMaxBandwidth(duk_context* dukCtx) { - duk_push_this(dukCtx); - if (!duk_get_prop_literal(dukCtx, -1, DUK_HIDDEN_SYMBOL("radio_ctx"))) { - printf("COULD NOT RETRIEVE POINTER\n"); - } - - RadioModule* _this = (RadioModule*)duk_require_pointer(dukCtx, -1); - - duk_pop_n(dukCtx, 2); // Pop demod name, this and context - - duk_push_number(dukCtx, _this->bandWidthMax); - - return 1; - } - - static duk_ret_t duk_getMinBandwidth(duk_context* dukCtx) { - duk_push_this(dukCtx); - if (!duk_get_prop_literal(dukCtx, -1, DUK_HIDDEN_SYMBOL("radio_ctx"))) { - printf("COULD NOT RETRIEVE POINTER\n"); - } - - RadioModule* _this = (RadioModule*)duk_require_pointer(dukCtx, -1); - - duk_pop_n(dukCtx, 2); // Pop demod name, this and context - - duk_push_number(dukCtx, _this->bandWidthMin); - - return 1; + void selectDemod(Demodulator* demod) { + if (currentDemod != NULL) { currentDemod->stop(); } + currentDemod = demod; + currentDemod->setAudioSampleRate(audioSampRate); + stream.setInput(currentDemod->getOutput()); + currentDemod->select(); + currentDemod->start(); } std::string name; - int demod = 1; - int deemp = 0; - int bandWidth; - int bandWidthMin; - int bandWidthMax; - double snapInterval = 100000.0; - SigPath sigPath; + int demodId = 0; + float audioSampRate = 48000; + Demodulator* currentDemod = NULL; + + VFOManager::VFO* vfo; + + WFMDemodulator wfmDemod; + FMDemodulator fmDemod; + AMDemodulator amDemod; + USBDemodulator usbDemod; + LSBDemodulator lsbDemod; + DSBDemodulator dsbDemod; + RAWDemodulator rawDemod; + CWDemodulator cwDemod; + + Event::EventHandler srChangeHandler; + SinkManager::Stream stream; }; diff --git a/radio/src/path.cpp b/radio/src/path.cpp deleted file mode 100644 index 01d352af..00000000 --- a/radio/src/path.cpp +++ /dev/null @@ -1,298 +0,0 @@ -#include -#include - -SigPath::SigPath() { - -} - -void SigPath::sampleRateChangeHandler(float _sampleRate, void* ctx) { - SigPath* _this = (SigPath*)ctx; - _this->outputSampleRate = _sampleRate; - _this->audioResamp.stop(); - _this->deemp.stop(); - float bw = std::min(_this->bandwidth, _sampleRate / 2.0f); - - - _this->audioResamp.setOutSampleRate(_sampleRate); - _this->audioWin.setSampleRate(_this->demodOutputSamplerate * _this->audioResamp.getInterpolation()); - _this->audioResamp.updateWindow(&_this->audioWin); - - _this->deemp.setSampleRate(_sampleRate); - _this->audioResamp.start(); - _this->deemp.start(); -} - -void SigPath::init(std::string vfoName) { - this->vfoName = vfoName; - - vfo = sigpath::vfoManager.createVFO(vfoName, ImGui::WaterfallVFO::REF_CENTER, 0, 200000, 200000, 1000); - - _demod = DEMOD_FM; - _deemp = DEEMP_50US; - bandwidth = 200000; - demodOutputSamplerate = 200000; - outputSampleRate = 48000; - - // TODO: Set default VFO options - // TODO: ajust deemphasis for different output sample rates - // TODO: Add a mono to stereo for different modes - - demod.init(vfo->output, 200000, 100000); - amDemod.init(vfo->output); - ssbDemod.init(vfo->output, 6000, 3000, dsp::SSBDemod::MODE_USB); - - agc.init(&amDemod.out, 1.0f / 125.0f); - - audioWin.init(24000, 24000, 200000); - audioResamp.init(&demod.out, &audioWin, 200000, 48000); - audioWin.setSampleRate(audioResamp.getInterpolation() * 200000); - audioResamp.updateWindow(&audioWin); - - deemp.init(&audioResamp.out, 48000, 50e-6); - - m2s.setInput(&deemp.out); - - Event::EventHandler evHandler; - evHandler.handler = sampleRateChangeHandler; - evHandler.ctx = this; - stream.init(&m2s.out, evHandler, outputSampleRate); - - sigpath::sinkManager.registerStream(vfoName, &stream); - - setDemodulator(_demod, bandwidth); -} - -void SigPath::setDemodulator(int demId, float bandWidth) { - if (demId < 0 || demId >= _DEMOD_COUNT) { - return; - } - - audioResamp.stop(); - deemp.stop(); - - bandwidth = bandWidth; - - // Stop current demodulator - if (_demod == DEMOD_FM) { - demod.stop(); - } - else if (_demod == DEMOD_NFM) { - demod.stop(); - } - else if (_demod == DEMOD_AM) { - agc.stop(); - amDemod.stop(); - } - else if (_demod == DEMOD_USB) { - agc.stop(); - ssbDemod.stop(); - } - else if (_demod == DEMOD_LSB) { - agc.stop(); - ssbDemod.stop(); - } - else if (_demod == DEMOD_DSB) { - agc.stop(); - ssbDemod.stop(); - } - else { - spdlog::error("UNIMPLEMENTED DEMODULATOR IN SigPath::setDemodulator (stop)"); - } - _demod = demId; - - // Set input of the audio resampler - // TODO: Set bandwidth from argument - if (demId == DEMOD_FM) { - demodOutputSamplerate = 200000; - vfo->setSampleRate(200000, bandwidth); - demod.setSampleRate(200000); - demod.setDeviation(bandwidth / 2.0f); - audioResamp.setInput(&demod.out); - audioBw = std::min(bandwidth, outputSampleRate / 2.0f); - audioBw = std::min(audioBw, 16000.0f); - - audioResamp.setInSampleRate(200000); - audioWin.setSampleRate(200000 * audioResamp.getInterpolation()); - audioWin.setCutoff(audioBw); - audioWin.setTransWidth(audioBw); - audioResamp.updateWindow(&audioWin); - - deemp.bypass = (_deemp == DEEMP_NONE); - vfo->setReference(ImGui::WaterfallVFO::REF_CENTER); - demod.start(); - } - else if (demId == DEMOD_NFM) { - demodOutputSamplerate = 16000; - vfo->setSampleRate(16000, bandwidth); - demod.setSampleRate(16000); - demod.setDeviation(bandwidth / 2.0f); - audioResamp.setInput(&demod.out); - audioBw = std::min(bandwidth, outputSampleRate / 2.0f); - - audioResamp.setInSampleRate(16000); - audioWin.setSampleRate(16000 * audioResamp.getInterpolation()); - audioWin.setCutoff(audioBw); - audioWin.setTransWidth(audioBw); - audioResamp.updateWindow(&audioWin); - - deemp.bypass = true; - vfo->setReference(ImGui::WaterfallVFO::REF_CENTER); - demod.start(); - } - else if (demId == DEMOD_AM) { - demodOutputSamplerate = 125000; - vfo->setSampleRate(12500, bandwidth); - agc.setInput(&amDemod.out); - audioResamp.setInput(&agc.out); - audioBw = std::min(bandwidth, outputSampleRate / 2.0f); - - audioResamp.setInSampleRate(12500); - audioWin.setSampleRate(12500 * audioResamp.getInterpolation()); - audioWin.setCutoff(audioBw); - audioWin.setTransWidth(audioBw); - audioResamp.updateWindow(&audioWin); - - deemp.bypass = true; - vfo->setReference(ImGui::WaterfallVFO::REF_CENTER); - agc.start(); - amDemod.start(); - } - else if (demId == DEMOD_USB) { - demodOutputSamplerate = 6000; - vfo->setSampleRate(6000, bandwidth); - ssbDemod.setMode(dsp::SSBDemod::MODE_USB); - agc.setInput(&ssbDemod.out); - audioResamp.setInput(&agc.out); - audioBw = std::min(bandwidth, outputSampleRate / 2.0f); - - audioResamp.setInSampleRate(6000); - audioWin.setSampleRate(6000 * audioResamp.getInterpolation()); - audioWin.setCutoff(audioBw); - audioWin.setTransWidth(audioBw); - audioResamp.updateWindow(&audioWin); - - deemp.bypass = true; - vfo->setReference(ImGui::WaterfallVFO::REF_LOWER); - agc.start(); - ssbDemod.start(); - } - else if (demId == DEMOD_LSB) { - demodOutputSamplerate = 6000; - vfo->setSampleRate(6000, bandwidth); - ssbDemod.setMode(dsp::SSBDemod::MODE_LSB); - agc.setInput(&ssbDemod.out); - audioResamp.setInput(&agc.out); - audioBw = std::min(bandwidth, outputSampleRate / 2.0f); - - audioResamp.setInSampleRate(6000); - audioWin.setSampleRate(6000 * audioResamp.getInterpolation()); - audioWin.setCutoff(audioBw); - audioWin.setTransWidth(audioBw); - audioResamp.updateWindow(&audioWin); - - deemp.bypass = true; - vfo->setReference(ImGui::WaterfallVFO::REF_UPPER); - agc.start(); - ssbDemod.start(); - } - else if (demId == DEMOD_DSB) { - demodOutputSamplerate = 6000; - vfo->setSampleRate(6000, bandwidth); - ssbDemod.setMode(dsp::SSBDemod::MODE_DSB); - agc.setInput(&ssbDemod.out); - audioResamp.setInput(&agc.out); - audioBw = std::min(bandwidth, outputSampleRate / 2.0f); - - audioResamp.setInSampleRate(6000); - audioWin.setSampleRate(6000 * audioResamp.getInterpolation()); - audioWin.setCutoff(audioBw); - audioWin.setTransWidth(audioBw); - audioResamp.updateWindow(&audioWin); - - deemp.bypass = true; - vfo->setReference(ImGui::WaterfallVFO::REF_CENTER); - agc.start(); - ssbDemod.start(); - } - else { - spdlog::error("UNIMPLEMENTED DEMODULATOR IN SigPath::setDemodulator (start): {0}", demId); - } - - audioResamp.start(); - deemp.start(); -} - -void SigPath::setDeemphasis(int deemph) { - _deemp = deemph; - deemp.stop(); - if (_deemp == DEEMP_NONE) { - deemp.bypass = true; - } - else if (_deemp == DEEMP_50US) { - deemp.bypass = false; - deemp.setTau(50e-6); - } - else if (_deemp == DEEMP_75US) { - deemp.bypass = false; - deemp.setTau(75e-6); - } - deemp.start(); -} - -void SigPath::setBandwidth(float bandWidth) { - bandwidth = bandWidth; - vfo->setBandwidth(bandwidth); - if (_demod == DEMOD_FM) { - demod.stop(); - demod.setDeviation(bandwidth / 2.0f); - demod.start(); - } - else if (_demod == DEMOD_NFM) { - demod.stop(); - demod.setDeviation(bandwidth / 2.0f); - demod.start(); - } - else if (_demod == DEMOD_AM) { - // Notbing to change - } - else if (_demod == DEMOD_USB) { - ssbDemod.stop(); - ssbDemod.setBandWidth(bandwidth); - ssbDemod.start(); - } - else if (_demod == DEMOD_LSB) { - ssbDemod.stop(); - ssbDemod.setBandWidth(bandwidth); - ssbDemod.start(); - } - else if (_demod == DEMOD_DSB) { - ssbDemod.stop(); - ssbDemod.setBandWidth(bandwidth); - ssbDemod.start(); - } - else if (_demod == DEMOD_RAW) { - // Notbing to change - } - else { - spdlog::error("UNIMPLEMENTED DEMODULATOR IN SigPath::setBandwidth"); - } - float _audioBw = std::min(bandwidth, outputSampleRate / 2.0f); - if (audioBw != _audioBw) { - audioBw = _audioBw; - audioResamp.stop(); - - audioWin.setCutoff(audioBw); - audioWin.setTransWidth(audioBw); - audioResamp.updateWindow(&audioWin); - - audioResamp.start(); - } -} - -void SigPath::start() { - demod.start(); - audioResamp.start(); - deemp.start(); - m2s.start(); - stream.start(); -} \ No newline at end of file diff --git a/radio/src/path.h b/radio/src/path.h deleted file mode 100644 index 627c7c04..00000000 --- a/radio/src/path.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class SigPath { -public: - SigPath(); - void init(std::string vfoName); - void start(); - void setDemodulator(int demod, float bandWidth); - void setDeemphasis(int deemph); - void setBandwidth(float bandWidth); - - enum { - DEMOD_FM, - DEMOD_NFM, - DEMOD_AM, - DEMOD_USB, - DEMOD_LSB, - DEMOD_DSB, - DEMOD_RAW, - _DEMOD_COUNT - }; - - enum { - DEEMP_50US, - DEEMP_75US, - DEEMP_NONE, - _DEEMP_COUNT - }; - - - dsp::BFMDeemp deemp; - VFOManager::VFO* vfo; - -private: - static void sampleRateChangeHandler(float _sampleRate, void* ctx); - - dsp::stream input; - - // Demodulators - dsp::FMDemod demod; - dsp::AMDemod amDemod; - dsp::SSBDemod ssbDemod; - - // Gain control - dsp::AGC agc; - - // Audio output - dsp::filter_window::BlackmanWindow audioWin; - dsp::PolyphaseResampler audioResamp; - dsp::MonoToStereo m2s; - SinkManager::Stream stream; - - std::string vfoName; - - // TODO: FIx all this sample rate BS (multiple names for same thing) - float bandwidth; - float demodOutputSamplerate; - float outputSampleRate; - int _demod; - int _deemp; - float audioBw; -}; \ No newline at end of file diff --git a/radio/src/radio_demod.h b/radio/src/radio_demod.h new file mode 100644 index 00000000..059b2426 --- /dev/null +++ b/radio/src/radio_demod.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +class Demodulator { +public: + virtual void start() = 0; + virtual void stop() = 0; + virtual bool isRunning() = 0; + virtual void select() = 0; + virtual void setVFO(VFOManager::VFO* vfo) = 0; + virtual VFOManager::VFO* getVFO() = 0; + virtual void setAudioSampleRate(float sampleRate) = 0; + virtual float getAudioSampleRate() = 0; + virtual dsp::stream* getOutput() = 0; + virtual void showMenu() = 0; +}; \ No newline at end of file diff --git a/radio/src/raw_demod.h b/radio/src/raw_demod.h new file mode 100644 index 00000000..42dcc448 --- /dev/null +++ b/radio/src/raw_demod.h @@ -0,0 +1,98 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +class RAWDemodulator : public Demodulator { +public: + RAWDemodulator() {} + RAWDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + init(prefix, vfo, audioSampleRate, bandWidth); + } + + void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + uiPrefix = prefix; + _vfo = vfo; + audioSampRate = audioSampleRate; + bw = bandWidth; + + c2s.init(_vfo->output); + } + + void start() { + c2s.start(); + running = true; + } + + void stop() { + c2s.stop(); + running = false; + } + + bool isRunning() { + return running; + } + + void select() { + _vfo->setSampleRate(audioSampRate, audioSampRate); + _vfo->setSnapInterval(snapInterval); + _vfo->setReference(ImGui::WaterfallVFO::REF_CENTER); + } + + void setVFO(VFOManager::VFO* vfo) { + _vfo = vfo; + c2s.setInput(_vfo->output); + } + + VFOManager::VFO* getVFO() { + return _vfo; + } + + void setAudioSampleRate(float sampleRate) { + audioSampRate = sampleRate; + if (running) { + _vfo->setSampleRate(audioSampRate, audioSampRate); + } + } + + float getAudioSampleRate() { + return audioSampRate; + } + + dsp::stream* getOutput() { + return &c2s.out; + } + + void showMenu() { + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + ImGui::SetNextItemWidth(menuWidth - ImGui::CalcTextSize("Snap Interval").x - 8); + ImGui::Text("Snap Interval"); + ImGui::SameLine(); + if (ImGui::InputFloat(("##_radio_raw_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, 0)) { + setSnapInterval(snapInterval); + } + + // TODO: Allow selection of the bandwidth + } + +private: + void setSnapInterval(float snapInt) { + snapInterval = snapInt; + _vfo->setSnapInterval(snapInterval); + } + + std::string uiPrefix; + float snapInterval = 10000; + float audioSampRate = 48000; + float bw = 12500; + bool running = false; + + VFOManager::VFO* _vfo; + dsp::ComplexToStereo c2s; + +}; \ No newline at end of file diff --git a/radio/src/usb_demod.h b/radio/src/usb_demod.h new file mode 100644 index 00000000..acfe0717 --- /dev/null +++ b/radio/src/usb_demod.h @@ -0,0 +1,140 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +class USBDemodulator : public Demodulator { +public: + USBDemodulator() {} + USBDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + init(prefix, vfo, audioSampleRate, bandWidth); + } + + void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + uiPrefix = prefix; + _vfo = vfo; + audioSampRate = audioSampleRate; + bw = bandWidth; + + demod.init(_vfo->output, bbSampRate, bandWidth, dsp::SSBDemod::MODE_USB); + + agc.init(&demod.out, 1.0f / 125.0f); + + float audioBW = std::min(audioSampRate / 2.0f, bw / 2.0f); + win.init(audioBW, audioBW, bbSampRate); + resamp.init(&agc.out, &win, bbSampRate, audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + resamp.updateWindow(&win); + + m2s.init(&resamp.out); + } + + void start() { + demod.start(); + agc.start(); + resamp.start(); + m2s.start(); + running = true; + } + + void stop() { + demod.stop(); + agc.stop(); + resamp.stop(); + m2s.stop(); + running = false; + } + + bool isRunning() { + return running; + } + + void select() { + _vfo->setSampleRate(bbSampRate, bw); + _vfo->setSnapInterval(snapInterval); + _vfo->setReference(ImGui::WaterfallVFO::REF_LOWER); + } + + void setVFO(VFOManager::VFO* vfo) { + _vfo = vfo; + demod.setInput(_vfo->output); + } + + VFOManager::VFO* getVFO() { + return _vfo; + } + + void setAudioSampleRate(float sampleRate) { + if (running) { + resamp.stop(); + } + audioSampRate = sampleRate; + float audioBW = std::min(audioSampRate / 2.0f, bw / 2.0f); + resamp.setOutSampleRate(audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + win.setCutoff(audioBW); + win.setTransWidth(audioBW); + resamp.updateWindow(&win); + if (running) { + resamp.start(); + } + } + + float getAudioSampleRate() { + return audioSampRate; + } + + dsp::stream* getOutput() { + return &m2s.out; + } + + void showMenu() { + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + ImGui::SetNextItemWidth(menuWidth); + if (ImGui::InputFloat(("##_radio_usb_bw_" + uiPrefix).c_str(), &bw, 1, 100, 0)) { + bw = std::clamp(bw, bwMin, bwMax); + setBandwidth(bw); + } + + ImGui::SetNextItemWidth(menuWidth - ImGui::CalcTextSize("Snap Interval").x - 8); + ImGui::Text("Snap Interval"); + ImGui::SameLine(); + if (ImGui::InputFloat(("##_radio_usb_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, 0)) { + setSnapInterval(snapInterval); + } + } + +private: + void setBandwidth(float bandWidth) { + bw = bandWidth; + _vfo->setBandwidth(bw); + } + + void setSnapInterval(float snapInt) { + snapInterval = snapInt; + _vfo->setSnapInterval(snapInterval); + } + + const float bwMax = 3000; + const float bwMin = 500; + const float bbSampRate = 6000; + + std::string uiPrefix; + float snapInterval = 100; + float audioSampRate = 48000; + float bw = 3000; + bool running = false; + + VFOManager::VFO* _vfo; + dsp::SSBDemod demod; + dsp::AGC agc; + dsp::filter_window::BlackmanWindow win; + dsp::PolyphaseResampler resamp; + dsp::MonoToStereo m2s; + +}; \ No newline at end of file diff --git a/radio/src/wfm_demod.h b/radio/src/wfm_demod.h new file mode 100644 index 00000000..c20b421b --- /dev/null +++ b/radio/src/wfm_demod.h @@ -0,0 +1,164 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +class WFMDemodulator : public Demodulator { +public: + WFMDemodulator() {} + WFMDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + init(prefix, vfo, audioSampleRate, bandWidth); + } + + void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth) { + uiPrefix = prefix; + _vfo = vfo; + audioSampRate = audioSampleRate; + bw = bandWidth; + + demod.init(_vfo->output, bbSampRate, bandWidth / 2.0f); + + float audioBW = std::min(audioSampleRate / 2.0f, 16000.0f); + win.init(audioBW, audioBW, bbSampRate); + resamp.init(&demod.out, &win, bbSampRate, audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + resamp.updateWindow(&win); + + deemp.init(&resamp.out, audioSampRate, tau); + + m2s.init(&deemp.out); + } + + void start() { + demod.start(); + resamp.start(); + deemp.start(); + m2s.start(); + running = true; + } + + void stop() { + demod.stop(); + resamp.stop(); + deemp.stop(); + m2s.stop(); + running = false; + } + + bool isRunning() { + return running; + } + + void select() { + _vfo->setSampleRate(bbSampRate, bw); + _vfo->setSnapInterval(snapInterval); + _vfo->setReference(ImGui::WaterfallVFO::REF_CENTER); + } + + void setVFO(VFOManager::VFO* vfo) { + _vfo = vfo; + demod.setInput(_vfo->output); + } + + VFOManager::VFO* getVFO() { + return _vfo; + } + + void setAudioSampleRate(float sampleRate) { + if (running) { + resamp.stop(); + deemp.stop(); + } + audioSampRate = sampleRate; + float audioBW = std::min(audioSampRate / 2.0f, 16000.0f); + resamp.setOutSampleRate(audioSampRate); + win.setSampleRate(bbSampRate * resamp.getInterpolation()); + win.setCutoff(audioBW); + win.setTransWidth(audioBW); + resamp.updateWindow(&win); + deemp.setSampleRate(audioSampRate); + if (running) { + resamp.start(); + deemp.start(); + } + } + + float getAudioSampleRate() { + return audioSampRate; + } + + dsp::stream* getOutput() { + return &m2s.out; + } + + void showMenu() { + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + ImGui::SetNextItemWidth(menuWidth); + if (ImGui::InputFloat(("##_radio_wfm_bw_" + uiPrefix).c_str(), &bw, 1, 100, 0)) { + bw = std::clamp(bw, bwMin, bwMax); + setBandwidth(bw); + } + + ImGui::SetNextItemWidth(menuWidth - ImGui::CalcTextSize("Snap Interval").x - 8); + ImGui::Text("Snap Interval"); + ImGui::SameLine(); + if (ImGui::InputFloat(("##_radio_wfm_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, 0)) { + setSnapInterval(snapInterval); + } + + ImGui::SetNextItemWidth(menuWidth - ImGui::CalcTextSize("De-emphasis").x - 8); + ImGui::Text("De-emphasis"); + ImGui::SameLine(); + if (ImGui::Combo(("##_radio_wfm_deemp_" + uiPrefix).c_str(), &deempId, deempModes)) { + setDeempIndex(deempId); + } + } + +private: + void setBandwidth(float bandWidth) { + bw = bandWidth; + _vfo->setBandwidth(bw); + demod.setDeviation(bw / 2.0f); + } + + void setDeempIndex(int id) { + if (id >= 2 || id < 0) { + deemp.bypass = true; + return; + } + deemp.setTau(deempVals[id]); + deemp.bypass = false; + } + + void setSnapInterval(float snapInt) { + snapInterval = snapInt; + _vfo->setSnapInterval(snapInterval); + } + + const float bwMax = 200000; + const float bwMin = 100000; + const float bbSampRate = 200000; + const char* deempModes = "50µS\00075µS\000none\000"; + const float deempVals[2] = { 50e-6, 75e-6 }; + + std::string uiPrefix; + float snapInterval = 100000; + float audioSampRate = 48000; + float bw = 200000; + int deempId = 0; + float tau = 50e-6; + bool running = false; + + VFOManager::VFO* _vfo; + dsp::FMDemod demod; + dsp::filter_window::BlackmanWindow win; + dsp::PolyphaseResampler resamp; + dsp::BFMDeemp deemp; + dsp::MonoToStereo m2s; + +}; \ No newline at end of file diff --git a/root_dev/config.json b/root_dev/config.json index 88c611be..afa4ed5e 100644 --- a/root_dev/config.json +++ b/root_dev/config.json @@ -19,7 +19,7 @@ "bandPlan": "General", "bandPlanEnabled": true, "fftHeight": 296, - "frequency": 99000000, + "frequency": 98930000, "max": 0.0, "maximized": false, "menuOrder": [ @@ -36,13 +36,13 @@ "min": -72.05882263183594, "offset": 0.0, "showWaterfall": true, - "source": "PlutoSDR", + "source": "SoapySDR", "sourceSettings": {}, "streams": { "Radio": { "muted": false, "sink": "Audio", - "volume": 0.5306122303009033 + "volume": 0.4183673560619354 }, "Radio 1": { "muted": true, diff --git a/root_dev/plutosdr_source_config.json b/root_dev/plutosdr_source_config.json index 96baa2e5..cf337e35 100644 --- a/root_dev/plutosdr_source_config.json +++ b/root_dev/plutosdr_source_config.json @@ -2,5 +2,5 @@ "IP": "192.168.2.1", "gain": 0.0, "gainMode": 2, - "sampleRate": 2000000.0 + "sampleRate": 4000000.0 } \ No newline at end of file diff --git a/root_dev/soapy_source_config.json b/root_dev/soapy_source_config.json index e1191d56..9350ecda 100644 --- a/root_dev/soapy_source_config.json +++ b/root_dev/soapy_source_config.json @@ -32,7 +32,7 @@ "LNA": 23.415000915527344, "VGA": 16.332000732421875 }, - "sampleRate": 8000000.0 + "sampleRate": 2000000.0 }, "Microphone (Realtek High Definition Audio)": { "sampleRate": 96000.0