mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-12-26 10:58:29 +01:00
commit
b02b6c30b5
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,3 +1,10 @@
|
|||||||
build/
|
build/
|
||||||
.vscode/
|
.vscode/
|
||||||
*.old
|
*.old
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
*.zip
|
||||||
|
*.wav
|
||||||
|
.DS_Store
|
||||||
|
sdrpp_v0.2.5_beta_x64
|
||||||
|
sdrpp_v0.2.5_beta_x64_new_wf
|
@ -1,59 +1,46 @@
|
|||||||
cmake_minimum_required(VERSION 3.9)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
project(sdrpp)
|
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)
|
if (MSVC)
|
||||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17")
|
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||||
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/")
|
|
||||||
else()
|
else()
|
||||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fsanitize=address -g")
|
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
|
||||||
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)
|
|
||||||
endif (MSVC)
|
endif (MSVC)
|
||||||
|
|
||||||
link_libraries(volk)
|
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
|
||||||
link_libraries(SoapySDR)
|
target_link_libraries(sdrpp PRIVATE sdrpp_core)
|
||||||
|
|
||||||
# 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})
|
|
||||||
|
|
||||||
|
# Copy dynamic libs over
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
# Glew
|
add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
|
||||||
find_package(GLEW REQUIRED)
|
add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
|
||||||
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)
|
|
||||||
endif (MSVC)
|
endif (MSVC)
|
||||||
|
|
||||||
# # Copy resource directories
|
|
||||||
# if (!MSVC)
|
if (${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
|
||||||
# add_custom_command(TARGET sdrpp POST_BUILD COMMAND cmake -E copy_directory ${CMAKE_SOURCE_DIR}/res ${CMAKE_BINARY_DIR}/res)
|
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||||
# add_custom_command(TARGET sdrpp POST_BUILD COMMAND cmake -E copy_directory ${CMAKE_SOURCE_DIR}/bandplans ${CMAKE_BINARY_DIR}/bandplans)
|
endif ()
|
||||||
# endif (MSVC)
|
|
||||||
|
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"
|
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64"
|
20
airspy_hf_source/CMakeLists.txt
Normal file
20
airspy_hf_source/CMakeLists.txt
Normal 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()
|
192
airspy_hf_source/src/main.cpp
Normal file
192
airspy_hf_source/src/main.cpp
Normal 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
16
audio_sink/CMakeLists.txt
Normal 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
287
audio_sink/src/main.cpp
Normal 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
103
core/CMakeLists.txt
Normal 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
118
core/src/config.cpp
Normal 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
55
core/src/config.h
Normal 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
295
core/src/core.cpp
Normal 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
15
core/src/core.h
Normal 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
95
core/src/dsp/audio.h
Normal 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
126
core/src/dsp/block.h
Normal 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;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
@ -1,34 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <condition_variable>
|
#include <dsp/block.h>
|
||||||
#include <algorithm>
|
|
||||||
#include <math.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#define STREAM_BUF_SZ 1000000
|
#define RING_BUF_SZ 1000000
|
||||||
|
|
||||||
namespace dsp {
|
namespace dsp {
|
||||||
template <class T>
|
template <class T>
|
||||||
class stream {
|
class RingBuffer {
|
||||||
public:
|
public:
|
||||||
stream() {
|
RingBuffer() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stream(int maxLatency) {
|
RingBuffer(int maxLatency) { init(maxLatency); }
|
||||||
size = STREAM_BUF_SZ;
|
|
||||||
_buffer = new T[size];
|
~RingBuffer() { delete _buffer; }
|
||||||
_stopReader = false;
|
|
||||||
_stopWriter = false;
|
|
||||||
this->maxLatency = maxLatency;
|
|
||||||
writec = 0;
|
|
||||||
readc = 0;
|
|
||||||
readable = 0;
|
|
||||||
writable = size;
|
|
||||||
memset(_buffer, 0, size * sizeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
void init(int maxLatency) {
|
void init(int maxLatency) {
|
||||||
size = STREAM_BUF_SZ;
|
size = RING_BUF_SZ;
|
||||||
_buffer = new T[size];
|
_buffer = new T[size];
|
||||||
_stopReader = false;
|
_stopReader = false;
|
||||||
_stopWriter = false;
|
_stopWriter = false;
|
138
core/src/dsp/convertion.h
Normal file
138
core/src/dsp/convertion.h
Normal 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
283
core/src/dsp/demodulator.h
Normal 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
173
core/src/dsp/filter.h
Normal 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
107
core/src/dsp/math.h
Normal 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
291
core/src/dsp/processing.h
Normal 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
154
core/src/dsp/resampling.h
Normal 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
186
core/src/dsp/routing.h
Normal 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
140
core/src/dsp/sink.h
Normal 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
75
core/src/dsp/source.h
Normal 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
108
core/src/dsp/stream.h
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
@ -5,4 +5,9 @@ namespace dsp {
|
|||||||
float q;
|
float q;
|
||||||
float i;
|
float i;
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
struct stereo_t {
|
||||||
|
float l;
|
||||||
|
float r;
|
||||||
|
};
|
||||||
|
}
|
112
core/src/dsp/vfo.h
Normal file
112
core/src/dsp/vfo.h
Normal 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
76
core/src/dsp/window.h
Normal 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;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
3783
core/src/duktape/duk_config.h
Normal file
3783
core/src/duktape/duk_config.h
Normal file
File diff suppressed because it is too large
Load Diff
185
core/src/duktape/duk_console.c
Normal file
185
core/src/duktape/duk_console.c
Normal 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;"
|
||||||
|
"}"
|
||||||
|
"});"
|
||||||
|
"})();"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
29
core/src/duktape/duk_console.h
Normal file
29
core/src/duktape/duk_console.h
Normal 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
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
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
43
core/src/event.h
Normal 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;
|
||||||
|
|
||||||
|
};
|
59
core/src/gui/dialogs/credits.cpp
Normal file
59
core/src/gui/dialogs/credits.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
6
core/src/gui/dialogs/credits.h
Normal file
6
core/src/gui/dialogs/credits.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace credits {
|
||||||
|
void init();
|
||||||
|
void show();
|
||||||
|
}
|
91
core/src/gui/dialogs/loading_screen.cpp
Normal file
91
core/src/gui/dialogs/loading_screen.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
10
core/src/gui/dialogs/loading_screen.h
Normal file
10
core/src/gui/dialogs/loading_screen.h
Normal 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
7
core/src/gui/gui.cpp
Normal 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
14
core/src/gui/gui.h
Normal 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
43
core/src/gui/icons.cpp
Normal 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
18
core/src/gui/icons.h
Normal 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();
|
||||||
|
}
|
612
core/src/gui/main_window.cpp
Normal file
612
core/src/gui/main_window.cpp
Normal 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;
|
||||||
|
}
|
8
core/src/gui/main_window.h
Normal file
8
core/src/gui/main_window.h
Normal 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);
|
51
core/src/gui/menus/bandplan.cpp
Normal file
51
core/src/gui/menus/bandplan.cpp
Normal 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());
|
||||||
|
}
|
||||||
|
};
|
6
core/src/gui/menus/bandplan.h
Normal file
6
core/src/gui/menus/bandplan.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace bandplanmenu {
|
||||||
|
void init();
|
||||||
|
void draw(void* ctx);
|
||||||
|
};
|
22
core/src/gui/menus/display.cpp
Normal file
22
core/src/gui/menus/display.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
core/src/gui/menus/display.h
Normal file
6
core/src/gui/menus/display.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace displaymenu {
|
||||||
|
void init();
|
||||||
|
void draw(void* ctx);
|
||||||
|
}
|
22
core/src/gui/menus/scripting.cpp
Normal file
22
core/src/gui/menus/scripting.cpp
Normal 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(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
core/src/gui/menus/scripting.h
Normal file
6
core/src/gui/menus/scripting.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace scriptingmenu {
|
||||||
|
void init();
|
||||||
|
void draw(void* ctx);
|
||||||
|
}
|
15
core/src/gui/menus/sink.cpp
Normal file
15
core/src/gui/menus/sink.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
};
|
6
core/src/gui/menus/sink.h
Normal file
6
core/src/gui/menus/sink.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace sinkmenu {
|
||||||
|
void init();
|
||||||
|
void draw(void* ctx);
|
||||||
|
};
|
54
core/src/gui/menus/source.cpp
Normal file
54
core/src/gui/menus/source.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
core/src/gui/menus/source.h
Normal file
6
core/src/gui/menus/source.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace sourecmenu {
|
||||||
|
void init();
|
||||||
|
void draw(void* ctx);
|
||||||
|
}
|
104
core/src/gui/style.cpp
Normal file
104
core/src/gui/style.cpp
Normal 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
14
core/src/gui/style.h
Normal 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();
|
||||||
|
}
|
@ -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 {
|
namespace bandplan {
|
||||||
std::map<std::string, BandPlan_t> bandplans;
|
std::map<std::string, BandPlan_t> bandplans;
|
||||||
@ -71,7 +76,7 @@ namespace bandplan {
|
|||||||
void loadBandPlan(std::string path) {
|
void loadBandPlan(std::string path) {
|
||||||
std::ifstream file(path.c_str());
|
std::ifstream file(path.c_str());
|
||||||
json data;
|
json data;
|
||||||
data << file;
|
file >> data;
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
BandPlan_t plan = data.get<BandPlan_t>();
|
BandPlan_t plan = data.get<BandPlan_t>();
|
||||||
@ -114,7 +119,7 @@ namespace bandplan {
|
|||||||
}
|
}
|
||||||
std::ifstream file(path.c_str());
|
std::ifstream file(path.c_str());
|
||||||
json data;
|
json data;
|
||||||
data << file;
|
file >> data;
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
colorTable = data.get<std::map<std::string, BandPlanColor_t>>();
|
colorTable = data.get<std::map<std::string, BandPlanColor_t>>();
|
@ -1,11 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <json.hpp>
|
#include <json.hpp>
|
||||||
#include <fstream>
|
|
||||||
#include <spdlog/spdlog.h>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <sstream>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <imgui/imgui.h>
|
#include <imgui/imgui.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
@ -13,8 +9,8 @@ namespace bandplan {
|
|||||||
struct Band_t {
|
struct Band_t {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string type;
|
std::string type;
|
||||||
float start;
|
double start;
|
||||||
float end;
|
double end;
|
||||||
};
|
};
|
||||||
|
|
||||||
void to_json(json& j, const Band_t& b);
|
void to_json(json& j, const Band_t& b);
|
@ -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) {
|
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;
|
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() {
|
void FrequencySelect::init() {
|
||||||
font = ImGui::GetIO().Fonts->AddFontFromFileTTF("res/fonts/Roboto-Medium.ttf", 42.0f);
|
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
digits[i] = 0;
|
digits[i] = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,13 +69,14 @@ void FrequencySelect::draw() {
|
|||||||
window = ImGui::GetCurrentWindow();
|
window = ImGui::GetCurrentWindow();
|
||||||
widgetPos = ImGui::GetWindowContentRegionMin();
|
widgetPos = ImGui::GetWindowContentRegionMin();
|
||||||
widgetEndPos = ImGui::GetWindowContentRegionMax();
|
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;
|
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;
|
widgetEndPos.y += window->Pos.y - 3;
|
||||||
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
|
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) {
|
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
|
||||||
lastWidgetPos = widgetPos;
|
lastWidgetPos = widgetPos;
|
||||||
@ -84,6 +87,9 @@ void FrequencySelect::draw() {
|
|||||||
onResize();
|
onResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImU32 disabledColor = ImGui::GetColorU32(ImGuiCol_Text, 0.3f);
|
||||||
|
ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text);
|
||||||
|
|
||||||
int commaOffset = 0;
|
int commaOffset = 0;
|
||||||
bool zeros = true;
|
bool zeros = true;
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
@ -92,11 +98,11 @@ void FrequencySelect::draw() {
|
|||||||
}
|
}
|
||||||
sprintf(buf, "%d", digits[i]);
|
sprintf(buf, "%d", digits[i]);
|
||||||
window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y),
|
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) {
|
if ((i + 1) % 3 == 0 && i < 11) {
|
||||||
commaOffset += 12;
|
commaOffset += 12;
|
||||||
window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset + 10, widgetPos.y),
|
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++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
freq += digits[i] * pow(10, 11 - i);
|
freq += digits[i] * pow(10, 11 - i);
|
||||||
}
|
}
|
||||||
frequency = freq;
|
frequency = freq;
|
||||||
|
|
||||||
ImGui::PopFont();
|
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;
|
int i = 11;
|
||||||
for (long f = freq; i >= 0; i--) {
|
for (uint64_t f = freq; i >= 0; i--) {
|
||||||
digits[i] = f % 10;
|
digits[i] = f % 10;
|
||||||
f -= digits[i];
|
f -= digits[i];
|
||||||
f /= 10;
|
f /= 10;
|
@ -1,15 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
class FrequencySelect {
|
class FrequencySelect {
|
||||||
public:
|
public:
|
||||||
FrequencySelect();
|
FrequencySelect();
|
||||||
void init();
|
void init();
|
||||||
void draw();
|
void draw();
|
||||||
void setFrequency(long freq);
|
void setFrequency(uint64_t freq);
|
||||||
|
|
||||||
long frequency;
|
uint64_t frequency;
|
||||||
bool frequencyChanged = false;
|
bool frequencyChanged = false;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -26,7 +27,6 @@ private:
|
|||||||
ImVec2 lastWidgetSize;
|
ImVec2 lastWidgetSize;
|
||||||
|
|
||||||
ImGuiWindow* window;
|
ImGuiWindow* window;
|
||||||
ImFont* font;
|
|
||||||
|
|
||||||
int digits[12];
|
int digits[12];
|
||||||
ImVec2 digitBottomMins[12];
|
ImVec2 digitBottomMins[12];
|
77
core/src/gui/widgets/menu.cpp
Normal file
77
core/src/gui/widgets/menu.cpp
Normal 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;
|
||||||
|
}
|
27
core/src/gui/widgets/menu.h
Normal file
27
core/src/gui/widgets/menu.h
Normal 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;
|
||||||
|
};
|
872
core/src/gui/widgets/waterfall.cpp
Normal file
872
core/src/gui/widgets/waterfall.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
202
core/src/gui/widgets/waterfall.h
Normal file
202
core/src/gui/widgets/waterfall.h
Normal 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;
|
||||||
|
};
|
||||||
|
};
|
@ -2640,6 +2640,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
|
|||||||
|
|
||||||
// Slider behavior
|
// Slider behavior
|
||||||
ImRect grab_bb;
|
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);
|
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)
|
if (value_changed)
|
||||||
MarkItemEdited(id);
|
MarkItemEdited(id);
|
2631
core/src/imgui/stb_image_resize.h
Normal file
2631
core/src/imgui/stb_image_resize.h
Normal file
File diff suppressed because it is too large
Load Diff
145
core/src/new_module.cpp
Normal file
145
core/src/new_module.cpp
Normal 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
91
core/src/new_module.h
Normal 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
93
core/src/scripting.cpp
Normal 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
51
core/src/scripting.h
Normal 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;
|
||||||
|
|
||||||
|
};
|
111
core/src/signal_path/dsp.cpp
Normal file
111
core/src/signal_path/dsp.cpp
Normal 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();
|
||||||
|
}
|
44
core/src/signal_path/dsp.h
Normal file
44
core/src/signal_path/dsp.h
Normal 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;
|
||||||
|
};
|
8
core/src/signal_path/signal_path.cpp
Normal file
8
core/src/signal_path/signal_path.cpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#include <signal_path/signal_path.h>
|
||||||
|
|
||||||
|
namespace sigpath {
|
||||||
|
SignalPath signalPath;
|
||||||
|
VFOManager vfoManager;
|
||||||
|
SourceManager sourceManager;
|
||||||
|
SinkManager sinkManager;
|
||||||
|
};
|
13
core/src/signal_path/signal_path.h
Normal file
13
core/src/signal_path/signal_path.h
Normal 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;
|
||||||
|
};
|
313
core/src/signal_path/sink.cpp
Normal file
313
core/src/signal_path/sink.cpp
Normal 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
121
core/src/signal_path/sink.h
Normal 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;
|
||||||
|
|
||||||
|
};
|
64
core/src/signal_path/source.cpp
Normal file
64
core/src/signal_path/source.cpp
Normal 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);
|
||||||
|
}
|
40
core/src/signal_path/source.h
Normal file
40
core/src/signal_path/source.h
Normal 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;
|
||||||
|
|
||||||
|
};
|
136
core/src/signal_path/vfo_manager.cpp
Normal file
136
core/src/signal_path/vfo_manager.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
core/src/signal_path/vfo_manager.h
Normal file
48
core/src/signal_path/vfo_manager.h
Normal 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
Loading…
Reference in New Issue
Block a user