diff --git a/CMakeLists.txt b/CMakeLists.txt index 68cc9415..6feea033 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ else() link_libraries(portaudio) link_libraries(X11) link_libraries(Xxf86vm) + link_libraries(DL) endif (MSVC) link_libraries(volk) diff --git a/modules/radio/CMakeLists.txt b/modules/radio/CMakeLists.txt new file mode 100644 index 00000000..1543b210 --- /dev/null +++ b/modules/radio/CMakeLists.txt @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.9) +project(radio) + +if (MSVC) + set(CMAKE_CXX_FLAGS "-O2 /std:c++17") + link_directories(radio "C:/Program Files/PothosSDR/lib/") + include_directories(radio "C:/Program Files/PothosSDR/include/volk/") + include_directories(radio "C:/Program Files/PothosSDR/include/") +else() + set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fsanitize=address -g") + include_directories(radio "/usr/include/volk") + link_libraries(pthread) + link_libraries(GL) + link_libraries(GLEW) + link_libraries(glfw) + link_libraries(fftw3) + link_libraries(fftw3f) + link_libraries(portaudio) + link_libraries(X11) + link_libraries(Xxf86vm) +endif (MSVC) + +link_libraries(volk) +link_libraries(SoapySDR) + +# Main code +include_directories(radio "src/") +include_directories(radio "../../src/") +include_directories(radio "../../src/imgui") +file(GLOB SRC "src/*.cpp") +file(GLOB IMGUI "../../src/imgui/*.cpp") +add_library(radio SHARED ${SRC} ${IMGUI}) +set_target_properties(radio PROPERTIES OUTPUT_NAME radio) + +if (MSVC) + # Glew + find_package(GLEW REQUIRED) + target_link_libraries(radio PRIVATE GLEW::GLEW) + + # GLFW3 + find_package(glfw3 CONFIG REQUIRED) + target_link_libraries(radio PRIVATE glfw) + + # FFTW3 + find_package(FFTW3 CONFIG REQUIRED) + target_link_libraries(radio PRIVATE FFTW3::fftw3) + find_package(FFTW3f CONFIG REQUIRED) + target_link_libraries(radio PRIVATE FFTW3::fftw3f) + + # PortAudio + find_package(portaudio CONFIG REQUIRED) + target_link_libraries(radio PRIVATE portaudio portaudio_static) +endif (MSVC) + +# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64" \ No newline at end of file diff --git a/modules/radio/src/main.cpp b/modules/radio/src/main.cpp new file mode 100644 index 00000000..34a97367 --- /dev/null +++ b/modules/radio/src/main.cpp @@ -0,0 +1,79 @@ +#include +#include +#include + +#define CONCAT(a, b) ((std::string(a) + b).c_str()) + +mod::API_t* API; + +struct RadioContext_t { + std::string name; + int demod = 1; + SigPath sigPath; +}; + +MOD_EXPORT void* _INIT_(mod::API_t* _API, ImGuiContext* imctx, std::string _name) { + API = _API; + RadioContext_t* ctx = new RadioContext_t; + ctx->name = _name; + ctx->sigPath.init(_name, 200000, 1000, API->registerVFO(_name, mod::API_t::REF_CENTER, 0, 200000, 200000, 1000)); + ctx->sigPath.start(); + ImGui::SetCurrentContext(imctx); + return ctx; +} + +MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) { + ImGui::BeginGroup(); + + ImGui::Columns(4, CONCAT("RadioModeColumns##_", ctx->name), false); + if (ImGui::RadioButton(CONCAT("NFM##_", ctx->name), ctx->demod == 0) && ctx->demod != 0) { + ctx->sigPath.setDemodulator(SigPath::DEMOD_NFM); + ctx->demod = 0; + API->setVFOBandwidth(ctx->name, 12500); + // vfo->setReference(ImGui::WaterFall::REF_CENTER); + } + if (ImGui::RadioButton(CONCAT("WFM##_", ctx->name), ctx->demod == 1) && ctx->demod != 1) { + ctx->sigPath.setDemodulator(SigPath::DEMOD_FM); + ctx->demod = 1; + API->setVFOBandwidth(ctx->name, 200000); + // vfo->setReference(ImGui::WaterFall::REF_CENTER); + } + ImGui::NextColumn(); + if (ImGui::RadioButton(CONCAT("AM##_", ctx->name), ctx->demod == 2) && ctx->demod != 2) { + ctx->sigPath.setDemodulator(SigPath::DEMOD_AM); + ctx->demod = 2; + API->setVFOBandwidth(ctx->name, 12500); + // vfo->setReference(ImGui::WaterFall::REF_CENTER); + } + if (ImGui::RadioButton(CONCAT("DSB##_", ctx->name), ctx->demod == 3) && ctx->demod != 3) { ctx->demod = 3; }; + ImGui::NextColumn(); + if (ImGui::RadioButton(CONCAT("USB##_", ctx->name), ctx->demod == 4) && ctx->demod != 4) { + ctx->sigPath.setDemodulator(SigPath::DEMOD_USB); + ctx->demod = 4; + API->setVFOBandwidth(ctx->name, 3000); + // vfo->setReference(ImGui::WaterFall::REF_LOWER); + } + if (ImGui::RadioButton(CONCAT("CW##_", ctx->name), ctx->demod == 5) && ctx->demod != 5) { ctx->demod = 5; }; + ImGui::NextColumn(); + if (ImGui::RadioButton(CONCAT("LSB##_", ctx->name), ctx->demod == 6) && ctx->demod != 6) { + ctx->sigPath.setDemodulator(SigPath::DEMOD_LSB); + ctx->demod = 6; + API->setVFOBandwidth(ctx->name, 3000); + // vfo->setReference(ImGui::WaterFall::REF_UPPER); + } + if (ImGui::RadioButton(CONCAT("RAW##_", ctx->name), ctx->demod == 7) && ctx->demod != 7) { ctx->demod = 7; }; + ImGui::Columns(1, CONCAT("EndRadioModeColumns##_", ctx->name), false); + + ImGui::EndGroup(); +} + +MOD_EXPORT void _HANDLE_EVENT_(RadioContext_t* ctx, int eventId) { + if (eventId == mod::EVENT_STREAM_PARAM_CHANGED) { + ctx->sigPath.updateBlockSize(); + } +} + +MOD_EXPORT void _STOP_(RadioContext_t* ctx) { + API->removeVFO(ctx->name); + delete ctx; +} \ No newline at end of file diff --git a/modules/radio/src/path.cpp b/modules/radio/src/path.cpp new file mode 100644 index 00000000..afa3dee7 --- /dev/null +++ b/modules/radio/src/path.cpp @@ -0,0 +1,114 @@ +#include + +SigPath::SigPath() { + +} + +void SigPath::init(std::string vfoName, uint64_t sampleRate, int blockSize, dsp::stream* input) { + this->sampleRate = sampleRate; + this->blockSize = blockSize; + this->vfoName = vfoName; + + _demod = DEMOD_FM; + + // TODO: Set default VFO options + + demod.init(input, 100000, 200000, 800); + amDemod.init(input, 50); + ssbDemod.init(input, 6000, 3000, 22); + + audioResamp.init(&demod.output, 200000, 48000, 800); + audio.init(&audioResamp.output, 64); +} + +void SigPath::setSampleRate(float sampleRate) { + this->sampleRate = sampleRate; + + // Reset the demodulator and audio systems + setDemodulator(_demod); +} + +void SigPath::setVolume(float volume) { + audio.setVolume(volume); +} + +void SigPath::setDemodulator(int demId) { + if (demId < 0 || demId >= _DEMOD_COUNT) { + return; + } + + audioResamp.stop(); + + // Stop current demodulator + if (_demod == DEMOD_FM) { + demod.stop(); + } + else if (_demod == DEMOD_NFM) { + demod.stop(); + } + else if (_demod == DEMOD_AM) { + amDemod.stop(); + } + else if (_demod == DEMOD_USB) { + ssbDemod.stop(); + } + else if (_demod == DEMOD_LSB) { + ssbDemod.stop(); + } + _demod = demId; + + // Set input of the audio resampler + if (demId == DEMOD_FM) { + API->setVFOSampleRate(vfoName, 200000, 200000); + demod.setBlockSize(API->getVFOOutputBlockSize(vfoName)); + demod.setSampleRate(200000); + demod.setDeviation(100000); + audioResamp.setInput(&demod.output); + audioResamp.setInputSampleRate(200000, API->getVFOOutputBlockSize(vfoName)); + demod.start(); + } + if (demId == DEMOD_NFM) { + API->setVFOSampleRate(vfoName, 12500, 12500); + demod.setBlockSize(API->getVFOOutputBlockSize(vfoName)); + demod.setSampleRate(12500); + demod.setDeviation(6250); + audioResamp.setInput(&demod.output); + audioResamp.setInputSampleRate(12500, API->getVFOOutputBlockSize(vfoName)); + demod.start(); + } + else if (demId == DEMOD_AM) { + API->setVFOSampleRate(vfoName, 12500, 12500); + amDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName)); + audioResamp.setInput(&amDemod.output); + audioResamp.setInputSampleRate(12500, API->getVFOOutputBlockSize(vfoName)); + amDemod.start(); + } + else if (demId == DEMOD_USB) { + API->setVFOSampleRate(vfoName, 6000, 3000); + ssbDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName)); + ssbDemod.setMode(dsp::SSBDemod::MODE_USB); + audioResamp.setInput(&ssbDemod.output); + audioResamp.setInputSampleRate(6000, API->getVFOOutputBlockSize(vfoName)); + ssbDemod.start(); + } + else if (demId == DEMOD_LSB) { + API->setVFOSampleRate(vfoName, 6000, 3000); + ssbDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName)); + ssbDemod.setMode(dsp::SSBDemod::MODE_LSB); + audioResamp.setInput(&ssbDemod.output); + audioResamp.setInputSampleRate(6000, API->getVFOOutputBlockSize(vfoName)); + ssbDemod.start(); + } + + audioResamp.start(); +} + +void SigPath::updateBlockSize() { + setDemodulator(_demod); +} + +void SigPath::start() { + demod.start(); + audioResamp.start(); + audio.start(); +} \ No newline at end of file diff --git a/modules/radio/src/path.h b/modules/radio/src/path.h new file mode 100644 index 00000000..e85088cd --- /dev/null +++ b/modules/radio/src/path.h @@ -0,0 +1,54 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class SigPath { +public: + SigPath(); + void init(std::string vfoName, uint64_t sampleRate, int blockSize, dsp::stream* input); + void start(); + void setSampleRate(float sampleRate); + + void setVFOFrequency(long frequency); + void setVolume(float volume); + + void updateBlockSize(); + + void setDemodulator(int demod); + + enum { + DEMOD_FM, + DEMOD_NFM, + DEMOD_AM, + DEMOD_USB, + DEMOD_LSB, + _DEMOD_COUNT + }; + +private: + dsp::stream input; + + // Demodulators + dsp::FMDemodulator demod; + dsp::AMDemodulator amDemod; + dsp::SSBDemod ssbDemod; + + // Audio output + dsp::FloatFIRResampler audioResamp; + io::AudioSink audio; + + std::string vfoName; + + float sampleRate; + int blockSize; + int _demod; +}; \ No newline at end of file diff --git a/src/dsp/routing.h b/src/dsp/routing.h index 05797b2e..7555d00b 100644 --- a/src/dsp/routing.h +++ b/src/dsp/routing.h @@ -75,4 +75,96 @@ namespace dsp { std::thread _workerThread; bool running = false; }; + + class DynamicSplitter { + public: + DynamicSplitter() { + + } + + DynamicSplitter(stream* input, int bufferSize) { + _in = input; + _bufferSize = bufferSize; + } + + void init(stream* input, int bufferSize) { + _in = input; + _bufferSize = bufferSize; + } + + void start() { + if (running) { + return; + } + _workerThread = std::thread(_worker, this); + running = true; + } + + void stop() { + if (!running) { + return; + } + _in->stopReader(); + int outputCount = outputs.size(); + for (int i = 0; i < outputCount; i++) { + outputs[i]->stopWriter(); + } + _workerThread.join(); + _in->clearReadStop(); + for (int i = 0; i < outputCount; i++) { + outputs[i]->clearWriteStop(); + } + running = false; + } + + void setBlockSize(int blockSize) { + if (running) { + return; + } + _bufferSize = blockSize; + int outputCount = outputs.size(); + for (int i = 0; i < outputCount; i++) { + outputs[i]->setMaxLatency(blockSize * 2); + } + } + + void bind(stream* stream) { + if (running) { + return; + } + outputs.push_back(stream); + } + + void unbind(stream* stream) { + if (running) { + return; + } + int outputCount = outputs.size(); + for (int i = 0; i < outputCount; i++) { + if (outputs[i] == stream) { + outputs.erase(outputs.begin() + i); + break; + } + } + } + + private: + static void _worker(DynamicSplitter* _this) { + complex_t* buf = new complex_t[_this->_bufferSize]; + int outputCount = _this->outputs.size(); + while (true) { + if (_this->_in->read(buf, _this->_bufferSize) < 0) { break; }; + for (int i = 0; i < outputCount; i++) { + if (_this->outputs[i]->write(buf, _this->_bufferSize) < 0) { break; }; + } + } + delete[] buf; + } + + stream* _in; + int _bufferSize; + std::thread _workerThread; + bool running = false; + std::vector*> outputs; + }; }; \ No newline at end of file diff --git a/src/dsp/source.h b/src/dsp/source.h index faf7e7ce..5d9531f0 100644 --- a/src/dsp/source.h +++ b/src/dsp/source.h @@ -3,6 +3,7 @@ #include #include #include +#include namespace dsp { class SineSource { diff --git a/src/dsp/vfo.h b/src/dsp/vfo.h index fe0bd803..bbcf7e03 100644 --- a/src/dsp/vfo.h +++ b/src/dsp/vfo.h @@ -3,6 +3,7 @@ #include #include #include +#include namespace dsp { class VFO { diff --git a/src/main.cpp b/src/main.cpp index 8902e10f..c2dc095f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -74,11 +74,12 @@ int main() { bandplan::loadColorTable("band_colors.json"); spdlog::info("Loading test module"); - //mod::loadModule("../modules/demo/build/Release/demo.dll", "Demo Module 1"); + mod::initAPI(); + mod::loadModule("../modules/radio/build/Release/radio.dll", "Radio 1"); + mod::loadModule("../modules/radio/build/Release/radio.dll", "Radio 2"); //mod::loadModule("../modules/demo/build/Release/demo.dll", "Demo Module 2"); //mod::loadModule("../modules/demo/build/Release/demo.dll", "Demo Module 3"); - spdlog::info("Ready."); diff --git a/src/main_window.cpp b/src/main_window.cpp index 38e1f1d9..57653b1c 100644 --- a/src/main_window.cpp +++ b/src/main_window.cpp @@ -36,24 +36,12 @@ void fftHandler(dsp::complex_t* samples) { _data.clear(); } +dsp::NullSink sink; + void windowInit() { int sampleRate = 8000000; wtf.setBandwidth(sampleRate); wtf.setCenterFrequency(90500000); - // wtf.setVFOBandwidth(200000); - // wtf.setVFOOffset(0); - - wtf.vfos["Radio"] = new ImGui::WaterfallVFO; - wtf.vfos["Radio"]->setReference(ImGui::WaterfallVFO::REF_CENTER); - wtf.vfos["Radio"]->setBandwidth(200000); - wtf.vfos["Radio"]->setOffset(0); - - wtf.vfos["Radio 2"] = new ImGui::WaterfallVFO; - wtf.vfos["Radio 2"]->setReference(ImGui::WaterfallVFO::REF_CENTER); - wtf.vfos["Radio 2"]->setBandwidth(200000); - wtf.vfos["Radio 2"]->setOffset(300000); - - wtf.selectedVFO = "Radio"; fSel.init(); fSel.setFrequency(90500000); @@ -65,6 +53,8 @@ void windowInit() { sigPath.init(sampleRate, 20, fftSize, &soapy.output, (dsp::complex_t*)fft_in, fftHandler); sigPath.start(); + vfoman::init(&wtf, &sigPath); + uiGains = new float[1]; } @@ -83,12 +73,8 @@ int sampleRate = 1000000; bool playing = false; watcher dcbias(false, false); watcher bandPlanEnabled(true, false); -bool selectedVFOChanged = false; void setVFO(float freq) { - if (wtf.selectedVFO == "") { - return; - } ImGui::WaterfallVFO* vfo = wtf.vfos[wtf.selectedVFO]; float currentOff = vfo->centerOffset; @@ -112,8 +98,7 @@ void setVFO(float freq) { // VFO still fints in the view if (vfoBottom > viewBottom && vfoTop < viewTop) { - sigPath.setVFOFrequency(newVFO); - vfo->setCenterOffset(newVFO); + vfoman::setCenterOffset(wtf.selectedVFO, newVFO); return; } @@ -121,8 +106,7 @@ void setVFO(float freq) { if (vfoBottom < bottom) { wtf.setViewOffset((BW / 2.0f) - (viewBW / 2.0f)); float newVFOOffset = (BW / 2.0f) - (vfoBW / 2.0f) - (viewBW / 10.0f); - sigPath.setVFOFrequency(newVFOOffset); - vfo->setCenterOffset(newVFOOffset); + vfoman::setCenterOffset(wtf.selectedVFO, newVFOOffset); wtf.setCenterFrequency(freq - newVFOOffset); soapy.setFrequency(freq - newVFOOffset); return; @@ -132,8 +116,7 @@ void setVFO(float freq) { if (vfoTop > top) { wtf.setViewOffset((viewBW / 2.0f) - (BW / 2.0f)); float newVFOOffset = (vfoBW / 2.0f) - (BW / 2.0f) + (viewBW / 10.0f); - sigPath.setVFOFrequency(newVFOOffset); - vfo->setCenterOffset(newVFOOffset); + vfoman::setCenterOffset(wtf.selectedVFO, newVFOOffset); wtf.setCenterFrequency(freq - newVFOOffset); soapy.setFrequency(freq - newVFOOffset); return; @@ -146,16 +129,14 @@ void setVFO(float freq) { float newViewTop = newViewOff + (viewBW / 2.0f); if (newViewBottom > bottom) { - vfo->setCenterOffset(newVFO); wtf.setViewOffset(newViewOff); - sigPath.setVFOFrequency(newVFO); + vfoman::setCenterOffset(wtf.selectedVFO, newVFO); return; } wtf.setViewOffset((BW / 2.0f) - (viewBW / 2.0f)); float newVFOOffset = (BW / 2.0f) - (vfoBW / 2.0f) - (viewBW / 10.0f); - sigPath.setVFOFrequency(newVFOOffset); - vfo->setCenterOffset(newVFOOffset); + vfoman::setCenterOffset(wtf.selectedVFO, newVFOOffset); wtf.setCenterFrequency(freq - newVFOOffset); soapy.setFrequency(freq - newVFOOffset); } @@ -165,32 +146,43 @@ void setVFO(float freq) { float newViewTop = newViewOff + (viewBW / 2.0f); if (newViewTop < top) { - vfo->setCenterOffset(newVFO); wtf.setViewOffset(newViewOff); - sigPath.setVFOFrequency(newVFO); + vfoman::setCenterOffset(wtf.selectedVFO, newVFO); return; } wtf.setViewOffset((viewBW / 2.0f) - (BW / 2.0f)); float newVFOOffset = (vfoBW / 2.0f) - (BW / 2.0f) + (viewBW / 10.0f); - sigPath.setVFOFrequency(newVFOOffset); - vfo->setCenterOffset(newVFOOffset); + vfoman::setCenterOffset(wtf.selectedVFO, newVFOOffset); wtf.setCenterFrequency(freq - newVFOOffset); soapy.setFrequency(freq - newVFOOffset); } } void drawWindow() { + if (wtf.selectedVFO == "" && wtf.vfos.size() > 0) { + wtf.selectFirstVFO(); + } + ImGui::WaterfallVFO* vfo = wtf.vfos[wtf.selectedVFO]; - if (selectedVFOChanged) { - selectedVFOChanged = false; + if (vfo->centerOffsetChanged) { + fSel.setFrequency(wtf.getCenterFrequency() + vfo->generalOffset); + } + + vfoman::updateFromWaterfall(); + + if (wtf.selectedVFOChanged) { + wtf.selectedVFOChanged = false; fSel.setFrequency(vfo->generalOffset + wtf.getCenterFrequency()); } if (fSel.frequencyChanged) { fSel.frequencyChanged = false; setVFO(fSel.frequency); + vfo->centerOffsetChanged = false; + vfo->lowerOffsetChanged = false; + vfo->upperOffsetChanged = false; } if (wtf.centerFreqMoved) { @@ -198,15 +190,6 @@ void drawWindow() { soapy.setFrequency(wtf.getCenterFrequency()); fSel.setFrequency(wtf.getCenterFrequency() + vfo->generalOffset); } - if (wtf.vfoFreqChanged) { - wtf.vfoFreqChanged = false; - sigPath.setVFOFrequency(vfo->centerOffset); - fSel.setFrequency(wtf.getCenterFrequency() + vfo->generalOffset); - } - - if (volume.changed()) { - sigPath.setVolume(volume.val); - } if (devId.changed() && soapy.devList.size() > 0) { spdlog::info("Changed input device: {0}", devId.val); @@ -320,52 +303,6 @@ void drawWindow() { mod = mod::modules[mod::moduleNames[i]]; mod._DRAW_MENU_(mod.ctx); } - } - - if (ImGui::CollapsingHeader("Radio")) { - ImGui::BeginGroup(); - - ImGui::Columns(4, "RadioModeColumns", false); - if (ImGui::RadioButton("NFM", demod == 0) && demod != 0) { - sigPath.setDemodulator(SignalPath::DEMOD_NFM); demod = 0; - vfo->setBandwidth(12500); - vfo->setReference(ImGui::WaterFall::REF_CENTER); - } - if (ImGui::RadioButton("WFM", demod == 1) && demod != 1) { - sigPath.setDemodulator(SignalPath::DEMOD_FM); - demod = 1; - vfo->setBandwidth(200000); - vfo->setReference(ImGui::WaterFall::REF_CENTER); - } - ImGui::NextColumn(); - if (ImGui::RadioButton("AM", demod == 2) && demod != 2) { - sigPath.setDemodulator(SignalPath::DEMOD_AM); - demod = 2; - vfo->setBandwidth(12500); - vfo->setReference(ImGui::WaterFall::REF_CENTER); - } - if (ImGui::RadioButton("DSB", demod == 3) && demod != 3) { demod = 3; }; - ImGui::NextColumn(); - if (ImGui::RadioButton("USB", demod == 4) && demod != 4) { - sigPath.setDemodulator(SignalPath::DEMOD_USB); - demod = 4; - vfo->setBandwidth(3000); - vfo->setReference(ImGui::WaterFall::REF_LOWER); - } - if (ImGui::RadioButton("CW", demod == 5) && demod != 5) { demod = 5; }; - ImGui::NextColumn(); - if (ImGui::RadioButton("LSB", demod == 6) && demod != 6) { - sigPath.setDemodulator(SignalPath::DEMOD_LSB); - demod = 6; - vfo->setBandwidth(3000); - vfo->setReference(ImGui::WaterFall::REF_UPPER); - } - if (ImGui::RadioButton("RAW", demod == 7) && demod != 7) { demod = 7; }; - ImGui::Columns(1, "EndRadioModeColumns", false); - - ImGui::Checkbox("DC Bias Removal", &dcbias.val); - - ImGui::EndGroup(); } ImGui::CollapsingHeader("Audio"); @@ -388,16 +325,6 @@ void drawWindow() { ImGui::Text("Frame time: %.3f ms/frame", 1000.0f / ImGui::GetIO().Framerate); ImGui::Text("Framerate: %.1f FPS", ImGui::GetIO().Framerate); ImGui::Text("Center Frequency: %.1f FPS", wtf.getCenterFrequency()); - - if (ImGui::Button("Radio##__sdsd__")) { - wtf.selectedVFO = "Radio"; - selectedVFOChanged = true; - } - ImGui::SameLine(); - if (ImGui::Button("Radio 2")) { - wtf.selectedVFO = "Radio 2"; - selectedVFOChanged = true; - } } ImGui::EndChild(); diff --git a/src/main_window.h b/src/main_window.h index 63be64d3..e7617d55 100644 --- a/src/main_window.h +++ b/src/main_window.h @@ -22,6 +22,7 @@ #include #include #include +#include #define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground diff --git a/src/module.cpp b/src/module.cpp index 704790ec..515ae0fb 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -1,10 +1,22 @@ #include +#include namespace mod { API_t API; std::map modules; std::vector moduleNames; + void initAPI() { + API.registerVFO = vfoman::create; + API.setVFOOffset = vfoman::setOffset; + API.setVFOCenterOffset = vfoman::setCenterOffset; + API.setVFOBandwidth = vfoman::setBandwidth; + API.setVFOSampleRate = vfoman::setSampleRate; + API.getVFOOutputBlockSize = vfoman::getOutputBlockSize; + API.setVFOReference = vfoman::setReference; + API.removeVFO = vfoman::remove; + } + void loadModule(std::string path, std::string name) { if (!std::filesystem::exists(path)) { spdlog::error("{0} does not exist", path); @@ -23,9 +35,18 @@ namespace mod { } mod._INIT_ = (void*(*)(mod::API_t*,ImGuiContext*,std::string))GetProcAddress(mod.inst, "_INIT_"); mod._DRAW_MENU_ = (void(*)(void*))GetProcAddress(mod.inst, "_DRAW_MENU_"); + mod._HANDLE_EVENT_ = (void(*)(void*, int))GetProcAddress(mod.inst, "_HANDLE_EVENT_"); mod._STOP_ = (void(*)(void*))GetProcAddress(mod.inst, "_STOP_"); #else - // Linux function here + mod.inst = dlopen(path.c_str(), RTLD_LAZY); + if (mod.inst == NULL) { + spdlog::error("Couldn't load {0}.", name); + return; + } + mod._INIT_ = (void*(*)(mod::API_t*,ImGuiContext*,std::string))dlsym(mod.inst, "_INIT_"); + mod._DRAW_MENU_ = (void(*)(void*))dlsym(mod.inst, "_DRAW_MENU_"); + mod._HANDLE_EVENT_ = (void(*)(void*, int))dlsym(mod.inst, "_HANDLE_EVENT_"); + mod._STOP_ = (void(*)(void*))dlsym(mod.inst, "_STOP_"); #endif if (mod._INIT_ == NULL) { spdlog::error("Couldn't load {0} because it's missing _INIT_.", name); @@ -35,6 +56,10 @@ namespace mod { spdlog::error("Couldn't load {0} because it's missing _DRAW_MENU_.", name); return; } + if (mod._HANDLE_EVENT_ == NULL) { + spdlog::error("Couldn't load {0} because it's missing _HANDLE_EVENT_.", name); + return; + } if (mod._STOP_ == NULL) { spdlog::error("Couldn't load {0} because it's missing _STOP_.", name); return; @@ -46,5 +71,14 @@ namespace mod { modules[name] = mod; moduleNames.push_back(name); } + + void broadcastEvent(int eventId) { + if (eventId < 0 || eventId >= _EVENT_COUNT) { + return; + } + for (auto const& [name, mod] : modules) { + mod._HANDLE_EVENT_(mod.ctx, eventId); + } + } }; diff --git a/src/module.h b/src/module.h index 46f4ca36..2c9231a1 100644 --- a/src/module.h +++ b/src/module.h @@ -5,18 +5,40 @@ #include #include #include +#include +#include #ifdef _WIN32 #include #define MOD_EXPORT extern "C" \ __declspec(dllexport) #else +#include #define MOD_EXPORT extern "C" #endif namespace mod { struct API_t { + dsp::stream* (*registerVFO)(std::string name, int reference, float offset, float bandwidth, float sampleRate, int blockSize); + void (*setVFOOffset)(std::string name, float offset); + void (*setVFOCenterOffset)(std::string name, float offset); + void (*setVFOBandwidth)(std::string name, float bandwidth); + void (*setVFOSampleRate)(std::string name, float sampleRate, float bandwidth); + int (*getVFOOutputBlockSize)(std::string name); + void (*setVFOReference)(std::string name, int ref); + void (*removeVFO)(std::string name); + enum { + REF_LOWER, + REF_CENTER, + REF_UPPER, + _REF_COUNT + }; + }; + + enum { + EVENT_STREAM_PARAM_CHANGED, + _EVENT_COUNT }; struct Module_t { @@ -27,12 +49,17 @@ namespace mod { #endif void* (*_INIT_)(API_t*, ImGuiContext*, std::string); void (*_DRAW_MENU_)(void*); + void (*_HANDLE_EVENT_)(void*, int); void (*_STOP_)(void*); void* ctx; }; + void initAPI(); void loadModule(std::string path, std::string name); + void broadcastEvent(int eventId); extern std::map modules; extern std::vector moduleNames; -}; \ No newline at end of file +}; + +extern mod::API_t* API; \ No newline at end of file diff --git a/src/signal_path.cpp b/src/signal_path.cpp index f3d2a237..777dcb3a 100644 --- a/src/signal_path.cpp +++ b/src/signal_path.cpp @@ -8,8 +8,7 @@ void SignalPath::init(uint64_t sampleRate, int fftRate, int fftSize, dsp::stream this->sampleRate = sampleRate; this->fftRate = fftRate; this->fftSize = fftSize; - - _demod = DEMOD_FM; + inputBlockSize = sampleRate / 200.0f; dcBiasRemover.init(input, 32000); dcBiasRemover.bypass = true; @@ -18,120 +17,41 @@ void SignalPath::init(uint64_t sampleRate, int fftRate, int fftSize, dsp::stream fftBlockDec.init(&split.output_a, (sampleRate / fftRate) - fftSize, fftSize); fftHandlerSink.init(&fftBlockDec.output, fftBuffer, fftSize, fftHandler); - mainVFO.init(&split.output_b, sampleRate, 200000, 200000, 0, 32000); - - demod.init(mainVFO.output, 100000, 200000, 800); - amDemod.init(mainVFO.output, 50); - ssbDemod.init(mainVFO.output, 6000, 3000, 22); - - audioResamp.init(&demod.output, 200000, 48000, 800); - audio.init(&audioResamp.output, 64); + dynSplit.init(&split.output_b, 32000); } void SignalPath::setSampleRate(float sampleRate) { + this->sampleRate = sampleRate; + inputBlockSize = sampleRate / 200.0f; + dcBiasRemover.stop(); split.stop(); fftBlockDec.stop(); fftHandlerSink.stop(); + dynSplit.stop(); - demod.stop(); - amDemod.stop(); - audioResamp.stop(); - - int inputBlockSize = sampleRate / 200.0f; + for (auto const& [name, vfo] : vfos) { + vfo.vfo->stop(); + } dcBiasRemover.setBlockSize(inputBlockSize); split.setBlockSize(inputBlockSize); int skip = (sampleRate / fftRate) - fftSize; fftBlockDec.setSkip(skip); - mainVFO.setInputSampleRate(sampleRate, inputBlockSize); + dynSplit.setBlockSize(inputBlockSize); - // // Reset the modulator and audio systems - setDemodulator(_demod); + mod::broadcastEvent(mod::EVENT_STREAM_PARAM_CHANGED); + + for (auto const& [name, vfo] : vfos) { + vfo.vfo->setInputSampleRate(sampleRate, inputBlockSize); + vfo.vfo->start(); + } fftHandlerSink.start(); fftBlockDec.start(); split.start(); dcBiasRemover.start(); -} - -void SignalPath::setVFOFrequency(long frequency) { - mainVFO.setOffset(frequency); -} - -void SignalPath::setVolume(float volume) { - audio.setVolume(volume); -} - -void SignalPath::setDemodulator(int demId) { - if (demId < 0 || demId >= _DEMOD_COUNT) { - return; - } - - audioResamp.stop(); - - // Stop current demodulator - if (_demod == DEMOD_FM) { - demod.stop(); - } - else if (_demod == DEMOD_NFM) { - demod.stop(); - } - else if (_demod == DEMOD_AM) { - amDemod.stop(); - } - else if (_demod == DEMOD_USB) { - ssbDemod.stop(); - } - else if (_demod == DEMOD_LSB) { - ssbDemod.stop(); - } - _demod = demId; - - // Set input of the audio resampler - if (demId == DEMOD_FM) { - mainVFO.setOutputSampleRate(200000, 200000); - demod.setBlockSize(mainVFO.getOutputBlockSize()); - demod.setSampleRate(200000); - demod.setDeviation(100000); - audioResamp.setInput(&demod.output); - audioResamp.setInputSampleRate(200000, mainVFO.getOutputBlockSize()); - demod.start(); - } - if (demId == DEMOD_NFM) { - mainVFO.setOutputSampleRate(12500, 12500); - demod.setBlockSize(mainVFO.getOutputBlockSize()); - demod.setSampleRate(12500); - demod.setDeviation(6250); - audioResamp.setInput(&demod.output); - audioResamp.setInputSampleRate(12500, mainVFO.getOutputBlockSize()); - demod.start(); - } - else if (demId == DEMOD_AM) { - mainVFO.setOutputSampleRate(12500, 12500); - amDemod.setBlockSize(mainVFO.getOutputBlockSize()); - audioResamp.setInput(&amDemod.output); - audioResamp.setInputSampleRate(12500, mainVFO.getOutputBlockSize()); - amDemod.start(); - } - else if (demId == DEMOD_USB) { - mainVFO.setOutputSampleRate(6000, 3000); - ssbDemod.setBlockSize(mainVFO.getOutputBlockSize()); - ssbDemod.setMode(dsp::SSBDemod::MODE_USB); - audioResamp.setInput(&ssbDemod.output); - audioResamp.setInputSampleRate(6000, mainVFO.getOutputBlockSize()); - ssbDemod.start(); - } - else if (demId == DEMOD_LSB) { - mainVFO.setOutputSampleRate(6000, 3000); - ssbDemod.setBlockSize(mainVFO.getOutputBlockSize()); - ssbDemod.setMode(dsp::SSBDemod::MODE_LSB); - audioResamp.setInput(&ssbDemod.output); - audioResamp.setInputSampleRate(6000, mainVFO.getOutputBlockSize()); - ssbDemod.start(); - } - - audioResamp.start(); + dynSplit.start(); } void SignalPath::start() { @@ -141,13 +61,39 @@ void SignalPath::start() { fftBlockDec.start(); fftHandlerSink.start(); - mainVFO.start(); - demod.start(); - - audioResamp.start(); - audio.start(); + dynSplit.start(); } void SignalPath::setDCBiasCorrection(bool enabled) { dcBiasRemover.bypass = !enabled; +} + +dsp::VFO* SignalPath::addVFO(std::string name, float outSampleRate, float bandwidth, float offset) { + if (vfos.find(name) != vfos.end()) { + return NULL; + } + dynSplit.stop(); + VFO_t vfo; + vfo.inputStream = new dsp::stream(inputBlockSize * 2); + dynSplit.bind(vfo.inputStream); + vfo.vfo = new dsp::VFO(); + vfo.vfo->init(vfo.inputStream, sampleRate, outSampleRate, bandwidth, offset, inputBlockSize); + vfo.vfo->start(); + vfos[name] = vfo; + dynSplit.start(); + return vfo.vfo; +} + +void SignalPath::removeVFO(std::string name) { + if (vfos.find(name) == vfos.end()) { + return; + } + dynSplit.stop(); + VFO_t vfo = vfos[name]; + vfo.vfo->stop(); + dynSplit.unbind(vfo.inputStream); + delete vfo.vfo; + delete vfo.inputStream; + dynSplit.start(); + vfos.erase(name); } \ No newline at end of file diff --git a/src/signal_path.h b/src/signal_path.h index a587c059..b7998f45 100644 --- a/src/signal_path.h +++ b/src/signal_path.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include class SignalPath { public: @@ -18,22 +20,15 @@ public: void setSampleRate(float sampleRate); void setDCBiasCorrection(bool enabled); void setFFTRate(float rate); - - void setVFOFrequency(long frequency); - void setVolume(float volume); - - void setDemodulator(int demod); - - enum { - DEMOD_FM, - DEMOD_NFM, - DEMOD_AM, - DEMOD_USB, - DEMOD_LSB, - _DEMOD_COUNT - }; + dsp::VFO* addVFO(std::string name, float outSampleRate, float bandwidth, float offset); + void removeVFO(std::string name); private: + struct VFO_t { + dsp::stream* inputStream; + dsp::VFO* vfo; + }; + dsp::DCBiasRemover dcBiasRemover; dsp::Splitter split; @@ -42,19 +37,11 @@ private: dsp::HandlerSink fftHandlerSink; // VFO - dsp::VFO mainVFO; - - // Demodulators - dsp::FMDemodulator demod; - dsp::AMDemodulator amDemod; - dsp::SSBDemod ssbDemod; - - // Audio output - dsp::FloatFIRResampler audioResamp; - io::AudioSink audio; + dsp::DynamicSplitter dynSplit; + std::map vfos; float sampleRate; float fftRate; int fftSize; - int _demod; + int inputBlockSize; }; \ No newline at end of file diff --git a/src/vfo_manager.cpp b/src/vfo_manager.cpp new file mode 100644 index 00000000..498eb9fe --- /dev/null +++ b/src/vfo_manager.cpp @@ -0,0 +1,97 @@ +#include + +namespace vfoman { + std::map vfos; + ImGui::WaterFall* _wtf; + SignalPath* _sigPath; + + void init(ImGui::WaterFall* wtf, SignalPath* sigPath) { + _wtf = wtf; + _sigPath = sigPath; + } + + dsp::stream* create(std::string name, int reference, float offset, float bandwidth, float sampleRate, int blockSize) { + if (vfos.find(name) != vfos.end()) { + spdlog::warn("Tried to add VFO with an already existing name: {0}", name); + return NULL; + } + spdlog::info("Creating new VFO '{0}'", name); + VFO_t vfo; + vfo.dspVFO = _sigPath->addVFO(name, sampleRate, bandwidth, offset); + vfo.wtfVFO = new ImGui::WaterfallVFO; + vfo.wtfVFO->setReference(reference); + vfo.wtfVFO->setBandwidth(bandwidth); + vfo.wtfVFO->setOffset(offset); + _wtf->vfos[name] = vfo.wtfVFO; + vfos[name] = vfo; + return vfo.dspVFO->output; + } + + void setOffset(std::string name, float offset) { + if (vfos.find(name) == vfos.end()) { + return; + } + VFO_t vfo = vfos[name]; + vfo.wtfVFO->setOffset(offset); + vfo.dspVFO->setOffset(vfo.wtfVFO->centerOffset); + } + + void setCenterOffset(std::string name, float offset) { + if (vfos.find(name) == vfos.end()) { + return; + } + VFO_t vfo = vfos[name]; + vfo.wtfVFO->setCenterOffset(offset); + vfo.dspVFO->setOffset(offset); + } + + void setBandwidth(std::string name, float bandwidth) { + if (vfos.find(name) == vfos.end()) { + return; + } + VFO_t vfo = vfos[name]; + vfo.wtfVFO->setBandwidth(bandwidth); + vfo.dspVFO->setBandwidth(bandwidth); + } + + void setSampleRate(std::string name, float sampleRate, float bandwidth) { + if (vfos.find(name) == vfos.end()) { + return; + } + vfos[name].dspVFO->setOutputSampleRate(sampleRate, bandwidth); + } + + void setReference(std::string name, int ref){ + if (vfos.find(name) == vfos.end()) { + return; + } + vfos[name].wtfVFO->setReference(ref); + } + + int getOutputBlockSize(std::string name) { + if (vfos.find(name) == vfos.end()) { + return -1; + } + return vfos[name].dspVFO->getOutputBlockSize(); + } + + void remove(std::string name) { + if (vfos.find(name) == vfos.end()) { + return; + } + VFO_t vfo = vfos[name]; + _wtf->vfos.erase(name); + _sigPath->removeVFO(name); + delete vfo.wtfVFO; + vfos.erase(name); + } + + void updateFromWaterfall() { + for (auto const& [name, vfo] : vfos) { + if (vfo.wtfVFO->centerOffsetChanged) { + vfo.wtfVFO->centerOffsetChanged = false; + vfo.dspVFO->setOffset(vfo.wtfVFO->centerOffset); + } + } + } +}; \ No newline at end of file diff --git a/src/vfo_manager.h b/src/vfo_manager.h index 20f8807d..6656db84 100644 --- a/src/vfo_manager.h +++ b/src/vfo_manager.h @@ -1,5 +1,24 @@ #pragma once +#include +#include +#include namespace vfoman { - + struct VFO_t { + dsp::VFO* dspVFO; + ImGui::WaterfallVFO* wtfVFO; + }; + + void init(ImGui::WaterFall* wtf, SignalPath* sigPath); + + dsp::stream* create(std::string name, int reference, float offset, float bandwidth, float sampleRate, int blockSize); + void setOffset(std::string name, float offset); + void setCenterOffset(std::string name, float offset); + void setBandwidth(std::string name, float bandwidth); + void setSampleRate(std::string name, float sampleRate, float bandwidth); + void setReference(std::string name, int ref); + int getOutputBlockSize(std::string name); + void remove(std::string name); + + void updateFromWaterfall(); }; \ No newline at end of file diff --git a/src/waterfall.cpp b/src/waterfall.cpp index 0664985c..10b4f63e 100644 --- a/src/waterfall.cpp +++ b/src/waterfall.cpp @@ -183,28 +183,51 @@ namespace ImGui { } } + void WaterFall::selectFirstVFO() { + for (auto const& [name, vfo] : vfos) { + selectedVFO = name; + return; + } + } + void WaterFall::processInputs() { + WaterfallVFO* vfo = vfos[selectedVFO]; ImVec2 mousePos = ImGui::GetMousePos(); ImVec2 drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left); ImVec2 dragOrigin(mousePos.x - drag.x, mousePos.y - drag.y); + bool draging = ImGui::IsMouseDragging(ImGuiMouseButton_Left); + bool mouseInFreq = IS_IN_AREA(dragOrigin, freqAreaMin, freqAreaMax); + bool mouseInFFT = IS_IN_AREA(dragOrigin, fftAreaMin, fftAreaMax); + // If mouse was clicked on a VFO, select VFO and return + // If mouse was clicked but not on a VFO, move selected VFO to position if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { - spdlog::info("Clicked!"); + for (auto const& [name, _vfo] : vfos) { + if (name == selectedVFO) { + continue; + } + if (IS_IN_AREA(mousePos, _vfo->rectMin, _vfo->rectMax)) { + selectedVFO = name; + selectedVFOChanged = true; + return; + } + } + int refCenter = mousePos.x - (widgetPos.x + 50); + if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y)) { + vfo->setOffset(((((float)refCenter / ((float)dataWidth / 2.0f)) - 1.0f) * (viewBandwidth / 2.0f)) + viewOffset); + } } - bool freqDrag = ImGui::IsMouseDragging(ImGuiMouseButton_Left) && IS_IN_AREA(dragOrigin, freqAreaMin, freqAreaMax); + // Draging VFO + if (draging && mouseInFFT) { + int refCenter = mousePos.x - (widgetPos.x + 50); + if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y)) { + vfo->setOffset(((((float)refCenter / ((float)dataWidth / 2.0f)) - 1.0f) * (viewBandwidth / 2.0f)) + viewOffset); + } + } - // TODO: Process VFO drag - - // if (ImGui::IsMouseDown(ImGuiMouseButton_Left) && IS_IN_AREA(mousePos, fftAreaMin, fftAreaMax) && !freqDrag) { - // int refCenter = mousePos.x - (widgetPos.x + 50); - // if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y)) { - // vfoOffset = ((((float)refCenter / ((float)dataWidth / 2.0f)) - 1.0f) * (viewBandwidth / 2.0f)) + viewOffset; - // } - // vfoFreqChanged = true; - // } - - if (freqDrag) { + // Dragon frequency scale + if (draging && mouseInFreq) { float deltax = drag.x - lastDrag; lastDrag = drag.x; float viewDelta = deltax * (viewBandwidth / (float)dataWidth); diff --git a/src/waterfall.h b/src/waterfall.h index 6729838b..defee9f4 100644 --- a/src/waterfall.h +++ b/src/waterfall.h @@ -85,6 +85,8 @@ namespace ImGui { void autoRange(); + void selectFirstVFO(); + bool centerFreqMoved = false; bool vfoFreqChanged = false; bool bandplanEnabled = false; @@ -92,6 +94,7 @@ namespace ImGui { std::map vfos; std::string selectedVFO; + bool selectedVFOChanged = false; enum { REF_LOWER,