Merge pull request #40 from AlexandreRouma/better_dsp

Better dsp
This commit is contained in:
AlexandreRouma 2020-12-14 00:23:35 +01:00 committed by GitHub
commit b02b6c30b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
299 changed files with 125024 additions and 3926 deletions

7
.gitignore vendored
View File

@ -1,3 +1,10 @@
build/
.vscode/
*.old
*.dll
*.exe
*.zip
*.wav
.DS_Store
sdrpp_v0.2.5_beta_x64
sdrpp_v0.2.5_beta_x64_new_wf

View File

@ -1,59 +1,46 @@
cmake_minimum_required(VERSION 3.9)
cmake_minimum_required(VERSION 3.13)
project(sdrpp)
# Core of SDR++
add_subdirectory("core")
# Cross platform modules
add_subdirectory("radio")
add_subdirectory("recorder")
add_subdirectory("soapy_source")
#add_subdirectory("file_source")
add_subdirectory("rtl_tcp_source")
add_subdirectory("audio_sink")
#add_subdirectory("rx888_source")
add_subdirectory("plutosdr_source")
#add_subdirectory("demo")
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17")
link_directories(sdrpp "C:/Program Files/PothosSDR/lib/")
include_directories(sdrpp "C:/Program Files/PothosSDR/include/volk/")
include_directories(sdrpp "C:/Program Files/PothosSDR/include/")
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
else()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fsanitize=address -g")
include_directories(sdrpp "/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)
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
endif (MSVC)
link_libraries(volk)
link_libraries(SoapySDR)
# Main code
include_directories(sdrpp "src/")
include_directories(sdrpp "src/imgui")
file(GLOB SRC "src/*.cpp")
file(GLOB IMGUI "src/imgui/*.cpp")
add_executable(sdrpp ${SRC} ${IMGUI})
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
target_link_libraries(sdrpp PRIVATE sdrpp_core)
# Copy dynamic libs over
if (MSVC)
# Glew
find_package(GLEW REQUIRED)
target_link_libraries(sdrpp PRIVATE GLEW::GLEW)
# GLFW3
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE glfw)
# FFTW3
find_package(FFTW3 CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3)
find_package(FFTW3f CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3f)
# PortAudio
find_package(portaudio CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE portaudio portaudio_static)
add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
endif (MSVC)
# # Copy resource directories
# if (!MSVC)
# add_custom_command(TARGET sdrpp POST_BUILD COMMAND cmake -E copy_directory ${CMAKE_SOURCE_DIR}/res ${CMAKE_BINARY_DIR}/res)
# add_custom_command(TARGET sdrpp POST_BUILD COMMAND cmake -E copy_directory ${CMAKE_SOURCE_DIR}/bandplans ${CMAKE_BINARY_DIR}/bandplans)
# endif (MSVC)
if (${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif ()
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif ()
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif ()
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64"

View File

@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.13)
project(airspy_hf_source)
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
else()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
endif (MSVC)
include_directories("src/")
file(GLOB SRC "src/*.cpp")
add_library(airspy_hf_source SHARED ${SRC})
target_link_libraries(airspy_hf_source PRIVATE sdrpp_core)
set_target_properties(airspy_hf_source PROPERTIES PREFIX "")
if(WIN32)
target_link_libraries(airspy_hf_source PRIVATE wsock32 ws2_32)
endif()

View File

@ -0,0 +1,192 @@
#include <imgui.h>
#include <spdlog/spdlog.h>
#include <new_module.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <core.h>
#include <gui/style.h>
#include <libairspyhf/airspyhf.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
/* Name: */ "airspy_hf_source",
/* Description: */ "Airspy HF source module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
class AirspyHFSourceModule : public ModuleManager::Instance {
public:
AirspyHFSourceModule(std::string name) {
this->name = name;
sampleRate = 2560000.0;
handler.ctx = this;
handler.selectHandler = menuSelected;
handler.deselectHandler = menuDeselected;
handler.menuHandler = menuHandler;
handler.startHandler = start;
handler.stopHandler = stop;
handler.tuneHandler = tune;
handler.stream = &stream;
sigpath::sourceManager.registerSource("Airspy HF+", &handler);
}
~AirspyHFSourceModule() {
}
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static void menuSelected(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
spdlog::info("AirspyHFSourceModule '{0}': Menu Select!", _this->name);
}
static void menuDeselected(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
spdlog::info("AirspyHFSourceModule '{0}': Menu Deselect!", _this->name);
}
static void start(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
if (_this->running) {
return;
}
// DO START HERE
_this->running = true;
_this->workerThread = std::thread(worker, _this);
spdlog::info("AirspyHFSourceModule '{0}': Start!", _this->name);
}
static void stop(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
if (!_this->running) {
return;
}
// DO STOP HERE
_this->running = false;
_this->stream.stopWriter();
_this->workerThread.join();
_this->stream.clearWriteStop();
spdlog::info("AirspyHFSourceModule '{0}': Stop!", _this->name);
}
static void tune(double freq, void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
if (_this->running) {
_this->client.setFrequency(freq);
}
_this->freq = freq;
spdlog::info("AirspyHFSourceModule '{0}': Tune: {1}!", _this->name, freq);
}
static void menuHandler(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvailWidth();
float portWidth = ImGui::CalcTextSize("00000").x + 20;
ImGui::SetNextItemWidth(menuWidth - portWidth);
ImGui::InputText(CONCAT("##_ip_select_", _this->name), _this->ip, 1024);
ImGui::SameLine();
ImGui::SetNextItemWidth(portWidth);
ImGui::InputInt(CONCAT("##_port_select_", _this->name), &_this->port, 0);
ImGui::SetNextItemWidth(ImGui::CalcTextSize("OOOOOOOOOO").x);
if (ImGui::Combo("Direct sampling", &_this->directSamplingMode, "Disabled\0I branch\0Q branch\0")) {
if (_this->running) {
_this->client.setDirectSampling(_this->directSamplingMode);
}
}
if (ImGui::Checkbox("RTL AGC", &_this->rtlAGC)) {
if (_this->running) {
_this->client.setAGCMode(_this->rtlAGC);
}
}
if (ImGui::Checkbox("Tuner AGC", &_this->tunerAGC)) {
if (_this->running) {
_this->client.setGainMode(!_this->tunerAGC);
if (!_this->tunerAGC) {
_this->client.setGainIndex(_this->gain);
}
}
}
if (_this->tunerAGC) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::SliderInt(CONCAT("##_gain_select_", _this->name), &_this->gain, 0, 29, "")) {
if (_this->running) {
_this->client.setGainIndex(_this->gain);
}
}
if (_this->tunerAGC) { style::endDisabled(); }
}
static void worker(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
int blockSize = _this->sampleRate / 200.0;
uint8_t* inBuf = new uint8_t[blockSize * 2];
while (true) {
// Read samples here
_this->client.receiveData(inBuf, blockSize * 2);
if (_this->stream.aquire() < 0) { break; }
for (int i = 0; i < blockSize; i++) {
_this->stream.data[i].q = ((double)inBuf[i * 2] - 128.0) / 128.0;
_this->stream.data[i].i = ((double)inBuf[(i * 2) + 1] - 128.0) / 128.0;
}
_this->stream.write(blockSize);
}
delete[] inBuf;
}
std::string name;
bool enabled = true;
dsp::stream<dsp::complex_t> stream;
double sampleRate;
SourceManager::SourceHandler handler;
std::thread workerThread;
RTLTCPClient client;
bool running = false;
double freq;
char ip[1024] = "localhost";
int port = 1234;
int gain = 0;
bool rtlAGC = false;
bool tunerAGC = false;
int directSamplingMode = 0;
};
MOD_EXPORT void _INIT_() {
// Do your one time init here
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new AirspyHFSourceModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
delete (AirspyHFSourceModule*)instance;
}
MOD_EXPORT void _END_() {
// Do your one shutdown here
}

16
audio_sink/CMakeLists.txt Normal file
View File

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.13)
project(audio_sink)
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
else()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
endif (MSVC)
file(GLOB SRC "src/*.cpp")
include_directories("src/")
add_library(audio_sink SHARED ${SRC})
target_link_libraries(audio_sink PRIVATE sdrpp_core)
set_target_properties(audio_sink PROPERTIES PREFIX "")

287
audio_sink/src/main.cpp Normal file
View File

@ -0,0 +1,287 @@
#include <imgui.h>
#include <new_module.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <signal_path/sink.h>
#include <portaudio.h>
#include <dsp/audio.h>
#include <spdlog/spdlog.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
/* Name: */ "audio_sink",
/* Description: */ "Audio sink module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
class AudioSink : SinkManager::Sink {
public:
struct AudioDevice_t {
std::string name;
int index;
int channels;
int srId;
std::vector<double> sampleRates;
std::string txtSampleRates;
};
AudioSink(SinkManager::Stream* stream, std::string streamName) {
_stream = stream;
_streamName = streamName;
s2m.init(_stream->sinkOut);
monoRB.init(&s2m.out);
stereoRB.init(_stream->sinkOut);
// Initialize PortAudio
Pa_Initialize();
devCount = Pa_GetDeviceCount();
devId = Pa_GetDefaultOutputDevice();
const PaDeviceInfo *deviceInfo;
PaStreamParameters outputParams;
outputParams.sampleFormat = paFloat32;
outputParams.hostApiSpecificStreamInfo = NULL;
// Gather hardware info
for(int i = 0; i < devCount; i++) {
deviceInfo = Pa_GetDeviceInfo(i);
if (deviceInfo->maxOutputChannels < 1) {
continue;
}
AudioDevice_t dev;
dev.name = deviceInfo->name;
dev.index = i;
dev.channels = std::min<int>(deviceInfo->maxOutputChannels, 2);
dev.sampleRates.clear();
dev.txtSampleRates = "";
for (int j = 0; j < 6; j++) {
outputParams.channelCount = dev.channels;
outputParams.device = dev.index;
outputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency;
PaError err = Pa_IsFormatSupported(NULL, &outputParams, POSSIBLE_SAMP_RATE[j]);
if (err != paFormatIsSupported) {
continue;
}
dev.sampleRates.push_back(POSSIBLE_SAMP_RATE[j]);
dev.txtSampleRates += std::to_string((int)POSSIBLE_SAMP_RATE[j]);
dev.txtSampleRates += '\0';
}
if (dev.sampleRates.size() == 0) {
continue;
}
if (i == devId) {
devListId = devices.size();
defaultDev = devListId;
}
dev.srId = 0;
AudioDevice_t* _dev = new AudioDevice_t;
*_dev = dev;
devices.push_back(_dev);
deviceNames.push_back(deviceInfo->name);
txtDevList += deviceInfo->name;
txtDevList += '\0';
}
// Load config from file
}
~AudioSink() {
for (auto const& dev : devices) {
delete dev;
}
}
void start() {
if (running) {
return;
}
doStart();
running = true;
}
void stop() {
if (!running) {
return;
}
doStop();
running = false;
}
void menuHandler() {
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(("##_audio_sink_dev_"+_streamName).c_str(), &devListId, txtDevList.c_str())) {
// TODO: Load SR from config
if (running) {
doStop();
doStart();
}
// TODO: Save to config
}
AudioDevice_t* dev = devices[devListId];
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(("##_audio_sink_sr_"+_streamName).c_str(), &dev->srId, dev->txtSampleRates.c_str())) {
_stream->setSampleRate(dev->sampleRates[dev->srId]);
if (running) {
doStop();
doStart();
}
// TODO: Save to config
}
}
private:
void doStart() {
const PaDeviceInfo *deviceInfo;
AudioDevice_t* dev = devices[devListId];
PaStreamParameters outputParams;
deviceInfo = Pa_GetDeviceInfo(dev->index);
outputParams.channelCount = 2;
outputParams.sampleFormat = paFloat32;
outputParams.hostApiSpecificStreamInfo = NULL;
outputParams.device = dev->index;
outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency;
PaError err;
float sampleRate = dev->sampleRates[dev->srId];
int bufferSize = sampleRate / 60.0f;
if (dev->channels == 2) {
stereoRB.data.setMaxLatency(bufferSize * 2);
stereoRB.start();
err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, paFramesPerBufferUnspecified, 0, _stereo_cb, this);
}
else {
monoRB.data.setMaxLatency(bufferSize * 2);
monoRB.start();
err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, paFramesPerBufferUnspecified, 0, _mono_cb, this);
}
if (err != 0) {
spdlog::error("Error while opening audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
return;
}
err = Pa_StartStream(stream);
if (err != 0) {
spdlog::error("Error while starting audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
return;
}
spdlog::info("Audio device open.");
running = true;
}
void doStop() {
monoRB.stop();
stereoRB.stop();
monoRB.data.stopReader();
stereoRB.data.stopReader();
Pa_StopStream(stream);
Pa_CloseStream(stream);
monoRB.data.clearReadStop();
stereoRB.data.clearReadStop();
}
static int _mono_cb(const void *input, void *output, unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) {
AudioSink* _this = (AudioSink*)userData;
_this->monoRB.data.read((float*)output, frameCount);
return 0;
}
static int _stereo_cb(const void *input, void *output, unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) {
AudioSink* _this = (AudioSink*)userData;
_this->stereoRB.data.read((dsp::stereo_t*)output, frameCount);
return 0;
}
SinkManager::Stream* _stream;
dsp::StereoToMono s2m;
dsp::RingBufferSink<float> monoRB;
dsp::RingBufferSink<dsp::stereo_t> stereoRB;
std::string _streamName;
PaStream *stream;
int srId = 0;
int devCount;
int devId = 0;
int devListId = 0;
int defaultDev = 0;
bool running = false;
const double POSSIBLE_SAMP_RATE[6] = {
48000.0f,
44100.0f,
24000.0f,
22050.0f,
12000.0f,
11025.0f
};
std::vector<AudioDevice_t*> devices;
std::vector<std::string> deviceNames;
std::string txtDevList;
};
class AudioSinkModule : public ModuleManager::Instance {
public:
AudioSinkModule(std::string name) {
this->name = name;
provider.create = create_sink;
provider.ctx = this;
sigpath::sinkManager.registerSinkProvider("Audio", provider);
}
~AudioSinkModule() {
}
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
return (SinkManager::Sink*)(new AudioSink(stream, streamName));
}
std::string name;
bool enabled = true;
SinkManager::SinkProvider provider;
};
MOD_EXPORT void _INIT_() {
// Nothing here
// TODO: Do instancing here (in source modules as well) to prevent multiple loads
}
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
AudioSinkModule* instance = new AudioSinkModule(name);
return instance;
}
MOD_EXPORT void _DELETE_INSTANCE_() {
}
MOD_EXPORT void _END_() {
}

103
core/CMakeLists.txt Normal file
View File

@ -0,0 +1,103 @@
cmake_minimum_required(VERSION 3.13)
project(sdrpp_core)
# Set compiler options
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
else()
set(CMAKE_CXX_FLAGS "-g -O3 -std=c++17 -fpermissive")
endif (MSVC)
add_definitions(-DSDRPP_IS_CORE)
# Main code
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
# Add code to dyn lib
add_library(sdrpp_core SHARED ${SRC})
# Include core headers
target_include_directories(sdrpp_core PUBLIC "src/")
target_include_directories(sdrpp_core PUBLIC "src/imgui")
if (MSVC)
# Lib path
target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/lib/")
# Misc headers
target_include_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/include/")
# Volk
target_link_libraries(sdrpp_core PUBLIC volk)
# SoapySDR
target_link_libraries(sdrpp_core PUBLIC SoapySDR)
# Glew
find_package(GLEW REQUIRED)
target_link_libraries(sdrpp_core PUBLIC GLEW::GLEW)
# GLFW3
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(sdrpp_core PUBLIC glfw)
# FFTW3
find_package(FFTW3f CONFIG REQUIRED)
target_link_libraries(sdrpp_core PUBLIC FFTW3::fftw3f)
# PortAudio
find_package(portaudio CONFIG REQUIRED)
target_link_libraries(sdrpp_core PUBLIC portaudio portaudio_static)
target_link_libraries(sdrpp_core PUBLIC volk)
else()
find_package(PkgConfig)
find_package(OpenGL REQUIRED)
pkg_check_modules(GLEW REQUIRED glew)
pkg_check_modules(FFTW3 REQUIRED fftw3f)
pkg_check_modules(VOLK REQUIRED volk)
pkg_check_modules(GLFW3 REQUIRED glfw3)
pkg_check_modules(PORTAUDIO REQUIRED portaudio-2.0)
target_include_directories(sdrpp_core PUBLIC
${GLEW_INCLUDE_DIRS}
${FFTW3_INCLUDE_DIRS}
${GLFW3_INCLUDE_DIRS}
${VOLK_INCLUDE_DIRS}
${PORTAUDIO_INCLUDE_DIRS}
)
target_link_directories(sdrpp_core PUBLIC
${GLEW_LIBRARY_DIRS}
${FFTW3_LIBRARY_DIRS}
${GLFW3_LIBRARY_DIRS}
${VOLK_LIBRARY_DIRS}
${PORTAUDIO_LIBRARY_DIRS}
)
target_link_libraries(sdrpp_core PUBLIC
${OPENGL_LIBRARIES}
${GLEW_STATIC_LIBRARIES}
${FFTW3_STATIC_LIBRARIES}
${GLFW3_STATIC_LIBRARIES}
${VOLK_STATIC_LIBRARIES}
${PORTAUDIO_STATIC_LIBRARIES}
)
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_link_libraries(sdrpp_core PUBLIC stdc++fs)
endif ()
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++")
endif ()
endif ()
set(CORE_FILES ${RUNTIME_OUTPUT_DIRECTORY} PARENT_SCOPE)
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64"

118
core/src/config.cpp Normal file
View File

@ -0,0 +1,118 @@
#include <config.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <filesystem>
ConfigManager::ConfigManager() {
}
ConfigManager::~ConfigManager() {
disableAutoSave();
}
void ConfigManager::setPath(std::string file) {
path = file;
}
void ConfigManager::load(json def, bool lock) {
if (lock) { mtx.lock(); }
if (path == "") {
spdlog::error("Config manager tried to load file with no path specified");
return;
}
if (!std::filesystem::exists(path)) {
spdlog::warn("Config file '{0}' does not exist, creating it", path);
conf = def;
save(false);
}
if (!std::filesystem::is_regular_file(path)) {
spdlog::error("Config file '{0}' isn't a file", path);
return;
}
std::ifstream file(path.c_str());
file >> conf;
file.close();
if (lock) { mtx.unlock(); }
}
void ConfigManager::save(bool lock) {
if (lock) { mtx.lock(); }
std::ofstream file(path.c_str());
file << conf.dump(4);
file.close();
if (lock) { mtx.unlock(); }
}
void ConfigManager::enableAutoSave() {
if (!autoSaveEnabled) {
autoSaveEnabled = true;
autoSaveThread = std::thread(autoSaveWorker, this);
}
}
void ConfigManager::disableAutoSave() {
if (autoSaveEnabled) {
autoSaveEnabled = false;
autoSaveThread.join();
}
}
void ConfigManager::aquire() {
mtx.lock();
}
void ConfigManager::release(bool changed) {
this->changed |= changed;
mtx.unlock();
}
void ConfigManager::autoSaveWorker(ConfigManager* _this) {
while (_this->autoSaveEnabled) {
if (!_this->mtx.try_lock()) {
spdlog::warn("ConfigManager locked, waiting...");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
continue;
}
if (_this->changed) {
_this->changed = false;
_this->save(false);
}
_this->mtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
// void ConfigManager::setResourceDir(std::string path) {
// if (!std::filesystem::exists(path)) {
// spdlog::error("Resource directory '{0}' does not exist", path);
// return;
// }
// if (!std::filesystem::is_regular_file(path)) {
// spdlog::error("Resource directory '{0}' is not a directory", path);
// return;
// }
// resDir = path;
// }
// std::string ConfigManager::getResourceDir() {
// return resDir;
// }
// void ConfigManager::setConfigDir(std::string path) {
// if (!std::filesystem::exists(path)) {
// spdlog::error("Resource directory '{0}' does not exist", path);
// return;
// }
// if (!std::filesystem::is_regular_file(path)) {
// spdlog::error("Resource directory '{0}' is not a directory", path);
// return;
// }
// resDir = path;
// }
// std::string ConfigManager::getConfigDir() {
// return configDir;
// }

55
core/src/config.h Normal file
View File

@ -0,0 +1,55 @@
#pragma once
#include <json.hpp>
#include <thread>
#include <string>
#include <mutex>
using nlohmann::json;
#define DEV_BUILD
#define SDRPP_RESOURCE_DIR "/usr/local/"
#ifndef ROOT_DIR
#ifdef DEV_BUILD
#define ROOT_DIR "../root_dev"
#elif _WIN32
#define ROOT_DIR "."
#else
#define ROOT_DIR "/etc/sdrpp"
#endif
#endif
class ConfigManager {
public:
ConfigManager();
~ConfigManager();
void setPath(std::string file);
void load(json def, bool lock = true);
void save(bool lock = true);
void enableAutoSave();
void disableAutoSave();
void aquire();
void release(bool changed = false);
// static void setResourceDir(std::string path);
// static std::string getResourceDir();
// static void setConfigDir(std::string path);
// static std::string getConfigDir();
json conf;
private:
static void autoSaveWorker(ConfigManager* _this);
//static std::string resDir;
//static std::string configDir;
std::string path = "";
bool changed = false;
bool autoSaveEnabled = false;
std::thread autoSaveThread;
std::mutex mtx;
};

295
core/src/core.cpp Normal file
View File

@ -0,0 +1,295 @@
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <gui/main_window.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <gui/icons.h>
#include <version.h>
#include <spdlog/spdlog.h>
#include <gui/widgets/bandplan.h>
#include <stb_image.h>
#include <config.h>
#include <core.h>
#include <duktape/duktape.h>
#include <duktape/duk_console.h>
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <stb_image_resize.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#ifdef _WIN32
#include <Windows.h>
#endif
namespace core {
ConfigManager configManager;
ScriptManager scriptManager;
ModuleManager moduleManager;
void setInputSampleRate(double samplerate) {
// NOTE: Zoom controls won't work
gui::waterfall.setBandwidth(samplerate);
sigpath::signalPath.setSampleRate(samplerate);
}
};
bool maximized = false;
bool fullScreen = false;
static void glfw_error_callback(int error, const char* description) {
spdlog::error("Glfw Error {0}: {1}", error, description);
}
static void maximized_callback(GLFWwindow* window, int n) {
if (n == GLFW_TRUE) {
maximized = true;
}
else {
maximized = false;
}
}
duk_ret_t test_func(duk_context *ctx) {
printf("Hello from C++\n");
return 1;
}
// main
int sdrpp_main() {
#ifdef _WIN32
//FreeConsole();
// ConfigManager::setResourceDir("./res");
// ConfigManager::setConfigDir(".");
#endif
spdlog::info("SDR++ v" VERSION_STR);
// ======== DEFAULT CONFIG ========
json defConfig;
defConfig["bandColors"]["amateur"] = "#FF0000FF";
defConfig["bandColors"]["aviation"] = "#00FF00FF";
defConfig["bandColors"]["broadcast"] = "#0000FFFF";
defConfig["bandColors"]["marine"] = "#00FFFFFF";
defConfig["bandColors"]["military"] = "#FFFF00FF";
defConfig["bandPlan"] = "General";
defConfig["bandPlanEnabled"] = true;
defConfig["centerTuning"] = false;
defConfig["fftHeight"] = 300;
defConfig["frequency"] = 100000000.0;
defConfig["max"] = 0.0;
defConfig["maximized"] = false;
defConfig["menuOrder"] = {
"Source",
"Radio",
"Recorder",
"Sinks",
"Audio",
"Scripting",
"Band Plan",
"Display"
};
defConfig["menuWidth"] = 300;
defConfig["min"] = -70.0;
defConfig["moduleInstances"]["Audio Sink"] = "audio_sink";
defConfig["moduleInstances"]["PlutoSDR Source"] = "plutosdr_source";
defConfig["moduleInstances"]["RTL-TCP Source"] = "rtl_tcp_source";
defConfig["moduleInstances"]["Radio"] = "radio";
defConfig["moduleInstances"]["Recorder"] = "recorder";
defConfig["moduleInstances"]["SoapySDR Source"] = "soapy_source";
defConfig["modules"] = json::array();
defConfig["offset"] = 0.0;
defConfig["showWaterfall"] = true;
defConfig["source"] = "";
defConfig["streams"] = json::object();
defConfig["windowSize"]["h"] = 720;
defConfig["windowSize"]["w"] = 1280;
// Load config
spdlog::info("Loading config");
core::configManager.setPath(ROOT_DIR "/config.json");
core::configManager.load(defConfig);
core::configManager.enableAutoSave();
// Setup window
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) {
return 1;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
core::configManager.aquire();
int winWidth = core::configManager.conf["windowSize"]["w"];
int winHeight = core::configManager.conf["windowSize"]["h"];
maximized = core::configManager.conf["maximized"];
core::configManager.release();
// Create window with graphics context
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
GLFWwindow* window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
if (window == NULL)
return 1;
glfwMakeContextCurrent(window);
#if (GLFW_VERSION_MAJOR == 3) && (GLFW_VERSION_MINOR >= 3)
if (maximized) {
glfwMaximizeWindow(window);
}
glfwSetWindowMaximizeCallback(window, maximized_callback);
#endif
// Load app icon
GLFWimage icons[10];
icons[0].pixels = stbi_load(((std::string)(ROOT_DIR "/res/icons/sdrpp.png")).c_str(), &icons[0].width, &icons[0].height, 0, 4);
icons[1].pixels = (unsigned char*)malloc(16 * 16 * 4); icons[1].width = icons[1].height = 16;
icons[2].pixels = (unsigned char*)malloc(24 * 24 * 4); icons[2].width = icons[2].height = 24;
icons[3].pixels = (unsigned char*)malloc(32 * 32 * 4); icons[3].width = icons[3].height = 32;
icons[4].pixels = (unsigned char*)malloc(48 * 48 * 4); icons[4].width = icons[4].height = 48;
icons[5].pixels = (unsigned char*)malloc(64 * 64 * 4); icons[5].width = icons[5].height = 64;
icons[6].pixels = (unsigned char*)malloc(96 * 96 * 4); icons[6].width = icons[6].height = 96;
icons[7].pixels = (unsigned char*)malloc(128 * 128 * 4); icons[7].width = icons[7].height = 128;
icons[8].pixels = (unsigned char*)malloc(196 * 196 * 4); icons[8].width = icons[8].height = 196;
icons[9].pixels = (unsigned char*)malloc(256 * 256 * 4); icons[9].width = icons[9].height = 256;
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[1].pixels, 16, 16, 16 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[2].pixels, 24, 24, 24 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[3].pixels, 32, 32, 32 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[4].pixels, 48, 48, 48 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[5].pixels, 64, 64, 64 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[6].pixels, 96, 96, 96 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[7].pixels, 128, 128, 128 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[8].pixels, 196, 196, 196 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[9].pixels, 256, 256, 256 * 4, 4);
glfwSetWindowIcon(window, 10, icons);
stbi_image_free(icons[0].pixels);
for (int i = 1; i < 10; i++) {
free(icons[i].pixels);
}
if (glewInit() != GLEW_OK) {
spdlog::error("Failed to initialize OpenGL loader!");
return 1;
}
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.IniFilename = NULL;
// Setup Platform/Renderer bindings
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 150");
style::setDarkStyle();
LoadingScreen::setWindow(window);
LoadingScreen::show("Loading icons");
spdlog::info("Loading icons");
icons::load();
LoadingScreen::show("Loading band plans");
spdlog::info("Loading band plans");
bandplan::loadFromDir(ROOT_DIR "/bandplans");
LoadingScreen::show("Loading band plan colors");
spdlog::info("Loading band plans color table");
bandplan::loadColorTable(ROOT_DIR "/band_colors.json");
windowInit();
spdlog::info("Ready.");
bool _maximized = maximized;
int fsWidth, fsHeight, fsPosX, fsPosY;
// Main loop
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
//ImGui::ShowDemoWindow();
if (_maximized != maximized) {
_maximized = maximized;
core::configManager.aquire();
core::configManager.conf["maximized"]= _maximized;
if (!maximized) {
glfwSetWindowSize(window, core::configManager.conf["windowSize"]["w"], core::configManager.conf["windowSize"]["h"]);
}
core::configManager.release(true);
}
int _winWidth, _winHeight;
glfwGetWindowSize(window, &_winWidth, &_winHeight);
if (ImGui::IsKeyPressed(GLFW_KEY_F11)) {
fullScreen = !fullScreen;
if (fullScreen) {
spdlog::info("Fullscreen: ON");
fsWidth = _winWidth;
fsHeight = _winHeight;
glfwGetWindowPos(window, &fsPosX, &fsPosY);
const GLFWvidmode * mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, 0);
}
else {
spdlog::info("Fullscreen: OFF");
glfwSetWindowMonitor(window, nullptr, fsPosX, fsPosY, fsWidth, fsHeight, 0);
}
}
if ((_winWidth != winWidth || _winHeight != winHeight) && !maximized && _winWidth > 0 && _winHeight > 0) {
winWidth = _winWidth;
winHeight = _winHeight;
core::configManager.aquire();
core::configManager.conf["windowSize"]["w"] = winWidth;
core::configManager.conf["windowSize"]["h"] = winHeight;
core::configManager.release(true);
}
if (winWidth > 0 && winHeight > 0) {
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(_winWidth, _winHeight));
drawWindow();
}
// Rendering
ImGui::Render();
int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClearColor(0.0666f, 0.0666f, 0.0666f, 1.0f);
//glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapInterval(1); // Enable vsync
glfwSwapBuffers(window);
}
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}

15
core/src/core.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <config.h>
#include <new_module.h>
#include <scripting.h>
#include <new_module.h>
namespace core {
SDRPP_EXPORT ConfigManager configManager;
SDRPP_EXPORT ScriptManager scriptManager;
SDRPP_EXPORT ModuleManager moduleManager;
void setInputSampleRate(double samplerate);
};
int sdrpp_main();

95
core/src/dsp/audio.h Normal file
View File

@ -0,0 +1,95 @@
#pragma once
#include <dsp/block.h>
namespace dsp {
class MonoToStereo : public generic_block<MonoToStereo> {
public:
MonoToStereo() {}
MonoToStereo(stream<float>* in) { init(in); }
~MonoToStereo() { generic_block<MonoToStereo>::stop(); }
void init(stream<float>* in) {
_in = in;
generic_block<MonoToStereo>::registerInput(_in);
generic_block<MonoToStereo>::registerOutput(&out);
}
void setInput(stream<float>* in) {
std::lock_guard<std::mutex> lck(generic_block<MonoToStereo>::ctrlMtx);
generic_block<MonoToStereo>::tempStop();
generic_block<MonoToStereo>::unregisterInput(_in);
_in = in;
generic_block<MonoToStereo>::registerInput(_in);
generic_block<MonoToStereo>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (out.aquire() < 0) { return -1; }
for (int i = 0; i < count; i++) {
out.data[i].l = _in->data[i];
out.data[i].r = _in->data[i];
}
_in->flush();
out.write(count);
return count;
}
stream<stereo_t> out;
private:
int count;
stream<float>* _in;
};
class StereoToMono : public generic_block<StereoToMono> {
public:
StereoToMono() {}
StereoToMono(stream<stereo_t>* in) { init(in); }
~StereoToMono() { generic_block<StereoToMono>::stop(); }
void init(stream<stereo_t>* in) {
_in = in;
generic_block<StereoToMono>::registerInput(_in);
generic_block<StereoToMono>::registerOutput(&out);
}
void setInput(stream<stereo_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<StereoToMono>::ctrlMtx);
generic_block<StereoToMono>::tempStop();
generic_block<StereoToMono>::unregisterInput(_in);
_in = in;
generic_block<StereoToMono>::registerInput(_in);
generic_block<StereoToMono>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (out.aquire() < 0) { return -1; }
for (int i = 0; i < count; i++) {
out.data[i] = (_in->data[i].l + _in->data[i].r) / 2.0f;
}
_in->flush();
out.write(count);
return count;
}
stream<float> out;
private:
int count;
stream<stereo_t>* _in;
};
}

126
core/src/dsp/block.h Normal file
View File

@ -0,0 +1,126 @@
#pragma once
#include <stdio.h>
#include <dsp/stream.h>
#include <dsp/types.h>
#include <thread>
#include <vector>
#include <algorithm>
#define FL_M_PI 3.1415926535f
namespace dsp {
template <class BLOCK>
class generic_block {
public:
virtual void init() {}
virtual ~generic_block() {
stop();
}
virtual void start() {
std::lock_guard<std::mutex> lck(ctrlMtx);
if (running) {
return;
}
running = true;
doStart();
}
virtual void stop() {
std::lock_guard<std::mutex> lck(ctrlMtx);
if (!running) {
return;
}
doStop();
running = false;
}
virtual int calcOutSize(int inSize) { return inSize; }
virtual int run() = 0;
friend BLOCK;
private:
void workerLoop() {
while (run() >= 0);
}
void aquire() {
ctrlMtx.lock();
}
void release() {
ctrlMtx.unlock();
}
void registerInput(untyped_steam* inStream) {
inputs.push_back(inStream);
}
void unregisterInput(untyped_steam* inStream) {
inputs.erase(std::remove(inputs.begin(), inputs.end(), inStream), inputs.end());
}
void registerOutput(untyped_steam* outStream) {
outputs.push_back(outStream);
}
void unregisterOutput(untyped_steam* outStream) {
outputs.erase(std::remove(outputs.begin(), outputs.end(), outStream), outputs.end());
}
virtual void doStart() {
workerThread = std::thread(&generic_block<BLOCK>::workerLoop, this);
}
virtual void doStop() {
for (auto const& in : inputs) {
in->stopReader();
}
for (auto const& out : outputs) {
out->stopWriter();
}
// TODO: Make sure this isn't needed, I don't know why it stops
if (workerThread.joinable()) {
workerThread.join();
}
for (auto const& in : inputs) {
in->clearReadStop();
}
for (auto const& out : outputs) {
out->clearWriteStop();
}
}
void tempStart() {
if (tempStopped) {
doStart();
tempStopped = false;
}
}
void tempStop() {
if (running && !tempStopped) {
doStop();
tempStopped = true;
}
}
std::vector<untyped_steam*> inputs;
std::vector<untyped_steam*> outputs;
bool running = false;
bool tempStopped = false;
std::thread workerThread;
protected:
std::mutex ctrlMtx;
};
}

View File

@ -1,34 +1,22 @@
#pragma once
#include <condition_variable>
#include <algorithm>
#include <math.h>
#include <string.h>
#include <dsp/block.h>
#define STREAM_BUF_SZ 1000000
#define RING_BUF_SZ 1000000
namespace dsp {
template <class T>
class stream {
class RingBuffer {
public:
stream() {
RingBuffer() {
}
stream(int maxLatency) {
size = STREAM_BUF_SZ;
_buffer = new T[size];
_stopReader = false;
_stopWriter = false;
this->maxLatency = maxLatency;
writec = 0;
readc = 0;
readable = 0;
writable = size;
memset(_buffer, 0, size * sizeof(T));
}
RingBuffer(int maxLatency) { init(maxLatency); }
~RingBuffer() { delete _buffer; }
void init(int maxLatency) {
size = STREAM_BUF_SZ;
size = RING_BUF_SZ;
_buffer = new T[size];
_stopReader = false;
_stopWriter = false;

138
core/src/dsp/convertion.h Normal file
View File

@ -0,0 +1,138 @@
#pragma once
#include <dsp/block.h>
namespace dsp {
class ComplexToStereo : public generic_block<ComplexToStereo> {
public:
ComplexToStereo() {}
ComplexToStereo(stream<complex_t>* in) { init(in); }
~ComplexToStereo() { generic_block<ComplexToStereo>::stop(); }
static_assert(sizeof(complex_t) == sizeof(stereo_t));
void init(stream<complex_t>* in) {
_in = in;
generic_block<ComplexToStereo>::registerInput(_in);
generic_block<ComplexToStereo>::registerOutput(&out);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<ComplexToStereo>::ctrlMtx);
generic_block<ComplexToStereo>::tempStop();
generic_block<ComplexToStereo>::unregisterInput(_in);
_in = in;
generic_block<ComplexToStereo>::registerInput(_in);
generic_block<ComplexToStereo>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (out.aquire() < 0) { return -1; }
memcpy(out.data, _in->data, count * sizeof(complex_t));
_in->flush();
out.write(count);
return count;
}
stream<stereo_t> out;
private:
float avg;
int count;
stream<complex_t>* _in;
};
class ComplexToReal : public generic_block<ComplexToReal> {
public:
ComplexToReal() {}
ComplexToReal(stream<complex_t>* in) { init(in); }
~ComplexToReal() { generic_block<ComplexToReal>::stop(); }
void init(stream<complex_t>* in) {
_in = in;
generic_block<ComplexToReal>::registerInput(_in);
generic_block<ComplexToReal>::registerOutput(&out);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<ComplexToReal>::ctrlMtx);
generic_block<ComplexToReal>::tempStop();
generic_block<ComplexToReal>::unregisterInput(_in);
_in = in;
generic_block<ComplexToReal>::registerInput(_in);
generic_block<ComplexToReal>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (out.aquire() < 0) { return -1; }
volk_32fc_deinterleave_real_32f(out.data, (lv_32fc_t*)_in->data, count);
_in->flush();
out.write(count);
return count;
}
stream<float> out;
private:
float avg;
int count;
stream<complex_t>* _in;
};
class ComplexToImag : public generic_block<ComplexToImag> {
public:
ComplexToImag() {}
ComplexToImag(stream<complex_t>* in) { init(in); }
~ComplexToImag() { generic_block<ComplexToImag>::stop(); }
void init(stream<complex_t>* in) {
_in = in;
generic_block<ComplexToImag>::registerInput(_in);
generic_block<ComplexToImag>::registerOutput(&out);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<ComplexToImag>::ctrlMtx);
generic_block<ComplexToImag>::tempStop();
generic_block<ComplexToImag>::unregisterInput(_in);
_in = in;
generic_block<ComplexToImag>::registerInput(_in);
generic_block<ComplexToImag>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (out.aquire() < 0) { return -1; }
volk_32fc_deinterleave_imag_32f(out.data, (lv_32fc_t*)_in->data, count);
_in->flush();
out.write(count);
return count;
}
stream<float> out;
private:
float avg;
int count;
stream<complex_t>* _in;
};
}

283
core/src/dsp/demodulator.h Normal file
View File

@ -0,0 +1,283 @@
#pragma once
#include <dsp/block.h>
#include <volk/volk.h>
#include <spdlog/spdlog.h>
#define FAST_ATAN2_COEF1 FL_M_PI / 4.0f
#define FAST_ATAN2_COEF2 3.0f * FAST_ATAN2_COEF1
inline float fast_arctan2(float y, float x) {
float abs_y = fabsf(y);
float r, angle;
if (x == 0.0f && y == 0.0f) { return 0.0f; }
if (x>=0.0f) {
r = (x - abs_y) / (x + abs_y);
angle = FAST_ATAN2_COEF1 - FAST_ATAN2_COEF1 * r;
}
else {
r = (x + abs_y) / (abs_y - x);
angle = FAST_ATAN2_COEF2 - FAST_ATAN2_COEF1 * r;
}
if (y < 0.0f) {
return -angle;
}
return angle;
}
namespace dsp {
class FMDemod : public generic_block<FMDemod> {
public:
FMDemod() {}
FMDemod(stream<complex_t>* in, float sampleRate, float deviation) { init(in, sampleRate, deviation); }
~FMDemod() { generic_block<FMDemod>::stop(); }
void init(stream<complex_t>* in, float sampleRate, float deviation) {
_in = in;
_sampleRate = sampleRate;
_deviation = deviation;
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
generic_block<FMDemod>::registerInput(_in);
generic_block<FMDemod>::registerOutput(&out);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
generic_block<FMDemod>::tempStop();
generic_block<FMDemod>::unregisterInput(_in);
_in = in;
generic_block<FMDemod>::registerInput(_in);
generic_block<FMDemod>::tempStart();
}
void setSampleRate(float sampleRate) {
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
generic_block<FMDemod>::tempStop();
_sampleRate = sampleRate;
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
generic_block<FMDemod>::tempStart();
}
float getSampleRate() {
return _sampleRate;
}
void setDeviation(float deviation) {
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
generic_block<FMDemod>::tempStop();
_deviation = deviation;
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
generic_block<FMDemod>::tempStart();
}
float getDeviation() {
return _deviation;
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
// This is somehow faster than volk...
float diff, currentPhase;
if (out.aquire() < 0) { return -1; }
for (int i = 0; i < count; i++) {
currentPhase = fast_arctan2(_in->data[i].i, _in->data[i].q);
diff = currentPhase - phase;
if (diff > 3.1415926535f) { diff -= 2 * 3.1415926535f; }
else if (diff <= -3.1415926535f) { diff += 2 * 3.1415926535f; }
out.data[i] = diff / phasorSpeed;
phase = currentPhase;
}
_in->flush();
out.write(count);
return count;
}
stream<float> out;
private:
int count;
float phase, phasorSpeed, _sampleRate, _deviation;
stream<complex_t>* _in;
};
class AMDemod : public generic_block<AMDemod> {
public:
AMDemod() {}
AMDemod(stream<complex_t>* in) { init(in); }
~AMDemod() { generic_block<AMDemod>::stop(); }
void init(stream<complex_t>* in) {
_in = in;
generic_block<AMDemod>::registerInput(_in);
generic_block<AMDemod>::registerOutput(&out);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<AMDemod>::ctrlMtx);
generic_block<AMDemod>::tempStop();
generic_block<AMDemod>::unregisterInput(_in);
_in = in;
generic_block<AMDemod>::registerInput(_in);
generic_block<AMDemod>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (out.aquire() < 0) { return -1; }
volk_32fc_magnitude_32f(out.data, (lv_32fc_t*)_in->data, count);
_in->flush();
volk_32f_accumulator_s32f(&avg, out.data, count);
avg /= (float)count;
for (int i = 0; i < count; i++) {
out.data[i] -= avg;
}
out.write(count);
return count;
}
stream<float> out;
private:
float avg;
int count;
stream<complex_t>* _in;
};
class SSBDemod : public generic_block<SSBDemod> {
public:
SSBDemod() {}
SSBDemod(stream<complex_t>* in, float sampleRate, float bandWidth, int mode) { init(in, sampleRate, bandWidth, mode); }
~SSBDemod() {
generic_block<SSBDemod>::stop();
delete[] buffer;
}
enum {
MODE_USB,
MODE_LSB,
MODE_DSB
};
void init(stream<complex_t>* in, float sampleRate, float bandWidth, int mode) {
_in = in;
_sampleRate = sampleRate;
_bandWidth = bandWidth;
_mode = mode;
phase = lv_cmake(1.0f, 0.0f);
switch (_mode) {
case MODE_USB:
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_LSB:
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_DSB:
phaseDelta = lv_cmake(1.0f, 0.0f);
break;
}
buffer = new lv_32fc_t[STREAM_BUFFER_SIZE];
generic_block<SSBDemod>::registerInput(_in);
generic_block<SSBDemod>::registerOutput(&out);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<SSBDemod>::ctrlMtx);
generic_block<SSBDemod>::tempStop();
generic_block<SSBDemod>::unregisterInput(_in);
_in = in;
generic_block<SSBDemod>::registerInput(_in);
generic_block<SSBDemod>::tempStart();
}
void setSampleRate(float sampleRate) {
// No need to restart
_sampleRate = sampleRate;
switch (_mode) {
case MODE_USB:
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_LSB:
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_DSB:
phaseDelta = lv_cmake(1.0f, 0.0f);
break;
}
}
void setBandWidth(float bandWidth) {
// No need to restart
_bandWidth = bandWidth;
switch (_mode) {
case MODE_USB:
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_LSB:
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_DSB:
phaseDelta = lv_cmake(1.0f, 0.0f);
break;
}
}
void setMode(int mode) {
_mode = mode;
switch (_mode) {
case MODE_USB:
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_LSB:
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_DSB:
phaseDelta = lv_cmake(1.0f, 0.0f);
break;
}
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (out.aquire() < 0) { return -1; }
volk_32fc_s32fc_x2_rotator_32fc(buffer, (lv_32fc_t*)_in->data, phaseDelta, &phase, count);
volk_32fc_deinterleave_real_32f(out.data, buffer, count);
_in->flush();
out.write(count);
return count;
}
stream<float> out;
private:
int count;
int _mode;
float _sampleRate, _bandWidth;
stream<complex_t>* _in;
lv_32fc_t* buffer;
lv_32fc_t phase;
lv_32fc_t phaseDelta;
};
}

173
core/src/dsp/filter.h Normal file
View File

@ -0,0 +1,173 @@
#pragma once
#include <dsp/block.h>
#include <dsp/window.h>
#include <string.h>
namespace dsp {
template <class T>
class FIR : public generic_block<FIR<T>> {
public:
FIR() {}
FIR(stream<T>* in, dsp::filter_window::generic_window* window) { init(in, window); }
~FIR() {
generic_block<FIR<T>>::stop();
volk_free(buffer);
volk_free(taps);
}
void init(stream<T>* in, dsp::filter_window::generic_window* window) {
_in = in;
tapCount = window->getTapCount();
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
window->createTaps(taps, tapCount);
buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment());
bufStart = &buffer[tapCount];
generic_block<FIR<T>>::registerInput(_in);
generic_block<FIR<T>>::registerOutput(&out);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<FIR<T>>::ctrlMtx);
generic_block<FIR<T>>::tempStop();
generic_block<FIR<T>>::unregisterInput(_in);
_in = in;
generic_block<FIR<T>>::registerInput(_in);
generic_block<FIR<T>>::tempStart();
}
void updateWindow(dsp::filter_window::generic_window* window) {
_window = window;
volk_free(taps);
tapCount = window->getTapCount();
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
window->createTaps(taps, tapCount);
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
memcpy(bufStart, _in->data, count * sizeof(T));
_in->flush();
// Write to output
if (out.aquire() < 0) { return -1; }
if constexpr (std::is_same_v<T, float>) {
for (int i = 0; i < count; i++) {
volk_32f_x2_dot_prod_32f((float*)&out.data[i], (float*)&buffer[i+1], taps, tapCount);
}
}
if constexpr (std::is_same_v<T, complex_t>) {
for (int i = 0; i < count; i++) {
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.data[i], (lv_32fc_t*)&buffer[i+1], taps, tapCount);
}
}
out.write(count);
memmove(buffer, &buffer[count], tapCount * sizeof(T));
return count;
}
stream<T> out;
private:
int count;
stream<T>* _in;
dsp::filter_window::generic_window* _window;
T* bufStart;
T* buffer;
int tapCount;
float* taps;
};
class BFMDeemp : public generic_block<BFMDeemp> {
public:
BFMDeemp() {}
BFMDeemp(stream<float>* in, float sampleRate, float tau) { init(in, sampleRate, tau); }
~BFMDeemp() { generic_block<BFMDeemp>::stop(); }
void init(stream<float>* in, float sampleRate, float tau) {
_in = in;
_sampleRate = sampleRate;
_tau = tau;
float dt = 1.0f / _sampleRate;
alpha = dt / (_tau + dt);
generic_block<BFMDeemp>::registerInput(_in);
generic_block<BFMDeemp>::registerOutput(&out);
}
void setInput(stream<float>* in) {
std::lock_guard<std::mutex> lck(generic_block<BFMDeemp>::ctrlMtx);
generic_block<BFMDeemp>::tempStop();
generic_block<BFMDeemp>::unregisterInput(_in);
_in = in;
generic_block<BFMDeemp>::registerInput(_in);
generic_block<BFMDeemp>::tempStart();
}
void setSampleRate(float sampleRate) {
_sampleRate = sampleRate;
float dt = 1.0f / _sampleRate;
alpha = dt / (_tau + dt);
}
void setTau(float tau) {
_tau = tau;
float dt = 1.0f / _sampleRate;
alpha = dt / (_tau + dt);
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (bypass) {
if (out.aquire() < 0) { return -1; }
memcpy(out.data, _in->data, count * sizeof(float));
_in->flush();
out.write(count);
return count;
}
if (isnan(lastOut)) {
lastOut = 0.0f;
}
if (out.aquire() < 0) { return -1; }
out.data[0] = (alpha * _in->data[0]) + ((1-alpha) * lastOut);
for (int i = 1; i < count; i++) {
out.data[i] = (alpha * _in->data[i]) + ((1 - alpha) * out.data[i - 1]);
}
lastOut = out.data[count - 1];
_in->flush();
out.write(count);
return count;
}
bool bypass = false;
stream<float> out;
private:
int count;
float lastOut = 0.0f;
float alpha;
float _tau;
float _sampleRate;
stream<float>* _in;
};
}

107
core/src/dsp/math.h Normal file
View File

@ -0,0 +1,107 @@
#pragma once
#include <dsp/block.h>
#include <volk/volk.h>
namespace dsp {
template <class T>
class Add : public generic_block<Add<T>> {
public:
Add() {}
Add(stream<T>* a, stream<T>* b) { init(a, b); }
~Add() { generic_block<Add>::stop(); }
void init(stream<T>* a, stream<T>* b) {
_a = a;
_b = b;
generic_block<Add>::registerInput(a);
generic_block<Add>::registerInput(b);
generic_block<Add>::registerOutput(&out);
}
int run() {
a_count = _a->read();
if (a_count < 0) { return -1; }
b_count = _b->read();
if (b_count < 0) { return -1; }
if (a_count != b_count) {
_a->flush();
_b->flush();
return 0;
}
if (out.aquire() < 0) { return -1; }
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
volk_32fc_x2_add_32fc(out.data, _a->data, _b->data, a_count);
}
else {
volk_32f_x2_add_32f(out.data, _a->data, _b->data, a_count);
}
_a->flush();
_b->flush();
out.write(a_count);
return a_count;
}
stream<T> out;
private:
int a_count, b_count;
stream<T>* _a;
stream<T>* _b;
};
template <class T>
class Multiply : public generic_block<Multiply<T>> {
public:
Multiply() {}
Multiply(stream<T>* a, stream<T>* b) { init(a, b); }
~Multiply() { generic_block<Multiply>::stop(); }
void init(stream<T>* a, stream<T>* b) {
_a = a;
_b = b;
generic_block<Multiply>::registerInput(a);
generic_block<Multiply>::registerInput(b);
generic_block<Multiply>::registerOutput(&out);
}
int run() {
a_count = _a->read();
if (a_count < 0) { return -1; }
b_count = _b->read();
if (b_count < 0) { return -1; }
if (a_count != b_count) {
_a->flush();
_b->flush();
return 0;
}
if (out.aquire() < 0) { return -1; }
if constexpr (std::is_same_v<T, complex_t>) {
volk_32fc_x2_multiply_32fc(out.data, _a->data, _b->data, a_count);
}
else {
volk_32f_x2_multiply_32f(out.data, _a->data, _b->data, a_count);
}
_a->flush();
_b->flush();
out.write(a_count);
return a_count;
}
stream<T> out;
private:
int a_count, b_count;
stream<T>* _a;
stream<T>* _b;
};
}

291
core/src/dsp/processing.h Normal file
View File

@ -0,0 +1,291 @@
#pragma once
#include <dsp/block.h>
#include <fftw3.h>
#include <volk/volk.h>
#include <spdlog/spdlog.h>
#include <string.h>
namespace dsp {
template <class T>
class FrequencyXlator : public generic_block<FrequencyXlator<T>> {
public:
FrequencyXlator() {}
FrequencyXlator(stream<complex_t>* in, float sampleRate, float freq) { init(in, sampleRate, freq); }
~FrequencyXlator() {
generic_block<FrequencyXlator<T>>::stop();
}
void init(stream<complex_t>* in, float sampleRate, float freq) {
_in = in;
_sampleRate = sampleRate;
_freq = freq;
phase = lv_cmake(1.0f, 0.0f);
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
generic_block<FrequencyXlator<T>>::registerInput(_in);
generic_block<FrequencyXlator<T>>::registerOutput(&out);
}
void setInputSize(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<FrequencyXlator<T>>::ctrlMtx);
generic_block<FrequencyXlator<T>>::tempStop();
generic_block<FrequencyXlator<T>>::unregisterInput(_in);
_in = in;
generic_block<FrequencyXlator<T>>::registerInput(_in);
generic_block<FrequencyXlator<T>>::tempStart();
}
void setSampleRate(float sampleRate) {
// No need to restart
_sampleRate = sampleRate;
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
}
float getSampleRate() {
return _sampleRate;
}
void setFrequency(float freq) {
// No need to restart
_freq = freq;
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
}
float getFrequency() {
return _freq;
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (out.aquire() < 0) { return -1; }
// TODO: Do float xlation
if constexpr (std::is_same_v<T, float>) {
spdlog::error("XLATOR NOT IMPLEMENTED FOR FLOAT");
}
if constexpr (std::is_same_v<T, complex_t>) {
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.data, (lv_32fc_t*)_in->data, phaseDelta, &phase, count);
}
_in->flush();
out.write(count);
return count;
}
stream<complex_t> out;
private:
int count;
float _sampleRate;
float _freq;
lv_32fc_t phaseDelta;
lv_32fc_t phase;
stream<complex_t>* _in;
};
class AGC : public generic_block<AGC> {
public:
AGC() {}
AGC(stream<float>* in, float ratio) { init(in, ratio); }
~AGC() { generic_block<AGC>::stop(); }
void init(stream<float>* in, float ratio) {
_in = in;
_ratio = ratio;
generic_block<AGC>::registerInput(_in);
generic_block<AGC>::registerOutput(&out);
}
void setInput(stream<float>* in) {
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
generic_block<AGC>::tempStop();
generic_block<AGC>::unregisterInput(_in);
_in = in;
generic_block<AGC>::registerInput(_in);
generic_block<AGC>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (out.aquire() < 0) { return -1; }
for (int i = 0; i < count; i++) {
level = (fabsf(_in->data[i]) * _ratio) + (level * (1.0f - _ratio));
out.data[i] = _in->data[i] / level;
}
_in->flush();
out.write(count);
return count;
}
stream<float> out;
private:
int count;
float level = 1.0f;
float _ratio;
stream<float>* _in;
};
template <class T>
class Volume : public generic_block<Volume<T>> {
public:
Volume() {}
Volume(stream<T>* in, float volume) { init(in, volume); }
~Volume() { generic_block<Volume<T>>::stop(); }
void init(stream<T>* in, float volume) {
_in = in;
_volume = volume;
generic_block<Volume<T>>::registerInput(_in);
generic_block<Volume<T>>::registerOutput(&out);
}
void setInputSize(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<Volume<T>>::ctrlMtx);
generic_block<Volume<T>>::tempStop();
generic_block<Volume<T>>::unregisterInput(_in);
_in = in;
generic_block<Volume<T>>::registerInput(_in);
generic_block<Volume<T>>::tempStart();
}
void setVolume(float volume) {
_volume = volume;
level = powf(_volume, 2);
}
float getVolume() {
return _volume;
}
void setMuted(bool muted) {
_muted = muted;
}
bool getMuted() {
return _muted;
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (out.aquire() < 0) { return -1; }
if (_muted) {
if constexpr (std::is_same_v<T, stereo_t>) {
memset(out.data, 0, sizeof(stereo_t) * count);
}
else {
memset(out.data, 0, sizeof(float) * count);
}
}
else {
if constexpr (std::is_same_v<T, stereo_t>) {
volk_32f_s32f_multiply_32f((float*)out.data, (float*)_in->data, level, count * 2);
}
else {
volk_32f_s32f_multiply_32f((float*)out.data, (float*)_in->data, level, count);
}
}
_in->flush();
out.write(count);
return count;
}
stream<T> out;
private:
int count;
float level = 1.0f;
float _volume = 1.0f;
bool _muted = false;
stream<T>* _in;
};
class Squelch : public generic_block<Squelch> {
public:
Squelch() {}
Squelch(stream<complex_t>* in, float level) { init(in, level); }
~Squelch() {
generic_block<Squelch>::stop();
delete[] normBuffer;
}
void init(stream<complex_t>* in, float level) {
_in = in;
_level = level;
normBuffer = new float[STREAM_BUFFER_SIZE];
generic_block<Squelch>::registerInput(_in);
generic_block<Squelch>::registerOutput(&out);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<Squelch>::ctrlMtx);
generic_block<Squelch>::tempStop();
generic_block<Squelch>::unregisterInput(_in);
_in = in;
generic_block<Squelch>::registerInput(_in);
generic_block<Squelch>::tempStart();
}
void setLevel(float level) {
_level = level;
}
float getLevel() {
return _level;
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (out.aquire() < 0) { return -1; }
float sum = 0.0f;
volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)_in->data, count);
volk_32f_accumulator_s32f(&sum, normBuffer, count);
sum /= (float)count;
if (10.0f * log10f(sum) >= _level) {
memcpy(out.data, _in->data, count * sizeof(complex_t));
}
else {
memset(out.data, 0, count * sizeof(complex_t));
}
_in->flush();
out.write(count);
return count;
}
stream<complex_t> out;
private:
int count;
float* normBuffer;
float _level = -50.0f;
stream<complex_t>* _in;
};
}

154
core/src/dsp/resampling.h Normal file
View File

@ -0,0 +1,154 @@
#pragma once
#include <dsp/block.h>
#include <dsp/window.h>
#include <numeric>
#include <string.h>
namespace dsp {
template <class T>
class PolyphaseResampler : public generic_block<PolyphaseResampler<T>> {
public:
PolyphaseResampler() {}
PolyphaseResampler(stream<T>* in, dsp::filter_window::generic_window* window, float inSampleRate, float outSampleRate) { init(in, window, inSampleRate, outSampleRate); }
~PolyphaseResampler() {
generic_block<PolyphaseResampler<T>>::stop();
volk_free(buffer);
volk_free(taps);
}
void init(stream<T>* in, dsp::filter_window::generic_window* window, float inSampleRate, float outSampleRate) {
_in = in;
_window = window;
_inSampleRate = inSampleRate;
_outSampleRate = outSampleRate;
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
_interp = _outSampleRate / _gcd;
_decim = _inSampleRate / _gcd;
tapCount = _window->getTapCount();
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
_window->createTaps(taps, tapCount, _interp);
buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment());
memset(buffer, 0, STREAM_BUFFER_SIZE * sizeof(T) * 2);
bufStart = &buffer[tapCount];
generic_block<PolyphaseResampler<T>>::registerInput(_in);
generic_block<PolyphaseResampler<T>>::registerOutput(&out);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
generic_block<PolyphaseResampler<T>>::tempStop();
generic_block<PolyphaseResampler<T>>::unregisterInput(_in);
_in = in;
generic_block<PolyphaseResampler<T>>::registerInput(_in);
generic_block<PolyphaseResampler<T>>::tempStart();
}
void setInSampleRate(float inSampleRate) {
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
generic_block<PolyphaseResampler<T>>::tempStop();
_inSampleRate = inSampleRate;
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
_interp = _outSampleRate / _gcd;
_decim = _inSampleRate / _gcd;
generic_block<PolyphaseResampler<T>>::tempStart();
}
void setOutSampleRate(float outSampleRate) {
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
generic_block<PolyphaseResampler<T>>::tempStop();
_outSampleRate = outSampleRate;
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
_interp = _outSampleRate / _gcd;
_decim = _inSampleRate / _gcd;
generic_block<PolyphaseResampler<T>>::tempStart();
}
int getInterpolation() {
return _interp;
}
int getDecimation() {
return _decim;
}
void updateWindow(dsp::filter_window::generic_window* window) {
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
generic_block<PolyphaseResampler<T>>::tempStop();
_window = window;
volk_free(taps);
tapCount = window->getTapCount();
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
window->createTaps(taps, tapCount, _interp);
bufStart = &buffer[tapCount];
generic_block<PolyphaseResampler<T>>::tempStart();
}
int calcOutSize(int in) {
return (in * _interp) / _decim;
}
virtual int run() override {
count = _in->read();
if (count < 0) {
return -1;
}
int outCount = calcOutSize(count);
memcpy(&buffer[tapCount], _in->data, count * sizeof(T));
_in->flush();
// Write to output
if (out.aquire() < 0) {
return -1;
}
int outIndex = 0;
if constexpr (std::is_same_v<T, float>) {
for (int i = 0; outIndex < outCount; i += _decim) {
out.data[outIndex] = 0;
for (int j = i % _interp; j < tapCount; j += _interp) {
out.data[outIndex] += buffer[((i - j) / _interp) + tapCount] * taps[j];
}
outIndex++;
}
}
if constexpr (std::is_same_v<T, complex_t>) {
for (int i = 0; outIndex < outCount; i += _decim) {
out.data[outIndex].i = 0;
out.data[outIndex].q = 0;
for (int j = i % _interp; j < tapCount; j += _interp) {
out.data[outIndex].i += buffer[((i - j) / _interp) + tapCount].i * taps[j];
out.data[outIndex].q += buffer[((i - j) / _interp) + tapCount].q * taps[j];
}
outIndex++;
}
}
out.write(outCount);
memmove(buffer, &buffer[count], tapCount * sizeof(T));
return count;
}
stream<T> out;
private:
int count;
stream<T>* _in;
dsp::filter_window::generic_window* _window;
T* bufStart;
T* buffer;
int tapCount;
int _interp, _decim;
float _inSampleRate, _outSampleRate;
float* taps;
};
}

186
core/src/dsp/routing.h Normal file
View File

@ -0,0 +1,186 @@
#pragma once
#include <dsp/block.h>
#include <dsp/buffer.h>
#include <string.h>
#include <numeric>
#include <spdlog/spdlog.h>
namespace dsp {
template <class T>
class Splitter : public generic_block<Splitter<T>> {
public:
Splitter() {}
Splitter(stream<T>* in) { init(in); }
~Splitter() { generic_block<Splitter>::stop(); }
void init(stream<T>* in) {
_in = in;
generic_block<Splitter>::registerInput(_in);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
generic_block<Splitter>::tempStop();
generic_block<Splitter>::unregisterInput(_in);
_in = in;
generic_block<Splitter>::registerInput(_in);
generic_block<Splitter>::tempStart();
}
void bindStream(stream<T>* stream) {
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
generic_block<Splitter>::tempStop();
out.push_back(stream);
generic_block<Splitter>::registerOutput(stream);
generic_block<Splitter>::tempStart();
}
void unbindStream(stream<T>* stream) {
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
generic_block<Splitter>::tempStop();
generic_block<Splitter>::unregisterOutput(stream);
out.erase(std::remove(out.begin(), out.end(), stream), out.end());
generic_block<Splitter>::tempStart();
}
private:
int run() {
// TODO: If too slow, buffering might be necessary
int count = _in->read();
if (count < 0) { return -1; }
for (const auto& stream : out) {
if (stream->aquire() < 0) { return -1; }
memcpy(stream->data, _in->data, count * sizeof(T));
stream->write(count);
}
_in->flush();
return count;
}
stream<T>* _in;
std::vector<stream<T>*> out;
};
// NOTE: I'm not proud of this, it's BAD and just taken from the previous DSP, but it works...
template <class T>
class Reshaper : public generic_block<Reshaper<T>> {
public:
Reshaper() {}
Reshaper(stream<T>* in, int keep, int skip) { init(in, keep, skip); }
~Reshaper() { generic_block<Reshaper<T>>::stop(); }
void init(stream<T>* in, int keep, int skip) {
_in = in;
_keep = keep;
_skip = skip;
ringBuf.init(keep * 2);
generic_block<Reshaper<T>>::registerInput(_in);
generic_block<Reshaper<T>>::registerOutput(&out);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
generic_block<Reshaper<T>>::tempStop();
generic_block<Reshaper<T>>::unregisterInput(_in);
_in = in;
generic_block<Reshaper<T>>::registerInput(_in);
generic_block<Reshaper<T>>::tempStart();
}
void setKeep(int keep) {
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
generic_block<Reshaper<T>>::tempStop();
generic_block<Reshaper<T>>::unregisterInput(_in);
_keep = keep;
ringBuf.setMaxLatency(keep * 2);
generic_block<Reshaper<T>>::registerInput(_in);
generic_block<Reshaper<T>>::tempStart();
}
void setSkip(int skip) {
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
generic_block<Reshaper<T>>::tempStop();
generic_block<Reshaper<T>>::unregisterInput(_in);
_skip = skip;
generic_block<Reshaper<T>>::registerInput(_in);
generic_block<Reshaper<T>>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
ringBuf.write(_in->data, count);
_in->flush();
return count;
}
stream<T> out;
private:
void doStart() {
workThread = std::thread(&Reshaper<T>::loop, this);
bufferWorkerThread = std::thread(&Reshaper<T>::bufferWorker, this);
}
void loop() {
while (run() >= 0);
}
void doStop() {
_in->stopReader();
ringBuf.stopReader();
out.stopWriter();
ringBuf.stopWriter();
if (workThread.joinable()) {
workThread.join();
}
if (bufferWorkerThread.joinable()) {
bufferWorkerThread.join();
}
_in->clearReadStop();
ringBuf.clearReadStop();
out.clearWriteStop();
ringBuf.clearWriteStop();
}
void bufferWorker() {
complex_t* buf = new complex_t[_keep];
bool delay = _skip < 0;
int readCount = std::min<int>(_keep + _skip, _keep);
int skip = std::max<int>(_skip, 0);
int delaySize = (-_skip) * sizeof(complex_t);
complex_t* start = &buf[std::max<int>(-_skip, 0)];
complex_t* delayStart = &buf[_keep + _skip];
while (true) {
if (delay) {
memmove(buf, delayStart, delaySize);
}
if (ringBuf.readAndSkip(start, readCount, skip) < 0) { break; };
if (out.aquire() < 0) { break; }
memcpy(out.data, buf, _keep * sizeof(complex_t));
out.write(_keep);
}
delete[] buf;
}
stream<T>* _in;
int _outBlockSize;
RingBuffer<T> ringBuf;
std::thread bufferWorkerThread;
std::thread workThread;
int _keep, _skip;
};
}

140
core/src/dsp/sink.h Normal file
View File

@ -0,0 +1,140 @@
#pragma once
#include <dsp/block.h>
#include <dsp/buffer.h>
namespace dsp {
template <class T>
class HandlerSink : public generic_block<HandlerSink<T>> {
public:
HandlerSink() {}
HandlerSink(stream<T>* in, void (*handler)(T* data, int count, void* ctx), void* ctx) { init(in, handler, ctx); }
~HandlerSink() { generic_block<HandlerSink<T>>::stop(); }
void init(stream<T>* in, void (*handler)(T* data, int count, void* ctx), void* ctx) {
_in = in;
_handler = handler;
_ctx = ctx;
generic_block<HandlerSink<T>>::registerInput(_in);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<HandlerSink<T>>::ctrlMtx);
generic_block<HandlerSink<T>>::tempStop();
generic_block<HandlerSink<T>>::unregisterInput(_in);
_in = in;
generic_block<HandlerSink<T>>::registerInput(_in);
generic_block<HandlerSink<T>>::tempStart();
}
void setHandler(void (*handler)(T* data, int count, void* ctx), void* ctx) {
std::lock_guard<std::mutex> lck(generic_block<HandlerSink<T>>::ctrlMtx);
generic_block<HandlerSink<T>>::tempStop();
_handler = handler;
_ctx = ctx;
generic_block<HandlerSink<T>>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
_handler(_in->data, count, _ctx);
_in->flush();
return count;
}
private:
int count;
stream<T>* _in;
void (*_handler)(T* data, int count, void* ctx);
void* _ctx;
};
template <class T>
class RingBufferSink : public generic_block<RingBufferSink<T>> {
public:
RingBufferSink() {}
RingBufferSink(stream<T>* in) { init(in); }
~RingBufferSink() { generic_block<RingBufferSink<T>>::stop(); }
void init(stream<T>* in) {
_in = in;
data.init(480); // TODO: Use an argument
generic_block<RingBufferSink<T>>::registerInput(_in);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<RingBufferSink<T>>::ctrlMtx);
generic_block<RingBufferSink<T>>::tempStop();
generic_block<RingBufferSink<T>>::unregisterInput(_in);
_in = in;
generic_block<RingBufferSink<T>>::registerInput(_in);
generic_block<RingBufferSink<T>>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (data.write(_in->data, count) < 0) { return -1; }
_in->flush();
return count;
}
RingBuffer<T> data;
private:
void doStop() {
_in->stopReader();
data.stopWriter();
if (generic_block<RingBufferSink<T>>::workerThread.joinable()) {
generic_block<RingBufferSink<T>>::workerThread.join();
}
_in->clearReadStop();
data.clearWriteStop();
}
int count;
stream<T>* _in;
};
template <class T>
class NullSink : public generic_block<NullSink<T>> {
public:
NullSink() {}
NullSink(stream<T>* in) { init(in); }
~NullSink() { generic_block<NullSink<T>>::stop(); }
void init(stream<T>* in) {
_in = in;
generic_block<NullSink<T>>::registerInput(_in);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<NullSink<T>>::ctrlMtx);
generic_block<NullSink<T>>::tempStop();
generic_block<NullSink<T>>::unregisterInput(_in);
_in = in;
generic_block<NullSink<T>>::registerInput(_in);
generic_block<NullSink<T>>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
_in->flush();
return count;
}
private:
int count;
stream<T>* _in;
};
}

75
core/src/dsp/source.h Normal file
View File

@ -0,0 +1,75 @@
#pragma once
#include <dsp/block.h>
namespace dsp {
class SineSource : public generic_block<SineSource> {
public:
SineSource() {}
SineSource(int blockSize, float sampleRate, float freq) { init(blockSize, sampleRate, freq); }
~SineSource() { generic_block<SineSource>::stop(); }
void init(int blockSize, float sampleRate, float freq) {
_blockSize = blockSize;
_sampleRate = sampleRate;
_freq = freq;
zeroPhase = (lv_32fc_t*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(lv_32fc_t), volk_get_alignment());
for (int i = 0; i < STREAM_BUFFER_SIZE; i++) {
zeroPhase[i] = lv_cmake(1.0f, 0.0f);
}
phase = lv_cmake(1.0f, 0.0f);
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
generic_block<SineSource>::registerOutput(&out);
}
void setBlockSize(int blockSize) {
std::lock_guard<std::mutex> lck(generic_block<SineSource>::ctrlMtx);
generic_block<SineSource>::tempStop();
_blockSize = blockSize;
generic_block<SineSource>::tempStart();
}
int getBlockSize() {
return _blockSize;
}
void setSampleRate(float sampleRate) {
// No need to restart
_sampleRate = sampleRate;
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
}
float getSampleRate() {
return _sampleRate;
}
void setFrequency(float freq) {
// No need to restart
_freq = freq;
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
}
float getFrequency() {
return _freq;
}
int run() {
if (out.aquire() < 0) { return -1; }
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.data, zeroPhase, phaseDelta, &phase, _blockSize);
out.write(_blockSize);
return _blockSize;
}
stream<complex_t> out;
private:
int _blockSize;
float _sampleRate;
float _freq;
lv_32fc_t phaseDelta;
lv_32fc_t phase;
lv_32fc_t* zeroPhase;
};
}

108
core/src/dsp/stream.h Normal file
View File

@ -0,0 +1,108 @@
#pragma once
#include <mutex>
#include <condition_variable>
#include <volk/volk.h>
// 1MB buffer
#define STREAM_BUFFER_SIZE 1000000
namespace dsp {
class untyped_steam {
public:
virtual int aquire() { return -1; }
virtual void write(int size) {}
virtual int read() { return -1; }
virtual void flush() {}
virtual void stopReader() {}
virtual void clearReadStop() {}
virtual void stopWriter() {}
virtual void clearWriteStop() {}
};
template <class T>
class stream : public untyped_steam {
public:
stream() {
data = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T), volk_get_alignment());
}
int aquire() {
waitReady();
if (writerStop) {
return -1;
}
return 0;
}
void write(int size) {
{
std::lock_guard<std::mutex> lck(sigMtx);
contentSize = size;
dataReady = true;
}
cv.notify_one();
}
int read() {
waitData();
if (readerStop) {
return -1;
}
return contentSize;
}
void flush() {
{
std::lock_guard<std::mutex> lck(sigMtx);
dataReady = false;
}
cv.notify_one();
}
void stopReader() {
{
std::lock_guard<std::mutex> lck(sigMtx);
readerStop = true;
}
cv.notify_one();
}
void clearReadStop() {
readerStop = false;
}
void stopWriter() {
{
std::lock_guard<std::mutex> lck(sigMtx);
writerStop = true;
}
cv.notify_one();
}
void clearWriteStop() {
writerStop = false;
}
T* data;
private:
void waitReady() {
std::unique_lock<std::mutex> lck(sigMtx);
cv.wait(lck, [this]{ return (!dataReady || writerStop); });
}
void waitData() {
std::unique_lock<std::mutex> lck(sigMtx);
cv.wait(lck, [this]{ return (dataReady || readerStop); });
}
std::mutex sigMtx;
std::condition_variable cv;
bool dataReady = false;
bool readerStop = false;
bool writerStop = false;
int contentSize = 0;
};
}

View File

@ -5,4 +5,9 @@ namespace dsp {
float q;
float i;
};
};
struct stereo_t {
float l;
float r;
};
}

112
core/src/dsp/vfo.h Normal file
View File

@ -0,0 +1,112 @@
#pragma once
#include <dsp/block.h>
#include <dsp/window.h>
#include <dsp/resampling.h>
#include <dsp/processing.h>
#include <algorithm>
namespace dsp {
class VFO {
public:
VFO() {}
~VFO() { stop(); }
VFO(stream<complex_t>* in, float offset, float inSampleRate, float outSampleRate, float bandWidth) {
init(in, offset, inSampleRate, outSampleRate, bandWidth);
};
void init(stream<complex_t>* in, float offset, float inSampleRate, float outSampleRate, float bandWidth) {
_in = in;
_offset = offset;
_inSampleRate = inSampleRate;
_outSampleRate = outSampleRate;
_bandWidth = bandWidth;
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
xlator.init(_in, _inSampleRate, -_offset);
win.init(realCutoff, realCutoff, inSampleRate);
resamp.init(&xlator.out, &win, _inSampleRate, _outSampleRate);
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
resamp.updateWindow(&win);
out = &resamp.out;
}
void start() {
if (running) { return; }
xlator.start();
resamp.start();
}
void stop() {
if (!running) { return; }
xlator.stop();
resamp.stop();
}
void setInSampleRate(float inSampleRate) {
_inSampleRate = inSampleRate;
if (running) { xlator.stop(); resamp.stop(); }
xlator.setSampleRate(_inSampleRate);
resamp.setInSampleRate(_inSampleRate);
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
win.setCutoff(realCutoff);
win.setTransWidth(realCutoff);
resamp.updateWindow(&win);
if (running) { xlator.start(); resamp.start(); }
}
void setOutSampleRate(float outSampleRate) {
_outSampleRate = outSampleRate;
if (running) { resamp.stop(); }
resamp.setOutSampleRate(_outSampleRate);
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
win.setCutoff(realCutoff);
win.setTransWidth(realCutoff);
resamp.updateWindow(&win);
if (running) { resamp.start(); }
}
void setOutSampleRate(float outSampleRate, float bandWidth) {
_outSampleRate = outSampleRate;
_bandWidth = bandWidth;
if (running) { resamp.stop(); }
resamp.setOutSampleRate(_outSampleRate);
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
win.setCutoff(realCutoff);
win.setTransWidth(realCutoff);
resamp.updateWindow(&win);
if (running) { resamp.start(); }
}
void setOffset(float offset) {
_offset = offset;
xlator.setFrequency(-_offset);
}
void setBandwidth(float bandWidth) {
_bandWidth = bandWidth;
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
win.setCutoff(realCutoff);
win.setTransWidth(realCutoff);
resamp.updateWindow(&win);
}
stream<complex_t>* out;
private:
bool running = false;
float _offset, _inSampleRate, _outSampleRate, _bandWidth;
filter_window::BlackmanWindow win;
stream<complex_t>* _in;
FrequencyXlator<complex_t> xlator;
PolyphaseResampler<complex_t> resamp;
};
}

76
core/src/dsp/window.h Normal file
View File

@ -0,0 +1,76 @@
#pragma once
#include <dsp/block.h>
namespace dsp {
namespace filter_window {
class generic_window {
public:
virtual int getTapCount() { return -1; }
virtual void createTaps(float* taps, int tapCount, float factor = 1.0f) {}
};
class BlackmanWindow : public filter_window::generic_window {
public:
BlackmanWindow() {}
BlackmanWindow(float cutoff, float transWidth, float sampleRate) { init(cutoff, transWidth, sampleRate); }
void init(float cutoff, float transWidth, float sampleRate) {
_cutoff = cutoff;
_transWidth = transWidth;
_sampleRate = sampleRate;
}
void setSampleRate(float sampleRate) {
_sampleRate = sampleRate;
}
void setCutoff(float cutoff) {
_cutoff = cutoff;
}
void setTransWidth(float transWidth) {
_transWidth = transWidth;
}
int getTapCount() {
float fc = _cutoff / _sampleRate;
if (fc > 1.0f) {
fc = 1.0f;
}
int _M = 4.0f / (_transWidth / _sampleRate);
if (_M < 4) {
_M = 4;
}
if (_M % 2 == 0) { _M++; }
return _M;
}
void createTaps(float* taps, int tapCount, float factor = 1.0f) {
float fc = _cutoff / _sampleRate;
if (fc > 1.0f) {
fc = 1.0f;
}
float tc = tapCount;
float sum = 0.0f;
float val;
for (int i = 0; i < tapCount; i++) {
val = (sin(2.0f * FL_M_PI * fc * ((float)i - (tc / 2))) / ((float)i - (tc / 2))) *
(0.42f - (0.5f * cos(2.0f * FL_M_PI / tc)) + (0.8f * cos(4.0f * FL_M_PI / tc)));
taps[i] = val; // tapCount - i - 1
sum += val;
}
for (int i = 0; i < tapCount; i++) {
taps[i] *= factor;
taps[i] /= sum;
}
}
private:
float _cutoff, _transWidth, _sampleRate;
};
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,185 @@
/*
* Minimal 'console' binding.
*
* https://github.com/DeveloperToolsWG/console-object/blob/master/api.md
* https://developers.google.com/web/tools/chrome-devtools/debug/console/console-reference
* https://developer.mozilla.org/en/docs/Web/API/console
*/
#include <stdio.h>
#include <stdarg.h>
#include "duktape.h"
#include "duk_console.h"
/* XXX: Add some form of log level filtering. */
/* XXX: Should all output be written via e.g. console.write(formattedMsg)?
* This would make it easier for user code to redirect all console output
* to a custom backend.
*/
/* XXX: Init console object using duk_def_prop() when that call is available. */
static duk_ret_t duk__console_log_helper(duk_context *ctx, const char *error_name) {
duk_uint_t flags = (duk_uint_t) duk_get_current_magic(ctx);
FILE *output = (flags & DUK_CONSOLE_STDOUT_ONLY) ? stdout : stderr;
duk_idx_t n = duk_get_top(ctx);
duk_idx_t i;
duk_get_global_string(ctx, "console");
duk_get_prop_string(ctx, -1, "format");
for (i = 0; i < n; i++) {
if (duk_check_type_mask(ctx, i, DUK_TYPE_MASK_OBJECT)) {
/* Slow path formatting. */
duk_dup(ctx, -1); /* console.format */
duk_dup(ctx, i);
duk_call(ctx, 1);
duk_replace(ctx, i); /* arg[i] = console.format(arg[i]); */
}
}
duk_pop_2(ctx);
duk_push_string(ctx, " ");
duk_insert(ctx, 0);
duk_join(ctx, n);
if (error_name) {
duk_push_error_object(ctx, DUK_ERR_ERROR, "%s", duk_require_string(ctx, -1));
duk_push_string(ctx, "name");
duk_push_string(ctx, error_name);
duk_def_prop(ctx, -3, DUK_DEFPROP_FORCE | DUK_DEFPROP_HAVE_VALUE); /* to get e.g. 'Trace: 1 2 3' */
duk_get_prop_string(ctx, -1, "stack");
}
fprintf(output, "%s\n", duk_to_string(ctx, -1));
if (flags & DUK_CONSOLE_FLUSH) {
fflush(output);
}
return 0;
}
static duk_ret_t duk__console_assert(duk_context *ctx) {
if (duk_to_boolean(ctx, 0)) {
return 0;
}
duk_remove(ctx, 0);
return duk__console_log_helper(ctx, "AssertionError");
}
static duk_ret_t duk__console_log(duk_context *ctx) {
return duk__console_log_helper(ctx, NULL);
}
static duk_ret_t duk__console_trace(duk_context *ctx) {
return duk__console_log_helper(ctx, "Trace");
}
static duk_ret_t duk__console_info(duk_context *ctx) {
return duk__console_log_helper(ctx, NULL);
}
static duk_ret_t duk__console_warn(duk_context *ctx) {
return duk__console_log_helper(ctx, NULL);
}
static duk_ret_t duk__console_error(duk_context *ctx) {
return duk__console_log_helper(ctx, "Error");
}
static duk_ret_t duk__console_dir(duk_context *ctx) {
/* For now, just share the formatting of .log() */
return duk__console_log_helper(ctx, 0);
}
static void duk__console_reg_vararg_func(duk_context *ctx, duk_c_function func, const char *name, duk_uint_t flags) {
duk_push_c_function(ctx, func, DUK_VARARGS);
duk_push_string(ctx, "name");
duk_push_string(ctx, name);
duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE); /* Improve stacktraces by displaying function name */
duk_set_magic(ctx, -1, (duk_int_t) flags);
duk_put_prop_string(ctx, -2, name);
}
void duk_console_init(duk_context *ctx, duk_uint_t flags) {
duk_uint_t flags_orig;
/* If both DUK_CONSOLE_STDOUT_ONLY and DUK_CONSOLE_STDERR_ONLY where specified,
* just turn off DUK_CONSOLE_STDOUT_ONLY and keep DUK_CONSOLE_STDERR_ONLY.
*/
if ((flags & DUK_CONSOLE_STDOUT_ONLY) && (flags & DUK_CONSOLE_STDERR_ONLY)) {
flags &= ~DUK_CONSOLE_STDOUT_ONLY;
}
/* Remember the (possibly corrected) flags we received. */
flags_orig = flags;
duk_push_object(ctx);
/* Custom function to format objects; user can replace.
* For now, try JX-formatting and if that fails, fall back
* to ToString(v).
*/
duk_eval_string(ctx,
"(function (E) {"
"return function format(v){"
"try{"
"return E('jx',v);"
"}catch(e){"
"return String(v);" /* String() allows symbols, ToString() internal algorithm doesn't. */
"}"
"};"
"})(Duktape.enc)");
duk_put_prop_string(ctx, -2, "format");
flags = flags_orig;
if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) {
/* No output indicators were specified; these levels go to stdout. */
flags |= DUK_CONSOLE_STDOUT_ONLY;
}
duk__console_reg_vararg_func(ctx, duk__console_assert, "assert", flags);
duk__console_reg_vararg_func(ctx, duk__console_log, "log", flags);
duk__console_reg_vararg_func(ctx, duk__console_log, "debug", flags); /* alias to console.log */
duk__console_reg_vararg_func(ctx, duk__console_trace, "trace", flags);
duk__console_reg_vararg_func(ctx, duk__console_info, "info", flags);
flags = flags_orig;
if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) {
/* No output indicators were specified; these levels go to stderr. */
flags |= DUK_CONSOLE_STDERR_ONLY;
}
duk__console_reg_vararg_func(ctx, duk__console_warn, "warn", flags);
duk__console_reg_vararg_func(ctx, duk__console_error, "error", flags);
duk__console_reg_vararg_func(ctx, duk__console_error, "exception", flags); /* alias to console.error */
duk__console_reg_vararg_func(ctx, duk__console_dir, "dir", flags);
duk_put_global_string(ctx, "console");
/* Proxy wrapping: ensures any undefined console method calls are
* ignored silently. This was required specifically by the
* DeveloperToolsWG proposal (and was implemented also by Firefox:
* https://bugzilla.mozilla.org/show_bug.cgi?id=629607). This is
* apparently no longer the preferred way of implementing console.
* When Proxy is enabled, whitelist at least .toJSON() to avoid
* confusing JX serialization of the console object.
*/
if (flags & DUK_CONSOLE_PROXY_WRAPPER) {
/* Tolerate failure to initialize Proxy wrapper in case
* Proxy support is disabled.
*/
(void) duk_peval_string_noresult(ctx,
"(function(){"
"var D=function(){};"
"var W={toJSON:true};" /* whitelisted */
"console=new Proxy(console,{"
"get:function(t,k){"
"var v=t[k];"
"return typeof v==='function'||W[k]?v:D;"
"}"
"});"
"})();"
);
}
}

View File

@ -0,0 +1,29 @@
#if !defined(DUK_CONSOLE_H_INCLUDED)
#define DUK_CONSOLE_H_INCLUDED
#include "duktape.h"
#if defined(__cplusplus)
extern "C" {
#endif
/* Use a proxy wrapper to make undefined methods (console.foo()) no-ops. */
#define DUK_CONSOLE_PROXY_WRAPPER (1U << 0)
/* Flush output after every call. */
#define DUK_CONSOLE_FLUSH (1U << 1)
/* Send output to stdout only (default is mixed stdout/stderr). */
#define DUK_CONSOLE_STDOUT_ONLY (1U << 2)
/* Send output to stderr only (default is mixed stdout/stderr). */
#define DUK_CONSOLE_STDERR_ONLY (1U << 3)
/* Initialize the console system */
extern void duk_console_init(duk_context *ctx, duk_uint_t flags);
#if defined(__cplusplus)
}
#endif /* end 'extern "C"' wrapper */
#endif /* DUK_CONSOLE_H_INCLUDED */

99755
core/src/duktape/duktape.c Normal file

File diff suppressed because it is too large Load Diff

1450
core/src/duktape/duktape.h Normal file

File diff suppressed because it is too large Load Diff

43
core/src/event.h Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <vector>
#include <spdlog/spdlog.h>
template <class T>
class Event {
public:
Event() {}
~Event() {}
struct EventHandler {
EventHandler() {}
EventHandler(void (*handler)(T, void*), void* ctx) {
this->handler = handler;
this->ctx = ctx;
}
void (*handler)(T, void*);
void* ctx;
};
void emit(T value) {
for (auto const& handler : handlers) {
handler.handler(value, handler.ctx);
}
}
void bindHandler(const EventHandler& handler) {
handlers.push_back(handler);
}
void unbindHandler(const EventHandler& handler) {
if (handlers.find(handler) == handlers.end()) {
spdlog::error("Tried to remove a non-existant event handler");
return;
}
handlers.erase(std::remove(handlers.begin(), handlers.end(), handler), handlers.end());
}
private:
std::vector<EventHandler> handlers;
};

View File

@ -0,0 +1,59 @@
#include <gui/dialogs/credits.h>
#include <imgui.h>
#include <gui/icons.h>
#include <gui/style.h>
#include <config.h>
namespace credits {
ImFont* bigFont;
void init() {
}
void show() {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
ImGui::OpenPopup("Credits");
ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
ImGui::PushFont(style::hugeFont);
ImGui::Text("SDR++ ");
ImGui::PopFont();
ImGui::SameLine();
ImGui::Image(icons::LOGO, ImVec2(128, 128));
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
ImGui::Text("This software is brought to you by\n\n");
ImGui::Columns(3, "CreditColumns", true);
// Contributors
ImGui::Text("Contributors");
ImGui::BulletText("Ryzerth (Creator)");
ImGui::BulletText("aosync");
ImGui::BulletText("Benjamin Kyd");
ImGui::BulletText("Tobias Mädel");
ImGui::BulletText("Raov");
ImGui::BulletText("Howard0su");
// Libraries
ImGui::NextColumn();
ImGui::Text("Libraries");
ImGui::BulletText("SoapySDR (PothosWare)");
ImGui::BulletText("Dear ImGui (ocornut)");
ImGui::BulletText("spdlog (gabime)");
ImGui::BulletText("json (nlohmann)");
ImGui::BulletText("portaudio (PA Comm.)");
// Patrons
ImGui::NextColumn();
ImGui::Text("Patrons");
ImGui::BulletText("SignalsEverywhere");
ImGui::BulletText("Lee Donaghy");
ImGui::EndPopup();
ImGui::PopStyleVar(1);
}
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace credits {
void init();
void show();
}

View File

@ -0,0 +1,91 @@
#include <GL/glew.h>
#include <gui/dialogs/loading_screen.h>
#include <gui/main_window.h>
#include <imgui.h>
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <gui/icons.h>
#include <gui/style.h>
namespace LoadingScreen {
GLFWwindow* _win;
void setWindow(GLFWwindow* win) {
_win = win;
}
void show(std::string msg) {
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
ImGui::OpenPopup("Credits");
ImGui::PushStyleColor(ImGuiCol_ModalWindowDarkening, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground);
ImGui::PushFont(style::hugeFont);
ImGui::Text("SDR++ ");
ImGui::PopFont();
ImGui::SameLine();
ImGui::Image(icons::LOGO, ImVec2(128, 128));
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
ImGui::Text("This software is brought to you by\n\n");
ImGui::Columns(3, "CreditColumns", true);
// Contributors
ImGui::Text("Contributors");
ImGui::BulletText("Ryzerth (Creator)");
ImGui::BulletText("aosync");
ImGui::BulletText("Benjamin Kyd");
ImGui::BulletText("Tobias Mädel");
ImGui::BulletText("Raov");
ImGui::BulletText("Howard0su");
// Libraries
ImGui::NextColumn();
ImGui::Text("Libraries");
ImGui::BulletText("SoapySDR (PothosWare)");
ImGui::BulletText("Dear ImGui (ocornut)");
ImGui::BulletText("spdlog (gabime)");
ImGui::BulletText("json (nlohmann)");
ImGui::BulletText("portaudio (PA Comm.)");
// Patrons
ImGui::NextColumn();
ImGui::Text("Patrons");
ImGui::BulletText("SignalsEverywhere");
ImGui::BulletText("Lee Donaghy");
ImGui::Columns(1, "CreditColumnsEnd", true);
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
ImGui::Text(msg.c_str());
ImGui::EndPopup();
ImGui::PopStyleVar(1);
ImGui::PopStyleColor(1);
ImGui::End();
ImGui::Render();
int display_w, display_h;
glfwGetFramebufferSize(_win, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClearColor(0.0666f, 0.0666f, 0.0666f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(_win);
}
}

View File

@ -0,0 +1,10 @@
#pragma once
#include <thread>
#include <string>
#include <mutex>
#include <GLFW/glfw3.h>
namespace LoadingScreen {
void setWindow(GLFWwindow* win);
void show(std::string msg);
};

7
core/src/gui/gui.cpp Normal file
View File

@ -0,0 +1,7 @@
#include <gui/gui.h>
namespace gui {
ImGui::WaterFall waterfall;
FrequencySelect freqSelect;
Menu menu;
};

14
core/src/gui/gui.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <gui/widgets/waterfall.h>
#include <gui/widgets/frequency_select.h>
#include <gui/widgets/menu.h>
#include <gui/dialogs/loading_screen.h>
#include <new_module.h>
namespace gui {
SDRPP_EXPORT ImGui::WaterFall waterfall;
SDRPP_EXPORT FrequencySelect freqSelect;
SDRPP_EXPORT Menu menu;
void selectSource(std::string name);
};

43
core/src/gui/icons.cpp Normal file
View File

@ -0,0 +1,43 @@
#include <gui/icons.h>
#include <stdint.h>
#include <GL/glew.h>
#include <config.h>
#define STB_IMAGE_IMPLEMENTATION
#include <imgui/stb_image.h>
namespace icons {
ImTextureID LOGO;
ImTextureID PLAY;
ImTextureID STOP;
ImTextureID MENU;
ImTextureID MUTED;
ImTextureID UNMUTED;
ImTextureID NORMAL_TUNING;
ImTextureID CENTER_TUNING;
GLuint loadTexture(std::string path) {
int w,h,n;
stbi_uc* data = stbi_load(path.c_str(), &w, &h, &n, 0);
GLuint texId;
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)data);
stbi_image_free(data);
return texId;
}
void load() {
LOGO = (ImTextureID)(uintptr_t)loadTexture(ROOT_DIR "/res/icons/sdrpp.png");
PLAY = (ImTextureID)(uintptr_t)loadTexture(ROOT_DIR "/res/icons/play.png");
STOP = (ImTextureID)(uintptr_t)loadTexture(ROOT_DIR "/res/icons/stop.png");
MENU = (ImTextureID)(uintptr_t)loadTexture(ROOT_DIR "/res/icons/menu.png");
MUTED = (ImTextureID)(uintptr_t)loadTexture(ROOT_DIR "/res/icons/muted.png");
UNMUTED = (ImTextureID)(uintptr_t)loadTexture(ROOT_DIR "/res/icons/unmuted.png");
NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(ROOT_DIR "/res/icons/normal_tuning.png");
CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(ROOT_DIR "/res/icons/center_tuning.png");
}
}

18
core/src/gui/icons.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <imgui/imgui.h>
#include <GL/glew.h>
#include <string>
namespace icons {
extern ImTextureID LOGO;
extern ImTextureID PLAY;
extern ImTextureID STOP;
extern ImTextureID MENU;
extern ImTextureID MUTED;
extern ImTextureID UNMUTED;
extern ImTextureID NORMAL_TUNING;
extern ImTextureID CENTER_TUNING;
GLuint loadTexture(std::string path);
void load();
}

View File

@ -0,0 +1,612 @@
#include <gui/main_window.h>
#include <gui/gui.h>
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <imgui_plot.h>
#include <thread>
#include <complex>
#include <gui/widgets/waterfall.h>
#include <gui/widgets/frequency_select.h>
#include <fftw3.h>
#include <signal_path/dsp.h>
#include <gui/icons.h>
#include <gui/widgets/bandplan.h>
#include <watcher.h>
#include <signal_path/vfo_manager.h>
#include <gui/style.h>
#include <config.h>
#include <signal_path/signal_path.h>
#include <core.h>
#include <gui/menus/source.h>
#include <gui/menus/display.h>
#include <gui/menus/bandplan.h>
#include <gui/menus/sink.h>
#include <gui/menus/scripting.h>
#include <gui/dialogs/credits.h>
#include <filesystem>
#include <signal_path/source.h>
#include <gui/dialogs/loading_screen.h>
// const int FFTSizes[] = {
// 65536,
// 32768,
// 16384,
// 8192,
// 4096,
// 2048
// };
// const char* FFTSizesStr[] = {
// "65536",
// "32768",
// "16384",
// "8192",
// "4096",
// "2048"
// };
// int fftSizeId = 0;
int fftSize = 8192 * 8;
std::thread worker;
std::mutex fft_mtx;
fftwf_complex *fft_in, *fft_out;
fftwf_plan p;
float* tempFFT;
float* FFTdata;
char buf[1024];
void fftHandler(dsp::complex_t* samples, int count, void* ctx) {
memcpy(fft_in, samples, count * sizeof(dsp::complex_t));
fftwf_execute(p);
int half = count / 2;
volk_32fc_s32f_power_spectrum_32f(tempFFT, (lv_32fc_t*)fft_out, count, count);
volk_32f_s32f_multiply_32f(FFTdata, tempFFT, 0.5f, count);
memcpy(tempFFT, &FFTdata[half], half * sizeof(float));
memmove(&FFTdata[half], FFTdata, half * sizeof(float));
memcpy(FFTdata, tempFFT, half * sizeof(float));
float* fftBuf = gui::waterfall.getFFTBuffer();
if (fftBuf == NULL) {
gui::waterfall.pushFFT();
return;
}
float last = FFTdata[0];
for (int i = 0; i < count; i++) {
last = (FFTdata[i] * 0.1f) + (last * 0.9f);
fftBuf[i] = last;
}
gui::waterfall.pushFFT();
}
watcher<uint64_t> freq((uint64_t)90500000);
watcher<double> vfoFreq(92000000.0);
float fftMin = -70.0;
float fftMax = 0.0;
watcher<double> offset(0.0, true);
float bw = 8000000;
bool playing = false;
watcher<bool> dcbias(false, false);
bool showCredits = false;
std::string audioStreamName = "";
std::string sourceName = "";
int menuWidth = 300;
bool grabbingMenu = false;
int newWidth = 300;
int fftHeight = 300;
bool showMenu = true;
bool centerTuning = false;
dsp::stream<dsp::complex_t> dummyStream;
bool demoWindow = false;
void windowInit() {
LoadingScreen::show("Initializing UI");
gui::waterfall.init();
gui::waterfall.setRawFFTSize(fftSize);
tempFFT = new float[fftSize];
FFTdata = new float[fftSize];
credits::init();
core::configManager.aquire();
gui::menu.order = core::configManager.conf["menuOrder"].get<std::vector<std::string>>();
core::configManager.release();
gui::menu.registerEntry("Source", sourecmenu::draw, NULL);
gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL);
gui::menu.registerEntry("Scripting", scriptingmenu::draw, NULL);
gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL);
gui::menu.registerEntry("Display", displaymenu::draw, NULL);
gui::freqSelect.init();
// Set default values for waterfall in case no source init's it
gui::waterfall.setBandwidth(8000000);
gui::waterfall.setViewBandwidth(8000000);
fft_in = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
fft_out = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
p = fftwf_plan_dft_1d(fftSize, fft_in, fft_out, FFTW_FORWARD, FFTW_ESTIMATE);
sigpath::signalPath.init(8000000, 20, fftSize, &dummyStream, (dsp::complex_t*)fft_in, fftHandler);
sigpath::signalPath.start();
spdlog::info("Loading modules");
// Load modules from /module directory
if (std::filesystem::is_directory(ROOT_DIR "/modules")) {
for (const auto & file : std::filesystem::directory_iterator(ROOT_DIR "/modules")) {
std::string path = file.path().generic_string();
if (file.path().extension().generic_string() != SDRPP_MOD_EXTENTSION) {
continue;
}
if (!file.is_regular_file()) { continue; }
spdlog::info("Loading {0}", path);
LoadingScreen::show("Loading " + path);
core::moduleManager.loadModule(path);
}
}
else {
spdlog::warn("Module directory {0} does not exist, not loading modules from directory");
}
// Read module config
core::configManager.aquire();
std::vector<std::string> modules = core::configManager.conf["modules"];
std::map<std::string, std::string> modList = core::configManager.conf["moduleInstances"];
core::configManager.release();
// Load additional modules specified through config
for (auto const& path : modules) {
spdlog::info("Loading {0}", path);
LoadingScreen::show("Loading " + path);
core::moduleManager.loadModule(path);
}
// Create module instances
for (auto const& [name, module] : modList) {
spdlog::info("Initializing {0} ({1})", name, module);
LoadingScreen::show("Initializing " + name + " (" + module + ")");
core::moduleManager.createInstance(name, module);
}
sourecmenu::init();
sinkmenu::init();
scriptingmenu::init();
bandplanmenu::init();
displaymenu::init();
// TODO for 0.2.5
// Add "select file" option for the file source
// Have a good directory system on both linux and windows
// Switch to double buffering (should fix occassional underruns)
// Fix gain not updated on startup, soapysdr
// TODO for 0.2.6
// Add a module add/remove/change order menu
// Update UI settings
LoadingScreen::show("Loading configuration");
core::configManager.aquire();
fftMin = core::configManager.conf["min"];
fftMax = core::configManager.conf["max"];
gui::waterfall.setFFTMin(fftMin);
gui::waterfall.setWaterfallMin(fftMin);
gui::waterfall.setFFTMax(fftMax);
gui::waterfall.setWaterfallMax(fftMax);
double frequency = core::configManager.conf["frequency"];
gui::freqSelect.setFrequency(frequency);
gui::freqSelect.frequencyChanged = false;
sigpath::sourceManager.tune(frequency);
gui::waterfall.setCenterFrequency(frequency);
bw = gui::waterfall.getBandwidth();
gui::waterfall.vfoFreqChanged = false;
gui::waterfall.centerFreqMoved = false;
gui::waterfall.selectFirstVFO();
menuWidth = core::configManager.conf["menuWidth"];
newWidth = menuWidth;
fftHeight = core::configManager.conf["fftHeight"];
gui::waterfall.setFFTHeight(fftHeight);
centerTuning = core::configManager.conf["centerTuning"];
core::configManager.release();
}
void setVFO(double freq) {
double viewBW = gui::waterfall.getViewBandwidth();
double BW = gui::waterfall.getBandwidth();
if (gui::waterfall.selectedVFO == "") {
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
gui::waterfall.setCenterFrequency(freq);
sigpath::sourceManager.tune(freq);
return;
}
ImGui::WaterfallVFO* vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO];
double currentOff = vfo->centerOffset;
double currentTune = gui::waterfall.getCenterFrequency() + vfo->generalOffset;
double delta = freq - currentTune;
double newVFO = currentOff + delta;
double vfoBW = vfo->bandwidth;
double vfoBottom = newVFO - (vfoBW / 2.0);
double vfoTop = newVFO + (vfoBW / 2.0);
double view = gui::waterfall.getViewOffset();
double viewBottom = view - (viewBW / 2.0);
double viewTop = view + (viewBW / 2.0);
double wholeFreq = gui::waterfall.getCenterFrequency();
double bottom = -(BW / 2.0);
double top = (BW / 2.0);
if (centerTuning) {
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
gui::waterfall.setCenterFrequency(freq);
gui::waterfall.setViewOffset(0);
sigpath::vfoManager.setOffset(gui::waterfall.selectedVFO, 0);
sigpath::sourceManager.tune(freq);
return;
}
// VFO still fints in the view
if (vfoBottom > viewBottom && vfoTop < viewTop) {
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFO);
return;
}
// VFO too low for current SDR tuning
if (vfoBottom < bottom) {
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
double newVFOOffset = (BW / 2.0) - (vfoBW / 2.0) - (viewBW / 10.0);
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFOOffset);
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
sigpath::sourceManager.tune(freq - newVFOOffset);
return;
}
// VFO too high for current SDR tuning
if (vfoTop > top) {
gui::waterfall.setViewOffset((viewBW / 2.0) - (BW / 2.0));
double newVFOOffset = (vfoBW / 2.0) - (BW / 2.0) + (viewBW / 10.0);
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFOOffset);
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
sigpath::sourceManager.tune(freq - newVFOOffset);
return;
}
// VFO is still without the SDR's bandwidth
if (delta < 0) {
double newViewOff = vfoTop - (viewBW / 2.0) + (viewBW / 10.0);
double newViewBottom = newViewOff - (viewBW / 2.0);
double newViewTop = newViewOff + (viewBW / 2.0);
if (newViewBottom > bottom) {
gui::waterfall.setViewOffset(newViewOff);
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFO);
return;
}
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
double newVFOOffset = (BW / 2.0) - (vfoBW / 2.0) - (viewBW / 10.0);
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFOOffset);
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
sigpath::sourceManager.tune(freq - newVFOOffset);
}
else {
double newViewOff = vfoBottom + (viewBW / 2.0) - (viewBW / 10.0);
double newViewBottom = newViewOff - (viewBW / 2.0);
double newViewTop = newViewOff + (viewBW / 2.0);
if (newViewTop < top) {
gui::waterfall.setViewOffset(newViewOff);
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFO);
return;
}
gui::waterfall.setViewOffset((viewBW / 2.0) - (BW / 2.0));
double newVFOOffset = (vfoBW / 2.0) - (BW / 2.0) + (viewBW / 10.0);
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFOOffset);
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
sigpath::sourceManager.tune(freq - newVFOOffset);
}
}
void drawWindow() {
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
ImGui::WaterfallVFO* vfo = NULL;
if (gui::waterfall.selectedVFO != "") {
vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO];
}
if (vfo != NULL) {
if (vfo->centerOffsetChanged) {
if (centerTuning) {
setVFO(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
}
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
gui::freqSelect.frequencyChanged = false;
core::configManager.aquire();
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
core::configManager.release(true);
}
}
sigpath::vfoManager.updateFromWaterfall(&gui::waterfall);
if (gui::waterfall.selectedVFOChanged && vfo != NULL) {
gui::waterfall.selectedVFOChanged = false;
gui::freqSelect.setFrequency(vfo->generalOffset + gui::waterfall.getCenterFrequency());
gui::freqSelect.frequencyChanged = false;
core::configManager.aquire();
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
core::configManager.release(true);
}
if (gui::freqSelect.frequencyChanged) {
gui::freqSelect.frequencyChanged = false;
setVFO(gui::freqSelect.frequency);
if (vfo != NULL) {
vfo->centerOffsetChanged = false;
vfo->lowerOffsetChanged = false;
vfo->upperOffsetChanged = false;
}
core::configManager.aquire();
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
core::configManager.release(true);
}
if (gui::waterfall.centerFreqMoved) {
gui::waterfall.centerFreqMoved = false;
sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency());
if (vfo != NULL) {
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
}
else {
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency());
}
core::configManager.aquire();
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
core::configManager.release(true);
}
int _fftHeight = gui::waterfall.getFFTHeight();
if (fftHeight != _fftHeight) {
fftHeight = _fftHeight;
core::configManager.aquire();
core::configManager.conf["fftHeight"] = fftHeight;
core::configManager.release(true);
}
ImVec2 vMin = ImGui::GetWindowContentRegionMin();
ImVec2 vMax = ImGui::GetWindowContentRegionMax();
int width = vMax.x - vMin.x;
int height = vMax.y - vMin.y;
// To Bar
ImGui::PushID(ImGui::GetID("sdrpp_menu_btn"));
if (ImGui::ImageButton(icons::MENU, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
showMenu = !showMenu;
}
ImGui::PopID();
ImGui::SameLine();
if (playing) {
ImGui::PushID(ImGui::GetID("sdrpp_stop_btn"));
if (ImGui::ImageButton(icons::STOP, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
sigpath::sourceManager.stop();
playing = false;
}
ImGui::PopID();
}
else { // TODO: Might need to check if there even is a device
ImGui::PushID(ImGui::GetID("sdrpp_play_btn"));
if (ImGui::ImageButton(icons::PLAY, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
sigpath::sourceManager.start();
// TODO: tune in module instead
sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency());
playing = true;
}
ImGui::PopID();
}
ImGui::SameLine();
//ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);
sigpath::sinkManager.showVolumeSlider(gui::waterfall.selectedVFO, "##_sdrpp_main_volume_", 248, 30, 5, true);
ImGui::SameLine();
gui::freqSelect.draw();
//ImGui::SameLine();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 9);
if (centerTuning) {
ImGui::PushID(ImGui::GetID("sdrpp_ena_st_btn"));
if (ImGui::ImageButton(icons::CENTER_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
centerTuning = false;
core::configManager.aquire();
core::configManager.conf["centerTuning"] = centerTuning;
core::configManager.release(true);
}
ImGui::PopID();
}
else { // TODO: Might need to check if there even is a device
ImGui::PushID(ImGui::GetID("sdrpp_dis_st_btn"));
if (ImGui::ImageButton(icons::NORMAL_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
centerTuning = true;
setVFO(gui::freqSelect.frequency);
core::configManager.aquire();
core::configManager.conf["centerTuning"] = centerTuning;
core::configManager.release(true);
}
ImGui::PopID();
}
ImGui::SameLine();
// Logo button
ImGui::SetCursorPosX(ImGui::GetWindowSize().x - 48);
ImGui::SetCursorPosY(10);
if (ImGui::ImageButton(icons::LOGO, ImVec2(32, 32), ImVec2(0, 0), ImVec2(1, 1), 0)) {
showCredits = true;
}
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
showCredits = false;
}
if (ImGui::IsKeyPressed(GLFW_KEY_ESCAPE)) {
showCredits = false;
}
// Handle menu resize
float curY = ImGui::GetCursorPosY();
ImVec2 winSize = ImGui::GetWindowSize();
ImVec2 mousePos = ImGui::GetMousePos();
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
if (grabbingMenu) {
newWidth = mousePos.x;
newWidth = std::clamp<float>(newWidth, 250, winSize.x - 250);
ImGui::GetForegroundDrawList()->AddLine(ImVec2(newWidth, curY), ImVec2(newWidth, winSize.y - 10), ImGui::GetColorU32(ImGuiCol_SeparatorActive));
}
if (mousePos.x >= newWidth - 2 && mousePos.x <= newWidth + 2 && mousePos.y > curY) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
if (click) {
grabbingMenu = true;
}
}
else {
ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
}
if(!down && grabbingMenu) {
grabbingMenu = false;
menuWidth = newWidth;
core::configManager.aquire();
core::configManager.conf["menuWidth"] = menuWidth;
core::configManager.release(true);
}
// Left Column
if (showMenu) {
ImGui::Columns(3, "WindowColumns", false);
ImGui::SetColumnWidth(0, menuWidth);
ImGui::SetColumnWidth(1, winSize.x - menuWidth - 60);
ImGui::SetColumnWidth(2, 60);
ImGui::BeginChild("Left Column");
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
gui::menu.draw();
if(ImGui::CollapsingHeader("Debug")) {
ImGui::Text("Frame time: %.3f ms/frame", 1000.0 / ImGui::GetIO().Framerate);
ImGui::Text("Framerate: %.1f FPS", ImGui::GetIO().Framerate);
ImGui::Text("Center Frequency: %.0f Hz", gui::waterfall.getCenterFrequency());
ImGui::Text("Source name: %s", sourceName.c_str());
if (ImGui::Checkbox("Test technique", &dcbias.val)) {
//sigpath::signalPath.setDCBiasCorrection(dcbias.val);
}
ImGui::Checkbox("Show demo window", &demoWindow);
ImGui::Spacing();
}
ImGui::EndChild();
}
else {
// When hiding the menu bar
ImGui::Columns(3, "WindowColumns", false);
ImGui::SetColumnWidth(0, 8);
ImGui::SetColumnWidth(1, winSize.x - 8 - 60);
ImGui::SetColumnWidth(2, 60);
}
// Right Column
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
ImGui::NextColumn();
ImGui::PopStyleVar();
ImGui::BeginChild("Waterfall");
gui::waterfall.draw();
ImGui::EndChild();
ImGui::NextColumn();
ImGui::BeginChild("WaterfallControls");
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Zoom").x / 2.0));
ImGui::Text("Zoom");
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
if (ImGui::VSliderFloat("##_7_", ImVec2(20.0, 150.0), &bw, gui::waterfall.getBandwidth(), 1000.0, "")) {
gui::waterfall.setViewBandwidth(bw);
if (vfo != NULL) {
gui::waterfall.setViewOffset(vfo->centerOffset); // center vfo on screen
}
}
ImGui::NewLine();
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Max").x / 2.0));
ImGui::Text("Max");
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
if (ImGui::VSliderFloat("##_8_", ImVec2(20.0, 150.0), &fftMax, 0.0, -100.0, "")) {
fftMax = std::max<float>(fftMax, fftMin + 10);
core::configManager.aquire();
core::configManager.conf["max"] = fftMax;
core::configManager.release(true);
}
ImGui::NewLine();
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Min").x / 2.0));
ImGui::Text("Min");
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
if (ImGui::VSliderFloat("##_9_", ImVec2(20.0, 150.0), &fftMin, 0.0, -100.0, "")) {
fftMin = std::min<float>(fftMax - 10, fftMin);
core::configManager.aquire();
core::configManager.conf["min"] = fftMin;
core::configManager.release(true);
}
ImGui::EndChild();
gui::waterfall.setFFTMin(fftMin);
gui::waterfall.setFFTMax(fftMax);
gui::waterfall.setWaterfallMin(fftMin);
gui::waterfall.setWaterfallMax(fftMax);
ImGui::End();
if (showCredits) {
credits::show();
}
if (demoWindow) {
ImGui::ShowDemoWindow();
}
}
void setViewBandwidthSlider(float bandwidth) {
bw = bandwidth;
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "imgui.h"
#define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground
void windowInit();
void drawWindow();
void setViewBandwidthSlider(float bandwidth);

View File

@ -0,0 +1,51 @@
#include <gui/menus/bandplan.h>
#include <gui/widgets/bandplan.h>
#include <gui/gui.h>
#include <core.h>
namespace bandplanmenu {
int bandplanId;
bool bandPlanEnabled;
void init() {
// todo: check if the bandplan wasn't removed
if (bandplan::bandplanNames.size() == 0) {
gui::waterfall.hideBandplan();
return;
}
if (bandplan::bandplans.find(core::configManager.conf["bandPlan"]) != bandplan::bandplans.end()) {
std::string name = core::configManager.conf["bandPlan"];
bandplanId = std::distance(bandplan::bandplanNames.begin(), std::find(bandplan::bandplanNames.begin(),
bandplan::bandplanNames.end(), name));
gui::waterfall.bandplan = &bandplan::bandplans[name];
}
else {
gui::waterfall.bandplan = &bandplan::bandplans[bandplan::bandplanNames[0]];
}
bandPlanEnabled = core::configManager.conf["bandPlanEnabled"];
bandPlanEnabled ? gui::waterfall.showBandplan() : gui::waterfall.hideBandplan();
}
void draw(void* ctx) {
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
ImGui::PushItemWidth(menuColumnWidth);
if (ImGui::Combo("##_4_", &bandplanId, bandplan::bandplanNameTxt.c_str())) {
gui::waterfall.bandplan = &bandplan::bandplans[bandplan::bandplanNames[bandplanId]];
core::configManager.aquire();
core::configManager.conf["bandPlan"] = bandplan::bandplanNames[bandplanId];
core::configManager.release(true);
}
ImGui::PopItemWidth();
if (ImGui::Checkbox("Enabled", &bandPlanEnabled)) {
bandPlanEnabled ? gui::waterfall.showBandplan() : gui::waterfall.hideBandplan();
core::configManager.aquire();
core::configManager.conf["bandPlanEnabled"] = bandPlanEnabled;
core::configManager.release(true);
}
bandplan::BandPlan_t plan = bandplan::bandplans[bandplan::bandplanNames[bandplanId]];
ImGui::Text("Country: %s (%s)", plan.countryName.c_str(), plan.countryCode.c_str());
ImGui::Text("Author: %s", plan.authorName.c_str());
}
};

View File

@ -0,0 +1,6 @@
#pragma once
namespace bandplanmenu {
void init();
void draw(void* ctx);
};

View File

@ -0,0 +1,22 @@
#include <gui/menus/display.h>
#include <imgui.h>
#include <gui/gui.h>
#include <core.h>
namespace displaymenu {
bool showWaterfall;
void init() {
showWaterfall = core::configManager.conf["showWaterfall"];
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
}
void draw(void* ctx) {
if (ImGui::Checkbox("Show Waterfall", &showWaterfall)) {
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
core::configManager.aquire();
core::configManager.conf["showWaterfall"] = showWaterfall;
core::configManager.release(true);
}
}
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace displaymenu {
void init();
void draw(void* ctx);
}

View File

@ -0,0 +1,22 @@
#include <gui/menus/scripting.h>
#include <core.h>
#include <gui/style.h>
#include <imgui/imgui.h>
namespace scriptingmenu {
void init() {
}
void draw(void* ctx) {
float menuWidth = ImGui::GetContentRegionAvailWidth();
for (const auto& [name, script] : core::scriptManager.scripts) {
bool running = script->running;
if (running) { style::beginDisabled(); }
if (ImGui::Button(name.c_str(), ImVec2(menuWidth, 0))) {
script->run();
}
if (running) { style::endDisabled(); }
}
}
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace scriptingmenu {
void init();
void draw(void* ctx);
}

View File

@ -0,0 +1,15 @@
#include <gui/menus/sink.h>
#include <signal_path/signal_path.h>
#include <core.h>
namespace sinkmenu {
void init() {
core::configManager.aquire();
sigpath::sinkManager.loadSinksFromConfig();
core::configManager.release();
}
void draw(void* ctx) {
sigpath::sinkManager.showMenu();
}
};

View File

@ -0,0 +1,6 @@
#pragma once
namespace sinkmenu {
void init();
void draw(void* ctx);
};

View File

@ -0,0 +1,54 @@
#include <gui/menus/source.h>
#include <imgui.h>
#include <gui/gui.h>
#include <core.h>
#include <signal_path/signal_path.h>
namespace sourecmenu {
int sourceId = 0;
double freqOffset = 0.0;
void init() {
core::configManager.aquire();
std::string name = core::configManager.conf["source"];
auto it = std::find(sigpath::sourceManager.sourceNames.begin(), sigpath::sourceManager.sourceNames.end(), name);
if (it != sigpath::sourceManager.sourceNames.end()) {
sigpath::sourceManager.selectSource(name);
sourceId = std::distance(sigpath::sourceManager.sourceNames.begin(), it);
}
else if (sigpath::sourceManager.sourceNames.size() > 0) {
sigpath::sourceManager.selectSource(sigpath::sourceManager.sourceNames[0]);
}
else {
spdlog::warn("No source available...");
}
sigpath::sourceManager.setTuningOffset(core::configManager.conf["offset"]);
core::configManager.release();
}
void draw(void* ctx) {
std::string items = "";
for (std::string name : sigpath::sourceManager.sourceNames) {
items += name;
items += '\0';
}
float itemWidth = ImGui::GetContentRegionAvailWidth();
ImGui::SetNextItemWidth(itemWidth);
if (ImGui::Combo("##source", &sourceId, items.c_str())) {
sigpath::sourceManager.selectSource(sigpath::sourceManager.sourceNames[sourceId]);
core::configManager.aquire();
core::configManager.conf["source"] = sigpath::sourceManager.sourceNames[sourceId];
core::configManager.release(true);
}
sigpath::sourceManager.showSelectedMenu();
ImGui::SetNextItemWidth(itemWidth - ImGui::CalcTextSize("Offset (Hz)").x - 10);
if (ImGui::InputDouble("Offset (Hz)##freq_offset", &freqOffset, 1.0, 100.0)) {
sigpath::sourceManager.setTuningOffset(freqOffset);
core::configManager.aquire();
core::configManager.conf["offset"] = freqOffset;
core::configManager.release(true);
}
}
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace sourecmenu {
void init();
void draw(void* ctx);
}

104
core/src/gui/style.cpp Normal file
View File

@ -0,0 +1,104 @@
#include <gui/style.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <config.h>
namespace style {
ImFont* baseFont;
ImFont* bigFont;
ImFont* hugeFont;
void setDefaultStyle() {
ImGui::GetStyle().WindowRounding = 0.0f;
ImGui::GetStyle().ChildRounding = 0.0f;
ImGui::GetStyle().FrameRounding = 0.0f;
ImGui::GetStyle().GrabRounding = 0.0f;
ImGui::GetStyle().PopupRounding = 0.0f;
ImGui::GetStyle().ScrollbarRounding = 0.0f;
baseFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(ROOT_DIR "/res/fonts/Roboto-Medium.ttf")).c_str(), 16.0f);
bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(ROOT_DIR "/res/fonts/Roboto-Medium.ttf")).c_str(), 42.0f);
hugeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(ROOT_DIR "/res/fonts/Roboto-Medium.ttf")).c_str(), 128.0f);
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
}
void testtt() {
ImGui::StyleColorsLight();
}
void setDarkStyle() {
ImGui::GetStyle().WindowRounding = 0.0f;
ImGui::GetStyle().ChildRounding = 0.0f;
ImGui::GetStyle().FrameRounding = 0.0f;
ImGui::GetStyle().GrabRounding = 0.0f;
ImGui::GetStyle().PopupRounding = 0.0f;
ImGui::GetStyle().ScrollbarRounding = 0.0f;
baseFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(ROOT_DIR "/res/fonts/Roboto-Medium.ttf")).c_str(), 16.0f);
bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(ROOT_DIR "/res/fonts/Roboto-Medium.ttf")).c_str(), 42.0f);
hugeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(ROOT_DIR "/res/fonts/Roboto-Medium.ttf")).c_str(), 128.0f);
ImGui::StyleColorsDark();
auto& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f);
colors[ImGuiCol_ChildBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.29f, 0.29f, 0.29f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.44f, 0.44f, 0.44f, 0.40f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.45f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.44f, 0.44f, 0.44f, 0.40f);
colors[ImGuiCol_Header] = ImVec4(0.63f, 0.63f, 0.70f, 0.31f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.63f, 0.63f, 0.70f, 0.40f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.63f, 0.63f, 0.70f, 0.31f);
colors[ImGuiCol_Separator] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.72f, 0.72f, 0.72f, 0.78f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.91f, 0.91f, 0.91f, 0.25f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.81f, 0.81f, 0.81f, 0.67f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.46f, 0.46f, 0.46f, 0.95f);
colors[ImGuiCol_PlotLines] = ImVec4(0.4f, 0.9f, 1.0f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(0.73f, 0.60f, 0.15f, 1.00f);
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.87f, 0.87f, 0.87f, 0.35f);
colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
}
void beginDisabled() {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.44f, 0.44f, 0.44f, 0.15f));
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.20f, 0.21f, 0.22f, 0.30f));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 0.65f));
}
void endDisabled() {
ImGui::PopItemFlag();
ImGui::PopStyleColor(3);
}
}

14
core/src/gui/style.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <imgui.h>
namespace style {
extern ImFont* baseFont;
extern ImFont* bigFont;
extern ImFont* hugeFont;
void setDefaultStyle();
void setDarkStyle();
void beginDisabled();
void endDisabled();
void testtt();
}

View File

@ -1,4 +1,9 @@
#include <bandplan.h>
#include <gui/widgets/bandplan.h>
#include <fstream>
#include <spdlog/spdlog.h>
#include <filesystem>
#include <sstream>
#include <iomanip>
namespace bandplan {
std::map<std::string, BandPlan_t> bandplans;
@ -71,7 +76,7 @@ namespace bandplan {
void loadBandPlan(std::string path) {
std::ifstream file(path.c_str());
json data;
data << file;
file >> data;
file.close();
BandPlan_t plan = data.get<BandPlan_t>();
@ -114,7 +119,7 @@ namespace bandplan {
}
std::ifstream file(path.c_str());
json data;
data << file;
file >> data;
file.close();
colorTable = data.get<std::map<std::string, BandPlanColor_t>>();

View File

@ -1,11 +1,7 @@
#pragma once
#include <json.hpp>
#include <fstream>
#include <spdlog/spdlog.h>
#include <filesystem>
#include <sstream>
#include <iomanip>
#include <imgui/imgui.h>
#include <stdint.h>
using nlohmann::json;
@ -13,8 +9,8 @@ namespace bandplan {
struct Band_t {
std::string name;
std::string type;
float start;
float end;
double start;
double end;
};
void to_json(json& j, const Band_t& b);

View File

@ -1,4 +1,6 @@
#include <frequency_select.h>
#include <gui/widgets/frequency_select.h>
#include <config.h>
#include <gui/style.h>
bool isInArea(ImVec2 val, ImVec2 min, ImVec2 max) {
return val.x >= min.x && val.x < max.x && val.y >= min.y && val.y < max.y;
@ -9,9 +11,9 @@ FrequencySelect::FrequencySelect() {
}
void FrequencySelect::init() {
font = ImGui::GetIO().Fonts->AddFontFromFileTTF("res/fonts/Roboto-Medium.ttf", 42.0f);
for (int i = 0; i < 12; i++) {
digits[i] = 0;
}
}
@ -67,13 +69,14 @@ void FrequencySelect::draw() {
window = ImGui::GetCurrentWindow();
widgetPos = ImGui::GetWindowContentRegionMin();
widgetEndPos = ImGui::GetWindowContentRegionMax();
widgetPos.x += window->Pos.x + 255;
ImVec2 cursorPos = ImGui::GetCursorPos();
widgetPos.x += window->Pos.x + cursorPos.x;
widgetPos.y += window->Pos.y - 3;
widgetEndPos.x += window->Pos.x + 255;
widgetEndPos.x += window->Pos.x + cursorPos.x;
widgetEndPos.y += window->Pos.y - 3;
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
ImGui::PushFont(font);
ImGui::PushFont(style::bigFont);
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
lastWidgetPos = widgetPos;
@ -84,6 +87,9 @@ void FrequencySelect::draw() {
onResize();
}
ImU32 disabledColor = ImGui::GetColorU32(ImGuiCol_Text, 0.3f);
ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text);
int commaOffset = 0;
bool zeros = true;
for (int i = 0; i < 12; i++) {
@ -92,11 +98,11 @@ void FrequencySelect::draw() {
}
sprintf(buf, "%d", digits[i]);
window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y),
zeros ? IM_COL32(90, 90, 90, 255) : IM_COL32(255, 255, 255, 255), buf);
zeros ? disabledColor : textColor, buf);
if ((i + 1) % 3 == 0 && i < 11) {
commaOffset += 12;
window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset + 10, widgetPos.y),
zeros ? IM_COL32(90, 90, 90, 255) : IM_COL32(255, 255, 255, 255), ".");
zeros ? disabledColor : textColor, ".");
}
}
@ -138,19 +144,22 @@ void FrequencySelect::draw() {
}
}
long freq = 0;
uint64_t freq = 0;
for (int i = 0; i < 12; i++) {
freq += digits[i] * pow(10, 11 - i);
}
frequency = freq;
ImGui::PopFont();
ImGui::NewLine();
ImGui::SetCursorPosX(digitBottomMaxs[11].x + 17);
//ImGui::NewLine();
}
void FrequencySelect::setFrequency(long freq) {
void FrequencySelect::setFrequency(uint64_t freq) {
int i = 11;
for (long f = freq; i >= 0; i--) {
for (uint64_t f = freq; i >= 0; i--) {
digits[i] = f % 10;
f -= digits[i];
f /= 10;

View File

@ -1,15 +1,16 @@
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
#include <stdint.h>
class FrequencySelect {
public:
FrequencySelect();
void init();
void draw();
void setFrequency(long freq);
void setFrequency(uint64_t freq);
long frequency;
uint64_t frequency;
bool frequencyChanged = false;
private:
@ -26,7 +27,6 @@ private:
ImVec2 lastWidgetSize;
ImGuiWindow* window;
ImFont* font;
int digits[12];
ImVec2 digitBottomMins[12];

View File

@ -0,0 +1,77 @@
#include <gui/widgets/menu.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
Menu::Menu() {
}
void Menu::registerEntry(std::string name, void (*drawHandler)(void* ctx), void* ctx, ModuleManager::Instance* inst) {
MenuItem_t item;
item.drawHandler = drawHandler;
item.ctx = ctx;
item.inst = inst;
items[name] = item;
if (!isInOrderList(name)) {
order.push_back(name);
}
}
void Menu::removeEntry(std::string name) {
items.erase(name);
}
void Menu::draw() {
MenuItem_t item;
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGuiWindow* window = ImGui::GetCurrentWindow();
for (std::string name : order) {
if (items.find(name) == items.end()) {
continue;
}
item = items[name];
ImRect orginalRect = window->WorkRect;
if (item.inst != NULL) {
window->WorkRect = ImRect(orginalRect.Min, ImVec2(orginalRect.Max.x - ImGui::GetTextLineHeight() - 6, orginalRect.Max.y));
}
if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
if (item.inst != NULL) {
window->WorkRect = orginalRect;
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6);
ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight());
bool enabled = item.inst->isEnabled();
if (ImGui::Checkbox(("##_menu_checkbox_" + name).c_str(), &enabled)) {
enabled ? item.inst->enable() : item.inst->disable();
}
ImGui::SetCursorPos(pos);
}
item.drawHandler(item.ctx);
ImGui::Spacing();
}
else if (item.inst != NULL) {
window->WorkRect = orginalRect;
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6);
ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight());
bool enabled = item.inst->isEnabled();
if (ImGui::Checkbox(("##_menu_checkbox_" + name).c_str(), &enabled)) {
enabled ? item.inst->enable() : item.inst->disable();
}
ImGui::SetCursorPos(pos);
}
}
}
bool Menu::isInOrderList(std::string name) {
for (std::string _name : order) {
if (_name == name) {
return true;
}
}
return false;
}

View File

@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <vector>
#include <map>
#include <new_module.h>
class Menu {
public:
Menu();
struct MenuItem_t {
void (*drawHandler)(void* ctx);
void* ctx;
ModuleManager::Instance* inst;
};
void registerEntry(std::string name, void (*drawHandler)(void* ctx), void* ctx = NULL, ModuleManager::Instance* inst = NULL);
void removeEntry(std::string name);
void draw();
std::vector<std::string> order;
private:
bool isInOrderList(std::string name);
std::map<std::string, MenuItem_t> items;
};

View File

@ -0,0 +1,872 @@
#include <gui/widgets/waterfall.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <GL/glew.h>
#include <imutils.h>
#include <algorithm>
#include <spdlog/spdlog.h>
float COLOR_MAP[][3] = {
{0x00, 0x00, 0x20},
{0x00, 0x00, 0x30},
{0x00, 0x00, 0x50},
{0x00, 0x00, 0x91},
{0x1E, 0x90, 0xFF},
{0xFF, 0xFF, 0xFF},
{0xFF, 0xFF, 0x00},
{0xFE, 0x6D, 0x16},
{0xFF, 0x00, 0x00},
{0xC6, 0x00, 0x00},
{0x9F, 0x00, 0x00},
{0x75, 0x00, 0x00},
{0x4A, 0x00, 0x00}
};
void doZoom(int offset, int width, int outWidth, float* data, float* out) {
// NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX
if (offset < 0) {
offset = 0;
}
if (width > 65535) {
width = 65535;
}
float factor = (float)width / (float)outWidth;
for (int i = 0; i < outWidth; i++) {
out[i] = data[(int)(offset + ((float)i * factor))];
}
}
// TODO: Fix this hacky BS
double freq_ranges[] = {
1.0, 2.0, 2.5, 5.0,
10.0, 20.0, 25.0, 50.0,
100.0, 200.0, 250.0, 500.0,
1000.0, 2000.0, 2500.0, 5000.0,
10000.0, 20000.0, 25000.0, 50000.0,
100000.0, 200000.0, 250000.0, 500000.0,
1000000.0, 2000000.0, 2500000.0, 5000000.0,
10000000.0, 20000000.0, 25000000.0, 50000000.0
};
double findBestRange(double bandwidth, int maxSteps) {
for (int i = 0; i < 32; i++) {
if (bandwidth / freq_ranges[i] < (double)maxSteps) {
return freq_ranges[i];
}
}
return 50000000.0;
}
void printAndScale(double freq, char* buf) {
if (freq < 1000) {
sprintf(buf, "%.3lf", freq);
}
else if (freq < 1000000) {
sprintf(buf, "%.3lfK", freq / 1000.0);
}
else if (freq < 1000000000) {
sprintf(buf, "%.3lfM", freq / 1000000.0);
}
else if (freq < 1000000000000) {
sprintf(buf, "%.3lfG", freq / 1000000000.0);
}
for (int i = strlen(buf) - 2; i >= 0; i--) {
if (buf[i] != '0') {
if (buf[i] == '.') {
i--;
}
char scale = buf[strlen(buf) - 1];
buf[i + 1] = scale;
buf[i + 2] = 0;
return;
}
}
}
namespace ImGui {
WaterFall::WaterFall() {
fftMin = -70.0;
fftMax = 0.0;
waterfallMin = -70.0;
waterfallMax = 0.0;
FFTAreaHeight = 300;
newFFTAreaHeight = FFTAreaHeight;
fftHeight = FFTAreaHeight - 50;
dataWidth = 600;
lastWidgetPos.x = 0;
lastWidgetPos.y = 0;
lastWidgetSize.x = 0;
lastWidgetSize.y = 0;
latestFFT = new float[1];
waterfallFb = new uint32_t[1];
viewBandwidth = 1.0;
wholeBandwidth = 1.0;
updatePallette(COLOR_MAP, 13);
}
void WaterFall::init() {
glGenTextures(1, &textureId);
}
void WaterFall::drawFFT() {
// Calculate scaling factor
float startLine = floorf(fftMax / vRange) * vRange;
float vertRange = fftMax - fftMin;
float scaleFactor = fftHeight / vertRange;
char buf[100];
ImU32 trace = ImGui::GetColorU32(ImGuiCol_PlotLines);
ImU32 shadow = ImGui::GetColorU32(ImGuiCol_PlotLines, 0.2);
// Vertical scale
for (float line = startLine; line > fftMin; line -= vRange) {
float yPos = widgetPos.y + fftHeight + 10 - ((line - fftMin) * scaleFactor);
window->DrawList->AddLine(ImVec2(roundf(widgetPos.x + 50), roundf(yPos)),
ImVec2(roundf(widgetPos.x + dataWidth + 50), roundf(yPos)),
IM_COL32(50, 50, 50, 255), 1.0);
sprintf(buf, "%d", (int)line);
ImVec2 txtSz = ImGui::CalcTextSize(buf);
window->DrawList->AddText(ImVec2(widgetPos.x + 40 - txtSz.x, roundf(yPos - (txtSz.y / 2.0))), IM_COL32( 255, 255, 255, 255 ), buf);
}
// Horizontal scale
double startFreq = ceilf(lowerFreq / range) * range;
double horizScale = (double)dataWidth / viewBandwidth;
for (double freq = startFreq; freq < upperFreq; freq += range) {
double xPos = widgetPos.x + 50 + ((freq - lowerFreq) * horizScale);
window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + 10),
ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10),
IM_COL32(50, 50, 50, 255), 1.0);
window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10),
ImVec2(roundf(xPos), widgetPos.y + fftHeight + 17),
IM_COL32(255, 255, 255, 255), 1.0);
printAndScale(freq, buf);
ImVec2 txtSz = ImGui::CalcTextSize(buf);
window->DrawList->AddText(ImVec2(roundf(xPos - (txtSz.x / 2.0)), widgetPos.y + fftHeight + 10 + txtSz.y), IM_COL32( 255, 255, 255, 255 ), buf);
}
// Data
for (int i = 1; i < dataWidth; i++) {
double aPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i - 1] - fftMin) * scaleFactor);
double bPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i] - fftMin) * scaleFactor);
aPos = std::clamp<double>(aPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10);
bPos = std::clamp<double>(bPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10);
window->DrawList->AddLine(ImVec2(widgetPos.x + 49 + i, roundf(aPos)),
ImVec2(widgetPos.x + 50 + i, roundf(bPos)), trace, 1.0);
window->DrawList->AddLine(ImVec2(widgetPos.x + 50 + i, roundf(bPos)),
ImVec2(widgetPos.x + 50 + i, widgetPos.y + fftHeight + 10), shadow, 1.0);
}
// X Axis
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 10),
ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10),
IM_COL32(255, 255, 255, 255), 1.0);
// Y Axis
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + 9),
ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 9),
IM_COL32(255, 255, 255, 255), 1.0);
}
void WaterFall::drawWaterfall() {
if (waterfallUpdate) {
waterfallUpdate = false;
updateWaterfallTexture();
}
window->DrawList->AddImage((void*)(intptr_t)textureId, ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 51),
ImVec2(widgetPos.x + 50 + dataWidth, widgetPos.y + fftHeight + 51 + waterfallHeight));
}
void WaterFall::drawVFOs() {
for (auto const& [name, vfo] : vfos) {
if (vfo->redrawRequired) {
vfo->redrawRequired = false;
vfo->updateDrawingVars(viewBandwidth, dataWidth, viewOffset, widgetPos, fftHeight);
}
vfo->draw(window, name == selectedVFO);
}
}
void WaterFall::selectFirstVFO() {
bool available = false;
for (auto const& [name, vfo] : vfos) {
available = true;
selectedVFO = name;
return;
}
if (!available) {
selectedVFO = "";
}
}
void WaterFall::processInputs() {
WaterfallVFO* vfo = NULL;
if (selectedVFO != "") {
vfo = vfos[selectedVFO];
}
ImVec2 mousePos = ImGui::GetMousePos();
ImVec2 drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left);
ImVec2 dragOrigin(mousePos.x - drag.x, mousePos.y - drag.y);
bool mouseHovered, mouseHeld;
bool mouseClicked = ImGui::ButtonBehavior(ImRect(fftAreaMin, fftAreaMax), GetID("WaterfallID"), &mouseHovered, &mouseHeld,
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_PressedOnClick);
bool draging = ImGui::IsMouseDragging(ImGuiMouseButton_Left) && ImGui::IsWindowFocused();
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 (mouseClicked) {
for (auto const& [name, _vfo] : vfos) {
if (name == selectedVFO) {
continue;
}
if (IS_IN_AREA(mousePos, _vfo->rectMin, _vfo->rectMax)) {
selectedVFO = name;
selectedVFOChanged = true;
return;
}
}
if (vfo != NULL) {
int refCenter = mousePos.x - (widgetPos.x + 50);
if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y)) {
double off = ((((double)refCenter / ((double)dataWidth / 2.0)) - 1.0) * (viewBandwidth / 2.0)) + viewOffset;
off += centerFreq;
off = (round(off / vfo->snapInterval) * vfo->snapInterval) - centerFreq;
vfo->setOffset(off);
}
}
}
// 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 != NULL) {
double off = ((((double)refCenter / ((double)dataWidth / 2.0)) - 1.0) * (viewBandwidth / 2.0)) + viewOffset;
off += centerFreq;
off = (round(off / vfo->snapInterval) * vfo->snapInterval) - centerFreq;
vfo->setOffset(off);
}
}
// Draging frequency scale
if (draging && mouseInFreq) {
double deltax = drag.x - lastDrag;
lastDrag = drag.x;
double viewDelta = deltax * (viewBandwidth / (double)dataWidth);
viewOffset -= viewDelta;
if (viewOffset + (viewBandwidth / 2.0) > wholeBandwidth / 2.0) {
double freqOffset = (viewOffset + (viewBandwidth / 2.0)) - (wholeBandwidth / 2.0);
viewOffset = (wholeBandwidth / 2.0) - (viewBandwidth / 2.0);
centerFreq += freqOffset;
centerFreqMoved = true;
}
if (viewOffset - (viewBandwidth / 2.0) < -(wholeBandwidth / 2.0)) {
double freqOffset = (viewOffset - (viewBandwidth / 2.0)) + (wholeBandwidth / 2.0);
viewOffset = (viewBandwidth / 2.0) - (wholeBandwidth / 2.0);
centerFreq += freqOffset;
centerFreqMoved = true;
}
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
if (viewBandwidth != wholeBandwidth) {
updateAllVFOs();
updateWaterfallFb();
}
}
else {
lastDrag = 0;
}
}
void WaterFall::updateWaterfallFb() {
if (!waterfallVisible || rawFFTs == NULL) {
return;
}
double offsetRatio = viewOffset / (wholeBandwidth / 2.0);
int drawDataSize;
int drawDataStart;
// TODO: Maybe put on the stack for faster alloc?
float* tempData = new float[dataWidth];
float pixel;
float dataRange = waterfallMax - waterfallMin;
int count = std::min<float>(waterfallHeight, fftLines);
if (rawFFTs != NULL) {
for (int i = 0; i < count; i++) {
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[((i + currentFFTLine) % waterfallHeight) * rawFFTSize], tempData);
for (int j = 0; j < dataWidth; j++) {
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
}
}
}
delete[] tempData;
waterfallUpdate = true;
}
void WaterFall::drawBandPlan() {
int count = bandplan->bands.size();
double horizScale = (double)dataWidth / viewBandwidth;
double start, end, center, aPos, bPos, cPos, width;
ImVec2 txtSz;
bool startVis, endVis;
uint32_t color, colorTrans;
for (int i = 0; i < count; i++) {
start = bandplan->bands[i].start;
end = bandplan->bands[i].end;
if (start < lowerFreq && end < lowerFreq) {
continue;
}
if (start > upperFreq && end > upperFreq) {
continue;
}
startVis = (start > lowerFreq);
endVis = (end < upperFreq);
start = std::clamp<double>(start, lowerFreq, upperFreq);
end = std::clamp<double>(end, lowerFreq, upperFreq);
center = (start + end) / 2.0;
aPos = widgetPos.x + 50 + ((start - lowerFreq) * horizScale);
bPos = widgetPos.x + 50 + ((end - lowerFreq) * horizScale);
cPos = widgetPos.x + 50 + ((center - lowerFreq) * horizScale);
width = bPos - aPos;
txtSz = ImGui::CalcTextSize(bandplan->bands[i].name.c_str());
if (bandplan::colorTable.find(bandplan->bands[i].type.c_str()) != bandplan::colorTable.end()) {
color = bandplan::colorTable[bandplan->bands[i].type].colorValue;
colorTrans = bandplan::colorTable[bandplan->bands[i].type].transColorValue;
}
else {
color = IM_COL32(255, 255, 255, 255);
colorTrans = IM_COL32(255, 255, 255, 100);
}
if (aPos <= widgetPos.x + 50) {
aPos = widgetPos.x + 51;
}
if (bPos <= widgetPos.x + 50) {
bPos = widgetPos.x + 51;
}
if (width >= 1.0) {
window->DrawList->AddRectFilled(ImVec2(roundf(aPos), widgetPos.y + fftHeight - 25),
ImVec2(roundf(bPos), widgetPos.y + fftHeight + 10), colorTrans);
if (startVis) {
window->DrawList->AddLine(ImVec2(roundf(aPos), widgetPos.y + fftHeight - 26),
ImVec2(roundf(aPos), widgetPos.y + fftHeight + 9), color);
}
if (endVis) {
window->DrawList->AddLine(ImVec2(roundf(bPos), widgetPos.y + fftHeight - 26),
ImVec2(roundf(bPos), widgetPos.y + fftHeight + 9), color);
}
}
if (txtSz.x <= width) {
window->DrawList->AddText(ImVec2(cPos - (txtSz.x / 2.0), widgetPos.y + fftHeight - 17),
IM_COL32(255, 255, 255, 255), bandplan->bands[i].name.c_str());
}
}
}
void WaterFall::updateWaterfallTexture() {
glBindTexture(GL_TEXTURE_2D, textureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dataWidth, waterfallHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)waterfallFb);
}
void WaterFall::onPositionChange() {
// Nothing to see here...
}
void WaterFall::onResize() {
// return if widget is too small
if (widgetSize.x < 100 || widgetSize.y < 100) {
return;
}
int lastWaterfallHeight = waterfallHeight;
if (waterfallVisible) {
FFTAreaHeight = std::min<int>(FFTAreaHeight, widgetSize.y - 50);
fftHeight = FFTAreaHeight - 50;
waterfallHeight = widgetSize.y - fftHeight - 52;
}
else {
fftHeight = widgetSize.y - 50;
}
dataWidth = widgetSize.x - 60.0;
delete[] latestFFT;
// Raw FFT resize
fftLines = std::min<int>(fftLines, waterfallHeight);
if (rawFFTs != NULL) {
if (currentFFTLine != 0) {
float* tempWF = new float[currentFFTLine * rawFFTSize];
int moveCount = lastWaterfallHeight - currentFFTLine;
memcpy(tempWF, rawFFTs, currentFFTLine * rawFFTSize * sizeof(float));
memmove(rawFFTs, &rawFFTs[currentFFTLine * rawFFTSize], moveCount * rawFFTSize * sizeof(float));
memcpy(&rawFFTs[moveCount * rawFFTSize], tempWF, currentFFTLine * rawFFTSize * sizeof(float));
delete[] tempWF;
}
currentFFTLine = 0;
rawFFTs = (float*)realloc(rawFFTs, waterfallHeight * rawFFTSize * sizeof(float));
}
else {
rawFFTs = (float*)malloc(waterfallHeight * rawFFTSize * sizeof(float));
}
// ==============
latestFFT = new float[dataWidth];
if (waterfallVisible) {
delete[] waterfallFb;
waterfallFb = new uint32_t[dataWidth * waterfallHeight];
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
}
for (int i = 0; i < dataWidth; i++) {
latestFFT[i] = -1000.0; // Hide everything
}
fftAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + 9);
fftAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10);
freqAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 11);
freqAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 50);
maxHSteps = dataWidth / (ImGui::CalcTextSize("000.000").x + 10);
maxVSteps = fftHeight / (ImGui::CalcTextSize("000.000").y);
range = findBestRange(viewBandwidth, maxHSteps);
vRange = findBestRange(fftMax - fftMin, maxVSteps);
vRange = 10.0;
updateWaterfallFb();
updateAllVFOs();
}
void WaterFall::draw() {
buf_mtx.lock();
window = GetCurrentWindow();
widgetPos = ImGui::GetWindowContentRegionMin();
widgetEndPos = ImGui::GetWindowContentRegionMax();
widgetPos.x += window->Pos.x;
widgetPos.y += window->Pos.y;
widgetEndPos.x += window->Pos.x - 4; // Padding
widgetEndPos.y += window->Pos.y;
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
if (selectedVFO == "" && vfos.size() > 0) {
selectFirstVFO();
}
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
lastWidgetPos = widgetPos;
onPositionChange();
}
if (widgetSize.x != lastWidgetSize.x || widgetSize.y != lastWidgetSize.y) {
lastWidgetSize = widgetSize;
onResize();
}
window->DrawList->AddRectFilled(widgetPos, widgetEndPos, IM_COL32( 0, 0, 0, 255 ));
window->DrawList->AddRect(widgetPos, widgetEndPos, IM_COL32( 50, 50, 50, 255 ));
window->DrawList->AddLine(ImVec2(widgetPos.x, widgetPos.y + fftHeight + 50), ImVec2(widgetPos.x + widgetSize.x, widgetPos.y + fftHeight + 50), IM_COL32(50, 50, 50, 255), 1.0);
processInputs();
drawFFT();
if (waterfallVisible) {
drawWaterfall();
}
drawVFOs();
if (bandplan != NULL && bandplanVisible) {
drawBandPlan();
}
if (!waterfallVisible) {
buf_mtx.unlock();
return;
}
// Handle fft resize
ImVec2 winSize = ImGui::GetWindowSize();
ImVec2 mousePos = ImGui::GetMousePos();
mousePos.x -= widgetPos.x;
mousePos.y -= widgetPos.y;
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
if (draggingFW) {
newFFTAreaHeight = mousePos.y;
newFFTAreaHeight = std::clamp<float>(newFFTAreaHeight, 150, widgetSize.y - 50);
ImGui::GetForegroundDrawList()->AddLine(ImVec2(widgetPos.x, newFFTAreaHeight + widgetPos.y), ImVec2(widgetEndPos.x, newFFTAreaHeight + widgetPos.y),
ImGui::GetColorU32(ImGuiCol_SeparatorActive));
}
if (mousePos.y >= newFFTAreaHeight - 2 && mousePos.y <= newFFTAreaHeight + 2 && mousePos.x > 0 && mousePos.x < widgetSize.x) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
if (click) {
draggingFW = true;
}
}
if(!down && draggingFW) {
draggingFW = false;
FFTAreaHeight = newFFTAreaHeight;
onResize();
}
buf_mtx.unlock();
}
float* WaterFall::getFFTBuffer() {
if (rawFFTs == NULL) { return NULL; }
buf_mtx.lock();
currentFFTLine--;
fftLines++;
currentFFTLine = ((currentFFTLine + waterfallHeight) % waterfallHeight);
fftLines = std::min<float>(fftLines, waterfallHeight);
return &rawFFTs[currentFFTLine * rawFFTSize];
}
void WaterFall::pushFFT() {
if (rawFFTs == NULL) { return; }
double offsetRatio = viewOffset / (wholeBandwidth / 2.0);
int drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT);
if (waterfallVisible) {
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
float pixel;
float dataRange = waterfallMax - waterfallMin;
for (int j = 0; j < dataWidth; j++) {
pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
int id = (int)(pixel * (WATERFALL_RESOLUTION - 1));
waterfallFb[j] = waterfallPallet[id];
}
waterfallUpdate = true;
}
buf_mtx.unlock();
}
void WaterFall::updatePallette(float colors[][3], int colorCount) {
for (int i = 0; i < WATERFALL_RESOLUTION; i++) {
int lowerId = floorf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
int upperId = ceilf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
lowerId = std::clamp<int>(lowerId, 0, colorCount - 1);
upperId = std::clamp<int>(upperId, 0, colorCount - 1);
float ratio = (((float)i / (float)WATERFALL_RESOLUTION) * colorCount) - lowerId;
float r = (colors[lowerId][0] * (1.0 - ratio)) + (colors[upperId][0] * (ratio));
float g = (colors[lowerId][1] * (1.0 - ratio)) + (colors[upperId][1] * (ratio));
float b = (colors[lowerId][2] * (1.0 - ratio)) + (colors[upperId][2] * (ratio));
waterfallPallet[i] = ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
}
}
void WaterFall::autoRange() {
float min = INFINITY;
float max = -INFINITY;
for (int i = 0; i < dataWidth; i++) {
if (latestFFT[i] < min) {
min = latestFFT[i];
}
if (latestFFT[i] > max) {
max = latestFFT[i];
}
}
fftMin = min - 5;
fftMax = max + 5;
}
void WaterFall::setCenterFrequency(double freq) {
centerFreq = freq;
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
updateAllVFOs();
}
double WaterFall::getCenterFrequency() {
return centerFreq;
}
void WaterFall::setBandwidth(double bandWidth) {
double currentRatio = viewBandwidth / wholeBandwidth;
wholeBandwidth = bandWidth;
setViewBandwidth(bandWidth * currentRatio);
for (auto const& [name, vfo] : vfos) {
if (vfo->lowerOffset < -(bandWidth / 2)) {
vfo->setCenterOffset(-(bandWidth / 2));
}
if (vfo->upperOffset > (bandWidth / 2)) {
vfo->setCenterOffset(bandWidth / 2);
}
}
updateAllVFOs();
}
double WaterFall::getBandwidth() {
return wholeBandwidth;
}
void WaterFall::setViewBandwidth(double bandWidth) {
if (bandWidth == viewBandwidth) {
return;
}
if (abs(viewOffset) + (bandWidth / 2.0) > wholeBandwidth / 2.0) {
if (viewOffset < 0) {
viewOffset = (bandWidth / 2.0) - (wholeBandwidth / 2.0);
}
else {
viewOffset = (wholeBandwidth / 2.0) - (bandWidth / 2.0);
}
}
viewBandwidth = bandWidth;
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
range = findBestRange(bandWidth, maxHSteps);
updateWaterfallFb();
updateAllVFOs();
}
double WaterFall::getViewBandwidth() {
return viewBandwidth;
}
void WaterFall::setViewOffset(double offset) {
if (offset == viewOffset) {
return;
}
if (offset - (viewBandwidth / 2.0) < -(wholeBandwidth / 2.0)) {
offset = (viewBandwidth / 2.0) - (wholeBandwidth / 2.0);
}
if (offset + (viewBandwidth / 2.0) > (wholeBandwidth / 2.0)) {
offset = (wholeBandwidth / 2.0) - (viewBandwidth / 2.0);
}
viewOffset = offset;
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
updateWaterfallFb();
updateAllVFOs();
}
double WaterFall::getViewOffset() {
return viewOffset;
}
void WaterFall::setFFTMin(float min) {
fftMin = min;
vRange = findBestRange(fftMax - fftMin, maxVSteps);
}
float WaterFall::getFFTMin() {
return fftMin;
}
void WaterFall::setFFTMax(float max) {
fftMax = max;
vRange = findBestRange(fftMax - fftMin, maxVSteps);
}
float WaterFall::getFFTMax() {
return fftMax;
}
void WaterFall::setWaterfallMin(float min) {
if (min == waterfallMin) {
return;
}
waterfallMin = min;
updateWaterfallFb();
}
float WaterFall::getWaterfallMin() {
return waterfallMin;
}
void WaterFall::setWaterfallMax(float max) {
if (max == waterfallMax) {
return;
}
waterfallMax = max;
updateWaterfallFb();
}
float WaterFall::getWaterfallMax() {
return waterfallMax;
}
void WaterFall::updateAllVFOs() {
for (auto const& [name, vfo] : vfos) {
vfo->updateDrawingVars(viewBandwidth, dataWidth, viewOffset, widgetPos, fftHeight);
}
}
void WaterFall::setRawFFTSize(int size, bool lock) {
if (lock) { buf_mtx.lock(); }
rawFFTSize = size;
if (rawFFTs != NULL) {
rawFFTs = (float*)realloc(rawFFTs, rawFFTSize * waterfallHeight * sizeof(float));
}
else {
rawFFTs = (float*)malloc(rawFFTSize * waterfallHeight * sizeof(float));
}
memset(rawFFTs, 0, rawFFTSize * waterfallHeight * sizeof(float));
if (lock) { buf_mtx.unlock(); }
}
void WaterfallVFO::setOffset(double offset) {
generalOffset = offset;
if (reference == REF_CENTER) {
centerOffset = offset;
lowerOffset = offset - (bandwidth / 2.0);
upperOffset = offset + (bandwidth / 2.0);
}
else if (reference == REF_LOWER) {
lowerOffset = offset;
centerOffset = offset + (bandwidth / 2.0);
upperOffset = offset + bandwidth;
}
else if (reference == REF_UPPER) {
upperOffset = offset;
centerOffset = offset - (bandwidth / 2.0);
lowerOffset = offset - bandwidth;
}
centerOffsetChanged = true;
upperOffsetChanged = true;
lowerOffsetChanged = true;
redrawRequired = true;
}
void WaterfallVFO::setCenterOffset(double offset) {
if (reference == REF_CENTER) {
generalOffset = offset;
}
else if (reference == REF_LOWER) {
generalOffset = offset - (bandwidth / 2.0);
}
else if (reference == REF_UPPER) {
generalOffset = offset + (bandwidth / 2.0);
}
centerOffset = offset;
lowerOffset = offset - (bandwidth / 2.0);
upperOffset = offset + (bandwidth / 2.0);
centerOffsetChanged = true;
upperOffsetChanged = true;
lowerOffsetChanged = true;
redrawRequired = true;
}
void WaterfallVFO::setBandwidth(double bw) {
if (bandwidth == bw || bw < 0) {
return;
}
bandwidth = bw;
if (reference == REF_CENTER) {
lowerOffset = centerOffset - (bandwidth / 2.0);
upperOffset = centerOffset + (bandwidth / 2.0);
}
else if (reference == REF_LOWER) {
centerOffset = lowerOffset + (bandwidth / 2.0);
upperOffset = lowerOffset + bandwidth;
centerOffsetChanged = true;
}
else if (reference == REF_UPPER) {
centerOffset = upperOffset - (bandwidth / 2.0);
lowerOffset = upperOffset - bandwidth;
centerOffsetChanged = true;
}
redrawRequired = true;
}
void WaterfallVFO::setReference(int ref) {
if (reference == ref || ref < 0 || ref >= _REF_COUNT) {
return;
}
reference = ref;
setOffset(generalOffset);
}
void WaterfallVFO::updateDrawingVars(double viewBandwidth, float dataWidth, double viewOffset, ImVec2 widgetPos, int fftHeight) {
double width = (bandwidth / viewBandwidth) * (double)dataWidth;
int center = roundf((((centerOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
int left = roundf((((lowerOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
int right = roundf((((upperOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
if (left >= 0 && left < dataWidth && reference == REF_LOWER) {
lineMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 9);
lineMax = ImVec2(widgetPos.x + 50 + left, widgetPos.y + fftHeight + 9);
lineVisible = true;
}
else if (center >= 0 && center < dataWidth && reference == REF_CENTER) {
lineMin = ImVec2(widgetPos.x + 50 + center, widgetPos.y + 9);
lineMax = ImVec2(widgetPos.x + 50 + center, widgetPos.y + fftHeight + 9);
lineVisible = true;
}
else if (right >= 0 && right < dataWidth && reference == REF_UPPER) {
lineMin = ImVec2(widgetPos.x + 50 + right, widgetPos.y + 9);
lineMax = ImVec2(widgetPos.x + 50 + right, widgetPos.y + fftHeight + 9);
lineVisible = true;
}
else {
lineVisible = false;
}
left = std::clamp<int>(left, 0, dataWidth - 1);
right = std::clamp<int>(right, 0, dataWidth - 1);
rectMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 10);
rectMax = ImVec2(widgetPos.x + 51 + right, widgetPos.y + fftHeight + 10);
}
void WaterfallVFO::draw(ImGuiWindow* window, bool selected) {
window->DrawList->AddRectFilled(rectMin, rectMax, IM_COL32(255, 255, 255, 50));
if (lineVisible) {
window->DrawList->AddLine(lineMin, lineMax, selected ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255));
}
};
void WaterFall::showWaterfall() {
waterfallVisible = true;
onResize();
}
void WaterFall::hideWaterfall() {
waterfallVisible = false;
onResize();
}
void WaterFall::setFFTHeight(int height) {
FFTAreaHeight = height;
newFFTAreaHeight = height;
onResize();
}
int WaterFall::getFFTHeight() {
return FFTAreaHeight;
}
void WaterFall::showBandplan() {
bandplanVisible = true;
}
void WaterFall::hideBandplan() {
bandplanVisible = false;
}
void WaterfallVFO::setSnapInterval(double interval) {
snapInterval = interval;
}
};

View File

@ -0,0 +1,202 @@
#pragma once
#include <vector>
#include <mutex>
#include <gui/widgets/bandplan.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include <GL/glew.h>
#define WATERFALL_RESOLUTION 1000000
namespace ImGui {
class WaterfallVFO {
public:
void setOffset(double offset);
void setCenterOffset(double offset);
void setBandwidth(double bw);
void setReference(int ref);
void setSnapInterval(double interval);
void updateDrawingVars(double viewBandwidth, float dataWidth, double viewOffset, ImVec2 widgetPos, int fftHeight); // NOTE: Datawidth double???
void draw(ImGuiWindow* window, bool selected);
enum {
REF_LOWER,
REF_CENTER,
REF_UPPER,
_REF_COUNT
};
double generalOffset;
double centerOffset;
double lowerOffset;
double upperOffset;
double bandwidth;
double snapInterval = 5000;
int reference = REF_CENTER;
ImVec2 rectMin;
ImVec2 rectMax;
ImVec2 lineMin;
ImVec2 lineMax;
bool centerOffsetChanged = false;
bool lowerOffsetChanged = false;
bool upperOffsetChanged = false;
bool redrawRequired = true;
bool lineVisible = true;
};
class WaterFall {
public:
WaterFall();
void init();
void draw();
float* getFFTBuffer();
void pushFFT();
void updatePallette(float colors[][3], int colorCount);
void setCenterFrequency(double freq);
double getCenterFrequency();
void setBandwidth(double bandWidth);
double getBandwidth();
void setViewBandwidth(double bandWidth);
double getViewBandwidth();
void setViewOffset(double offset);
double getViewOffset();
void setFFTMin(float min);
float getFFTMin();
void setFFTMax(float max);
float getFFTMax();
void setWaterfallMin(float min);
float getWaterfallMin();
void setWaterfallMax(float max);
float getWaterfallMax();
void setZoom(double zoomLevel);
void setOffset(double zoomOffset);
void autoRange();
void selectFirstVFO();
void showWaterfall();
void hideWaterfall();
void showBandplan();
void hideBandplan();
void setFFTHeight(int height);
int getFFTHeight();
void setRawFFTSize(int size, bool lock = true);
bool centerFreqMoved = false;
bool vfoFreqChanged = false;
bool bandplanEnabled = false;
bandplan::BandPlan_t* bandplan = NULL;
std::map<std::string, WaterfallVFO*> vfos;
std::string selectedVFO = "";
bool selectedVFOChanged = false;
enum {
REF_LOWER,
REF_CENTER,
REF_UPPER,
_REF_COUNT
};
private:
void drawWaterfall();
void drawFFT();
void drawVFOs();
void drawBandPlan();
void processInputs();
void onPositionChange();
void onResize();
void updateWaterfallFb();
void updateWaterfallTexture();
void updateAllVFOs();
bool waterfallUpdate = false;
uint32_t waterfallPallet[WATERFALL_RESOLUTION];
ImVec2 widgetPos;
ImVec2 widgetEndPos;
ImVec2 widgetSize;
ImVec2 lastWidgetPos;
ImVec2 lastWidgetSize;
ImVec2 fftAreaMin;
ImVec2 fftAreaMax;
ImVec2 freqAreaMin;
ImVec2 freqAreaMax;
ImVec2 waterfallAreaMin;
ImVec2 waterfallAreaMax;
ImGuiWindow* window;
GLuint textureId;
std::mutex buf_mtx;
float vRange;
int maxVSteps;
int maxHSteps;
int dataWidth; // Width of the FFT and waterfall
int fftHeight; // Height of the fft graph
int waterfallHeight = 0; // Height of the waterfall
double viewBandwidth;
double viewOffset;
double lowerFreq;
double upperFreq;
double range;
float lastDrag;
int vfoRef = REF_CENTER;
// Absolute values
double centerFreq;
double wholeBandwidth;
// Ranges
float fftMin;
float fftMax;
float waterfallMin;
float waterfallMax;
//std::vector<std::vector<float>> rawFFTs;
int rawFFTSize;
float* rawFFTs = NULL;
float* latestFFT;
int currentFFTLine = 0;
int fftLines = 0;
uint32_t* waterfallFb;
bool draggingFW = false;
int FFTAreaHeight;
int newFFTAreaHeight;
bool waterfallVisible = true;
bool bandplanVisible = false;
};
};

View File

@ -2640,6 +2640,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
// Slider behavior
ImRect grab_bb;
const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, power, ImGuiSliderFlags_None, &grab_bb);
if (value_changed)
MarkItemEdited(id);

File diff suppressed because it is too large Load Diff

145
core/src/new_module.cpp Normal file
View File

@ -0,0 +1,145 @@
#include <new_module.h>
#include <filesystem>
#include <spdlog/spdlog.h>
ModuleManager::Module_t ModuleManager::loadModule(std::string path) {
Module_t mod;
if (!std::filesystem::exists(path)) {
spdlog::error("{0} does not exist", path);
mod.handle = NULL;
return mod;
}
if (!std::filesystem::is_regular_file(path)) {
spdlog::error("{0} isn't a loadable module", path);
mod.handle = NULL;
return mod;
}
#ifdef _WIN32
mod.handle = LoadLibraryA(path.c_str());
if (mod.handle == NULL) {
spdlog::error("Couldn't load {0}.", path);
mod.handle = NULL;
return mod;
}
mod.info = (ModuleInfo_t*)GetProcAddress(mod.handle, "_INFO_");
mod.init = (void(*)())GetProcAddress(mod.handle, "_INIT_");
mod.createInstance = (Instance*(*)(std::string))GetProcAddress(mod.handle, "_CREATE_INSTANCE_");
mod.deleteInstance = (void(*)(Instance*))GetProcAddress(mod.handle, "_DELETE_INSTANCE_");
mod.end = (void(*)())GetProcAddress(mod.handle, "_END_");
#else
mod.handle = dlopen(path.c_str(), RTLD_LAZY);
if (mod.handle == NULL) {
spdlog::error("Couldn't load {0}.", path);
mod.handle = NULL;
return mod;
}
mod.info = (ModuleInfo_t*)dlsym(mod.handle, "_INFO_");
mod.init = (void(*)())dlsym(mod.handle, "_INIT_");
mod.createInstance = (Instance*(*)(std::string))dlsym(mod.handle, "_CREATE_INSTANCE_");
mod.deleteInstance = (void(*)(Instance*))dlsym(mod.handle, "_DELETE_INSTANCE_");
mod.end = (void(*)())dlsym(mod.handle, "_END_");
#endif
if (mod.info == NULL) {
spdlog::error("{0} is missing _INFO_ symbol", path);
mod.handle = NULL;
return mod;
}
if (mod.init == NULL) {
spdlog::error("{0} is missing _INIT_ symbol", path);
mod.handle = NULL;
return mod;
}
if (mod.createInstance == NULL) {
spdlog::error("{0} is missing _CREATE_INSTANCE_ symbol", path);
mod.handle = NULL;
return mod;
}
if (mod.deleteInstance == NULL) {
spdlog::error("{0} is missing _DELETE_INSTANCE_ symbol", path);
mod.handle = NULL;
return mod;
}
if (mod.end == NULL) {
spdlog::error("{0} is missing _END_ symbol", path);
mod.handle = NULL;
return mod;
}
if (modules.find(mod.info->name) != modules.end()) {
spdlog::error("{0} has the same name as an already loaded module", path);
mod.handle = NULL;
return mod;
}
for (auto const& [name, _mod] : modules) {
if (mod.handle == _mod.handle) {
return _mod;
}
}
mod.init();
modules[mod.info->name] = mod;
return mod;
}
void ModuleManager::createInstance(std::string name, std::string module) {
if (modules.find(module) == modules.end()) {
spdlog::error("Module '{0}' doesn't exist", module);
return;
}
if (instances.find(name) != instances.end()) {
spdlog::error("A module instance with the name '{0}' already exists", name);
return;
}
int maxCount = modules[module].info->maxInstances;
if (countModuleInstances(module) >= maxCount && maxCount > 0) {
spdlog::error("Maximum number of instances reached for '{0}'", module);
return;
}
Instance_t inst;
inst.module = modules[module];
inst.instance = inst.module.createInstance(name);
instances[name] = inst;
}
void ModuleManager::deleteInstance(std::string name) {
spdlog::error("DELETE INSTANCE NOT IMPLEMENTED");
}
void ModuleManager::deleteInstance(ModuleManager::Instance* instance) {
spdlog::error("DELETE INSTANCE NOT IMPLEMENTED");
}
void ModuleManager::enableInstance(std::string name) {
if (instances.find(name) == instances.end()) {
spdlog::error("Cannot enable '{0}', instance doesn't exist", name);
return;
}
instances[name].instance->enable();
}
void ModuleManager::disableInstance(std::string name) {
if (instances.find(name) == instances.end()) {
spdlog::error("Cannot disable '{0}', instance doesn't exist", name);
return;
}
instances[name].instance->disable();
}
bool ModuleManager::instanceEnabled(std::string name) {
if (instances.find(name) == instances.end()) {
spdlog::error("Cannot check if '{0}' is enabled, instance doesn't exist", name);
return false;
}
return instances[name].instance->isEnabled();
}
int ModuleManager::countModuleInstances(std::string module) {
if (modules.find(module) == modules.end()) {
spdlog::error("Cannot count instances of '{0}', Module doesn't exist", module);
return -1;
}
ModuleManager::Module_t mod = modules[module];
int count = 0;
for (auto const& [name, instance] : instances) {
if (instance.module == mod) { count++; }
}
return count;
}

91
core/src/new_module.h Normal file
View File

@ -0,0 +1,91 @@
#pragma once
#include <string>
#include <map>
#include <json.hpp>
#ifdef _WIN32
#ifdef SDRPP_IS_CORE
#define SDRPP_EXPORT extern "C" __declspec(dllexport)
#else
#define SDRPP_EXPORT extern "C" __declspec(dllimport)
#endif
#else
#define SDRPP_EXPORT extern
#endif
#ifdef _WIN32
#include <Windows.h>
#define MOD_EXPORT extern "C" __declspec(dllexport)
#define SDRPP_MOD_EXTENTSION ".dll"
#else
#include <dlfcn.h>
#define MOD_EXPORT extern "C"
#define SDRPP_MOD_EXTENTSION ".so"
#endif
class ModuleManager {
public:
struct ModuleInfo_t {
const char* name;
const char* description;
const char* author;
const int versionMajor;
const int versionMinor;
const int versionBuild;
const int maxInstances;
};
class Instance {
public:
virtual void enable() = 0;
virtual void disable() = 0;
virtual bool isEnabled() = 0;
};
struct Module_t {
#ifdef _WIN32
HMODULE handle;
#else
void* handle;
#endif
ModuleManager::ModuleInfo_t* info;
void (*init)();
ModuleManager::Instance* (*createInstance)(std::string name);
void (*deleteInstance)(ModuleManager::Instance* instance);
void (*end)();
friend bool operator==(const Module_t& a, const Module_t& b) {
if (a.handle != b.handle) { return false; }
if (a.info != b.info) { return false; }
if (a.init != b.init) { return false; }
if (a.createInstance != b.createInstance) { return false; }
if (a.deleteInstance != b.deleteInstance) { return false; }
if (a.end != b.end) { return false; }
return true;
}
};
struct Instance_t {
ModuleManager::Module_t module;
ModuleManager::Instance* instance;
};
ModuleManager::Module_t loadModule(std::string path);
void createInstance(std::string name, std::string module);
void deleteInstance(std::string name);
void deleteInstance(ModuleManager::Instance* instance);
void enableInstance(std::string name);
void disableInstance(std::string name);
bool instanceEnabled(std::string name);
int countModuleInstances(std::string module);
private:
std::map<std::string, ModuleManager::Module_t> modules;
std::map<std::string, ModuleManager::Instance_t> instances;
};
#define SDRPP_MOD_INFO MOD_EXPORT const ModuleManager::ModuleInfo_t _INFO_

93
core/src/scripting.cpp Normal file
View File

@ -0,0 +1,93 @@
#include <scripting.h>
#include <duktape/duk_console.h>
#include <version.h>
#include <fstream>
#include <sstream>
#include <signal_path/signal_path.h>
#include <imgui/imgui.h>
ScriptManager::ScriptManager() {
}
ScriptManager::Script* ScriptManager::createScript(std::string name, std::string path) {
ScriptManager::Script* script = new ScriptManager::Script(this, name, path);
scripts[name] = script;
return script;
}
ScriptManager::Script::Script(ScriptManager* man, std::string name, std::string path) {
this->name = name;
manager = man;
std::ifstream file(path, std::ios::in);
std::stringstream ss;
ss << file.rdbuf();
code = ss.str();
}
void ScriptManager::Script::run() {
ctx = ScriptManager::createContext(manager, name);
running = true;
if (worker.joinable()) {
worker.join();
}
worker = std::thread(scriptWorker, this);
}
duk_context* ScriptManager::createContext(ScriptManager* _this, std::string name) {
duk_context* ctx = duk_create_heap_default();
duk_console_init(ctx, DUK_CONSOLE_PROXY_WRAPPER);
duk_push_string(ctx, name.c_str());
duk_put_global_string(ctx, "SCRIPT_NAME");
duk_idx_t sdrppBase = duk_push_object(ctx);
// API
duk_push_string(ctx, VERSION_STR);
duk_put_prop_string(ctx, sdrppBase, "version");
duk_push_c_function(ctx, ScriptManager::duk_setSource, 1);
duk_put_prop_string(ctx, sdrppBase, "selectSource");
// Modules
duk_idx_t modObjId = duk_push_object(ctx);
for (const auto& [name, handler] : _this->handlers) {
duk_idx_t objId = duk_push_object(ctx);
handler.handler(handler.ctx, ctx, objId);
duk_put_prop_string(ctx, modObjId, name.c_str());
}
duk_put_prop_string(ctx, sdrppBase, "modules");
duk_put_global_string(ctx, "sdrpp");
return ctx;
}
// TODO: Switch to spdlog
void ScriptManager::Script::scriptWorker(Script* script) {
if (duk_peval_string(script->ctx, script->code.c_str()) != 0) {
printf("Error: %s\n", duk_safe_to_string(script->ctx, -1));
//return;
}
duk_destroy_heap(script->ctx);
script->running = false;
}
void ScriptManager::bindScriptRunHandler(std::string name, ScriptManager::ScriptRunHandler_t handler) {
// TODO: check if it exists and add a "unbind" function
handlers[name] = handler;
}
duk_ret_t ScriptManager::duk_setSource(duk_context* dukCtx) {
const char* name = duk_require_string(dukCtx, -1);
sigpath::sourceManager.selectSource(name);
duk_pop_n(dukCtx, 1); // Pop demod name, this and context
return 0;
}

51
core/src/scripting.h Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#include <string>
#include <thread>
#include <vector>
#include <map>
#include <duktape/duktape.h>
class ScriptManager {
public:
ScriptManager();
friend class Script;
class Script {
public:
Script(ScriptManager* man, std::string name, std::string path);
void run();
bool running = false;
private:
static void scriptWorker(Script* _this);
duk_context* ctx;
std::thread worker;
std::string code;
std::string name;
ScriptManager* manager;
};
struct ScriptRunHandler_t {
void (*handler)(void* ctx, duk_context* dukCtx, duk_idx_t objId);
void* ctx;
};
void bindScriptRunHandler(std::string name, ScriptManager::ScriptRunHandler_t handler);
ScriptManager::Script* createScript(std::string name, std::string path);
std::map<std::string, ScriptManager::Script*> scripts;
private:
static duk_context* createContext(ScriptManager* _this, std::string name);
// API
static duk_ret_t duk_setSource(duk_context* ctx);
std::map<std::string, ScriptManager::ScriptRunHandler_t> handlers;
};

View File

@ -0,0 +1,111 @@
#include <signal_path/dsp.h>
SignalPath::SignalPath() {
}
void SignalPath::init(uint64_t sampleRate, int fftRate, int fftSize, dsp::stream<dsp::complex_t>* input, dsp::complex_t* fftBuffer, void fftHandler(dsp::complex_t*,int,void*)) {
this->sampleRate = sampleRate;
this->fftRate = fftRate;
this->fftSize = fftSize;
inputBlockSize = sampleRate / 200.0f;
split.init(input);
reshape.init(&fftStream, fftSize, (sampleRate / fftRate) - fftSize);
split.bindStream(&fftStream);
fftHandlerSink.init(&reshape.out, fftHandler, NULL);
}
void SignalPath::setSampleRate(double sampleRate) {
this->sampleRate = sampleRate;
split.stop();
for (auto const& [name, vfo] : vfos) {
vfo.vfo->stop();
}
// Claculate skip to maintain a constant fft rate
int skip = (sampleRate / fftRate) - fftSize;
reshape.setSkip(skip);
// TODO: Tell modules that the block size has changed (maybe?)
for (auto const& [name, vfo] : vfos) {
vfo.vfo->setInSampleRate(sampleRate);
vfo.vfo->start();
}
split.start();
}
double SignalPath::getSampleRate() {
return sampleRate;
}
void SignalPath::start() {
split.start();
reshape.start();
fftHandlerSink.start();
}
void SignalPath::stop() {
split.stop();
reshape.stop();
fftHandlerSink.stop();
}
dsp::VFO* SignalPath::addVFO(std::string name, double outSampleRate, double bandwidth, double offset) {
if (vfos.find(name) != vfos.end()) {
return NULL;
}
VFO_t vfo;
vfo.inputStream = new dsp::stream<dsp::complex_t>;
split.bindStream(vfo.inputStream);
vfo.vfo = new dsp::VFO();
vfo.vfo->init(vfo.inputStream, offset, sampleRate, outSampleRate, bandwidth);
vfo.vfo->start();
vfos[name] = vfo;
return vfo.vfo;
}
void SignalPath::removeVFO(std::string name) {
if (vfos.find(name) == vfos.end()) {
return;
}
VFO_t vfo = vfos[name];
vfo.vfo->stop();
split.unbindStream(vfo.inputStream);
delete vfo.vfo;
delete vfo.inputStream;
vfos.erase(name);
}
void SignalPath::setInput(dsp::stream<dsp::complex_t>* input) {
split.setInput(input);
}
void SignalPath::bindIQStream(dsp::stream<dsp::complex_t>* stream) {
split.bindStream(stream);
}
void SignalPath::unbindIQStream(dsp::stream<dsp::complex_t>* stream) {
split.unbindStream(stream);
}
void SignalPath::setFFTSize(int size) {
fftSize = size;
int skip = (sampleRate / fftRate) - fftSize;
reshape.setSkip(skip);
}
void SignalPath::startFFT() {
reshape.start();
fftHandlerSink.start();
}
void SignalPath::stopFFT() {
reshape.stop();
fftHandlerSink.stop();
}

View File

@ -0,0 +1,44 @@
#pragma once
#include <dsp/routing.h>
#include <dsp/vfo.h>
#include <map>
#include <dsp/sink.h>
class SignalPath {
public:
SignalPath();
void init(uint64_t sampleRate, int fftRate, int fftSize, dsp::stream<dsp::complex_t>* input, dsp::complex_t* fftBuffer, void fftHandler(dsp::complex_t*,int,void*));
void start();
void stop();
void setSampleRate(double sampleRate);
double getSampleRate();
dsp::VFO* addVFO(std::string name, double outSampleRate, double bandwidth, double offset);
void removeVFO(std::string name);
void setInput(dsp::stream<dsp::complex_t>* input);
void bindIQStream(dsp::stream<dsp::complex_t>* stream);
void unbindIQStream(dsp::stream<dsp::complex_t>* stream);
void setFFTSize(int size);
void startFFT();
void stopFFT();
private:
struct VFO_t {
dsp::stream<dsp::complex_t>* inputStream;
dsp::VFO* vfo;
};
dsp::Splitter<dsp::complex_t> split;
// FFT
dsp::stream<dsp::complex_t> fftStream;
dsp::Reshaper<dsp::complex_t> reshape;
dsp::HandlerSink<dsp::complex_t> fftHandlerSink;
// VFO
std::map<std::string, VFO_t> vfos;
double sampleRate;
double fftRate;
int fftSize;
int inputBlockSize;
};

View File

@ -0,0 +1,8 @@
#include <signal_path/signal_path.h>
namespace sigpath {
SignalPath signalPath;
VFOManager vfoManager;
SourceManager sourceManager;
SinkManager sinkManager;
};

View File

@ -0,0 +1,13 @@
#pragma once
#include <signal_path/dsp.h>
#include <signal_path/vfo_manager.h>
#include <signal_path/source.h>
#include <signal_path/sink.h>
#include <new_module.h>
namespace sigpath {
SDRPP_EXPORT SignalPath signalPath;
SDRPP_EXPORT VFOManager vfoManager;
SDRPP_EXPORT SourceManager sourceManager;
SDRPP_EXPORT SinkManager sinkManager;
};

View File

@ -0,0 +1,313 @@
#include <signal_path/sink.h>
#include <spdlog/spdlog.h>
#include <imgui/imgui.h>
#include <gui/style.h>
#include <gui/icons.h>
#include <core.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SinkManager::SinkManager() {
SinkManager::SinkProvider prov;
prov.create = SinkManager::NullSink::create;
registerSinkProvider("None", prov);
}
SinkManager::Stream::Stream(dsp::stream<dsp::stereo_t>* in, const Event<float>::EventHandler& srChangeHandler, float sampleRate) {
init(in, srChangeHandler, sampleRate);
}
void SinkManager::Stream::init(dsp::stream<dsp::stereo_t>* in, const Event<float>::EventHandler& srChangeHandler, float sampleRate) {
_in = in;
srChange.bindHandler(srChangeHandler);
_sampleRate = sampleRate;
splitter.init(_in);
splitter.bindStream(&volumeInput);
volumeAjust.init(&volumeInput, 1.0f);
sinkOut = &volumeAjust.out;
}
void SinkManager::Stream::start() {
if (running) {
return;
}
splitter.start();
volumeAjust.start();
sink->start();
running = true;
}
void SinkManager::Stream::stop() {
if (!running) {
return;
}
splitter.stop();
volumeAjust.stop();
sink->stop();
running = false;
}
void SinkManager::Stream::setVolume(float volume) {
guiVolume = volume;
volumeAjust.setVolume(volume);
}
float SinkManager::Stream::getVolume() {
return guiVolume;
}
float SinkManager::Stream::getSampleRate() {
return _sampleRate;
}
void SinkManager::Stream::setInput(dsp::stream<dsp::stereo_t>* in) {
std::lock_guard<std::mutex> lck(ctrlMtx);
_in = in;
splitter.setInput(_in);
}
dsp::stream<dsp::stereo_t>* SinkManager::Stream::bindStream() {
dsp::stream<dsp::stereo_t>* stream = new dsp::stream<dsp::stereo_t>;
splitter.bindStream(stream);
return stream;
}
void SinkManager::Stream::unbindStream(dsp::stream<dsp::stereo_t>* stream) {
splitter.unbindStream(stream);
delete stream;
}
void SinkManager::Stream::setSampleRate(float sampleRate) {
std::lock_guard<std::mutex> lck(ctrlMtx);
_sampleRate = sampleRate;
srChange.emit(sampleRate);
}
void SinkManager::registerSinkProvider(std::string name, SinkProvider provider) {
if (providers.find(name) != providers.end()) {
spdlog::error("Cannot create sink provider '{0}', this name is already taken", name);
return;
}
providers[name] = provider;
providerNames.push_back(name);
}
void SinkManager::registerStream(std::string name, SinkManager::Stream* stream) {
if (streams.find(name) != streams.end()) {
spdlog::error("Cannot register stream '{0}', this name is already taken", name);
return;
}
SinkManager::SinkProvider provider;
provider = providers["None"];
stream->sink = provider.create(stream, name, provider.ctx);
streams[name] = stream;
streamNames.push_back(name);
}
void SinkManager::unregisterStream(std::string name) {
if (streams.find(name) == streams.end()) {
spdlog::error("Cannot unregister stream '{0}', this stream doesn't exist", name);
return;
}
spdlog::error("unregisterStream NOT IMPLEMENTED!!!!!!!");
SinkManager::Stream* stream = streams[name];
delete stream->sink;
delete stream;
}
void SinkManager::startStream(std::string name) {
if (streams.find(name) == streams.end()) {
spdlog::error("Cannot start stream '{0}', this stream doesn't exist", name);
return;
}
streams[name]->start();
}
void SinkManager::stopStream(std::string name) {
if (streams.find(name) == streams.end()) {
spdlog::error("Cannot stop stream '{0}', this stream doesn't exist", name);
return;
}
streams[name]->stop();
}
float SinkManager::getStreamSampleRate(std::string name) {
if (streams.find(name) == streams.end()) {
spdlog::error("Cannot get sample rate of stream '{0}', this stream doesn't exist", name);
return -1.0f;
}
return streams[name]->getSampleRate();
}
dsp::stream<dsp::stereo_t>* SinkManager::bindStream(std::string name) {
if (streams.find(name) == streams.end()) {
spdlog::error("Cannot bind to stream '{0}'. Stream doesn't exist", name);
return NULL;
}
return streams[name]->bindStream();
}
void SinkManager::unbindStream(std::string name, dsp::stream<dsp::stereo_t>* stream) {
if (streams.find(name) == streams.end()) {
spdlog::error("Cannot unbind from stream '{0}'. Stream doesn't exist", name);
return;
}
streams[name]->unbindStream(stream);
}
void SinkManager::setStreamSink(std::string name, std::string providerName) {
spdlog::warn("setStreamSink is NOT implemented!!!");
}
void SinkManager::showVolumeSlider(std::string name, std::string prefix, float width, float btnHeight, int btwBorder, bool sameLine) {
// TODO: Replace map with some hashmap for it to be faster
float height = ImGui::GetTextLineHeightWithSpacing() + 2;
float sliderHeight = height;
if (btnHeight > 0) {
height = btnHeight;
}
float ypos = ImGui::GetCursorPosY();
if (streams.find(name) == streams.end() || name == "") {
float dummy = 0.0f;
style::beginDisabled();
ImGui::PushID(ImGui::GetID(("sdrpp_unmute_btn_" + name).c_str()));
ImGui::ImageButton(icons::MUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), btwBorder);
ImGui::PopID();
ImGui::SameLine();
ImGui::SetNextItemWidth(width - height - 8);
ImGui::SetCursorPosY(ypos + ((height - sliderHeight) / 2.0f) + btwBorder);
ImGui::SliderFloat((prefix + name).c_str(), &dummy, 0.0f, 1.0f, "");
style::endDisabled();
if (sameLine) { ImGui::SetCursorPosY(ypos); }
return;
}
SinkManager::Stream* stream = streams[name];
if (stream->volumeAjust.getMuted()) {
ImGui::PushID(ImGui::GetID(("sdrpp_unmute_btn_" + name).c_str()));
if (ImGui::ImageButton(icons::MUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), btwBorder)) {
stream->volumeAjust.setMuted(false);
core::configManager.aquire();
saveStreamConfig(name);
core::configManager.release(true);
}
ImGui::PopID();
}
else {
ImGui::PushID(ImGui::GetID(("sdrpp_mute_btn_" + name).c_str()));
if (ImGui::ImageButton(icons::UNMUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), btwBorder)) {
stream->volumeAjust.setMuted(true);
core::configManager.aquire();
saveStreamConfig(name);
core::configManager.release(true);
}
ImGui::PopID();
}
ImGui::SameLine();
ImGui::SetNextItemWidth(width - height - 8);
ImGui::SetCursorPosY(ypos + ((height - sliderHeight) / 2.0f) + btwBorder);
if (ImGui::SliderFloat((prefix + name).c_str(), &stream->guiVolume, 0.0f, 1.0f, "")) {
stream->setVolume(stream->guiVolume);
core::configManager.aquire();
saveStreamConfig(name);
core::configManager.release(true);
}
if (sameLine) { ImGui::SetCursorPosY(ypos); }
//ImGui::SetCursorPosY(ypos);
}
void SinkManager::loadStreamConfig(std::string name) {
json conf = core::configManager.conf["streams"][name];
SinkManager::Stream* stream = streams[name];
std::string provName = conf["sink"];
if (providers.find(provName) == providers.end()) {
provName = providerNames[0];
}
if (stream->running) {
stream->sink->stop();
}
delete stream->sink;
SinkManager::SinkProvider prov = providers[provName];
stream->providerId = std::distance(providerNames.begin(), std::find(providerNames.begin(), providerNames.end(), provName));
stream->sink = prov.create(stream, name, prov.ctx);
if (stream->running) {
stream->sink->start();
}
stream->setVolume(conf["volume"]);
stream->volumeAjust.setMuted(conf["muted"]);
}
void SinkManager::saveStreamConfig(std::string name) {
SinkManager::Stream* stream = streams[name];
json conf;
conf["sink"] = providerNames[stream->providerId];
conf["volume"] = stream->getVolume();
conf["muted"] = stream->volumeAjust.getMuted();
core::configManager.conf["streams"][name] = conf;
}
// Note: aquire and release config before running this
void SinkManager::loadSinksFromConfig() {
for (auto const& [name, stream] : streams) {
if (!core::configManager.conf["streams"].contains(name)) { continue; }
loadStreamConfig(name);
}
}
void SinkManager::showMenu() {
float menuWidth = ImGui::GetContentRegionAvailWidth();
int count = 0;
int maxCount = streams.size();
std::string provStr = "";
for (auto const& name : providerNames) {
provStr += name;
provStr += '\0';
}
for (auto const& [name, stream] : streams) {
ImGui::SetCursorPosX((menuWidth / 2.0f) - (ImGui::CalcTextSize(name.c_str()).x / 2.0f));
ImGui::Text("%s", name.c_str());
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(CONCAT("##_sdrpp_sink_select_", name), &stream->providerId, provStr.c_str())) {
if (stream->running) {
stream->sink->stop();
}
delete stream->sink;
SinkManager::SinkProvider prov = providers[providerNames[stream->providerId]];
stream->sink = prov.create(stream, name, prov.ctx);
if (stream->running) {
stream->sink->start();
}
core::configManager.aquire();
saveStreamConfig(name);
core::configManager.release(true);
}
stream->sink->menuHandler();
showVolumeSlider(name, "##_sdrpp_sink_menu_vol_", menuWidth);
count++;
if (count < maxCount) {
ImGui::Spacing();
ImGui::Separator();
}
ImGui::Spacing();
}
}
std::vector<std::string> SinkManager::getStreamNames() {
return streamNames;
}

121
core/src/signal_path/sink.h Normal file
View File

@ -0,0 +1,121 @@
#pragma once
#include <map>
#include <string>
#include <dsp/stream.h>
#include <dsp/types.h>
#include <dsp/routing.h>
#include <dsp/processing.h>
#include <dsp/sink.h>
#include <mutex>
#include <event.h>
#include <vector>
class SinkManager {
public:
SinkManager();
class Sink {
public:
virtual void start() = 0;
virtual void stop() = 0;
virtual void menuHandler() = 0;
};
class Stream {
public:
Stream() {}
Stream(dsp::stream<dsp::stereo_t>* in, const Event<float>::EventHandler& srChangeHandler, float sampleRate);
void init(dsp::stream<dsp::stereo_t>* in, const Event<float>::EventHandler& srChangeHandler, float sampleRate);
void start();
void stop();
void setVolume(float volume);
float getVolume();
void setSampleRate(float sampleRate);
float getSampleRate();
void setInput(dsp::stream<dsp::stereo_t>* in);
dsp::stream<dsp::stereo_t>* bindStream();
void unbindStream(dsp::stream<dsp::stereo_t>* stream);
friend SinkManager;
friend SinkManager::Sink;
dsp::stream<dsp::stereo_t>* sinkOut;
Event<float> srChange;
private:
dsp::stream<dsp::stereo_t>* _in;
dsp::Splitter<dsp::stereo_t> splitter;
SinkManager::Sink* sink;
dsp::stream<dsp::stereo_t> volumeInput;
dsp::Volume<dsp::stereo_t> volumeAjust;
std::mutex ctrlMtx;
float _sampleRate;
int providerId = 0;
bool running = false;
float guiVolume = 1.0f;
};
struct SinkProvider {
SinkManager::Sink* (*create)(SinkManager::Stream* stream, std::string streamName, void* ctx);
void* ctx;
};
class NullSink : SinkManager::Sink {
public:
NullSink(SinkManager::Stream* stream) {
ns.init(stream->sinkOut);
}
void start() { ns.start(); }
void stop() { ns.stop(); }
void menuHandler() {}
static SinkManager::Sink* create(SinkManager::Stream* stream, std::string streamName, void* ctx) {
stream->srChange.emit(48000);
return new SinkManager::NullSink(stream);
}
private:
dsp::NullSink<dsp::stereo_t> ns;
};
void registerSinkProvider(std::string name, SinkProvider provider);
void registerStream(std::string name, Stream* stream);
void unregisterStream(std::string name);
void startStream(std::string name);
void stopStream(std::string name);
float getStreamSampleRate(std::string name);
void setStreamSink(std::string name, std::string providerName);
void showVolumeSlider(std::string name, std::string prefix, float width, float btnHeight = -1.0f, int btwBorder = 0, bool sameLine = false);
dsp::stream<dsp::stereo_t>* bindStream(std::string name);
void unbindStream(std::string name, dsp::stream<dsp::stereo_t>* stream);
void loadSinksFromConfig();
void showMenu();
std::vector<std::string> getStreamNames();
private:
void loadStreamConfig(std::string name);
void saveStreamConfig(std::string name);
std::map<std::string, SinkProvider> providers;
std::map<std::string, Stream*> streams;
std::vector<std::string> providerNames;
std::vector<std::string> streamNames;
};

View File

@ -0,0 +1,64 @@
#include <signal_path/source.h>
#include <spdlog/spdlog.h>
#include <signal_path/signal_path.h>
SourceManager::SourceManager() {
}
void SourceManager::registerSource(std::string name, SourceHandler* handler) {
if (sources.find(name) != sources.end()) {
spdlog::error("Tried to register new source with existing name: {0}", name);
return;
}
sources[name] = handler;
sourceNames.push_back(name);
}
void SourceManager::selectSource(std::string name) {
if (sources.find(name) == sources.end()) {
spdlog::error("Tried to select non existant source: {0}", name);
return;
}
if (selectedName != "") {
sources[selectedName]->deselectHandler(sources[selectedName]->ctx);
}
selectedHandler = sources[name];
selectedHandler->selectHandler(selectedHandler->ctx);
selectedName = name;
sigpath::signalPath.setInput(selectedHandler->stream);
}
void SourceManager::showSelectedMenu() {
if (selectedHandler == NULL) {
return;
}
selectedHandler->menuHandler(selectedHandler->ctx);
}
void SourceManager::start() {
if (selectedHandler == NULL) {
return;
}
selectedHandler->startHandler(selectedHandler->ctx);
}
void SourceManager::stop() {
if (selectedHandler == NULL) {
return;
}
selectedHandler->stopHandler(selectedHandler->ctx);
}
void SourceManager::tune(double freq) {
if (selectedHandler == NULL) {
return;
}
selectedHandler->tuneHandler(freq + tuneOffset, selectedHandler->ctx);
currentFreq = freq;
}
void SourceManager::setTuningOffset(double offset) {
tuneOffset = offset;
tune(currentFreq);
}

View File

@ -0,0 +1,40 @@
#pragma once
#include <string>
#include <vector>
#include <map>
#include <dsp/stream.h>
#include <dsp/types.h>
class SourceManager {
public:
SourceManager();
struct SourceHandler {
dsp::stream<dsp::complex_t>* stream;
void (*menuHandler)(void* ctx);
void (*selectHandler)(void* ctx);
void (*deselectHandler)(void* ctx);
void (*startHandler)(void* ctx);
void (*stopHandler)(void* ctx);
void (*tuneHandler)(double freq, void* ctx);
void* ctx;
};
void registerSource(std::string name, SourceHandler* handler);
void selectSource(std::string name);
void showSelectedMenu();
void start();
void stop();
void tune(double freq);
void setTuningOffset(double offset);
std::vector<std::string> sourceNames;
private:
std::map<std::string, SourceHandler*> sources;
std::string selectedName;
SourceHandler* selectedHandler = NULL;
double tuneOffset;
double currentFreq;
};

View File

@ -0,0 +1,136 @@
#include <signal_path/vfo_manager.h>
#include <signal_path/signal_path.h>
VFOManager::VFO::VFO(std::string name, int reference, double offset, double bandwidth, double sampleRate, int blockSize) {
this->name = name;
dspVFO = sigpath::signalPath.addVFO(name, sampleRate, bandwidth, offset);
wtfVFO = new ImGui::WaterfallVFO;
wtfVFO->setReference(reference);
wtfVFO->setBandwidth(bandwidth);
wtfVFO->setOffset(offset);
output = dspVFO->out;
gui::waterfall.vfos[name] = wtfVFO;
}
VFOManager::VFO::~VFO() {
dspVFO->stop();
gui::waterfall.vfos.erase(name);
if (gui::waterfall.selectedVFO == name) {
gui::waterfall.selectFirstVFO();
}
sigpath::signalPath.removeVFO(name);
delete wtfVFO;
}
void VFOManager::VFO::setOffset(double offset) {
wtfVFO->setOffset(offset);
dspVFO->setOffset(wtfVFO->centerOffset);
}
void VFOManager::VFO::setCenterOffset(double offset) {
wtfVFO->setCenterOffset(offset);
dspVFO->setOffset(offset);
}
void VFOManager::VFO::setBandwidth(double bandwidth) {
wtfVFO->setBandwidth(bandwidth);
dspVFO->setBandwidth(bandwidth);
}
void VFOManager::VFO::setSampleRate(double sampleRate, double bandwidth) {
dspVFO->setOutSampleRate(sampleRate, bandwidth);
wtfVFO->setBandwidth(bandwidth);
}
void VFOManager::VFO::setReference(int ref) {
wtfVFO->setReference(ref);
}
int VFOManager::VFO::getOutputBlockSize() {
// NOTE: This shouldn't be needed anymore
return 1; //dspVFO->getOutputBlockSize();
}
void VFOManager::VFO::setSnapInterval(double interval) {
wtfVFO->setSnapInterval(interval);
}
VFOManager::VFOManager() {
}
VFOManager::VFO* VFOManager::createVFO(std::string name, int reference, double offset, double bandwidth, double sampleRate, int blockSize) {
if (vfos.find(name) != vfos.end() || name == "") {
return NULL;
}
VFOManager::VFO* vfo = new VFO(name, reference, offset, bandwidth, sampleRate, blockSize);
vfos[name] = vfo;
return vfo;
}
void VFOManager::deleteVFO(VFOManager::VFO* vfo) {
std::string name = "";
for (auto const& [_name, _vfo] : vfos) {
if (_vfo == vfo) {
name = _name;
break;
}
}
if (name == "") {
return;
}
vfos.erase(name);
delete vfo;
}
void VFOManager::setOffset(std::string name, double offset) {
if (vfos.find(name) == vfos.end()) {
return;
}
vfos[name]->setOffset(offset);
}
void VFOManager::setCenterOffset(std::string name, double offset) {
if (vfos.find(name) == vfos.end()) {
return;
}
vfos[name]->setCenterOffset(offset);
}
void VFOManager::setBandwidth(std::string name, double bandwidth) {
if (vfos.find(name) == vfos.end()) {
return;
}
vfos[name]->setBandwidth(bandwidth);
}
void VFOManager::setSampleRate(std::string name, double sampleRate, double bandwidth) {
if (vfos.find(name) == vfos.end()) {
return;
}
vfos[name]->setSampleRate(sampleRate, bandwidth);
}
void VFOManager::setReference(std::string name, int ref) {
if (vfos.find(name) == vfos.end()) {
return;
}
vfos[name]->setReference(ref);
}
int VFOManager::getOutputBlockSize(std::string name) {
if (vfos.find(name) == vfos.end()) {
return -1;
}
return vfos[name]->getOutputBlockSize();
}
void VFOManager::updateFromWaterfall(ImGui::WaterFall* wtf) {
for (auto const& [name, vfo] : vfos) {
if (vfo->wtfVFO->centerOffsetChanged) {
vfo->wtfVFO->centerOffsetChanged = false;
vfo->dspVFO->setOffset(vfo->wtfVFO->centerOffset);
}
}
}

View File

@ -0,0 +1,48 @@
#pragma once
#include <dsp/vfo.h>
#include <gui/widgets/waterfall.h>
#include <gui/gui.h>
class VFOManager {
public:
VFOManager();
class VFO {
public:
VFO(std::string name, int reference, double offset, double bandwidth, double sampleRate, int blockSize);
~VFO();
void setOffset(double offset);
void setCenterOffset(double offset);
void setBandwidth(double bandwidth);
void setSampleRate(double sampleRate, double bandwidth);
void setReference(int ref);
int getOutputBlockSize();
void setSnapInterval(double interval);
dsp::stream<dsp::complex_t>* output;
friend class VFOManager;
private:
std::string name;
dsp::VFO* dspVFO;
ImGui::WaterfallVFO* wtfVFO;
};
VFOManager::VFO* createVFO(std::string name, int reference, double offset, double bandwidth, double sampleRate, int blockSize);
void deleteVFO(VFOManager::VFO* vfo);
void setOffset(std::string name, double offset);
void setCenterOffset(std::string name, double offset);
void setBandwidth(std::string name, double bandwidth);
void setSampleRate(std::string name, double sampleRate, double bandwidth);
void setReference(std::string name, int ref);
int getOutputBlockSize(std::string name);
void updateFromWaterfall(ImGui::WaterFall* wtf);
private:
std::map<std::string, VFO*> vfos;
};

Some files were not shown because too many files have changed in this diff Show More