From 60342de9c06d79ff843415968658822b44335eed Mon Sep 17 00:00:00 2001 From: Ryzerth Date: Sun, 4 Oct 2020 02:56:02 +0200 Subject: [PATCH] Finished soapy module + added file source + added RTL_TCP source (windows only rn) --- CMakeLists.txt | 2 + core/src/core.cpp | 2 + core/src/dsp/source.h | 3 +- core/src/gui/main_window.cpp | 5 +- core/src/gui/main_window.h | 1 - core/src/gui/menu.cpp | 1 + core/src/gui/menus/audio.cpp | 1 - core/src/gui/menus/bandplan.cpp | 1 - core/src/gui/menus/display.cpp | 1 - core/src/signal_path/signal_path.h | 1 - file_source/CMakeLists.txt | 2 + file_source/src/main.cpp | 38 +++++- file_source/src/wavreader.h | 62 ++++++++++ root/config.json | 3 +- root_dev/config.json | 3 +- root_dev/module_list.json | 3 +- root_dev/soapy_source_config.json | 19 +++ rtl_tcp_source/CMakeLists.txt | 20 +++ rtl_tcp_source/src/main.cpp | 188 +++++++++++++++++++++++++++++ rtl_tcp_source/src/rtltcp_client.h | 120 ++++++++++++++++++ soapy/src/main.cpp | 153 ++++++++++++++++++++--- 21 files changed, 602 insertions(+), 27 deletions(-) create mode 100644 file_source/src/wavreader.h create mode 100644 root_dev/soapy_source_config.json create mode 100644 rtl_tcp_source/CMakeLists.txt create mode 100644 rtl_tcp_source/src/main.cpp create mode 100644 rtl_tcp_source/src/rtltcp_client.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c4ac7f96..0d353fe4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,13 @@ cmake_minimum_required(VERSION 3.13) project(sdrpp_core) +# Cross platform modules add_subdirectory("core") add_subdirectory("radio") add_subdirectory("recorder") add_subdirectory("soapy") add_subdirectory("file_source") +add_subdirectory("rtl_tcp_source") add_subdirectory("demo") add_executable(sdrpp "src/main.cpp" "win32/resources.rc") diff --git a/core/src/core.cpp b/core/src/core.cpp index 37815b36..06aa9143 100644 --- a/core/src/core.cpp +++ b/core/src/core.cpp @@ -186,6 +186,8 @@ int sdrpp_main() { ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); + //ImGui::ShowDemoWindow(); + if (_maximized != maximized) { _maximized = maximized; core::configManager.aquire(); diff --git a/core/src/dsp/source.h b/core/src/dsp/source.h index 2e4ec42b..5130abbc 100644 --- a/core/src/dsp/source.h +++ b/core/src/dsp/source.h @@ -48,7 +48,8 @@ namespace dsp { } void setFrequency(float frequency) { - _phasorSpeed = (2 * 3.1415926535) / (_sampleRate / frequency); + _frequency = frequency; + _phasorSpeed = (2 * 3.1415926535 * frequency) / _sampleRate; } void setBlockSize(int blockSize) { diff --git a/core/src/gui/main_window.cpp b/core/src/gui/main_window.cpp index 16aebbb4..f515c0a0 100644 --- a/core/src/gui/main_window.cpp +++ b/core/src/gui/main_window.cpp @@ -123,6 +123,8 @@ void windowInit() { fftHeight = core::configManager.conf["fftHeight"]; gui::waterfall.setFFTHeight(fftHeight); + gui::menu.order = core::configManager.conf["menuOrder"].get>(); + core::configManager.release(); } @@ -411,8 +413,7 @@ void drawWindow() { ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - (ImGui::CalcTextSize("Zoom").x / 2.0f)); ImGui::Text("Zoom"); ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - 10); - // TODO: use global sample rate value from DSP instead of 8000000 - ImGui::VSliderFloat("##_7_", ImVec2(20.0f, 150.0f), &bw.val, 8000000, 1000.0f, ""); + ImGui::VSliderFloat("##_7_", ImVec2(20.0f, 150.0f), &bw.val, gui::waterfall.getBandwidth(), 1000.0f, ""); ImGui::NewLine(); diff --git a/core/src/gui/main_window.h b/core/src/gui/main_window.h index 03eb370f..e079f577 100644 --- a/core/src/gui/main_window.h +++ b/core/src/gui/main_window.h @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include diff --git a/core/src/gui/menu.cpp b/core/src/gui/menu.cpp index 278f1f5c..c5611f72 100644 --- a/core/src/gui/menu.cpp +++ b/core/src/gui/menu.cpp @@ -27,6 +27,7 @@ void Menu::draw() { item = items[name]; if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { item.drawHandler(item.ctx); + ImGui::Spacing(); } } } diff --git a/core/src/gui/menus/audio.cpp b/core/src/gui/menus/audio.cpp index e5dd0470..f39fdcaa 100644 --- a/core/src/gui/menus/audio.cpp +++ b/core/src/gui/menus/audio.cpp @@ -134,6 +134,5 @@ namespace audiomenu { } ImGui::Spacing(); } - ImGui::Spacing(); } }; \ No newline at end of file diff --git a/core/src/gui/menus/bandplan.cpp b/core/src/gui/menus/bandplan.cpp index 78f7a16f..8588b78e 100644 --- a/core/src/gui/menus/bandplan.cpp +++ b/core/src/gui/menus/bandplan.cpp @@ -47,6 +47,5 @@ namespace bandplanmenu { 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()); - ImGui::Spacing(); } }; \ No newline at end of file diff --git a/core/src/gui/menus/display.cpp b/core/src/gui/menus/display.cpp index 76093a56..cb1c9105 100644 --- a/core/src/gui/menus/display.cpp +++ b/core/src/gui/menus/display.cpp @@ -18,6 +18,5 @@ namespace displaymenu { core::configManager.conf["showWaterfall"] = showWaterfall; core::configManager.release(true); } - ImGui::Spacing(); } } \ No newline at end of file diff --git a/core/src/signal_path/signal_path.h b/core/src/signal_path/signal_path.h index 5b3c79bd..d179306d 100644 --- a/core/src/signal_path/signal_path.h +++ b/core/src/signal_path/signal_path.h @@ -2,7 +2,6 @@ #include #include #include -#include #include namespace sigpath { diff --git a/file_source/CMakeLists.txt b/file_source/CMakeLists.txt index adc08b05..e6046129 100644 --- a/file_source/CMakeLists.txt +++ b/file_source/CMakeLists.txt @@ -7,6 +7,8 @@ else() set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive") endif (MSVC) +include_directories("src/") + file(GLOB SRC "src/*.cpp") add_library(file_source SHARED ${SRC}) diff --git a/file_source/src/main.cpp b/file_source/src/main.cpp index 66e10369..6aae4b04 100644 --- a/file_source/src/main.cpp +++ b/file_source/src/main.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #define CONCAT(a, b) ((std::string(a) + b).c_str()) @@ -29,6 +31,11 @@ public: handler.tuneHandler = tune; handler.stream = &stream; sigpath::sourceManager.registerSource("File", &handler); + + reader = new WavReader("D:/satpic/raw_recordings/NOAA-18_09-08-2018_21-39-00_baseband_NR.wav"); + + spdlog::info("Samplerate: {0}, Bit depth: {1}, Channel count: {2}", reader->getSampleRate(), reader->getBitDepth(), reader->getChannelCount()); + spdlog::info("FileSourceModule '{0}': Instance created!", name); } @@ -39,6 +46,7 @@ public: private: static void menuSelected(void* ctx) { FileSourceModule* _this = (FileSourceModule*)ctx; + core::setInputSampleRate(_this->reader->getSampleRate()); spdlog::info("FileSourceModule '{0}': Menu Select!", _this->name); } @@ -49,11 +57,15 @@ private: static void start(void* ctx) { FileSourceModule* _this = (FileSourceModule*)ctx; + _this->workerThread = std::thread(worker, _this); spdlog::info("FileSourceModule '{0}': Start!", _this->name); } static void stop(void* ctx) { FileSourceModule* _this = (FileSourceModule*)ctx; + _this->stream.stopWriter(); + _this->workerThread.join(); + _this->stream.clearWriteStop(); spdlog::info("FileSourceModule '{0}': Stop!", _this->name); } @@ -62,15 +74,39 @@ private: spdlog::info("FileSourceModule '{0}': Tune: {1}!", _this->name, freq); } - static void menuHandler(void* ctx) { FileSourceModule* _this = (FileSourceModule*)ctx; ImGui::Text("Hi from %s!", _this->name.c_str()); } + static void worker(void* ctx) { + FileSourceModule* _this = (FileSourceModule*)ctx; + double sampleRate = _this->reader->getSampleRate(); + int blockSize = sampleRate / 200.0; + int16_t* inBuf = new int16_t[blockSize * 2]; + dsp::complex_t* outBuf = new dsp::complex_t[blockSize]; + + _this->stream.setMaxLatency(blockSize * 2); + + while (true) { + _this->reader->readSamples(inBuf, blockSize * 2 * sizeof(int16_t)); + for (int i = 0; i < blockSize; i++) { + outBuf[i].q = (float)inBuf[i * 2] / (float)0x7FFF; + outBuf[i].i = (float)inBuf[(i * 2) + 1] / (float)0x7FFF; + } + if (_this->stream.write(outBuf, blockSize) < 0) { break; }; + //std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + delete[] inBuf; + delete[] outBuf; + } + std::string name; dsp::stream stream; SourceManager::SourceHandler handler; + WavReader* reader; + std::thread workerThread; }; MOD_EXPORT void _INIT_() { diff --git a/file_source/src/wavreader.h b/file_source/src/wavreader.h new file mode 100644 index 00000000..95b65fa4 --- /dev/null +++ b/file_source/src/wavreader.h @@ -0,0 +1,62 @@ +#pragma once + +#pragma once +#include +#include + +#define WAV_SIGNATURE "RIFF" +#define WAV_TYPE "WAVE" +#define WAV_FORMAT_MARK "fmt " +#define WAV_DATA_MARK "data" +#define WAV_SAMPLE_TYPE_PCM 1 + +class WavReader { +public: + WavReader(std::string path) { + file = std::ifstream(path.c_str(), std::ios::binary); + file.read((char*)&hdr, sizeof(WavHeader_t)); + } + + uint16_t getBitDepth() { + return hdr.bitDepth; + } + + uint16_t getChannelCount() { + return hdr.channelCount; + } + + uint32_t getSampleRate() { + return hdr.sampleRate; + } + + void readSamples(void* data, size_t size) { + file.read((char*)data, size); + bytesRead += size; + } + + void close() { + + file.close(); + } + +private: + struct WavHeader_t { + char signature[4]; // "RIFF" + uint32_t fileSize; // data bytes + sizeof(WavHeader_t) - 8 + char fileType[4]; // "WAVE" + char formatMarker[4]; // "fmt " + uint32_t formatHeaderLength; // Always 16 + uint16_t sampleType; // PCM (1) + uint16_t channelCount; + uint32_t sampleRate; + uint32_t bytesPerSecond; + uint16_t bytesPerSample; + uint16_t bitDepth; + char dataMarker[4]; // "data" + uint32_t dataSize; + }; + + std::ifstream file; + size_t bytesRead = 0; + WavHeader_t hdr; +}; \ No newline at end of file diff --git a/root/config.json b/root/config.json index 93f423d3..f96b5c9d 100644 --- a/root/config.json +++ b/root/config.json @@ -29,5 +29,6 @@ "windowSize": { "h": 720, "w": 1280 - } + }, + "menuOrder": ["Source", "Radio", "Recorder", "Audio", "Band Plan", "Display"] } \ No newline at end of file diff --git a/root_dev/config.json b/root_dev/config.json index 57340ef6..3e634e81 100644 --- a/root_dev/config.json +++ b/root_dev/config.json @@ -14,5 +14,6 @@ "windowSize": { "h": 720, "w": 1280 - } + }, + "menuOrder": ["Source", "Radio", "Recorder", "Audio", "Band Plan", "Display"] } \ No newline at end of file diff --git a/root_dev/module_list.json b/root_dev/module_list.json index b83e4c69..bb2ebb0c 100644 --- a/root_dev/module_list.json +++ b/root_dev/module_list.json @@ -2,5 +2,6 @@ "Radio": "./radio/Release/radio.dll", "Recorder": "./recorder/Release/recorder.dll", "Soapy": "./soapy/Release/soapy.dll", - "FileSource": "./file_source/Release/file_source.dll" + "FileSource": "./file_source/Release/file_source.dll", + "RTLTCPSource": "./rtl_tcp_source/Release/rtl_tcp_source.dll" } \ No newline at end of file diff --git a/root_dev/soapy_source_config.json b/root_dev/soapy_source_config.json new file mode 100644 index 00000000..1e606654 --- /dev/null +++ b/root_dev/soapy_source_config.json @@ -0,0 +1,19 @@ +{ + "device": "Generic RTL2832U OEM :: 00000001", + "devices": { + "Generic RTL2832U OEM :: 00000001": { + "gains": { + "TUNER": 12.817999839782715 + }, + "sampleRate": 2560000.0 + }, + "HackRF One #0 901868dc282c8f8b": { + "gains": { + "AMP": 0.0, + "LNA": 24.711999893188477, + "VGA": 15.906000137329102 + }, + "sampleRate": 8000000.0 + } + } +} \ No newline at end of file diff --git a/rtl_tcp_source/CMakeLists.txt b/rtl_tcp_source/CMakeLists.txt new file mode 100644 index 00000000..20796ec7 --- /dev/null +++ b/rtl_tcp_source/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.13) +project(rtl_tcp_source) + +if (MSVC) + set(CMAKE_CXX_FLAGS "-O2 /std:c++17") +else() + set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive") +endif (MSVC) + +include_directories("src/") + +file(GLOB SRC "src/*.cpp") + +add_library(rtl_tcp_source SHARED ${SRC}) +target_link_libraries(rtl_tcp_source PRIVATE sdrpp_core) +set_target_properties(rtl_tcp_source PROPERTIES PREFIX "") + +if(WIN32) + target_link_libraries(rtl_tcp_source PRIVATE wsock32 ws2_32) +endif() \ No newline at end of file diff --git a/rtl_tcp_source/src/main.cpp b/rtl_tcp_source/src/main.cpp new file mode 100644 index 00000000..ad35dc7a --- /dev/null +++ b/rtl_tcp_source/src/main.cpp @@ -0,0 +1,188 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONCAT(a, b) ((std::string(a) + b).c_str()) + +MOD_INFO { + /* Name: */ "fike_source", + /* Description: */ "File input module for SDR++", + /* Author: */ "Ryzerth", + /* Version: */ "0.1.0" +}; + +class RTLTCPSourceModule { +public: + RTLTCPSourceModule(std::string name) { + this->name = name; + + stream.init(100); + + 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("RTL-TCP", &handler); + + spdlog::info("RTLTCPSourceModule '{0}': Instance created!", name); + } + + ~RTLTCPSourceModule() { + spdlog::info("RTLTCPSourceModule '{0}': Instance deleted!", name); + } + +private: + static void menuSelected(void* ctx) { + RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx; + core::setInputSampleRate(_this->sampleRate); + spdlog::info("RTLTCPSourceModule '{0}': Menu Select!", _this->name); + } + + static void menuDeselected(void* ctx) { + RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx; + spdlog::info("RTLTCPSourceModule '{0}': Menu Deselect!", _this->name); + } + + static void start(void* ctx) { + RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx; + if (!_this->client.connectToRTL(_this->ip, _this->port)) { + spdlog::error("Could not connect to {0}:{1}", _this->ip, _this->port); + return; + } + _this->client.setFrequency(_this->freq); + _this->client.setSampleRate(_this->sampleRate); + _this->client.setGainIndex(_this->gain); + _this->client.setGainMode(!_this->tunerAGC); + _this->client.setDirectSampling(_this->directSamplingMode); + _this->client.setAGCMode(_this->rtlAGC); + _this->running = true; + _this->workerThread = std::thread(worker, _this); + spdlog::info("RTLTCPSourceModule '{0}': Start!", _this->name); + } + + static void stop(void* ctx) { + RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx; + _this->running = false; + _this->stream.stopWriter(); + _this->workerThread.join(); + _this->stream.clearWriteStop(); + _this->client.disconnect(); + spdlog::info("RTLTCPSourceModule '{0}': Stop!", _this->name); + } + + static void tune(double freq, void* ctx) { + RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx; + if (_this->running) { + _this->client.setFrequency(freq); + } + _this->freq = freq; + spdlog::info("RTLTCPSourceModule '{0}': Tune: {1}!", _this->name, freq); + } + + static void menuHandler(void* ctx) { + RTLTCPSourceModule* _this = (RTLTCPSourceModule*)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) { + RTLTCPSourceModule* _this = (RTLTCPSourceModule*)ctx; + int blockSize = _this->sampleRate / 200.0; + uint8_t* inBuf = new uint8_t[blockSize * 2]; + dsp::complex_t* outBuf = new dsp::complex_t[blockSize]; + + _this->stream.setMaxLatency(blockSize * 2); + + while (true) { + // Read samples here + _this->client.receiveData(inBuf, blockSize * 2); + for (int i = 0; i < blockSize; i++) { + outBuf[i].q = ((double)inBuf[i * 2] - 128.0) / 128.0; + outBuf[i].i = ((double)inBuf[(i * 2) + 1] - 128.0) / 128.0; + } + if (_this->stream.write(outBuf, blockSize) < 0) { break; }; + } + + delete[] inBuf; + delete[] outBuf; + } + + std::string name; + dsp::stream 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 void* _CREATE_INSTANCE_(std::string name) { + return new RTLTCPSourceModule(name); +} + +MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { + delete (RTLTCPSourceModule*)instance; +} + +MOD_EXPORT void _STOP_() { + // Do your one shutdown here +} \ No newline at end of file diff --git a/rtl_tcp_source/src/rtltcp_client.h b/rtl_tcp_source/src/rtltcp_client.h new file mode 100644 index 00000000..2fc4435c --- /dev/null +++ b/rtl_tcp_source/src/rtltcp_client.h @@ -0,0 +1,120 @@ +#pragma once +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define __attribute__(x) +#pragma pack(push, 1) +#endif +struct command_t{ + unsigned char cmd; + unsigned int param; +}__attribute__((packed)); +#ifdef _WIN32 +#pragma pack(pop) +#endif + +class RTLTCPClient { +public: + RTLTCPClient() { + + } + + bool connectToRTL(char* host, uint16_t port) { + struct addrinfo *result = NULL; + struct addrinfo *ptr = NULL; + struct addrinfo hints; + + ZeroMemory( &hints, sizeof(hints) ); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + char buf[128]; + sprintf(buf, "%hu", port); + + int iResult = getaddrinfo(host, buf, &hints, &result); + if (iResult != 0) { + // TODO: log error + WSACleanup(); + return false; + } + ptr = result; + + sock = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); + + if (sock == INVALID_SOCKET) { + // TODO: log error + freeaddrinfo(result); + WSACleanup(); + return false; + } + + iResult = connect(sock, ptr->ai_addr, (int)ptr->ai_addrlen); + if (iResult == SOCKET_ERROR) { + closesocket(sock); + freeaddrinfo(result); + WSACleanup(); + return false; + } + freeaddrinfo(result); + + return true; + } + + void disconnect() { + closesocket(sock); + WSACleanup(); + } + + // struct command_t { + // uint8_t cmd; + // uint32_t arg; + // }; + + void sendCommand(uint8_t command, uint32_t param) { + command_t cmd; + cmd.cmd = command; + cmd.param = htonl(param); + send(sock, (char*)&cmd, sizeof(command_t), 0); + } + + void receiveData(uint8_t* buf, size_t count) { + recv(sock, (char*)buf, count, 0); + } + + void setFrequency(double freq) { + sendCommand(1, freq); + } + + void setSampleRate(double sr) { + sendCommand(2, sr); + } + + void setGainMode(int mode) { + sendCommand(3, mode); + } + + void setGain(double gain) { + sendCommand(4, gain); + } + + void setAGCMode(int mode) { + sendCommand(8, mode); + } + + void setDirectSampling(int mode) { + sendCommand(9, mode); + } + + void setGainIndex(int index) { + sendCommand(13, index); + } + +private: + SOCKET sock; + +}; \ No newline at end of file diff --git a/soapy/src/main.cpp b/soapy/src/main.cpp index ae6be718..5bb6773e 100644 --- a/soapy/src/main.cpp +++ b/soapy/src/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #define CONCAT(a, b) ((std::string(a) + b).c_str()) @@ -17,6 +18,8 @@ MOD_INFO { /* Version: */ "0.1.0" }; +ConfigManager config; + class SoapyModule { public: SoapyModule(std::string name) { @@ -24,15 +27,18 @@ public: //TODO: Make module tune on source select change (in sdrpp_core) - devList = SoapySDR::Device::enumerate(); - txtDevList = ""; - for (auto& dev : devList) { - txtDevList += dev["label"]; - txtDevList += '\0'; - } + uiGains = new float[1]; + + refresh(); stream.init(100); + // Select default device + config.aquire(); + std::string devName = config.conf["device"]; + config.release(); + selectDevice(devName); + handler.ctx = this; handler.selectHandler = menuSelected; handler.deselectHandler = menuDeselected; @@ -42,10 +48,17 @@ public: handler.tuneHandler = tune; handler.stream = &stream; sigpath::sourceManager.registerSource("SoapySDR", &handler); - spdlog::info("SoapyModule '{0}': Instance created!", name); - // Select default device - selectDevice(devList[0]["label"]); + spdlog::info("SoapyModule '{0}': Instance created!", name); + } + + void refresh() { + devList = SoapySDR::Device::enumerate(); + txtDevList = ""; + for (auto& dev : devList) { + txtDevList += dev["label"]; + txtDevList += '\0'; + } } ~SoapyModule() { @@ -54,6 +67,7 @@ public: private: void selectSampleRate(double samplerate) { + spdlog::info("Setting sample rate to {0}", samplerate); if (sampleRates.size() == 0) { devId = -1; return; @@ -65,6 +79,7 @@ private: srId = i; sampleRate = sr; found = true; + core::setInputSampleRate(sampleRate); break; } i++; @@ -98,15 +113,57 @@ private: } SoapySDR::Device* dev = SoapySDR::Device::make(devArgs); + + gainList = dev->listGains(SOAPY_SDR_RX, 0); + delete[] uiGains; + uiGains = new float[gainList.size()]; + for (auto gain : gainList) { + gainRanges.push_back(dev->getGainRange(SOAPY_SDR_RX, 0, gain)); + } + sampleRates = dev->listSampleRates(SOAPY_SDR_RX, 0); txtSrList = ""; for (double sr : sampleRates) { txtSrList += std::to_string((int)sr); txtSrList += '\0'; } - SoapySDR::Device::unmake(dev); - selectSampleRate(sampleRates[0]); + SoapySDR::Device::unmake(dev); + + config.aquire(); + if (config.conf["devices"].contains(name)) { + int i = 0; + for (auto gain : gainList) { + if (config.conf["devices"][name]["gains"].contains(gain)) { + uiGains[i] = config.conf["devices"][name]["gains"][gain]; + } + i++; + } + selectSampleRate(config.conf["devices"][name]["sampleRate"]); + } + else { + int i = 0; + for (auto gain : gainList) { + uiGains[i] = gainRanges[i].minimum(); + i++; + } + selectSampleRate(sampleRates[0]); // Select default + } + config.release(); + + } + + void saveCurrent() { + json conf; + conf["sampleRate"] = sampleRate; + int i = 0; + for (auto gain : gainList) { + conf["gains"][gain] = uiGains[i]; + i++; + } + config.aquire(); + config.conf["devices"][devArgs["label"]] = conf; + config.release(true); } static void menuSelected(void* ctx) { @@ -126,7 +183,17 @@ private: static void start(void* ctx) { SoapyModule* _this = (SoapyModule*)ctx; _this->dev = SoapySDR::Device::make(_this->devArgs); + _this->dev->setSampleRate(SOAPY_SDR_RX, 0, _this->sampleRate); + + int i = 0; + for (auto gain : _this->gainList) { + _this->dev->setGain(SOAPY_SDR_RX, 0, gain, _this->uiGains[i]); + i++; + } + + _this->dev->setFrequency(SOAPY_SDR_RX, 0, _this->freq); + _this->devStream = _this->dev->setupStream(SOAPY_SDR_RX, "CF32"); _this->dev->activateStream(_this->devStream); _this->running = true; @@ -137,6 +204,11 @@ private: static void stop(void* ctx) { SoapyModule* _this = (SoapyModule*)ctx; _this->running = false; + _this->dev->deactivateStream(_this->devStream); + _this->dev->closeStream(_this->devStream); + _this->workerThread.join(); + SoapySDR::Device::unmake(_this->dev); + spdlog::info("SoapyModule '{0}': Stop!", _this->name); } @@ -159,14 +231,56 @@ private: } float menuWidth = ImGui::GetContentRegionAvailWidth(); + + if (_this->running) { style::beginDisabled(); } + ImGui::SetNextItemWidth(menuWidth); if (ImGui::Combo(CONCAT("##_dev_select_", _this->name), &_this->devId, _this->txtDevList.c_str())) { _this->selectDevice(_this->devList[_this->devId]["label"]); + config.aquire(); + config.conf["device"] = _this->devList[_this->devId]["label"]; + config.release(true); } - ImGui::SetNextItemWidth(menuWidth); + + if (ImGui::Combo(CONCAT("##_sr_select_", _this->name), &_this->srId, _this->txtSrList.c_str())) { _this->selectSampleRate(_this->sampleRates[_this->srId]); - core::setInputSampleRate(_this->sampleRate); + _this->saveCurrent(); + } + + ImGui::SameLine(); + float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX(); + if (ImGui::Button(CONCAT("Refresh##_dev_select_", _this->name), ImVec2(refreshBtnWdith, 0))) { + _this->refresh(); + _this->selectDevice(config.conf["device"]); + } + + if (_this->running) { style::endDisabled(); } + + float gainNameLen = 0; + float len; + for (auto gain : _this->gainList) { + len = ImGui::CalcTextSize((gain + " gain").c_str()).x; + if (len > gainNameLen) { + gainNameLen = len; + } + } + gainNameLen += 5.0f; + + int i = 0; + for (auto gain : _this->gainList) { + ImGui::Text((gain + " gain").c_str()); + ImGui::SameLine(); + ImGui::SetCursorPosX(gainNameLen); + ImGui::SetNextItemWidth(menuWidth - gainNameLen); + if (ImGui::SliderFloat((gain + std::string("##_gain_sel_") + _this->name).c_str(), &_this->uiGains[i], + _this->gainRanges[i].minimum(), _this->gainRanges[i].maximum())) { + if (_this->running) { + _this->dev->setGain(SOAPY_SDR_RX, 0, gain, _this->uiGains[i]); + } + _this->saveCurrent(); + } + i++; } } @@ -203,10 +317,18 @@ private: bool running = false; std::vector sampleRates; int srId = -1; + float* uiGains; + std::vector gainList; + std::vector gainRanges; }; MOD_EXPORT void _INIT_() { - // Do your one time init here + config.setPath(ROOT_DIR "/soapy_source_config.json"); + json defConf; + defConf["device"] = ""; + defConf["devices"] = json({}); + config.load(defConf); + config.enableAutoSave(); } MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) { @@ -218,5 +340,6 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { } MOD_EXPORT void _STOP_() { - // Do your one shutdown here + config.disableAutoSave(); + config.save(); } \ No newline at end of file