mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-11-06 10:47:34 +01:00
modules
This commit is contained in:
parent
b65bddc1b3
commit
cdea80f8c5
@ -18,6 +18,7 @@ else()
|
||||
link_libraries(portaudio)
|
||||
link_libraries(X11)
|
||||
link_libraries(Xxf86vm)
|
||||
link_libraries(DL)
|
||||
endif (MSVC)
|
||||
|
||||
link_libraries(volk)
|
||||
|
55
modules/radio/CMakeLists.txt
Normal file
55
modules/radio/CMakeLists.txt
Normal 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"
|
79
modules/radio/src/main.cpp
Normal file
79
modules/radio/src/main.cpp
Normal 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
114
modules/radio/src/path.cpp
Normal 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
54
modules/radio/src/path.h
Normal 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;
|
||||
};
|
@ -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;
|
||||
};
|
||||
};
|
@ -3,6 +3,7 @@
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/types.h>
|
||||
#include <volk.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace dsp {
|
||||
class SineSource {
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <dsp/math.h>
|
||||
#include <dsp/resampling.h>
|
||||
#include <dsp/filter.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace dsp {
|
||||
class VFO {
|
||||
|
@ -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.");
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
29
src/module.h
29
src/module.h
@ -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;
|
@ -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);
|
||||
}
|
@ -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
97
src/vfo_manager.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@ -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();
|
||||
};
|
@ -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);
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user