This commit is contained in:
Ryzerth 2020-08-11 18:33:42 +02:00
parent b65bddc1b3
commit cdea80f8c5
19 changed files with 706 additions and 244 deletions

View File

@ -18,6 +18,7 @@ else()
link_libraries(portaudio)
link_libraries(X11)
link_libraries(Xxf86vm)
link_libraries(DL)
endif (MSVC)
link_libraries(volk)

View File

@ -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"

View File

@ -0,0 +1,79 @@
#include <imgui.h>
#include <module.h>
#include <path.h>
#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;
}

114
modules/radio/src/path.cpp Normal file
View File

@ -0,0 +1,114 @@
#include <path.h>
SigPath::SigPath() {
}
void SigPath::init(std::string vfoName, uint64_t sampleRate, int blockSize, dsp::stream<dsp::complex_t>* 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();
}

54
modules/radio/src/path.h Normal file
View File

@ -0,0 +1,54 @@
#pragma once
#include <dsp/filter.h>
#include <dsp/resampling.h>
#include <dsp/source.h>
#include <dsp/math.h>
#include <dsp/demodulator.h>
#include <dsp/routing.h>
#include <dsp/sink.h>
#include <dsp/correction.h>
#include <dsp/vfo.h>
#include <io/audio.h>
#include <module.h>
class SigPath {
public:
SigPath();
void init(std::string vfoName, uint64_t sampleRate, int blockSize, dsp::stream<dsp::complex_t>* 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<dsp::complex_t> 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;
};

View File

@ -75,4 +75,96 @@ namespace dsp {
std::thread _workerThread;
bool running = false;
};
class DynamicSplitter {
public:
DynamicSplitter() {
}
DynamicSplitter(stream<complex_t>* input, int bufferSize) {
_in = input;
_bufferSize = bufferSize;
}
void init(stream<complex_t>* 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<complex_t>* stream) {
if (running) {
return;
}
outputs.push_back(stream);
}
void unbind(stream<complex_t>* 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<complex_t>* _in;
int _bufferSize;
std::thread _workerThread;
bool running = false;
std::vector<stream<complex_t>*> outputs;
};
};

View File

@ -3,6 +3,7 @@
#include <dsp/stream.h>
#include <dsp/types.h>
#include <volk.h>
#include <spdlog/spdlog.h>
namespace dsp {
class SineSource {

View File

@ -3,6 +3,7 @@
#include <dsp/math.h>
#include <dsp/resampling.h>
#include <dsp/filter.h>
#include <spdlog/spdlog.h>
namespace dsp {
class VFO {

View File

@ -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.");

View File

@ -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<bool> dcbias(false, false);
watcher<bool> 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();

View File

@ -22,6 +22,7 @@
#include <bandplan.h>
#include <watcher.h>
#include <module.h>
#include <vfo_manager.h>
#define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground

View File

@ -1,10 +1,22 @@
#include <module.h>
#include <vfo_manager.h>
namespace mod {
API_t API;
std::map<std::string, Module_t> modules;
std::vector<std::string> 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);
}
}
};

View File

@ -5,18 +5,40 @@
#include <stdint.h>
#include <imgui.h>
#include <spdlog/spdlog.h>
#include <dsp/types.h>
#include <dsp/stream.h>
#ifdef _WIN32
#include <Windows.h>
#define MOD_EXPORT extern "C" \
__declspec(dllexport)
#else
#include <dlfcn.h>
#define MOD_EXPORT extern "C"
#endif
namespace mod {
struct API_t {
dsp::stream<dsp::complex_t>* (*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<std::string, Module_t> modules;
extern std::vector<std::string> moduleNames;
};
};
extern mod::API_t* API;

View File

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

View File

@ -9,6 +9,8 @@
#include <dsp/correction.h>
#include <dsp/vfo.h>
#include <io/audio.h>
#include <map>
#include <module.h>
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<dsp::complex_t>* 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<std::string, VFO_t> vfos;
float sampleRate;
float fftRate;
int fftSize;
int _demod;
int inputBlockSize;
};

97
src/vfo_manager.cpp Normal file
View File

@ -0,0 +1,97 @@
#include <vfo_manager.h>
namespace vfoman {
std::map<std::string, VFO_t> vfos;
ImGui::WaterFall* _wtf;
SignalPath* _sigPath;
void init(ImGui::WaterFall* wtf, SignalPath* sigPath) {
_wtf = wtf;
_sigPath = sigPath;
}
dsp::stream<dsp::complex_t>* 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);
}
}
}
};

View File

@ -1,5 +1,24 @@
#pragma once
#include <dsp/vfo.h>
#include <waterfall.h>
#include <signal_path.h>
namespace vfoman {
struct VFO_t {
dsp::VFO* dspVFO;
ImGui::WaterfallVFO* wtfVFO;
};
void init(ImGui::WaterFall* wtf, SignalPath* sigPath);
dsp::stream<dsp::complex_t>* 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();
};

View File

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

View File

@ -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<std::string, WaterfallVFO*> vfos;
std::string selectedVFO;
bool selectedVFOChanged = false;
enum {
REF_LOWER,