mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-10-31 00:48:11 +01:00 
			
		
		
		
	Compare commits
	
		
			27 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 40f1ee5651 | ||
|  | c4b0cb37a6 | ||
|  | cd7dabda51 | ||
|  | 139f63ad25 | ||
|  | 75800e0ca2 | ||
|  | 84da183559 | ||
|  | 0144d8e8ce | ||
|  | 7ef88f23a8 | ||
|  | c934e41a61 | ||
|  | 9dd6c8546d | ||
|  | 191f652fc3 | ||
|  | e208511bde | ||
|  | 348bf75281 | ||
|  | cbf1d6703e | ||
|  | 03c30f202e | ||
|  | 25bc9f60ed | ||
|  | 2e80882ab5 | ||
|  | 4c584847de | ||
|  | 2a741932e0 | ||
|  | ff655caf31 | ||
|  | 0277232bdb | ||
|  | 2e3e2a7dca | ||
|  | 271a77e4ce | ||
|  | 48e9708d74 | ||
|  | 961cd3f133 | ||
|  | 2676190d3a | ||
|  | 8851735cb8 | 
							
								
								
									
										37
									
								
								.github/workflows/build_all.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										37
									
								
								.github/workflows/build_all.yml
									
									
									
									
										vendored
									
									
								
							| @@ -68,15 +68,11 @@ jobs: | ||||
|  | ||||
|         - name: Prepare CMake | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
|           run: cmake "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DCMAKE_BUILD_TYPE=Debug -DCOPY_MSVC_REDISTRIBUTABLES=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON | ||||
|           run: cmake "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON | ||||
|  | ||||
|         - name: Build | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
|           run: cmake --build . --config Debug --verbose | ||||
|  | ||||
|         - name: Run tests | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
|           run: ./Debug/min_broken.exe | ||||
|           run: cmake --build . --config Release --verbose | ||||
|  | ||||
|         - name: Create Archive | ||||
|           working-directory: ${{runner.workspace}} | ||||
| @@ -104,7 +100,7 @@ jobs: | ||||
|           run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
|  | ||||
|         - name: Install SDRplay API | ||||
|           run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.0.pkg -target / | ||||
|           run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target / | ||||
|  | ||||
|         - name: Install libiio | ||||
|           run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
| @@ -155,7 +151,7 @@ jobs: | ||||
|           run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
|  | ||||
|         - name: Install SDRplay API | ||||
|           run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.0.pkg -target / | ||||
|           run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target / | ||||
|  | ||||
|         - name: Install libiio | ||||
|           run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
| @@ -344,28 +340,6 @@ jobs: | ||||
|               name: sdrpp_ubuntu_mantic_amd64 | ||||
|               path: ${{runner.workspace}}/sdrpp_debian_amd64.deb | ||||
|  | ||||
|     build_ubuntu_noble: | ||||
|         runs-on: ubuntu-latest | ||||
|  | ||||
|         steps: | ||||
|         - uses: actions/checkout@v4 | ||||
|          | ||||
|         - name: Create Docker Image | ||||
|           run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && docker build . --tag sdrpp_build | ||||
|  | ||||
|         - name: Run Container | ||||
|           run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh | ||||
|  | ||||
|         - name: Recover Deb Archive | ||||
|           working-directory: ${{runner.workspace}} | ||||
|           run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ | ||||
|  | ||||
|         - name: Save Deb Archive | ||||
|           uses: actions/upload-artifact@v4 | ||||
|           with: | ||||
|               name: sdrpp_ubuntu_noble_amd64 | ||||
|               path: ${{runner.workspace}}/sdrpp_debian_amd64.deb | ||||
|  | ||||
|     build_raspios_bullseye_armhf: | ||||
|         runs-on: ARM | ||||
|  | ||||
| @@ -421,7 +395,7 @@ jobs: | ||||
|               path: ${{runner.workspace}}/sdrpp.apk | ||||
|  | ||||
|     create_full_archive: | ||||
|         needs: ['build_windows', 'build_macos_intel', 'build_macos_arm', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_ubuntu_noble', 'build_raspios_bullseye_armhf', 'build_android'] | ||||
|         needs: ['build_windows', 'build_macos_intel', 'build_macos_arm', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_raspios_bullseye_armhf', 'build_android'] | ||||
|         runs-on: ubuntu-latest | ||||
|  | ||||
|         steps: | ||||
| @@ -441,7 +415,6 @@ jobs: | ||||
|             mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb && | ||||
|             mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb && | ||||
|             mv sdrpp_ubuntu_mantic_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_mantic_amd64.deb && | ||||
|             mv sdrpp_ubuntu_noble_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_amd64.deb && | ||||
|             mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb && | ||||
|             mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,6 @@ option(OPT_OVERRIDE_STD_FILESYSTEM "Use a local version of std::filesystem on sy | ||||
| option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Dependencies: libairspy)" ON) | ||||
| option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Dependencies: libairspyhf)" ON) | ||||
| option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio)" ON) | ||||
| option(OPT_BUILD_BADGESDR_SOURCE "Build BadgeSDR Source Module (Dependencies: libusb)" OFF) | ||||
| option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF) | ||||
| option(OPT_BUILD_FILE_SOURCE "Wav file source" ON) | ||||
| option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON) | ||||
| @@ -21,7 +20,6 @@ option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: libl | ||||
| option(OPT_BUILD_NETWORK_SOURCE "Build Network Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_PERSEUS_SOURCE "Build Perseus Source Module (Dependencies: libperseus-sdr)" OFF) | ||||
| option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON) | ||||
| option(OPT_BUILD_RFNM_SOURCE "Build RFNM Source Module (Dependencies: librfnm)" OFF) | ||||
| option(OPT_BUILD_RFSPACE_SOURCE "Build RFspace Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_RTL_SDR_SOURCE "Build RTL-SDR Source Module (Dependencies: librtlsdr)" ON) | ||||
| option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON) | ||||
| @@ -63,7 +61,6 @@ option(OPT_BUILD_SCHEDULER "Build the scheduler" OFF) | ||||
| # Other options | ||||
| option(USE_INTERNAL_LIBCORRECT "Use an internal version of libcorrect" ON) | ||||
| option(USE_BUNDLE_DEFAULTS "Set the default resource and module directories to the right ones for a MacOS .app" OFF) | ||||
| option(COPY_MSVC_REDISTRIBUTABLES "Copy over the Visual C++ Redistributable" OFF) | ||||
|  | ||||
| # Module cmake path | ||||
| set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake") | ||||
| @@ -128,10 +125,6 @@ if (OPT_BUILD_AUDIO_SOURCE) | ||||
| add_subdirectory("source_modules/audio_source") | ||||
| endif (OPT_BUILD_AUDIO_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_BADGESDR_SOURCE) | ||||
| add_subdirectory("source_modules/badgesdr_source") | ||||
| endif (OPT_BUILD_BADGESDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_BLADERF_SOURCE) | ||||
| add_subdirectory("source_modules/bladerf_source") | ||||
| endif (OPT_BUILD_BLADERF_SOURCE) | ||||
| @@ -164,10 +157,6 @@ if (OPT_BUILD_PLUTOSDR_SOURCE) | ||||
| add_subdirectory("source_modules/plutosdr_source") | ||||
| endif (OPT_BUILD_PLUTOSDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_RFNM_SOURCE) | ||||
| add_subdirectory("source_modules/rfnm_source") | ||||
| endif (OPT_BUILD_RFNM_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_RFSPACE_SOURCE) | ||||
| add_subdirectory("source_modules/rfspace_source") | ||||
| endif (OPT_BUILD_RFSPACE_SOURCE) | ||||
| @@ -300,7 +289,6 @@ endif (OPT_BUILD_SCHEDULER) | ||||
|  | ||||
| if (MSVC) | ||||
|     add_executable(sdrpp "src/main.cpp" "win32/resources.rc") | ||||
|     add_executable(min_broken "min_broken/main.cpp" "win32/resources.rc") | ||||
| else () | ||||
|     add_executable(sdrpp "src/main.cpp") | ||||
| endif () | ||||
| @@ -309,29 +297,14 @@ target_link_libraries(sdrpp PRIVATE sdrpp_core) | ||||
|  | ||||
| # Compiler arguments | ||||
| target_compile_options(sdrpp PRIVATE ${SDRPP_COMPILER_FLAGS}) | ||||
| target_compile_options(min_broken PRIVATE ${SDRPP_COMPILER_FLAGS}) | ||||
|  | ||||
| # Copy dynamic libs over | ||||
| if (MSVC) | ||||
|     add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y) | ||||
|     add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y) | ||||
|      | ||||
|     if (COPY_MSVC_REDISTRIBUTABLES) | ||||
|         # Get the list of Visual C++ runtime DLLs | ||||
|         set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP True) | ||||
|         include(InstallRequiredSystemLibraries) | ||||
|  | ||||
|         # Create a space sperated list | ||||
|         set(REDIST_DLLS_STR "") | ||||
|         foreach(DLL IN LISTS CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS) | ||||
|             set(REDIST_DLLS_STR COMMAND xcopy /F \"${DLL}\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y ${REDIST_DLLS_STR}) | ||||
|         endforeach() | ||||
|          | ||||
|         # Create target | ||||
|         add_custom_target(do_always_msvc ALL ${REDIST_DLLS_STR}) | ||||
|     endif () | ||||
| endif () | ||||
|  | ||||
|  | ||||
| if (${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") | ||||
|     add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\") | ||||
| endif () | ||||
|   | ||||
| @@ -10,7 +10,7 @@ android { | ||||
|         minSdkVersion 28 | ||||
|         targetSdkVersion 28 | ||||
|         versionCode 1 | ||||
|         versionName "1.2.0" | ||||
|         versionName "1.1.0" | ||||
|  | ||||
|         externalNativeBuild { | ||||
|             cmake { | ||||
|   | ||||
| @@ -42,10 +42,8 @@ namespace sdrpp_credits { | ||||
|         "Ettus Research", | ||||
|         "Howard Su", | ||||
|         "MicroPhase", | ||||
|         "Microtelecom", | ||||
|         "MyriadRF", | ||||
|         "Nuand", | ||||
|         "RFNM", | ||||
|         "RFspace", | ||||
|         "RTL-SDRblog", | ||||
|         "SDRplay" | ||||
|   | ||||
| @@ -9,7 +9,6 @@ | ||||
| namespace dsp { | ||||
|     class generic_block { | ||||
|     public: | ||||
|         virtual ~generic_block() {} | ||||
|         virtual void start() {} | ||||
|         virtual void stop() {} | ||||
|         virtual int run() { return -1; } | ||||
| @@ -17,6 +16,8 @@ namespace dsp { | ||||
|  | ||||
|     class block : public generic_block { | ||||
|     public: | ||||
|         virtual void init() {} | ||||
|  | ||||
|         virtual ~block() { | ||||
|             if (!_block_init) { return; } | ||||
|             stop(); | ||||
|   | ||||
| @@ -10,8 +10,6 @@ namespace dsp { | ||||
|  | ||||
|         Operator(stream<A>* a, stream<B>* b) { init(a, b); } | ||||
|  | ||||
|         virtual ~Operator() {} | ||||
|  | ||||
|         virtual void init(stream<A>* a, stream<B>* b) { | ||||
|             _a = a; | ||||
|             _b = b; | ||||
|   | ||||
| @@ -11,7 +11,6 @@ | ||||
| namespace dsp { | ||||
|     class untyped_stream { | ||||
|     public: | ||||
|         virtual ~untyped_stream() {} | ||||
|         virtual bool swap(int size) { return false; } | ||||
|         virtual int read() { return -1; } | ||||
|         virtual void flush() {} | ||||
|   | ||||
| @@ -16,6 +16,7 @@ namespace icons { | ||||
|     ImTextureID UNMUTED; | ||||
|     ImTextureID NORMAL_TUNING; | ||||
|     ImTextureID CENTER_TUNING; | ||||
|     ImTextureID ALIGN_CENTER; | ||||
|  | ||||
|     GLuint loadTexture(std::string path) { | ||||
|         int w, h, n; | ||||
| @@ -45,6 +46,7 @@ namespace icons { | ||||
|         UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png"); | ||||
|         NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png"); | ||||
|         CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_tuning.png"); | ||||
|         ALIGN_CENTER = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/align_center.png"); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|   | ||||
| @@ -13,7 +13,8 @@ namespace icons { | ||||
|     extern ImTextureID UNMUTED; | ||||
|     extern ImTextureID NORMAL_TUNING; | ||||
|     extern ImTextureID CENTER_TUNING; | ||||
|  | ||||
|     extern ImTextureID ALIGN_CENTER; | ||||
|      | ||||
|     GLuint loadTexture(std::string path); | ||||
|     bool load(std::string resDir); | ||||
| } | ||||
| @@ -17,6 +17,7 @@ | ||||
| #include <gui/menus/display.h> | ||||
| #include <gui/menus/bandplan.h> | ||||
| #include <gui/menus/sink.h> | ||||
| #include <gui/menus/streams.h> | ||||
| #include <gui/menus/vfo_color.h> | ||||
| #include <gui/menus/module_manager.h> | ||||
| #include <gui/menus/theme.h> | ||||
| @@ -72,6 +73,7 @@ void MainWindow::init() { | ||||
|  | ||||
|     gui::menu.registerEntry("Source", sourcemenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Streams", streamsmenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Display", displaymenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Theme", thememenu::draw, NULL); | ||||
| @@ -165,6 +167,7 @@ void MainWindow::init() { | ||||
|  | ||||
|     sourcemenu::init(); | ||||
|     sinkmenu::init(); | ||||
|     streamsmenu::init(); | ||||
|     bandplanmenu::init(); | ||||
|     displaymenu::init(); | ||||
|     vfo_color_menu::init(); | ||||
|   | ||||
| @@ -19,7 +19,6 @@ namespace displaymenu { | ||||
|     std::string colorMapAuthor = ""; | ||||
|     int selectedWindow = 0; | ||||
|     int fftRate = 20; | ||||
|     int fftSizeId = 0; | ||||
|     int uiScaleId = 0; | ||||
|     bool restartRequired = false; | ||||
|     bool fftHold = false; | ||||
| @@ -29,9 +28,34 @@ namespace displaymenu { | ||||
|     bool snrSmoothing = false; | ||||
|     int snrSmoothingSpeed = 20; | ||||
|  | ||||
|     OptionList<int, int> fftSizes; | ||||
|     OptionList<float, float> uiScales; | ||||
|  | ||||
|     const int FFTSizes[] = { | ||||
|         524288, | ||||
|         262144, | ||||
|         131072, | ||||
|         65536, | ||||
|         32768, | ||||
|         16384, | ||||
|         8192, | ||||
|         4096, | ||||
|         2048, | ||||
|         1024 | ||||
|     }; | ||||
|  | ||||
|     const char* FFTSizesStr = "524288\0" | ||||
|                               "262144\0" | ||||
|                               "131072\0" | ||||
|                               "65536\0" | ||||
|                               "32768\0" | ||||
|                               "16384\0" | ||||
|                               "8192\0" | ||||
|                               "4096\0" | ||||
|                               "2048\0" | ||||
|                               "1024\0"; | ||||
|  | ||||
|     int fftSizeId = 0; | ||||
|  | ||||
|     const IQFrontEnd::FFTWindow fftWindowList[] = { | ||||
|         IQFrontEnd::FFTWindow::RECTANGULAR, | ||||
|         IQFrontEnd::FFTWindow::BLACKMAN, | ||||
| @@ -45,18 +69,6 @@ namespace displaymenu { | ||||
|     } | ||||
|  | ||||
|     void init() { | ||||
|         // Define FFT sizes | ||||
|         fftSizes.define(524288, "524288", 524288); | ||||
|         fftSizes.define(262144, "262144", 262144); | ||||
|         fftSizes.define(131072, "131072", 131072); | ||||
|         fftSizes.define(65536, "65536", 65536); | ||||
|         fftSizes.define(32768, "32768", 32768); | ||||
|         fftSizes.define(16384, "16384", 16384); | ||||
|         fftSizes.define(8192, "8192", 8192); | ||||
|         fftSizes.define(4096, "4096", 4096); | ||||
|         fftSizes.define(2048, "2048", 2048); | ||||
|         fftSizes.define(1024, "1024", 1024); | ||||
|  | ||||
|         showWaterfall = core::configManager.conf["showWaterfall"]; | ||||
|         showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall(); | ||||
|         std::string colormapName = core::configManager.conf["colorMap"]; | ||||
| @@ -78,12 +90,15 @@ namespace displaymenu { | ||||
|         fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"]; | ||||
|         gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate); | ||||
|  | ||||
|         fftSizeId = fftSizes.valueId(65536); | ||||
|         int size = core::configManager.conf["fftSize"]; | ||||
|         if (fftSizes.keyExists(size)) { | ||||
|             fftSizeId = fftSizes.keyId(size); | ||||
|         fftSizeId = 3; | ||||
|         int fftSize = core::configManager.conf["fftSize"]; | ||||
|         for (int i = 0; i < 7; i++) { | ||||
|             if (fftSize == FFTSizes[i]) { | ||||
|                 fftSizeId = i; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId)); | ||||
|         sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]); | ||||
|  | ||||
|         fftRate = core::configManager.conf["fftRate"]; | ||||
|         sigpath::iqFrontEnd.setFFTRate(fftRate); | ||||
| @@ -214,10 +229,10 @@ namespace displaymenu { | ||||
|  | ||||
|         ImGui::LeftLabel("FFT Size"); | ||||
|         ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, fftSizes.txt)) { | ||||
|             sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId)); | ||||
|         if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, FFTSizesStr)) { | ||||
|             sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["fftSize"] = fftSizes.key(fftSizeId); | ||||
|             core::configManager.conf["fftSize"] = FFTSizes[fftSizeId]; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
|   | ||||
							
								
								
									
										177
									
								
								core/src/gui/menus/streams.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								core/src/gui/menus/streams.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | ||||
| #include "streams.h" | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <imgui.h> | ||||
| #include <utils/flog.h> | ||||
| #include <gui/style.h> | ||||
| #include <gui/icons.h> | ||||
| #include <utils/optionlist.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| namespace streamsmenu { | ||||
|     std::vector<SinkID> sinksToBeRemoved; | ||||
|  | ||||
|     std::recursive_mutex sinkTypesMtx; | ||||
|     OptionList<std::string, std::string> sinkTypes; | ||||
|  | ||||
|     std::map<std::string, int> selectedSinkTypeId; | ||||
|     std::map<std::string, int> addSinkTypeId; | ||||
|  | ||||
|     int addType = 0; | ||||
|  | ||||
|     void updateSinkTypeList(const std::string& removed = "") { | ||||
|         std::lock_guard<std::recursive_mutex> lck1(sinkTypesMtx); | ||||
|         auto lck2 = sigpath::streamManager.getSinkTypesLock(); | ||||
|         const auto& types = sigpath::streamManager.getSinkTypes(); | ||||
|         sinkTypes.clear(); | ||||
|         for (const auto& type : types) { | ||||
|             if (type == removed) { continue; } | ||||
|             sinkTypes.define(type, type, type); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void onSinkProviderRegistered(const std::string& type) { | ||||
|         // Update the list | ||||
|         updateSinkTypeList(); | ||||
|  | ||||
|         // Update the ID of the Add dropdown | ||||
|         // TODO | ||||
|  | ||||
|         // Update the selected ID of each drop down | ||||
|         // TODO | ||||
|     } | ||||
|  | ||||
|     void onSinkProviderUnregister(const std::string& type) { | ||||
|         // Update the list | ||||
|         updateSinkTypeList(type); | ||||
|  | ||||
|         // Update the ID of the Add dropdown | ||||
|         // TODO | ||||
|  | ||||
|         // Update the selected ID of each drop down | ||||
|         // TODO | ||||
|     } | ||||
|  | ||||
|     void init() { | ||||
|         sigpath::streamManager.onSinkProviderRegistered.bind(onSinkProviderRegistered); | ||||
|         sigpath::streamManager.onSinkProviderUnregister.bind(onSinkProviderUnregister); | ||||
|         updateSinkTypeList(); | ||||
|     } | ||||
|  | ||||
|     void draw(void* ctx) { | ||||
|         float menuWidth = ImGui::GetContentRegionAvail().x; | ||||
|         auto lck = sigpath::streamManager.getStreamsLock(); | ||||
|         const auto& streams = sigpath::streamManager.getStreams(); | ||||
|  | ||||
|         int count = 0; | ||||
|         int maxCount = streams.size(); | ||||
|         for (auto& [name, stream] : streams) { | ||||
|             // Stream name  | ||||
|             ImGui::SetCursorPosX((menuWidth / 2.0f) - (ImGui::CalcTextSize(name.c_str()).x / 2.0f)); | ||||
|             ImGui::Text("%s", name.c_str()); | ||||
|  | ||||
|             // Display ever sink | ||||
|             if (ImGui::BeginTable(CONCAT("sdrpp_streams_tbl_", name), 1, ImGuiTableFlags_Borders)) { | ||||
|                 auto lck2 = stream->getSinksLock(); | ||||
|                 auto sinks = stream->getSinks(); | ||||
|                 for (auto& [id, sink] : sinks) { | ||||
|                     std::string sid = sink->getStringID(); | ||||
|                     ImGui::TableNextRow(); | ||||
|                     ImGui::TableSetColumnIndex(0); | ||||
|                     float tableWidth = ImGui::GetContentRegionAvail().x; | ||||
|                      | ||||
|                     ImGui::Spacing(); | ||||
|  | ||||
|                     // Sink type | ||||
|                     int ttttt = 0; | ||||
|                     ImGui::FillWidth(); | ||||
|                     if (ImGui::Combo(CONCAT("##sdrpp_streams_type_", sid), &ttttt, sinkTypes.txt)) { | ||||
|                          | ||||
|                     } | ||||
|  | ||||
|                     sink->showMenu(); | ||||
|                     float vol = sink->getVolume(); | ||||
|                     bool muted = sink->getMuted(); | ||||
|                     float pan = sink->getPanning(); | ||||
|                     bool linked = true; | ||||
|  | ||||
|                     float height = ImGui::GetTextLineHeightWithSpacing() + 2; | ||||
|                     ImGui::PushID(ImGui::GetID(("sdrpp_streams_center_btn_" + sid).c_str())); | ||||
|                     if (ImGui::ImageButton(icons::ALIGN_CENTER, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) { | ||||
|                         sink->setPanning(0.0f); | ||||
|                     } | ||||
|                     ImGui::PopID(); | ||||
|                     ImGui::SameLine(); | ||||
|                     ImGui::FillWidth(); | ||||
|                     if (ImGui::SliderFloat(CONCAT("##sdrpp_streams_pan_", sid), &pan, -1, 1, "")) { | ||||
|                         sink->setPanning(pan); | ||||
|                     } | ||||
|                      | ||||
|                     if (muted) { | ||||
|                         ImGui::PushID(ImGui::GetID(("sdrpp_unmute_btn_" + sid).c_str())); | ||||
|                         if (ImGui::ImageButton(icons::MUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) { | ||||
|                             sink->setMuted(false); | ||||
|                         } | ||||
|                         ImGui::PopID(); | ||||
|                     } | ||||
|                     else { | ||||
|                         ImGui::PushID(ImGui::GetID(("sdrpp_mute_btn_" + sid).c_str())); | ||||
|                         if (ImGui::ImageButton(icons::UNMUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) { | ||||
|                             sink->setMuted(true); | ||||
|                         } | ||||
|                         ImGui::PopID(); | ||||
|                     } | ||||
|                     ImGui::SameLine(); | ||||
|                     ImGui::FillWidth(); | ||||
|                     if (ImGui::SliderFloat(CONCAT("##sdrpp_streams_vol_", sid), &vol, 0, 1, "")) { | ||||
|                         sink->setVolume(vol); | ||||
|                     } | ||||
|  | ||||
|  | ||||
|                     int startCur = ImGui::GetCursorPosX(); | ||||
|                     if (ImGui::Checkbox(CONCAT("Link volume##sdrpp_streams_vol_", sid), &linked)) { | ||||
|                         // TODO | ||||
|                     } | ||||
|                     ImGui::SameLine(); | ||||
|                     if (ImGui::Button(CONCAT("Remove##sdrpp_streams_remove_type_", sid), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) { | ||||
|                         sinksToBeRemoved.push_back(id); | ||||
|                     } | ||||
|                     ImGui::Spacing(); | ||||
|                 } | ||||
|                 lck2.unlock(); | ||||
|  | ||||
|                 ImGui::TableNextRow(); | ||||
|                 ImGui::TableSetColumnIndex(0); | ||||
|                 float tableWidth = ImGui::GetContentRegionAvail().x; | ||||
|  | ||||
|                 ImGui::Spacing(); | ||||
|                 int startCur = ImGui::GetCursorPosX(); | ||||
|                 { | ||||
|                     std::lock_guard<std::recursive_mutex> lck(sinkTypesMtx); | ||||
|                     ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &addType, sinkTypes.txt); | ||||
|                     ImGui::SameLine(); | ||||
|                     if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) { | ||||
|                         stream->addSink(sinkTypes.value(addType)); | ||||
|                     } | ||||
|                     ImGui::Spacing(); | ||||
|                 } | ||||
|  | ||||
|                 ImGui::EndTable(); | ||||
|  | ||||
|                 // Remove sinks that need to be removed | ||||
|                 if (!sinksToBeRemoved.empty()) { | ||||
|                     for (auto& id : sinksToBeRemoved) { | ||||
|                         stream->removeSink(id); | ||||
|                     } | ||||
|                     sinksToBeRemoved.clear(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             count++; | ||||
|             if (count < maxCount) { | ||||
|                 ImGui::Spacing(); | ||||
|             } | ||||
|             ImGui::Spacing(); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										6
									
								
								core/src/gui/menus/streams.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								core/src/gui/menus/streams.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace streamsmenu { | ||||
|     void init(); | ||||
|     void draw(void* ctx); | ||||
| }; | ||||
| @@ -52,15 +52,4 @@ namespace ImGui { | ||||
|         bufferMtx.unlock(); | ||||
|     } | ||||
|  | ||||
|     void SymbolDiagram::setCount(int count) { | ||||
|         std::lock_guard<std::mutex> lck(bufferMtx); | ||||
|         delete[] buffer; | ||||
|         buffer = new float[count]; | ||||
|         sampleCount = count; | ||||
|         memset(buffer, 0, sampleCount * sizeof(float)); | ||||
|     } | ||||
|  | ||||
|     int SymbolDiagram::getCount() { | ||||
|         return sampleCount; | ||||
|     } | ||||
| } | ||||
| @@ -18,10 +18,6 @@ namespace ImGui { | ||||
|  | ||||
|         void releaseBuffer(); | ||||
|  | ||||
|         void setCount(int count); | ||||
|  | ||||
|         int getCount(); | ||||
|  | ||||
|         std::vector<float> lines; | ||||
|  | ||||
|     private: | ||||
|   | ||||
| @@ -110,7 +110,7 @@ namespace ImGui { | ||||
|         viewBandwidth = 1.0; | ||||
|         wholeBandwidth = 1.0; | ||||
|  | ||||
|         //updatePallette(DEFAULT_COLOR_MAP, 13); | ||||
|         updatePallette(DEFAULT_COLOR_MAP, 13); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::init() { | ||||
|   | ||||
| @@ -42,7 +42,6 @@ public: | ||||
|  | ||||
|     class Instance { | ||||
|     public: | ||||
|         virtual ~Instance() {} | ||||
|         virtual void postInit() = 0; | ||||
|         virtual void enable() = 0; | ||||
|         virtual void disable() = 0; | ||||
|   | ||||
| @@ -5,4 +5,5 @@ namespace sigpath { | ||||
|     VFOManager vfoManager; | ||||
|     SourceManager sourceManager; | ||||
|     SinkManager sinkManager; | ||||
|     StreamManager streamManager; | ||||
| }; | ||||
| @@ -3,6 +3,7 @@ | ||||
| #include "vfo_manager.h" | ||||
| #include "source.h" | ||||
| #include "sink.h" | ||||
| #include "stream.h" | ||||
| #include <module.h> | ||||
|  | ||||
| namespace sigpath { | ||||
| @@ -10,4 +11,5 @@ namespace sigpath { | ||||
|     SDRPP_EXPORT VFOManager vfoManager; | ||||
|     SDRPP_EXPORT SourceManager sourceManager; | ||||
|     SDRPP_EXPORT SinkManager sinkManager; | ||||
|     SDRPP_EXPORT StreamManager streamManager; | ||||
| }; | ||||
							
								
								
									
										519
									
								
								core/src/signal_path/stream.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										519
									
								
								core/src/signal_path/stream.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,519 @@ | ||||
| #include "stream.h" | ||||
| #include <utils/flog.h> | ||||
|  | ||||
| Sink::Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream,  const std::string& name, SinkID id, const std::string& stringId) : | ||||
|     entry(entry), | ||||
|     stream(stream), | ||||
|     streamName(name), | ||||
|     id(id), | ||||
|     stringId(stringId) | ||||
| {} | ||||
|  | ||||
| void Sink::showMenu() {} | ||||
|  | ||||
| SinkEntry::SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate) : | ||||
|     manager(manager), | ||||
|     parentStream(parentStream), | ||||
|     id(id) | ||||
| { | ||||
|     this->type = type; | ||||
|     this->inputSamplerate = inputSamplerate; | ||||
|  | ||||
|     // Generate string ID | ||||
|     stringId = parentStream->getName(); | ||||
|     char buf[16]; | ||||
|     sprintf(buf, "%d", (int)id); | ||||
|     stringId += buf; | ||||
|      | ||||
|     // Initialize DSP | ||||
|     resamp.init(&input, inputSamplerate, inputSamplerate); | ||||
|     volumeAdjust.init(&resamp.out, 1.0f, false); | ||||
|  | ||||
|     // Initialize the sink | ||||
|     setType(type); | ||||
| } | ||||
|  | ||||
| std::string SinkEntry::getType() const { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     return type; | ||||
| } | ||||
|  | ||||
| void SinkEntry::setType(const std::string& type) { | ||||
|     // Get unique lock on the entry | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     // Delete existing sink | ||||
|     if (sink) { | ||||
|         provider->destroySink(std::move(sink)); | ||||
|     } | ||||
|  | ||||
|     // Get shared lock on sink types | ||||
|     auto lck2 = manager->getSinkTypesLock(); | ||||
|  | ||||
|     // Get the provider or throw error | ||||
|     const auto& types = manager->getSinkTypes(); | ||||
|     if (std::find(types.begin(), types.end(), type) == types.end()) { | ||||
|         this->type.clear(); | ||||
|         throw SinkEntryCreateException("Invalid sink type"); | ||||
|     } | ||||
|  | ||||
|     // Create sink | ||||
|     this->type = type; | ||||
|     provider = manager->providers[type]; | ||||
|     sink = provider->createSink(this, &volumeAdjust.out, parentStream->getName(), id, stringId); | ||||
| } | ||||
|  | ||||
| SinkID SinkEntry::getID() const { | ||||
|     return id; | ||||
| } | ||||
|  | ||||
| float SinkEntry::getVolume() const { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     return volume; | ||||
| } | ||||
|  | ||||
| void SinkEntry::setVolume(float volume) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     this->volume = volume; | ||||
|     volumeAdjust.setVolume(volume); | ||||
|     onVolumeChanged(volume); | ||||
| } | ||||
|  | ||||
| bool SinkEntry::getMuted() const { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     return muted; | ||||
| } | ||||
|  | ||||
| void SinkEntry::setMuted(bool muted) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     this->muted = muted; | ||||
|     volumeAdjust.setMuted(muted); | ||||
|     onMutedChanged(muted); | ||||
| } | ||||
|  | ||||
| float SinkEntry::getPanning() const { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     return panning; | ||||
| } | ||||
|  | ||||
| void SinkEntry::setPanning(float panning) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     this->panning = panning; | ||||
|     // TODO | ||||
|     onPanningChanged(panning); | ||||
| } | ||||
|  | ||||
| void SinkEntry::showMenu() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     sink->showMenu(); | ||||
| } | ||||
|  | ||||
| void SinkEntry::startSink() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     sink->start(); | ||||
| } | ||||
|  | ||||
| void SinkEntry::stopSink() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     sink->stop(); | ||||
| } | ||||
|  | ||||
| std::lock_guard<std::recursive_mutex> SinkEntry::getLock() const { | ||||
|     return std::lock_guard<std::recursive_mutex>(mtx); | ||||
| } | ||||
|  | ||||
| void SinkEntry::setSamplerate(double samplerate) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     resamp.setOutSamplerate(samplerate); | ||||
| } | ||||
|  | ||||
| void SinkEntry::startDSP() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     resamp.start(); | ||||
|     volumeAdjust.start(); | ||||
| } | ||||
|  | ||||
| void SinkEntry::stopDSP() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     resamp.stop(); | ||||
|     volumeAdjust.stop(); | ||||
| } | ||||
|  | ||||
| void SinkEntry::destroy(bool forgetSettings) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     if (sink) { | ||||
|         provider->destroySink(std::move(sink)); | ||||
|     } | ||||
|     type.clear(); | ||||
| } | ||||
|  | ||||
| void SinkEntry::setInputSamplerate(double samplerate) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     resamp.setInSamplerate(samplerate); | ||||
| } | ||||
|  | ||||
| std::string SinkEntry::getStringID() const { | ||||
|     return stringId; | ||||
| } | ||||
|  | ||||
| Stream::Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) : | ||||
|     manager(manager), | ||||
|     name(name) | ||||
| { | ||||
|     this->samplerate = samplerate; | ||||
|  | ||||
|     // Initialize DSP | ||||
|     split.init(stream); | ||||
| } | ||||
|  | ||||
| Stream::~Stream() { | ||||
|     // Copy sink IDs | ||||
|     std::vector<SinkID> ids; | ||||
|     for (auto& [id, sink] : sinks) { | ||||
|         ids.push_back(id); | ||||
|     } | ||||
|  | ||||
|     // Remove them all | ||||
|     for (auto& id : ids) { | ||||
|         removeSink(id, false); | ||||
|     } | ||||
| } | ||||
|  | ||||
| const std::string& Stream::getName() const { | ||||
|     return name; | ||||
| } | ||||
|  | ||||
| SinkID Stream::addSink(const std::string& type, SinkID id) { | ||||
|     std::unique_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|     // Find a free ID if not provided | ||||
|     if (id < 0) { | ||||
|         for (id = 0; sinks.find(id) != sinks.end(); id++); | ||||
|     } | ||||
|     else { | ||||
|         // Check that the provided ID is valid | ||||
|         if (sinks.find(id) != sinks.end()) { | ||||
|             flog::error("Tried to create sink for stream '{}' with existing ID: {}", name, id); | ||||
|             return -1; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // Create sink entry | ||||
|     std::shared_ptr<SinkEntry> sink; | ||||
|     try { | ||||
|         sink = std::make_shared<SinkEntry>(manager, this, type, id, samplerate); | ||||
|     } | ||||
|     catch (SinkEntryCreateException e) { | ||||
|         flog::error("Tried to create sink for stream '{}' with ID '{}': {}", name, id, e.what()); | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     // Start the sink and DSP | ||||
|     sink->startSink(); | ||||
|     if (running) { sink->startDSP(); } | ||||
|  | ||||
|     // Bind the sinks's input | ||||
|     split.bindStream(&sink->input); | ||||
|  | ||||
|     // Add sink to list | ||||
|     sinks[id] = sink; | ||||
|  | ||||
|     // Release lock and emit event | ||||
|     lck.unlock(); | ||||
|     onSinkAdded(sink); | ||||
|  | ||||
|     return id; | ||||
| } | ||||
|  | ||||
| void Stream::removeSink(SinkID id, bool forgetSettings) { | ||||
|     // Acquire shared lock | ||||
|     std::shared_ptr<SinkEntry> sink; | ||||
|     { | ||||
|         std::shared_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|         // Check that the ID exists | ||||
|         if (sinks.find(id) == sinks.end()) { | ||||
|             flog::error("Tried to remove sink with unknown ID: {}", id); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get sink | ||||
|         sink = sinks[id]; | ||||
|     } | ||||
|      | ||||
|     // Emit event | ||||
|     onSinkRemove(sink); | ||||
|  | ||||
|     // Acquire unique lock | ||||
|     { | ||||
|         std::unique_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|         // Check that it's still in the list | ||||
|         if (sinks.find(id) == sinks.end()) { | ||||
|             flog::error("Tried to remove sink with unknown ID: {}", id); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Remove from list | ||||
|         sinks.erase(id); | ||||
|  | ||||
|         // Unbind the sink's steam | ||||
|         split.unbindStream(&sink->input); | ||||
|          | ||||
|         // Stop the sink and DSP | ||||
|         sink->stopDSP(); | ||||
|         sink->stopSink(); | ||||
|  | ||||
|         // Delete instance | ||||
|         sink->destroy(forgetSettings); | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::shared_lock<std::shared_mutex> Stream::getSinksLock() { | ||||
|     return std::shared_lock<std::shared_mutex>(sinksMtx); | ||||
| } | ||||
|  | ||||
| const std::map<SinkID, std::shared_ptr<SinkEntry>>& Stream::getSinks() const { | ||||
|     return sinks; | ||||
| } | ||||
|  | ||||
| MasterStream::MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) : | ||||
|     Stream(manager, name, stream, samplerate) | ||||
| {} | ||||
|  | ||||
| void MasterStream::setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate) { | ||||
|     std::unique_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|     // If all that's needed is to set the input, do it and return | ||||
|     if (samplerate == 0.0) { | ||||
|         split.setInput(stream); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Update samplerate | ||||
|     this->samplerate = samplerate; | ||||
|  | ||||
|     // Stop DSP | ||||
|     if (running) { | ||||
|         split.stop(); | ||||
|         for (auto& [id, sink] : sinks) { | ||||
|             sink->stopDSP(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // Set input and samplerate | ||||
|     split.setInput(stream); | ||||
|     for (auto& [id, sink] : sinks) { | ||||
|         sink->setInputSamplerate(samplerate); | ||||
|     } | ||||
|  | ||||
|     // Start DSP | ||||
|     if (running) { | ||||
|         for (auto& [id, sink] : sinks) { | ||||
|             sink->startDSP(); | ||||
|         } | ||||
|         split.start(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MasterStream::setSamplerate(double samplerate) { | ||||
|     std::unique_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|     // Update samplerate | ||||
|     this->samplerate = samplerate; | ||||
|  | ||||
|     // TODO: Maybe simply disallow while running? | ||||
|  | ||||
|     // Stop DSP if it was running | ||||
|     if (running) { | ||||
|         split.stop(); | ||||
|         for (auto& [id, sink] : sinks) { | ||||
|             sink->stopDSP(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Set samplerate | ||||
|     for (auto& [id, sink] : sinks) { | ||||
|         sink->setInputSamplerate(samplerate); | ||||
|     } | ||||
|  | ||||
|     // Start DSP if it was running | ||||
|     if (running) { | ||||
|         for (auto& [id, sink] : sinks) { | ||||
|             sink->startDSP(); | ||||
|         } | ||||
|         split.start(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MasterStream::startDSP() { | ||||
|     // TODO: Maybe add a different mutex for the stream? | ||||
|     std::unique_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|     // Check if already running | ||||
|     if (running) { return; } | ||||
|  | ||||
|     // Start all DSP | ||||
|     split.start(); | ||||
|     for (auto& [id, sink] : sinks) { | ||||
|         sink->startDSP(); | ||||
|     } | ||||
|     running = true; | ||||
| } | ||||
|  | ||||
| void MasterStream::stopDSP() { | ||||
|     // TODO: Maybe add a different mutex for the stream? | ||||
|     std::unique_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|     // Check if already running | ||||
|     if (!running) { return; } | ||||
|  | ||||
|     // Start all DSP | ||||
|     split.stop(); | ||||
|     for (auto& [id, sink] : sinks) { | ||||
|         sink->stopDSP(); | ||||
|     } | ||||
|     running = false; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<MasterStream> StreamManager::createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) { | ||||
|     std::unique_lock<std::shared_mutex> lck(streamsMtx); | ||||
|  | ||||
|     // Check that no stream with that name already exists | ||||
|     if (streams.find(name) != streams.end()) { | ||||
|         flog::error("Tried to created stream with an existing name: {}", name); | ||||
|         return NULL; | ||||
|     } | ||||
|  | ||||
|     // Create and save stream | ||||
|     std::shared_ptr<MasterStream> newStream(new MasterStream(this, name, stream, samplerate)); | ||||
|     streams[name] = newStream; | ||||
|  | ||||
|     // Release lock and emit event | ||||
|     lck.unlock(); | ||||
|     onStreamCreated(newStream); | ||||
|  | ||||
|     return newStream; | ||||
| } | ||||
|  | ||||
| void StreamManager::destroyStream(std::shared_ptr<MasterStream>& stream) { | ||||
|     // Emit event | ||||
|     onStreamDestroy(stream); | ||||
|  | ||||
|     // Aquire complete lock on the stream list | ||||
|     { | ||||
|         std::unique_lock<std::shared_mutex> lck(streamsMtx); | ||||
|  | ||||
|         // Get iterator of the stream | ||||
|         auto it = std::find_if(streams.begin(), streams.end(), [&stream](std::pair<const std::string, std::shared_ptr<Stream>> e) { | ||||
|             return e.second == stream; | ||||
|         }); | ||||
|         if (it == streams.end()) { | ||||
|             flog::error("Tried to delete a stream using an invalid pointer. Stream not found in list"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Delete entry from list | ||||
|         flog::debug("Stream pointer uses, should be 2 and is {}", (int)stream.use_count()); | ||||
|         streams.erase(it); | ||||
|     } | ||||
|  | ||||
|     // Reset passed pointer | ||||
|     stream.reset(); | ||||
| } | ||||
|  | ||||
| std::shared_lock<std::shared_mutex> StreamManager::getStreamsLock() { | ||||
|     return std::shared_lock<std::shared_mutex>(streamsMtx); | ||||
| } | ||||
|  | ||||
| const std::map<std::string, std::shared_ptr<Stream>>& StreamManager::getStreams() const { | ||||
|     return streams; | ||||
| } | ||||
|  | ||||
| void StreamManager::registerSinkProvider(const std::string& name, SinkProvider* provider) { | ||||
|     std::unique_lock<std::shared_mutex> lck(providersMtx); | ||||
|  | ||||
|     // Check that a provider with that name doesn't already exist | ||||
|     if (providers.find(name) != providers.end()) { | ||||
|         flog::error("Tried to register a sink provider with an existing name: {}", name); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Add provider to the list and sort name list | ||||
|     providers[name] = provider; | ||||
|     sinkTypes.push_back(name); | ||||
|     std::sort(sinkTypes.begin(), sinkTypes.end()); | ||||
|  | ||||
|     // Release lock and emit event | ||||
|     lck.unlock(); | ||||
|     onSinkProviderRegistered(name); | ||||
| } | ||||
|  | ||||
| void StreamManager::unregisterSinkProvider(SinkProvider* provider) { | ||||
|     // Get provider name for event | ||||
|     std::string type; | ||||
|     { | ||||
|         std::shared_lock<std::shared_mutex> lck(providersMtx); | ||||
|         auto it = std::find_if(providers.begin(), providers.end(), [&provider](std::pair<const std::string, SinkProvider *> e) { | ||||
|             return e.second == provider; | ||||
|         }); | ||||
|         if (it == providers.end()) { | ||||
|             flog::error("Tried to unregister sink provider using invalid pointer"); | ||||
|             return; | ||||
|         } | ||||
|         type = (*it).first; | ||||
|     } | ||||
|  | ||||
|     // Emit event | ||||
|     onSinkProviderUnregister(type); | ||||
|  | ||||
|     // Acquire shared lock on streams | ||||
|     { | ||||
|         std::unique_lock<std::shared_mutex> lck1(providersMtx); | ||||
|         std::shared_lock<std::shared_mutex> lck2(streamsMtx); | ||||
|         for (auto& [name, stream] : streams) { | ||||
|             // Aquire lock on sink list | ||||
|             auto sLock = stream->getSinksLock(); | ||||
|             const auto& sinks = stream->getSinks(); | ||||
|  | ||||
|             // Find all sinks with the type that is about to be removed | ||||
|             std::vector<SinkID> toRemove; | ||||
|             for (auto& [id, sink] : sinks) { | ||||
|                 if (sink->getType() != type) { continue; } | ||||
|                 toRemove.push_back(id); | ||||
|             } | ||||
|  | ||||
|             // Remove them all (TODO: THERE IS RACE CONDITION IF A SINK IS CHANGED AFTER LISTING) | ||||
|             sLock.unlock(); | ||||
|             for (auto& id : toRemove) { | ||||
|                 stream->removeSink(id); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Remove from the lists | ||||
|         if (providers.find(type) != providers.end()) { | ||||
|             providers.erase(type); | ||||
|         } | ||||
|         else { | ||||
|             flog::error("Could not remove sink provider from list"); | ||||
|         } | ||||
|  | ||||
|         auto it = std::find(sinkTypes.begin(), sinkTypes.end(), type); | ||||
|         if (it != sinkTypes.end()) { | ||||
|             sinkTypes.erase(it); | ||||
|         } | ||||
|         else { | ||||
|             flog::error("Could not remove sink provider from sink type list"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::shared_lock<std::shared_mutex> StreamManager::getSinkTypesLock() { | ||||
|     return std::shared_lock<std::shared_mutex>(providersMtx); | ||||
| } | ||||
|  | ||||
| const std::vector<std::string>& StreamManager::getSinkTypes() const { | ||||
|     // TODO: This allows code to modify the names... | ||||
|     return sinkTypes; | ||||
| } | ||||
							
								
								
									
										334
									
								
								core/src/signal_path/stream.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								core/src/signal_path/stream.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,334 @@ | ||||
| #pragma once | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <dsp/stream.h> | ||||
| #include <dsp/types.h> | ||||
| #include <dsp/routing/splitter.h> | ||||
| #include <dsp/multirate/rational_resampler.h> | ||||
| #include <dsp/audio/volume.h> | ||||
| #include <utils/new_event.h> | ||||
| #include <shared_mutex> | ||||
| #include <stdexcept> | ||||
|  | ||||
| class SinkEntry; | ||||
| class Stream; | ||||
| class MasterStream; | ||||
| class StreamManager; | ||||
|  | ||||
| using SinkID = int; | ||||
|  | ||||
| class Sink { | ||||
| public: | ||||
|     Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId); | ||||
|     virtual ~Sink() {} | ||||
|  | ||||
|     virtual void start() = 0; | ||||
|     virtual void stop() = 0; | ||||
|     virtual void showMenu(); | ||||
|  | ||||
| protected: | ||||
|     SinkEntry* const entry; | ||||
|     dsp::stream<dsp::stereo_t>* const stream; | ||||
|     const std::string streamName; | ||||
|     const SinkID id; | ||||
|     const std::string stringId; | ||||
| }; | ||||
|  | ||||
| class SinkProvider { | ||||
|     friend Sink; | ||||
| public: | ||||
|     /** | ||||
|      * Create a sink instance. | ||||
|      * @param name Name of the audio stream. | ||||
|      * @param index Index of the sink in the menu. Should be use to keep settings. | ||||
|     */ | ||||
|     virtual std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) = 0; | ||||
|  | ||||
|     /** | ||||
|      * Destroy a sink instance. This function is so that the provide knows at all times how many instances there are. | ||||
|      * @param sink Instance of the sink. | ||||
|     */ | ||||
|     virtual void destroySink(std::unique_ptr<Sink> sink) { | ||||
|         sink.reset(); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class SinkEntryCreateException : public std::runtime_error { | ||||
| public: | ||||
|     SinkEntryCreateException(const char* what) : std::runtime_error(what) {} | ||||
| }; | ||||
|  | ||||
| // TODO: Would be cool to have data and audio sinks instead of just audio. | ||||
| class SinkEntry { | ||||
|     friend Sink; | ||||
|     friend Stream; | ||||
|     friend MasterStream; | ||||
| public: | ||||
|     SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate); | ||||
|      | ||||
|     /** | ||||
|      * Get the type of the sink. | ||||
|      * @return Type of the sink. | ||||
|     */ | ||||
|     std::string getType() const; | ||||
|  | ||||
|     /** | ||||
|      * Change the type of the sink. | ||||
|      * @param type New sink type. | ||||
|     */ | ||||
|     void setType(const std::string& type); | ||||
|  | ||||
|     /** | ||||
|      * Get the ID of the sink. | ||||
|      * @return ID of the sink. | ||||
|     */ | ||||
|     SinkID getID() const; | ||||
|  | ||||
|     /** | ||||
|      * Get sink volume. | ||||
|      * @return Volume as value between 0.0 and 1.0. | ||||
|     */ | ||||
|     float getVolume() const; | ||||
|  | ||||
|     /** | ||||
|      * Set sink volume. | ||||
|      * @param volume Volume as value between 0.0 and 1.0. | ||||
|     */ | ||||
|     void setVolume(float volume); | ||||
|  | ||||
|     /** | ||||
|      * Check if the sink is muted. | ||||
|      * @return True if muted, false if not. | ||||
|     */ | ||||
|     bool getMuted() const; | ||||
|  | ||||
|     /** | ||||
|      * Set wether or not the sink is muted | ||||
|      * @param muted True to mute, false to unmute. | ||||
|     */ | ||||
|     void setMuted(bool muted); | ||||
|  | ||||
|     /** | ||||
|      * Get sink panning. | ||||
|      * @return Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively. | ||||
|     */ | ||||
|     float getPanning() const; | ||||
|  | ||||
|     /** | ||||
|      * Set sink panning. | ||||
|      * @param panning Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively. | ||||
|     */ | ||||
|     void setPanning(float panning); | ||||
|  | ||||
|     /** | ||||
|      * Show the sink type-specific menu. | ||||
|     */ | ||||
|     void showMenu(); | ||||
|  | ||||
|     /** | ||||
|      * Get the string form ID unique to both the sink and stream. Be used to reference settings. | ||||
|      * @return Unique string ID. | ||||
|     */ | ||||
|     std::string getStringID() const; | ||||
|  | ||||
|     // Emitted when the type of the sink was changed | ||||
|     NewEvent<const std::string&> onTypeChanged; | ||||
|     // Emmited when volume of the sink was changed | ||||
|     NewEvent<float> onVolumeChanged; | ||||
|     // Emitted when the muted state of the sink was changed | ||||
|     NewEvent<bool> onMutedChanged; | ||||
|     // Emitted when the panning of the sink was changed | ||||
|     NewEvent<float> onPanningChanged; | ||||
|  | ||||
|     // TODO: Need to allow the sink to change the entry samplerate and start/stop the DSP | ||||
|     //       This will also require allowing it to get a lock on the sink so others don't attempt to mess with it. | ||||
|     std::lock_guard<std::recursive_mutex> getLock() const; | ||||
|     void startDSP(); | ||||
|     void stopDSP(); | ||||
|     void setSamplerate(double samplerate); | ||||
|  | ||||
| private: | ||||
|     void startSink(); | ||||
|     void stopSink(); | ||||
|      | ||||
|     void destroy(bool forgetSettings); | ||||
|     void setInputSamplerate(double samplerate); | ||||
|  | ||||
|     mutable std::recursive_mutex mtx; | ||||
|     dsp::stream<dsp::stereo_t> input; | ||||
|     dsp::multirate::RationalResampler<dsp::stereo_t> resamp; | ||||
|     dsp::audio::Volume volumeAdjust; | ||||
|  | ||||
|     SinkProvider* provider = NULL; | ||||
|     std::unique_ptr<Sink> sink; | ||||
|     std::string type; | ||||
|     const SinkID id; | ||||
|     double inputSamplerate; | ||||
|     Stream* const parentStream; | ||||
|     StreamManager* const manager; | ||||
|  | ||||
|     std::string stringId; | ||||
|      | ||||
|     float volume = 1.0f; | ||||
|     bool muted = false; | ||||
|     float panning = 0.0f; | ||||
| }; | ||||
|  | ||||
| class Stream { | ||||
| protected: | ||||
|     Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate); | ||||
| public: | ||||
|     ~Stream(); | ||||
|  | ||||
|     /** | ||||
|      * Get the name of the stream. | ||||
|      * @return Name of the stream. | ||||
|     */ | ||||
|     const std::string& getName() const; | ||||
|  | ||||
|     /** | ||||
|      * Add a sink to the stream. | ||||
|      * @param type Type of the sink. | ||||
|      * @param id ID of the sink. Optional, -1 if automatic. | ||||
|      * @return ID of the new sink or -1 on error. | ||||
|     */ | ||||
|     SinkID addSink(const std::string& type, SinkID id = -1); | ||||
|  | ||||
|     /** | ||||
|      * Remove a sink from a stream. | ||||
|      * @param id ID of the sink. | ||||
|      * @param forgetSettings Forget the settings for the sink. | ||||
|     */ | ||||
|     void removeSink(SinkID id, bool forgetSettings = true); | ||||
|  | ||||
|     /** | ||||
|      * Aquire a lock for the sink list. | ||||
|      * @return Shared lock for the sink list. | ||||
|     */ | ||||
|     std::shared_lock<std::shared_mutex> getSinksLock(); | ||||
|  | ||||
|     /** | ||||
|      * Get the list of all sinks belonging to this stream. | ||||
|      * @return Sink list. | ||||
|     */ | ||||
|     const std::map<SinkID, std::shared_ptr<SinkEntry>>& getSinks() const; | ||||
|  | ||||
|     // Emitted when the samplerate of the stream was changed | ||||
|     NewEvent<double> onSamplerateChanged; | ||||
|     // Emitted when a sink was added | ||||
|     NewEvent<std::shared_ptr<SinkEntry>> onSinkAdded; | ||||
|     // Emitted when a sink is being removed | ||||
|     NewEvent<std::shared_ptr<SinkEntry>> onSinkRemove; | ||||
|  | ||||
| protected: | ||||
|     StreamManager* const manager; | ||||
|     const std::string name; | ||||
|     double samplerate; | ||||
|     dsp::routing::Splitter<dsp::stereo_t> split; | ||||
|     bool running = false; | ||||
|  | ||||
|     std::map<SinkID, std::shared_ptr<SinkEntry>> sinks; | ||||
|     std::shared_mutex sinksMtx; | ||||
| }; | ||||
|  | ||||
| class MasterStream : public Stream { | ||||
|     friend StreamManager; | ||||
|     MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate); | ||||
| public: | ||||
|     /** | ||||
|      * Set DSP stream input. | ||||
|      * @param stream DSP stream. | ||||
|      * @param samplerate New samplerate (optional, 0.0 if not used). | ||||
|     */ | ||||
|     void setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate = 0.0); | ||||
|  | ||||
|     /** | ||||
|      * Set the samplerate of the input stream. | ||||
|      * @param samplerate Samplerate in Hz. | ||||
|     */ | ||||
|     void setSamplerate(double samplerate); | ||||
|  | ||||
|     /** | ||||
|      * Start the DSP. | ||||
|     */ | ||||
|     void startDSP(); | ||||
|  | ||||
|     /** | ||||
|      * Stop the DSP. | ||||
|     */ | ||||
|     void stopDSP(); | ||||
| }; | ||||
|  | ||||
| class StreamManager { | ||||
|     friend SinkEntry; | ||||
| public: | ||||
|     /** | ||||
|      * Create an audio stream. | ||||
|      * @param name Name of the stream. | ||||
|      * @param stream DSP stream that outputs the audio. | ||||
|      * @param samplerate Samplerate of the audio data. | ||||
|      * @return Audio stream instance. | ||||
|     */ | ||||
|     std::shared_ptr<MasterStream> createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate); | ||||
|  | ||||
|     /** | ||||
|      * Destroy an audio stream. | ||||
|      * @param stream Stream to destroy. The passed shared pointer will be automatically reset. | ||||
|     */ | ||||
|     void destroyStream(std::shared_ptr<MasterStream>& stream); | ||||
|  | ||||
|     /** | ||||
|      * Aquire a lock for the stream list. | ||||
|      * @return Shared lock for the stream list. | ||||
|     */ | ||||
|     std::shared_lock<std::shared_mutex> getStreamsLock(); | ||||
|  | ||||
|     /** | ||||
|      * Get a list of streams and their associated names. | ||||
|      * @return Map of names to stream instance. | ||||
|     */ | ||||
|     const std::map<std::string, std::shared_ptr<Stream>>& getStreams() const; | ||||
|  | ||||
|     /** | ||||
|      * Register a sink provider. | ||||
|      * @param name Name of the sink type. | ||||
|      * @param provider Sink provider instance. | ||||
|     */ | ||||
|     void registerSinkProvider(const std::string& name, SinkProvider* provider); | ||||
|  | ||||
|     /** | ||||
|      * Unregister a sink provider. | ||||
|      * @param name Name of the sink type. | ||||
|     */ | ||||
|     void unregisterSinkProvider(SinkProvider* provider); | ||||
|  | ||||
|     /** | ||||
|      * Aquire a lock for the sink type list. | ||||
|      * @return Shared lock for the sink type list. | ||||
|     */ | ||||
|     std::shared_lock<std::shared_mutex> getSinkTypesLock(); | ||||
|  | ||||
|     /** | ||||
|      * Get a list of sink types. | ||||
|      * @return List of sink type names in alphabetical order. | ||||
|     */ | ||||
|     const std::vector<std::string>& getSinkTypes() const; | ||||
|  | ||||
|     // Emitted when a stream was created | ||||
|     NewEvent<std::shared_ptr<Stream>> onStreamCreated; | ||||
|     // Emitted when a stream is about to be destroyed | ||||
|     NewEvent<std::shared_ptr<Stream>> onStreamDestroy; | ||||
|     // Emitted when a sink provider was registered | ||||
|     NewEvent<const std::string&> onSinkProviderRegistered; | ||||
|     // Emitted when a sink provider is about to be unregistered | ||||
|     NewEvent<const std::string&> onSinkProviderUnregister; | ||||
|  | ||||
| private: | ||||
|     std::map<std::string, std::shared_ptr<Stream>> streams; | ||||
|     std::shared_mutex streamsMtx; | ||||
|  | ||||
|     std::map<std::string, SinkProvider*> providers; | ||||
|     std::vector<std::string> sinkTypes; | ||||
|     std::shared_mutex providersMtx; | ||||
| }; | ||||
| @@ -63,6 +63,7 @@ namespace net::http { | ||||
|  | ||||
|     std::string MessageHeader::getField(const std::string name) { | ||||
|         // TODO: Check if exists | ||||
|         // TODO: Maybe declare the set/get field functions to do type conversions automatically? | ||||
|         return fields[name]; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| #pragma once | ||||
|  | ||||
| #define VERSION_STR "1.2.0" | ||||
| #define VERSION_STR "1.1.0" | ||||
| @@ -8,15 +8,15 @@ | ||||
| #include "dsp.h" | ||||
| #include "pocsag.h" | ||||
|  | ||||
| #define BAUDRATE    2400 | ||||
| #define SAMPLERATE  (BAUDRATE*10) | ||||
|  | ||||
| class POCSAGDecoder : public Decoder { | ||||
| public: | ||||
|     POCSAGDecoder(const std::string& name, VFOManager::VFO* vfo) : diag(0.6, 2400) { | ||||
|     POCSAGDecoder(const std::string& name, VFOManager::VFO* vfo) : diag(0.6, BAUDRATE) { | ||||
|         this->name = name; | ||||
|         this->vfo = vfo; | ||||
|  | ||||
|         // Default baudrate (TODO: Load from config) | ||||
|         baudrate = 2400; | ||||
|  | ||||
|         // Define baudrate options | ||||
|         baudrates.define(512, "512 Baud", 512); | ||||
|         baudrates.define(1200, "1200 Baud", 1200); | ||||
| @@ -24,9 +24,9 @@ public: | ||||
|  | ||||
|         // Init DSP | ||||
|         vfo->setBandwidthLimits(12500, 12500, true); | ||||
|         vfo->setSampleRate(baudrate*10.0, 12500); | ||||
|         dsp.init(vfo->output, baudrate*10.0, baudrate); | ||||
|         reshape.init(&dsp.soft, baudrate, (baudrate / 30.0) - baudrate); | ||||
|         vfo->setSampleRate(SAMPLERATE, 12500); | ||||
|         dsp.init(vfo->output, SAMPLERATE, BAUDRATE); | ||||
|         reshape.init(&dsp.soft, BAUDRATE, (BAUDRATE / 30.0) - BAUDRATE); | ||||
|         dataHandler.init(&dsp.out, _dataHandler, this); | ||||
|         diagHandler.init(&reshape.out, _diagHandler, this); | ||||
|  | ||||
| @@ -42,7 +42,7 @@ public: | ||||
|         ImGui::LeftLabel("Baudrate"); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::Combo(("##pager_decoder_pocsag_br_" + name).c_str(), &brId, baudrates.txt)) { | ||||
|             setBaudrate(baudrates.value(brId)); | ||||
|             // TODO | ||||
|         } | ||||
|  | ||||
|         ImGui::FillWidth(); | ||||
| @@ -79,8 +79,7 @@ private: | ||||
|     static void _diagHandler(float* data, int count, void* ctx) { | ||||
|         POCSAGDecoder* _this = (POCSAGDecoder*)ctx; | ||||
|         float* buf = _this->diag.acquireBuffer(); | ||||
|         int maxCount = std::min<int>(count, _this->diag.getCount()); | ||||
|         memcpy(buf, data, maxCount * sizeof(float)); | ||||
|         memcpy(buf, data, count * sizeof(float)); | ||||
|         _this->diag.releaseBuffer(); | ||||
|     } | ||||
|  | ||||
| @@ -88,15 +87,6 @@ private: | ||||
|         flog::debug("[{}]: '{}'", (uint32_t)addr, msg); | ||||
|     } | ||||
|  | ||||
|     void setBaudrate(double baudrate) { | ||||
|         vfo->setSampleRate(baudrate*10.0, 12500); | ||||
|         stop(); | ||||
|         reshape.setKeep(baudrate); | ||||
|         reshape.setSkip((baudrate / 30.0) - baudrate); | ||||
|         diag.setCount(baudrate); | ||||
|         start(); | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     VFOManager::VFO* vfo; | ||||
|  | ||||
| @@ -110,7 +100,6 @@ private: | ||||
|     ImGui::SymbolDiagram diag; | ||||
|  | ||||
|     int brId = 2; | ||||
|     double baudrate = 2400; | ||||
|  | ||||
|     OptionList<int, int> baudrates; | ||||
| }; | ||||
| @@ -24,13 +24,12 @@ namespace demod { | ||||
|     class Demodulator { | ||||
|     public: | ||||
|         virtual ~Demodulator() {} | ||||
|         virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) = 0; | ||||
|         virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) = 0; | ||||
|         virtual void start() = 0; | ||||
|         virtual void stop() = 0; | ||||
|         virtual void showMenu() = 0; | ||||
|         virtual void setBandwidth(double bandwidth) = 0; | ||||
|         virtual void setInput(dsp::stream<dsp::complex_t>* input) = 0; | ||||
|         virtual void AFSampRateChanged(double newSR) = 0; | ||||
|         virtual const char* getName() = 0; | ||||
|         virtual double getIFSampleRate() = 0; | ||||
|         virtual double getAFSampleRate() = 0; | ||||
|   | ||||
| @@ -7,13 +7,13 @@ namespace demod { | ||||
|     public: | ||||
|         AM() {} | ||||
|  | ||||
|         AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~AM() { stop(); } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             _config = config; | ||||
|  | ||||
| @@ -68,8 +68,6 @@ namespace demod { | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "AM"; } | ||||
|   | ||||
| @@ -7,15 +7,15 @@ namespace demod { | ||||
|     public: | ||||
|         CW() {} | ||||
|  | ||||
|         CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~CW() { | ||||
|             stop(); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             this->_config = config; | ||||
|             this->afbwChangeHandler = afbwChangeHandler; | ||||
| @@ -74,8 +74,6 @@ namespace demod { | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "CW"; } | ||||
|   | ||||
| @@ -7,15 +7,15 @@ namespace demod { | ||||
|     public: | ||||
|         DSB() {} | ||||
|  | ||||
|         DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~DSB() { | ||||
|             stop(); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             _config = config; | ||||
|  | ||||
| @@ -61,8 +61,6 @@ namespace demod { | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "DSB"; } | ||||
|   | ||||
| @@ -7,15 +7,15 @@ namespace demod { | ||||
|     public: | ||||
|         LSB() {} | ||||
|  | ||||
|         LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~LSB() { | ||||
|             stop(); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             _config = config; | ||||
|  | ||||
| @@ -61,8 +61,6 @@ namespace demod { | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "LSB"; } | ||||
|   | ||||
| @@ -7,13 +7,13 @@ namespace demod { | ||||
|     public: | ||||
|         NFM() {} | ||||
|  | ||||
|         NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~NFM() { stop(); } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             this->_config = config; | ||||
|  | ||||
| @@ -57,8 +57,6 @@ namespace demod { | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "FM"; } | ||||
|   | ||||
| @@ -7,17 +7,18 @@ namespace demod { | ||||
|     public: | ||||
|         RAW() {} | ||||
|  | ||||
|         RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~RAW() { | ||||
|             stop(); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             audioSampleRate = audioSR; | ||||
|             audioSampleRate = 48000; | ||||
|             // TODO: This needs to be selectable | ||||
|  | ||||
|             // Define structure | ||||
|             c2s.init(input); | ||||
| @@ -39,10 +40,6 @@ namespace demod { | ||||
|             c2s.setInput(input); | ||||
|         } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) { | ||||
|             audioSampleRate = newSR; | ||||
|         } | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "RAW"; } | ||||
|   | ||||
| @@ -8,15 +8,15 @@ namespace demod { | ||||
|     public: | ||||
|         USB() {} | ||||
|  | ||||
|         USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~USB() { | ||||
|             stop(); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             _config = config; | ||||
|  | ||||
| @@ -62,8 +62,6 @@ namespace demod { | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "USB"; } | ||||
|   | ||||
| @@ -16,8 +16,8 @@ namespace demod { | ||||
|     public: | ||||
|         WFM() : diag(0.5, 4096)  {} | ||||
|  | ||||
|         WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) : diag(0.5, 4096) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) : diag(0.5, 4096) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~WFM() { | ||||
| @@ -25,7 +25,7 @@ namespace demod { | ||||
|             gui::waterfall.onFFTRedraw.unbindHandler(&fftRedrawHandler); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             _config = config; | ||||
|  | ||||
| @@ -252,8 +252,6 @@ namespace demod { | ||||
|             demod.setInput(input); | ||||
|         } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "WFM"; } | ||||
|   | ||||
| @@ -81,30 +81,16 @@ public: | ||||
|         // Initialize audio DSP chain | ||||
|         afChain.init(&dummyAudioStream); | ||||
|  | ||||
|         resamp.init(NULL, 250000.0, 48000.0); | ||||
|         deemp.init(NULL, 50e-6, 48000.0); | ||||
|  | ||||
|         afChain.addBlock(&resamp, true); | ||||
|         afChain.addBlock(&deemp, false); | ||||
|  | ||||
|         // Initialize the sink | ||||
|         srChangeHandler.ctx = this; | ||||
|         srChangeHandler.handler = sampleRateChangeHandler; | ||||
|         stream.init(afChain.out, &srChangeHandler, audioSampleRate); | ||||
|         sigpath::sinkManager.registerStream(name, &stream); | ||||
|         stream = sigpath::streamManager.createStream(name, afChain.out, 48000); | ||||
|  | ||||
|         // Select the demodulator | ||||
|         selectDemodByID((DemodID)selectedDemodID); | ||||
|  | ||||
|         // Start IF chain | ||||
|         ifChain.start(); | ||||
|  | ||||
|         // Start AF chain | ||||
|         afChain.start(); | ||||
|  | ||||
|         // Start stream, the rest was started when selecting the demodulator | ||||
|         stream.start(); | ||||
|  | ||||
|         // Register the menu | ||||
|         gui::menu.registerEntry(name, menuHandler, this, this); | ||||
|  | ||||
| @@ -115,11 +101,10 @@ public: | ||||
|     ~RadioModule() { | ||||
|         core::modComManager.unregisterInterface(name); | ||||
|         gui::menu.removeEntry(name); | ||||
|         stream.stop(); | ||||
|         if (enabled) { | ||||
|             disable(); | ||||
|         } | ||||
|         sigpath::sinkManager.unregisterStream(name); | ||||
|         sigpath::streamManager.destroyStream(stream); | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
| @@ -131,9 +116,7 @@ public: | ||||
|             vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler); | ||||
|         } | ||||
|         ifChain.setInput(vfo->output, [=](dsp::stream<dsp::complex_t>* out){ ifChainOutputChangeHandler(out, this); }); | ||||
|         ifChain.start(); | ||||
|         selectDemodByID((DemodID)selectedDemodID); | ||||
|         afChain.start(); | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
| @@ -141,6 +124,7 @@ public: | ||||
|         ifChain.stop(); | ||||
|         if (selectedDemod) { selectedDemod->stop(); } | ||||
|         afChain.stop(); | ||||
|         stream->stopDSP(); | ||||
|         if (vfo) { sigpath::vfoManager.deleteVFO(vfo); } | ||||
|         vfo = NULL; | ||||
|     } | ||||
| @@ -313,7 +297,7 @@ private: | ||||
|         bw = std::clamp<double>(bw, demod->getMinBandwidth(), demod->getMaxBandwidth()); | ||||
|  | ||||
|         // Initialize | ||||
|         demod->init(name, &config, ifChain.out, bw, stream.getSampleRate()); | ||||
|         demod->init(name, &config, ifChain.out, bw); | ||||
|  | ||||
|         return demod; | ||||
|     } | ||||
| @@ -337,22 +321,34 @@ private: | ||||
|     } | ||||
|  | ||||
|     void selectDemod(demod::Demodulator* demod) { | ||||
|         // Stopcurrently selected demodulator and select new | ||||
|         afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); | ||||
|         // Stop the IF chain | ||||
|         ifChain.stop(); | ||||
|  | ||||
|         // Stop the current demodulator | ||||
|         if (selectedDemod) { | ||||
|             selectedDemod->stop(); | ||||
|         } | ||||
|  | ||||
|         // Stop AF chain | ||||
|         afChain.stop(); | ||||
|  | ||||
|         // Stop audio stream's DSP | ||||
|         stream->stopDSP(); | ||||
|  | ||||
|         // Destroy the old demodulator | ||||
|         afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); }); | ||||
|         if (selectedDemod) { | ||||
|             delete selectedDemod; | ||||
|         } | ||||
|         selectedDemod = demod; | ||||
|  | ||||
|         // Give the demodulator the most recent audio SR | ||||
|         selectedDemod->AFSampRateChanged(audioSampleRate); | ||||
|         // Select the new demodulator | ||||
|         selectedDemod = demod; | ||||
|  | ||||
|         // Set the demodulator's input | ||||
|         selectedDemod->setInput(ifChain.out); | ||||
|  | ||||
|         // Set AF chain's input | ||||
|         afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); | ||||
|         afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); }); | ||||
|  | ||||
|         // Load config | ||||
|         bandwidth = selectedDemod->getDefaultBandwidth(); | ||||
| @@ -440,21 +436,30 @@ private: | ||||
|         // Configure AF chain | ||||
|         if (postProcEnabled) { | ||||
|             // Configure resampler | ||||
|             afChain.stop(); | ||||
|             resamp.setInSamplerate(selectedDemod->getAFSampleRate()); | ||||
|             setAudioSampleRate(audioSampleRate); | ||||
|             afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); | ||||
|             deemp.setSamplerate(selectedDemod->getAFSampleRate()); | ||||
|  | ||||
|             // Configure deemphasis | ||||
|             setDeemphasisMode(deempModes[deempId]); | ||||
|         } | ||||
|         else { | ||||
|             // Disable everything if post processing is disabled | ||||
|             afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); | ||||
|             afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); }); | ||||
|         } | ||||
|  | ||||
|         // Update audo samplerate | ||||
|         stream->setSamplerate(selectedDemod->getAFSampleRate()); | ||||
|  | ||||
|         // Start the IF chain | ||||
|         ifChain.start(); | ||||
|  | ||||
|         // Start new demodulator | ||||
|         selectedDemod->start(); | ||||
|  | ||||
|         // Start the AF chain | ||||
|         afChain.start(); | ||||
|  | ||||
|         // Start the audio stream | ||||
|         stream->startDSP(); | ||||
|     } | ||||
|  | ||||
|  | ||||
| @@ -470,37 +475,12 @@ private: | ||||
|         config.release(true); | ||||
|     } | ||||
|  | ||||
|     void setAudioSampleRate(double sr) { | ||||
|         audioSampleRate = sr; | ||||
|         if (!selectedDemod) { return; } | ||||
|         selectedDemod->AFSampRateChanged(audioSampleRate); | ||||
|         if (!postProcEnabled && vfo) { | ||||
|             // If postproc is disabled, IF SR = AF SR | ||||
|             minBandwidth = selectedDemod->getMinBandwidth(); | ||||
|             maxBandwidth = selectedDemod->getMaxBandwidth(); | ||||
|             bandwidth = selectedDemod->getIFSampleRate(); | ||||
|             vfo->setBandwidthLimits(minBandwidth, maxBandwidth, selectedDemod->getBandwidthLocked()); | ||||
|             vfo->setSampleRate(selectedDemod->getIFSampleRate(), bandwidth); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         afChain.stop(); | ||||
|  | ||||
|         // Configure resampler | ||||
|         resamp.setOutSamplerate(audioSampleRate); | ||||
|  | ||||
|         // Configure deemphasis sample rate | ||||
|         deemp.setSamplerate(audioSampleRate); | ||||
|  | ||||
|         afChain.start(); | ||||
|     } | ||||
|  | ||||
|     void setDeemphasisMode(DeemphasisMode mode) { | ||||
|         deempId = deempModes.valueId(mode); | ||||
|         if (!postProcEnabled || !selectedDemod) { return; } | ||||
|         bool deempEnabled = (mode != DEEMP_MODE_NONE); | ||||
|         if (deempEnabled) { deemp.setTau(deempTaus[mode]); } | ||||
|         afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); | ||||
|         afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); }); | ||||
|  | ||||
|         // Save config | ||||
|         config.acquire(); | ||||
| @@ -584,11 +564,6 @@ private: | ||||
|         _this->setBandwidth(newBw); | ||||
|     } | ||||
|  | ||||
|     static void sampleRateChangeHandler(float sampleRate, void* ctx) { | ||||
|         RadioModule* _this = (RadioModule*)ctx; | ||||
|         _this->setAudioSampleRate(sampleRate); | ||||
|     } | ||||
|  | ||||
|     static void ifChainOutputChangeHandler(dsp::stream<dsp::complex_t>* output, void* ctx) { | ||||
|         RadioModule* _this = (RadioModule*)ctx; | ||||
|         if (!_this->selectedDemod) { return; } | ||||
| @@ -597,16 +572,14 @@ private: | ||||
|  | ||||
|     static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) { | ||||
|         RadioModule* _this = (RadioModule*)ctx; | ||||
|  | ||||
|         // If no demod is selected, reject the command | ||||
|         if (!_this->selectedDemod) { return; } | ||||
|         if (!_this->enabled || !_this->selectedDemod) { return; } | ||||
|  | ||||
|         // Execute commands | ||||
|         if (code == RADIO_IFACE_CMD_GET_MODE && out) { | ||||
|             int* _out = (int*)out; | ||||
|             *_out = _this->selectedDemodID; | ||||
|         } | ||||
|         else if (code == RADIO_IFACE_CMD_SET_MODE && in && _this->enabled) { | ||||
|         else if (code == RADIO_IFACE_CMD_SET_MODE && in) { | ||||
|             int* _in = (int*)in; | ||||
|             _this->selectDemodByID((DemodID)*_in); | ||||
|         } | ||||
| @@ -614,7 +587,7 @@ private: | ||||
|             float* _out = (float*)out; | ||||
|             *_out = _this->bandwidth; | ||||
|         } | ||||
|         else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in && _this->enabled) { | ||||
|         else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in) { | ||||
|             float* _in = (float*)in; | ||||
|             if (_this->bandwidthLocked) { return; } | ||||
|             _this->setBandwidth(*_in); | ||||
| @@ -623,7 +596,7 @@ private: | ||||
|             bool* _out = (bool*)out; | ||||
|             *_out = _this->squelchEnabled; | ||||
|         } | ||||
|         else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in && _this->enabled) { | ||||
|         else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in) { | ||||
|             bool* _in = (bool*)in; | ||||
|             _this->setSquelchEnabled(*_in); | ||||
|         } | ||||
| @@ -631,7 +604,7 @@ private: | ||||
|             float* _out = (float*)out; | ||||
|             *_out = _this->squelchLevel; | ||||
|         } | ||||
|         else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in && _this->enabled) { | ||||
|         else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in) { | ||||
|             float* _in = (float*)in; | ||||
|             _this->setSquelchLevel(*_in); | ||||
|         } | ||||
| @@ -645,7 +618,6 @@ private: | ||||
|  | ||||
|     // Handlers | ||||
|     EventHandler<double> onUserChangedBandwidthHandler; | ||||
|     EventHandler<float> srChangeHandler; | ||||
|     EventHandler<dsp::stream<dsp::complex_t>*> ifChainOutputChanged; | ||||
|     EventHandler<dsp::stream<dsp::stereo_t>*> afChainOutputChanged; | ||||
|  | ||||
| @@ -660,10 +632,9 @@ private: | ||||
|     // Audio chain | ||||
|     dsp::stream<dsp::stereo_t> dummyAudioStream; | ||||
|     dsp::chain<dsp::stereo_t> afChain; | ||||
|     dsp::multirate::RationalResampler<dsp::stereo_t> resamp; | ||||
|     dsp::filter::Deemphasis<dsp::stereo_t> deemp; | ||||
|  | ||||
|     SinkManager::Stream stream; | ||||
|     std::shared_ptr<MasterStream> stream; | ||||
|  | ||||
|     demod::Demodulator* selectedDemod = NULL; | ||||
|  | ||||
|   | ||||
| @@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
|   | ||||
| @@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
|   | ||||
| @@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
|   | ||||
| @@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev l | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
|   | ||||
| @@ -15,10 +15,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev | ||||
|             libcodec2-dev libudev-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install a more recent libusb version | ||||
| @@ -71,4 +71,4 @@ make VERBOSE=1 -j2 | ||||
|  | ||||
| # Generate package | ||||
| cd .. | ||||
| sh make_debian_package.sh ./build 'libfftw3-bin, libglfw3, libvolk1-bin, librtaudio6, libzstd1' | ||||
| sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk1-dev, librtaudio-dev, libzstd-dev' | ||||
| @@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| @@ -32,4 +32,4 @@ cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
| sh make_debian_package.sh ./build 'libfftw3-bin, libglfw3, libvolk2-bin, librtaudio6, libzstd1' | ||||
| sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev, libzstd-dev' | ||||
| @@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| @@ -32,4 +32,4 @@ cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
| sh make_debian_package.sh ./build 'libfftw3-bin, libglfw3, libvolk2-bin, librtaudio6, libzstd1' | ||||
| sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev, libzstd-dev' | ||||
| @@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev l | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| @@ -32,4 +32,4 @@ cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
| sh make_debian_package.sh ./build 'libfftw3-bin, libglfw3, libvolk-bin, librtaudio6, libzstd1' | ||||
| sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev' | ||||
| @@ -1,4 +0,0 @@ | ||||
| FROM ubuntu:noble | ||||
| ENV DEBIAN_FRONTEND=noninteractive  | ||||
| COPY do_build.sh /root | ||||
| RUN chmod +x /root/do_build.sh | ||||
| @@ -1,35 +0,0 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
| cd /root | ||||
|  | ||||
| # Install dependencies and tools | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-dev libairspyhf-dev libairspy-dev \ | ||||
|             libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| git clone https://github.com/Microtelecom/libperseus-sdr | ||||
| cd libperseus-sdr | ||||
| autoreconf -i | ||||
| ./configure | ||||
| make | ||||
| make install | ||||
| ldconfig | ||||
| cd .. | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
| sh make_debian_package.sh ./build 'libfftw3-bin, libglfw3, libvolk-bin, librtaudio6, libzstd1' | ||||
| @@ -8,7 +8,7 @@ mkdir sdrpp_debian_amd64/DEBIAN | ||||
| # Create package info | ||||
| echo Create package info | ||||
| echo Package: sdrpp >> sdrpp_debian_amd64/DEBIAN/control | ||||
| echo Version: 1.2.0$BUILD_NO >> sdrpp_debian_amd64/DEBIAN/control | ||||
| echo Version: 1.1.0$BUILD_NO >> sdrpp_debian_amd64/DEBIAN/control | ||||
| echo Maintainer: Ryzerth >> sdrpp_debian_amd64/DEBIAN/control | ||||
| echo Architecture: all >> sdrpp_debian_amd64/DEBIAN/control | ||||
| echo Description: Bloat-free SDR receiver software >> sdrpp_debian_amd64/DEBIAN/control | ||||
|   | ||||
| @@ -22,7 +22,7 @@ cp -R root/res/* $BUNDLE/Contents/Resources/ | ||||
| bundle_create_icns root/res/icons/sdrpp.macos.png $BUNDLE/Contents/Resources/sdrpp | ||||
|  | ||||
| # Create the property list | ||||
| bundle_create_plist sdrpp SDR++ org.sdrpp.sdrpp 1.2.0 sdrp sdrpp sdrpp $BUNDLE/Contents/Info.plist | ||||
| bundle_create_plist sdrpp SDR++ org.sdrpp.sdrpp 1.1.0 sdrp sdrpp sdrpp $BUNDLE/Contents/Info.plist | ||||
|  | ||||
| # ========================= Install binaries ========================= | ||||
|  | ||||
|   | ||||
| @@ -7,86 +7,85 @@ mkdir sdrpp_windows_x64 | ||||
| cp -Recurse $root_dir/* sdrpp_windows_x64/ | ||||
|  | ||||
| # Copy core | ||||
| cp $build_dir/Debug/* sdrpp_windows_x64/ | ||||
| cp $build_dir/core/Debug/sdrpp_core.pdb sdrpp_windows_x64/ | ||||
| cp $build_dir/Release/* sdrpp_windows_x64/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/volk.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| # Copy source modules | ||||
| cp $build_dir/source_modules/airspy_source/Debug/airspy_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/airspy_source/Release/airspy_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/airspy.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/airspyhf_source/Debug/airspyhf_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/airspyhf_source/Release/airspyhf_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/airspyhf.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/audio_source/Debug/audio_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/audio_source/Release/audio_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/source_modules/bladerf_source/Debug/bladerf_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/bladerf_source/Release/bladerf_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/bladeRF.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/file_source/Debug/file_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/file_source/Release/file_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/source_modules/hackrf_source/Debug/hackrf_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/hackrf_source/Release/hackrf_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/hackrf.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/hermes_source/Debug/hermes_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/hermes_source/Release/hermes_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/source_modules/limesdr_source/Debug/limesdr_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/limesdr_source/Release/limesdr_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/LimeSuite.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/perseus_source/Debug/perseus_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/perseus_source/Release/perseus_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/perseus-sdr.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/plutosdr_source/Debug/plutosdr_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/plutosdr_source/Release/plutosdr_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/libiio.dll' sdrpp_windows_x64/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/libad9361.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/rfspace_source/Debug/rfspace_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/rfspace_source/Release/rfspace_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/source_modules/rtl_sdr_source/Debug/rtl_sdr_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/rtl_sdr_source/Release/rtl_sdr_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/rtlsdr.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/rtl_tcp_source/Debug/rtl_tcp_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/rtl_tcp_source/Release/rtl_tcp_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/source_modules/sdrplay_source/Debug/sdrplay_source.dll sdrpp_windows_x64/modules/ -ErrorAction SilentlyContinue | ||||
| cp $build_dir/source_modules/sdrplay_source/Release/sdrplay_source.dll sdrpp_windows_x64/modules/ -ErrorAction SilentlyContinue | ||||
| cp 'C:/Program Files/SDRplay/API/x64/sdrplay_api.dll' sdrpp_windows_x64/ -ErrorAction SilentlyContinue | ||||
|  | ||||
| cp $build_dir/source_modules/sdrpp_server_source/Debug/sdrpp_server_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/sdrpp_server_source/Release/sdrpp_server_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/source_modules/spyserver_source/Debug/spyserver_source.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/source_modules/spyserver_source/Release/spyserver_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| # cp $build_dir/source_modules/usrp_source/Debug/usrp_source.dll sdrpp_windows_x64/modules/ | ||||
| # cp $build_dir/source_modules/usrp_source/Release/usrp_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
|  | ||||
| # Copy sink modules | ||||
| cp $build_dir/sink_modules/audio_sink/Debug/audio_sink.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/sink_modules/audio_sink/Release/audio_sink.dll sdrpp_windows_x64/modules/ | ||||
| cp "C:/Program Files (x86)/RtAudio/bin/rtaudio.dll" sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/sink_modules/network_sink/Debug/network_sink.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/sink_modules/network_sink/Release/network_sink.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
|  | ||||
| # Copy decoder modules | ||||
| cp $build_dir/decoder_modules/m17_decoder/Debug/m17_decoder.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/decoder_modules/m17_decoder/Release/m17_decoder.dll sdrpp_windows_x64/modules/ | ||||
| cp "C:/Program Files/codec2/lib/libcodec2.dll" sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/decoder_modules/meteor_demodulator/Debug/meteor_demodulator.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/decoder_modules/meteor_demodulator/Release/meteor_demodulator.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/decoder_modules/radio/Debug/radio.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/decoder_modules/radio/Release/radio.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
|  | ||||
| # Copy misc modules | ||||
| cp $build_dir/misc_modules/discord_integration/Debug/discord_integration.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/misc_modules/discord_integration/Release/discord_integration.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/misc_modules/frequency_manager/Debug/frequency_manager.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/misc_modules/frequency_manager/Release/frequency_manager.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/misc_modules/iq_exporter/Debug/iq_exporter.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/misc_modules/iq_exporter/Release/iq_exporter.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/misc_modules/recorder/Debug/recorder.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/misc_modules/recorder/Release/recorder.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/misc_modules/rigctl_client/Debug/rigctl_client.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/misc_modules/rigctl_client/Release/rigctl_client.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/misc_modules/rigctl_server/Debug/rigctl_server.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/misc_modules/rigctl_server/Release/rigctl_server.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/misc_modules/scanner/Debug/scanner.dll sdrpp_windows_x64/modules/ | ||||
| cp $build_dir/misc_modules/scanner/Release/scanner.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
|  | ||||
| # Copy supporting libs | ||||
|   | ||||
| @@ -1,12 +0,0 @@ | ||||
| #include <stdio.h> | ||||
| #include <mutex> | ||||
|  | ||||
| std::recursive_mutex mtx; | ||||
|  | ||||
| int main() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     printf("Works just fine!\n"); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
| @@ -476,9 +476,9 @@ private: | ||||
|         sprintf(monStr, "%02d", ltm->tm_mon + 1); | ||||
|         sprintf(yearStr, "%02d", ltm->tm_year + 1900); | ||||
|         if (core::modComManager.getModuleName(name) == "radio") { | ||||
|             int mode = -1; | ||||
|             int mode; | ||||
|             core::modComManager.callInterface(name, RADIO_IFACE_CMD_GET_MODE, NULL, &mode); | ||||
|             if (mode >= 0) { modeStr = radioModeToString[mode]; }; | ||||
|             modeStr = radioModeToString[mode]; | ||||
|         } | ||||
|  | ||||
|         // Replace in template | ||||
|   | ||||
| @@ -331,7 +331,6 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
| | network_source       | Unfinished | -                 | OPT_BUILD_NETWORK_SOURCE       | ✅              | ✅                     | ⛔                         | | ||||
| | perseus_source       | Beta       | libperseus-sdr    | OPT_BUILD_PERSEUS_SOURCE       | ⛔              | ✅                     | ✅                         | | ||||
| | plutosdr_source      | Working    | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE      | ✅              | ✅                     | ✅                         | | ||||
| | rfnm_source          | Beta       | librfnm           | OPT_BUILD_RFNM_SOURCE          | ⛔              | ⛔                     | ⛔                         | | ||||
| | rfspace_source       | Working    | -                 | OPT_BUILD_RFSPACE_SOURCE       | ✅              | ✅                     | ✅                         | | ||||
| | rtl_sdr_source       | Working    | librtlsdr         | OPT_BUILD_RTL_SDR_SOURCE       | ✅              | ✅                     | ✅                         | | ||||
| | rtl_tcp_source       | Working    | -                 | OPT_BUILD_RTL_TCP_SOURCE       | ✅              | ✅                     | ✅                         | | ||||
|   | ||||
| @@ -1,645 +0,0 @@ | ||||
| { | ||||
|     "name": "Brazilian Ham Bands", | ||||
|     "country_name": "Brazil", | ||||
|     "country_code": "BR", | ||||
|     "author_name": "Rafael Beraldo", | ||||
|     "author_url": "https://github.com/rberaldo/", | ||||
|     "bands": [ | ||||
|         { | ||||
|             "start": 135700, | ||||
|             "end": 137800, | ||||
|             "type": "amateur", | ||||
|             "name": "2200m Ham Band CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 472000, | ||||
|             "end": 479000, | ||||
|             "type": "amateur", | ||||
|             "name": "635m Ham Band CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1800000, | ||||
|             "end": 1810000, | ||||
|             "type": "amateur", | ||||
|             "name": "|160m Ham Band CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1810000, | ||||
|             "end": 1839000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1839000, | ||||
|             "end": 1840000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1840000, | ||||
|             "end": 1843000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1843000, | ||||
|             "end": 1850000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1850000, | ||||
|             "end": 2000000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, AM, DV, Digital 160 Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3500000, | ||||
|             "end": 3570000, | ||||
|             "type": "amateur", | ||||
|             "name": "|80m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3570000, | ||||
|             "end": 3590000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3590000, | ||||
|             "end": 3600000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSD, AM, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3600000, | ||||
|             "end": 3775000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSD, AM, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3775000, | ||||
|             "end": 3875000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSD, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3775000, | ||||
|             "end": 3875000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSD, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3875000, | ||||
|             "end": 4000000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSD, AM, DV, Digital, 80m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 5351500, | ||||
|             "end": 5354000, | ||||
|             "type": "amateur", | ||||
|             "name": "|60m Ham Band CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 5354000, | ||||
|             "end": 5366000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 5366000, | ||||
|             "end": 5366500, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, Digital 60m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 7000000, | ||||
|             "end": 7040000, | ||||
|             "type": "amateur", | ||||
|             "name": "|40m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 7040000, | ||||
|             "end": 7047000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 7047000, | ||||
|             "end": 7050000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 7050000, | ||||
|             "end": 7100000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 7100000, | ||||
|             "end": 7300000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB, AM, DV, Digital 40m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 10100000, | ||||
|             "end": 10130000, | ||||
|             "type": "amateur", | ||||
|             "name": "|30m Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 10130000, | ||||
|             "end": 10150000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital 30m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 14000000, | ||||
|             "end": 14070000, | ||||
|             "type": "amateur", | ||||
|             "name": "|20m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 14070000, | ||||
|             "end": 14099000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 14099000, | ||||
|             "end": 14101000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW IBP" | ||||
|         }, | ||||
|         { | ||||
|             "start": 14101000, | ||||
|             "end": 14282000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 14285000, | ||||
|             "end": 14350000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB, AM, DV, Digital 20m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 18068000, | ||||
|             "end": 18095000, | ||||
|             "type": "amateur", | ||||
|             "name": "|17m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 18095000, | ||||
|             "end": 18109000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 18109000, | ||||
|             "end": 18111000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW IBP" | ||||
|         }, | ||||
|         { | ||||
|             "start": 18111000, | ||||
|             "end": 18168000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, DV, Digital 17m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 21000000, | ||||
|             "end": 21070000, | ||||
|             "type": "amateur", | ||||
|             "name": "|15m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 21070000, | ||||
|             "end": 21149000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 21149000, | ||||
|             "end": 21151000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, IBP" | ||||
|         }, | ||||
|         { | ||||
|             "start": 21151000, | ||||
|             "end": 21380000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 21380000, | ||||
|             "end": 21450000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB, AM, DV, Digital 15m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 24890000, | ||||
|             "end": 24915000, | ||||
|             "type": "amateur", | ||||
|             "name": "|12m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 24915000, | ||||
|             "end": 24929000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 24929000, | ||||
|             "end": 24931000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW IBP" | ||||
|         }, | ||||
|         { | ||||
|             "start": 24931000, | ||||
|             "end": 24990000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, DV, Digital 12m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28000000, | ||||
|             "end": 28070000, | ||||
|             "type": "amateur", | ||||
|             "name": "|10m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28070000, | ||||
|             "end": 28190000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28190000, | ||||
|             "end": 28199000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW - Pilot Emissions" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28199000, | ||||
|             "end": 28201000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW IBP" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28201000, | ||||
|             "end": 28225000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW - Pilot Emissions" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28225000, | ||||
|             "end": 28300000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital - Pilot Emissions" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28300000, | ||||
|             "end": 29000000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 29000000, | ||||
|             "end": 29300000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 29300000, | ||||
|             "end": 29510000, | ||||
|             "type": "amateur", | ||||
|             "name": "All Modes - Satellites" | ||||
|         }, | ||||
|         { | ||||
|             "start": 29510000, | ||||
|             "end": 29520000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 29520000, | ||||
|             "end": 29590000, | ||||
|             "type": "amateur", | ||||
|             "name": "FM, DV - Repeater input" | ||||
|         }, | ||||
|         { | ||||
|             "start": 29590000, | ||||
|             "end": 29620000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, FM, DV - FM calling freq: 29.600 kHz" | ||||
|         }, | ||||
|         { | ||||
|             "start": 29620000, | ||||
|             "end": 29700000, | ||||
|             "type": "amateur", | ||||
|             "name": "FM, DV - Repeater output 10m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 50000000, | ||||
|             "end": 54000000, | ||||
|             "type": "amateur", | ||||
|             "name": "6m Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144000000, | ||||
|             "end": 144025000, | ||||
|             "type": "amateur", | ||||
|             "name": "|2m Ham Band All Modes - Satellites" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144025000, | ||||
|             "end": 144110000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW - EME" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144110000, | ||||
|             "end": 144150000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, Digital - EME" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144150000, | ||||
|             "end": 144180000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144180000, | ||||
|             "end": 144275000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB - Calling freq: 144.2 MHz" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144275000, | ||||
|             "end": 144300000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW - Pilot Emissions" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144300000, | ||||
|             "end": 144360000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB - Calling freq: 144.2 MHz" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144360000, | ||||
|             "end": 144400000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144400000, | ||||
|             "end": 144600000, | ||||
|             "type": "amateur", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144600000, | ||||
|             "end": 144900000, | ||||
|             "type": "amateur1", | ||||
|             "name": "FM, DV - Repeater input" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144900000, | ||||
|             "end": 145000000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, FM, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145000000, | ||||
|             "end": 145200000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes, IVG" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145200000, | ||||
|             "end": 145500000, | ||||
|             "type": "amateur", | ||||
|             "name": "FM, DV - Repeater output" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145500000, | ||||
|             "end": 145565000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145565000, | ||||
|             "end": 145575000, | ||||
|             "type": "amateur", | ||||
|             "name": "APRS" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145575000, | ||||
|             "end": 145790000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145790000, | ||||
|             "end": 145800000, | ||||
|             "type": "amateur", | ||||
|             "name": "Guard Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145800000, | ||||
|             "end": 146000000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes - Satellites" | ||||
|         }, | ||||
|         { | ||||
|             "start": 146000000, | ||||
|             "end": 146390000, | ||||
|             "type": "amateur", | ||||
|             "name": "FM, DV - Repeater input" | ||||
|         }, | ||||
|         { | ||||
|             "start": 146390000, | ||||
|             "end": 146600000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, FM, DV - Calling freq: 146.52 MHz" | ||||
|         }, | ||||
|         { | ||||
|             "start": 146600000, | ||||
|             "end": 146990000, | ||||
|             "type": "amateur", | ||||
|             "name": "FM, DV - Repeater output" | ||||
|         }, | ||||
|         { | ||||
|             "start": 146990000, | ||||
|             "end": 147400000, | ||||
|             "type": "amateur1", | ||||
|             "name": "FM, DV - Repeater input" | ||||
|         }, | ||||
|         { | ||||
|             "start": 147400000, | ||||
|             "end": 147590000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, FM, DV" | ||||
|         }, | ||||
|         { | ||||
|             "start": 147590000, | ||||
|             "end": 148000000, | ||||
|             "type": "amateur1", | ||||
|             "name": "FM, DV - Repeater output 2m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 220000000, | ||||
|             "end": 225000000, | ||||
|             "type": "amateur", | ||||
|             "name": "1.3m Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 430000000, | ||||
|             "end": 432000000, | ||||
|             "type": "amateur", | ||||
|             "name": "|70cm Ham Band All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 432000000, | ||||
|             "end": 432025000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW - EME" | ||||
|         }, | ||||
|         { | ||||
|             "start": 432025000, | ||||
|             "end": 432100000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, Digital - EME" | ||||
|         }, | ||||
|         { | ||||
|             "start": 432100000, | ||||
|             "end": 432300000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB - Calling freq: 432.1 MHz" | ||||
|         }, | ||||
|         { | ||||
|             "start": 432300000, | ||||
|             "end": 432400000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW - Pilot Emissions" | ||||
|         }, | ||||
|         { | ||||
|             "start": 432400000, | ||||
|             "end": 432420000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital - Pilot Emissions" | ||||
|         }, | ||||
|         { | ||||
|             "start": 432420000, | ||||
|             "end": 433000000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 433000000, | ||||
|             "end": 433050000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 433050000, | ||||
|             "end": 434000000, | ||||
|             "type": "amateur", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 434000000, | ||||
|             "end": 435000000, | ||||
|             "type": "amateur1", | ||||
|             "name": "Fm, DV - Repeater input" | ||||
|         }, | ||||
|         { | ||||
|             "start": 435000000, | ||||
|             "end": 438000000, | ||||
|             "type": "amateur", | ||||
|             "name": "All Modes - Satellites" | ||||
|         }, | ||||
|         { | ||||
|             "start": 438000000, | ||||
|             "end": 439000000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 439000000, | ||||
|             "end": 440000000, | ||||
|             "type": "amateur", | ||||
|             "name": "FM, DV - Repeater output 70cm Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 902000000, | ||||
|             "end": 928000000, | ||||
|             "type": "amateur", | ||||
|             "name": "33cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1240000000, | ||||
|             "end": 1300000000, | ||||
|             "type": "amateur", | ||||
|             "name": "23cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 2330000000, | ||||
|             "end": 2450000000, | ||||
|             "type": "amateur", | ||||
|             "name": "13cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3400000000, | ||||
|             "end": 3500000000, | ||||
|             "type": "amateur", | ||||
|             "name": "9cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 5650000000, | ||||
|             "end": 5925000000, | ||||
|             "type": "amateur", | ||||
|             "name": "5cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 10000000000, | ||||
|             "end": 10500000000, | ||||
|             "type": "amateur", | ||||
|             "name": "3cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 24000000000, | ||||
|             "end": 24250000000, | ||||
|             "type": "amateur", | ||||
|             "name": "1.2cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 47000000000, | ||||
|             "end": 47200000000, | ||||
|             "type": "amateur", | ||||
|             "name": "6mm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 122250000000, | ||||
|             "end": 123000000000, | ||||
|             "type": "amateur", | ||||
|             "name": "2.5mm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 134000000000, | ||||
|             "end": 141000000000, | ||||
|             "type": "amateur", | ||||
|             "name": "2mm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 241000000000, | ||||
|             "end": 250000000000, | ||||
|             "type": "amateur", | ||||
|             "name": "1mm Ham Band" | ||||
|         } | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								root/res/icons/align_center.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								root/res/icons/align_center.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 8.8 KiB | 
| @@ -2,10 +2,10 @@ | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <signal_path/sink.h> | ||||
| #include <dsp/buffer/packer.h> | ||||
| #include <dsp/convert/stereo_to_mono.h> | ||||
| #include <utils/flog.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <RtAudio.h> | ||||
| #include <config.h> | ||||
| #include <core.h> | ||||
| @@ -16,36 +16,51 @@ SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "audio_sink", | ||||
|     /* Description:     */ "Audio sink module for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Version:         */ 0, 2, 0, | ||||
|     /* Max instances    */ 1 | ||||
| }; | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| class AudioSink : SinkManager::Sink { | ||||
| bool operator==(const RtAudio::DeviceInfo& a, const RtAudio::DeviceInfo& b) { | ||||
|     return a.name == b.name; | ||||
| } | ||||
|  | ||||
| class AudioSink : public Sink { | ||||
| public: | ||||
|     AudioSink(SinkManager::Stream* stream, std::string streamName) { | ||||
|         _stream = stream; | ||||
|         _streamName = streamName; | ||||
|         s2m.init(_stream->sinkOut); | ||||
|     AudioSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) : | ||||
|         Sink(entry, stream, name, id, stringId) | ||||
|     { | ||||
|         s2m.init(stream); | ||||
|         monoPacker.init(&s2m.out, 512); | ||||
|         stereoPacker.init(_stream->sinkOut, 512); | ||||
|         stereoPacker.init(stream, 512); | ||||
|  | ||||
| #if RTAUDIO_VERSION_MAJOR >= 6 | ||||
|         audio.setErrorCallback(&errorCallback); | ||||
| #endif | ||||
|  | ||||
| #if RTAUDIO_VERSION_MAJOR >= 6 | ||||
|         audio.setErrorCallback(&errorCallback); | ||||
| #endif | ||||
|  | ||||
|         // Load config (TODO) | ||||
|         bool created = false; | ||||
|         std::string device = ""; | ||||
|         config.acquire(); | ||||
|         if (!config.conf.contains(_streamName)) { | ||||
|             created = true; | ||||
|             config.conf[_streamName]["device"] = ""; | ||||
|             config.conf[_streamName]["devices"] = json({}); | ||||
|         } | ||||
|         device = config.conf[_streamName]["device"]; | ||||
|         config.release(created); | ||||
|         // config.acquire(); | ||||
|         // if (config.conf.contains(streamName)) { | ||||
|         //     if (!config.conf[streamName].is_array()) { | ||||
|         //         json tmp = config.conf[streamName]; | ||||
|         //         config.conf[streamName] = json::array(); | ||||
|         //         config.conf[streamName][0] = tmp; | ||||
|         //         modified = true; | ||||
|         //     } | ||||
|         //     if (config.conf[streamName].contains((int)id)) { | ||||
|         //         device = config.conf[streamName][(int)id]["device"]; | ||||
|         //     } | ||||
|         // } | ||||
|         // config.release(modified); | ||||
|  | ||||
|         // List devices | ||||
|         RtAudio::DeviceInfo info; | ||||
| #if RTAUDIO_VERSION_MAJOR >= 6 | ||||
|         for (int i : audio.getDeviceIds()) { | ||||
| @@ -60,15 +75,13 @@ public: | ||||
| #endif | ||||
|                 if (info.outputChannels == 0) { continue; } | ||||
|                 if (info.isDefaultOutput) { defaultDevId = devList.size(); } | ||||
|                 devList.push_back(info); | ||||
|                 deviceIds.push_back(i); | ||||
|                 txtDevList += info.name; | ||||
|                 txtDevList += '\0'; | ||||
|                 devList.define(i, info.name, info); | ||||
|             } | ||||
|             catch (const std::exception& e) { | ||||
|                 flog::error("AudioSinkModule Error getting audio device ({}) info: {}", i, e.what()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         selectByName(device); | ||||
|     } | ||||
|  | ||||
| @@ -102,67 +115,92 @@ public: | ||||
|     } | ||||
|  | ||||
|     void selectById(int id) { | ||||
|         // Update ID | ||||
|         devId = id; | ||||
|         bool created = false; | ||||
|         config.acquire(); | ||||
|         if (!config.conf[_streamName]["devices"].contains(devList[id].name)) { | ||||
|             created = true; | ||||
|             config.conf[_streamName]["devices"][devList[id].name] = devList[id].preferredSampleRate; | ||||
|         } | ||||
|         sampleRate = config.conf[_streamName]["devices"][devList[id].name]; | ||||
|         config.release(created); | ||||
|         selectedDevName = devList[id].name; | ||||
|  | ||||
|         sampleRates = devList[id].sampleRates; | ||||
|         sampleRatesTxt = ""; | ||||
|         // List samplerates and select default SR | ||||
|         char buf[256]; | ||||
|         bool found = false; | ||||
|         unsigned int defaultId = 0; | ||||
|         sampleRates.clear(); | ||||
|         const auto& srList = devList[id].sampleRates; | ||||
|         unsigned int defaultSr = devList[id].preferredSampleRate; | ||||
|         for (int i = 0; i < sampleRates.size(); i++) { | ||||
|             if (sampleRates[i] == sampleRate) { | ||||
|                 found = true; | ||||
|                 srId = i; | ||||
|         for (auto& sr : srList) { | ||||
|             if (sr == defaultSr) { | ||||
|                 srId = sampleRates.size(); | ||||
|                 sampleRate = sr; | ||||
|             } | ||||
|             if (sampleRates[i] == defaultSr) { | ||||
|                 defaultId = i; | ||||
|             } | ||||
|             sprintf(buf, "%d", sampleRates[i]); | ||||
|             sampleRatesTxt += buf; | ||||
|             sampleRatesTxt += '\0'; | ||||
|         } | ||||
|         if (!found) { | ||||
|             sampleRate = defaultSr; | ||||
|             srId = defaultId; | ||||
|             sprintf(buf, "%d", sr); | ||||
|             sampleRates.define(sr, buf, sr); | ||||
|         } | ||||
|      | ||||
|         // // Load config | ||||
|         // config.acquire(); | ||||
|         // if (config.conf[streamName][(int)id].contains(selectedDevName)) { | ||||
|         //     unsigned int wantedSr = config.conf[streamName][id][selectedDevName]; | ||||
|         //     if (sampleRates.keyExists(wantedSr)) { | ||||
|         //         srId = sampleRates.keyId(wantedSr); | ||||
|         //         sampleRate = sampleRates[srId]; | ||||
|         //     } | ||||
|         // } | ||||
|         // config.release(); | ||||
|  | ||||
|         _stream->setSampleRate(sampleRate); | ||||
|         // Lock the sink | ||||
|         auto lck = entry->getLock(); | ||||
|  | ||||
|         // Stop the sink DSP | ||||
|         // TODO: Only if the sink DSP is running, otherwise you risk starting it when  it shouldn't | ||||
|         entry->stopDSP(); | ||||
|  | ||||
|         // Stop the sink | ||||
|         if (running) { doStop(); } | ||||
|  | ||||
|         // Update stream samplerate | ||||
|         entry->setSamplerate(sampleRate); | ||||
|  | ||||
|         // Start the DSP | ||||
|         entry->startDSP(); | ||||
|  | ||||
|         // Start the sink | ||||
|         if (running) { doStart(); } | ||||
|     } | ||||
|  | ||||
|     void menuHandler() { | ||||
|     void showMenu() { | ||||
|         float menuWidth = ImGui::GetContentRegionAvail().x; | ||||
|  | ||||
|         ImGui::SetNextItemWidth(menuWidth); | ||||
|         if (ImGui::Combo(("##_audio_sink_dev_" + _streamName).c_str(), &devId, txtDevList.c_str())) { | ||||
|         if (ImGui::Combo(("##_audio_sink_dev_" + stringId).c_str(), &devId, devList.txt)) { | ||||
|             selectById(devId); | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["device"] = devList[devId].name; | ||||
|             config.release(true); | ||||
|             // config.acquire(); | ||||
|             // config.conf[streamName]["device"] = devList[devId].name; | ||||
|             // config.release(true); | ||||
|         } | ||||
|  | ||||
|         ImGui::SetNextItemWidth(menuWidth); | ||||
|         if (ImGui::Combo(("##_audio_sink_sr_" + _streamName).c_str(), &srId, sampleRatesTxt.c_str())) { | ||||
|         if (ImGui::Combo(("##_audio_sink_sr_" + stringId).c_str(), &srId, sampleRates.txt)) { | ||||
|             sampleRate = sampleRates[srId]; | ||||
|             _stream->setSampleRate(sampleRate); | ||||
|             if (running) { | ||||
|                 doStop(); | ||||
|                 doStart(); | ||||
|             } | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["devices"][devList[devId].name] = sampleRate; | ||||
|             config.release(true); | ||||
|              | ||||
|             // Lock the sink | ||||
|             auto lck = entry->getLock(); | ||||
|  | ||||
|             // Stop the sink DSP | ||||
|             // TODO: Only if the sink DSP is running, otherwise you risk starting it when  it shouldn't | ||||
|             entry->stopDSP(); | ||||
|  | ||||
|             // Stop the sink | ||||
|             if (running) { doStop(); } | ||||
|  | ||||
|             // Update stream samplerate | ||||
|             entry->setSamplerate(sampleRate); | ||||
|  | ||||
|             // Start the DSP | ||||
|             entry->startDSP(); | ||||
|  | ||||
|             // Start the sink | ||||
|             if (running) { doStart(); } | ||||
|  | ||||
|             // config.acquire(); | ||||
|             // config.conf[streamName]["devices"][devList[devId].name] = sampleRate; | ||||
|             // config.release(true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -185,12 +223,12 @@ public: | ||||
| private: | ||||
|     bool doStart() { | ||||
|         RtAudio::StreamParameters parameters; | ||||
|         parameters.deviceId = deviceIds[devId]; | ||||
|         parameters.deviceId = devList.key(devId); | ||||
|         parameters.nChannels = 2; | ||||
|         unsigned int bufferFrames = sampleRate / 60; | ||||
|         RtAudio::StreamOptions opts; | ||||
|         opts.flags = RTAUDIO_MINIMIZE_LATENCY; | ||||
|         opts.streamName = _streamName; | ||||
|         opts.streamName = streamName; | ||||
|  | ||||
|         try { | ||||
|             audio.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts); | ||||
| @@ -229,44 +267,34 @@ private: | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     SinkManager::Stream* _stream; | ||||
|     dsp::convert::StereoToMono s2m; | ||||
|     dsp::buffer::Packer<float> monoPacker; | ||||
|     dsp::buffer::Packer<dsp::stereo_t> stereoPacker; | ||||
|  | ||||
|     std::string _streamName; | ||||
|  | ||||
|     int srId = 0; | ||||
|     int devCount; | ||||
|     int devId = 0; | ||||
|     bool running = false; | ||||
|     std::string selectedDevName; | ||||
|  | ||||
|     unsigned int defaultDevId = 0; | ||||
|  | ||||
|     std::vector<RtAudio::DeviceInfo> devList; | ||||
|     std::vector<unsigned int> deviceIds; | ||||
|     std::string txtDevList; | ||||
|     OptionList<unsigned int, RtAudio::DeviceInfo> devList; | ||||
|     OptionList<unsigned int, unsigned int> sampleRates; | ||||
|  | ||||
|     std::vector<unsigned int> sampleRates; | ||||
|     std::string sampleRatesTxt; | ||||
|     unsigned int sampleRate = 48000; | ||||
|  | ||||
|     RtAudio audio; | ||||
| }; | ||||
|  | ||||
| class AudioSinkModule : public ModuleManager::Instance { | ||||
| class AudioSinkModule : public ModuleManager::Instance, public SinkProvider { | ||||
| public: | ||||
|     AudioSinkModule(std::string name) { | ||||
|         this->name = name; | ||||
|         provider.create = create_sink; | ||||
|         provider.ctx = this; | ||||
|  | ||||
|         sigpath::sinkManager.registerSinkProvider("Audio", provider); | ||||
|         sigpath::streamManager.registerSinkProvider("Audio", this); | ||||
|     } | ||||
|  | ||||
|     ~AudioSinkModule() { | ||||
|         // Unregister sink, this will automatically stop and delete all instances of the audio sink | ||||
|         sigpath::sinkManager.unregisterSinkProvider("Audio"); | ||||
|         sigpath::streamManager.unregisterSinkProvider(this); | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
| @@ -283,14 +311,13 @@ public: | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) { | ||||
|         return (SinkManager::Sink*)(new AudioSink(stream, streamName)); | ||||
|     std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) { | ||||
|         return std::make_unique<AudioSink>(entry, stream, name, id, stringId); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     SinkManager::SinkProvider provider; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
| @@ -312,4 +339,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
| } | ||||
| @@ -3,13 +3,14 @@ | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <signal_path/sink.h> | ||||
| #include <signal_path/stream.h> | ||||
| #include <dsp/buffer/packer.h> | ||||
| #include <dsp/convert/stereo_to_mono.h> | ||||
| #include <dsp/sink/handler_sink.h> | ||||
| #include <utils/flog.h> | ||||
| #include <config.h> | ||||
| #include <gui/style.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <core.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
| @@ -18,84 +19,97 @@ SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "network_sink", | ||||
|     /* Description:     */ "Network sink module for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Version:         */ 0, 2, 0, | ||||
|     /* Max instances    */ 1 | ||||
| }; | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| enum { | ||||
| enum SinkMode { | ||||
|     SINK_MODE_TCP, | ||||
|     SINK_MODE_UDP | ||||
| }; | ||||
|  | ||||
| const char* sinkModesTxt = "TCP\0UDP\0"; | ||||
|  | ||||
| class NetworkSink : SinkManager::Sink { | ||||
| class NetworkSink : public Sink { | ||||
| public: | ||||
|     NetworkSink(SinkManager::Stream* stream, std::string streamName) { | ||||
|         _stream = stream; | ||||
|         _streamName = streamName; | ||||
|     NetworkSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) : | ||||
|         Sink(entry, stream, name, id, stringId) | ||||
|     { | ||||
|         // Define modes | ||||
|         modes.define("TCP", SINK_MODE_TCP); | ||||
|         modes.define("UDP", SINK_MODE_UDP); | ||||
|  | ||||
|         // Load config | ||||
|         config.acquire(); | ||||
|         if (!config.conf.contains(_streamName)) { | ||||
|             config.conf[_streamName]["hostname"] = "localhost"; | ||||
|             config.conf[_streamName]["port"] = 7355; | ||||
|             config.conf[_streamName]["protocol"] = SINK_MODE_UDP; // UDP | ||||
|             config.conf[_streamName]["sampleRate"] = 48000.0; | ||||
|             config.conf[_streamName]["stereo"] = false; | ||||
|             config.conf[_streamName]["listening"] = false; | ||||
|         // Create a list of sample rates | ||||
|         std::vector<int> srList; | ||||
|         for (int sr = 12000; sr <= 200000; sr += 12000) { | ||||
|             srList.push_back(sr); | ||||
|         } | ||||
|         for (int sr = 11025; sr <= 192000; sr += 11025) { | ||||
|             srList.push_back(sr); | ||||
|         } | ||||
|         std::string host = config.conf[_streamName]["hostname"]; | ||||
|         strcpy(hostname, host.c_str()); | ||||
|         port = config.conf[_streamName]["port"]; | ||||
|         modeId = config.conf[_streamName]["protocol"]; | ||||
|         sampleRate = config.conf[_streamName]["sampleRate"]; | ||||
|         stereo = config.conf[_streamName]["stereo"]; | ||||
|         bool startNow = config.conf[_streamName]["listening"]; | ||||
|         config.release(true); | ||||
|  | ||||
|         // Sort sample rate list | ||||
|         std::sort(srList.begin(), srList.end(), [](double a, double b) { return (a < b); }); | ||||
|  | ||||
|         // Define samplerate options | ||||
|         for (int sr : srList) { | ||||
|             char buf[16]; | ||||
|             sprintf(buf, "%d", sr); | ||||
|             samplerates.define(sr, buf, sr); | ||||
|         } | ||||
|  | ||||
|         // Allocate buffer | ||||
|         netBuf = new int16_t[STREAM_BUFFER_SIZE]; | ||||
|  | ||||
|         packer.init(_stream->sinkOut, 512); | ||||
|         // Init DSP | ||||
|         packer.init(stream, 512); | ||||
|         s2m.init(&packer.out); | ||||
|         monoSink.init(&s2m.out, monoHandler, this); | ||||
|         stereoSink.init(&packer.out, stereoHandler, this); | ||||
|  | ||||
|  | ||||
|         // Create a list of sample rates | ||||
|         for (int sr = 12000; sr < 200000; sr += 12000) { | ||||
|             sampleRates.push_back(sr); | ||||
|         // Load config | ||||
|         config.acquire(); | ||||
|         bool startNow = false; | ||||
|         if (config.conf[stringId].contains("hostname")) { | ||||
|             std::string host = config.conf[stringId]["hostname"]; | ||||
|             strcpy(hostname, host.c_str()); | ||||
|         } | ||||
|         for (int sr = 11025; sr < 192000; sr += 11025) { | ||||
|             sampleRates.push_back(sr); | ||||
|         if (config.conf[stringId].contains("port")) { | ||||
|             port = config.conf[stringId]["port"]; | ||||
|         } | ||||
|  | ||||
|         // Sort sample rate list | ||||
|         std::sort(sampleRates.begin(), sampleRates.end(), [](double a, double b) { return (a < b); }); | ||||
|  | ||||
|         // Generate text list for UI | ||||
|         char buffer[128]; | ||||
|         int id = 0; | ||||
|         int _48kId; | ||||
|         bool found = false; | ||||
|         for (auto sr : sampleRates) { | ||||
|             sprintf(buffer, "%d", (int)sr); | ||||
|             sampleRatesTxt += buffer; | ||||
|             sampleRatesTxt += '\0'; | ||||
|             if (sr == sampleRate) { | ||||
|                 srId = id; | ||||
|                 found = true; | ||||
|         if (config.conf[stringId].contains("mode")) { | ||||
|             std::string modeStr = config.conf[stringId]["mode"]; | ||||
|             if (modes.keyExists(modeStr)) { | ||||
|                 mode = modes.value(modes.keyId(modeStr)); | ||||
|             } | ||||
|             else { | ||||
|                 mode = SINK_MODE_TCP; | ||||
|             } | ||||
|             if (sr == 48000.0) { _48kId = id; } | ||||
|             id++; | ||||
|         } | ||||
|         if (!found) { | ||||
|             srId = _48kId; | ||||
|             sampleRate = 48000.0; | ||||
|         if (config.conf[stringId].contains("samplerate")) { | ||||
|             int nSr = config.conf[stringId]["samplerate"]; | ||||
|             if (samplerates.keyExists(nSr)) { | ||||
|                 sampleRate = samplerates.value(samplerates.keyId(nSr)); | ||||
|             } | ||||
|             else { | ||||
|                 sampleRate = 48000; | ||||
|             } | ||||
|         } | ||||
|         _stream->setSampleRate(sampleRate); | ||||
|         if (config.conf[stringId].contains("stereo")) { | ||||
|             stereo = config.conf[stringId]["stereo"]; | ||||
|         } | ||||
|         if (config.conf[stringId].contains("running")) { | ||||
|             startNow = config.conf[stringId]["running"]; | ||||
|         } | ||||
|         config.release(); | ||||
|  | ||||
|         // Set mode ID | ||||
|         modeId = modes.valueId(mode); | ||||
|  | ||||
|         // Set samplerate ID | ||||
|         srId = samplerates.valueId(sampleRate); | ||||
|  | ||||
|         // Start if needed | ||||
|         if (startNow) { startServer(); } | ||||
| @@ -122,30 +136,30 @@ public: | ||||
|         running = false; | ||||
|     } | ||||
|  | ||||
|     void menuHandler() { | ||||
|     void showMenu() { | ||||
|         float menuWidth = ImGui::GetContentRegionAvail().x; | ||||
|  | ||||
|         bool listening = (listener && listener->isListening()) || (conn && conn->isOpen()); | ||||
|  | ||||
|         if (listening) { style::beginDisabled(); } | ||||
|         if (ImGui::InputText(CONCAT("##_network_sink_host_", _streamName), hostname, 1023)) { | ||||
|         if (ImGui::InputText(CONCAT("##_network_sink_host_", stringId), hostname, 1023)) { | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["hostname"] = hostname; | ||||
|             config.conf[stringId]["hostname"] = hostname; | ||||
|             config.release(true); | ||||
|         } | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::InputInt(CONCAT("##_network_sink_port_", _streamName), &port, 0, 0)) { | ||||
|         if (ImGui::InputInt(CONCAT("##_network_sink_port_", stringId), &port, 0, 0)) { | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["port"] = port; | ||||
|             config.conf[stringId]["port"] = port; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         ImGui::LeftLabel("Protocol"); | ||||
|         ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo(CONCAT("##_network_sink_mode_", _streamName), &modeId, sinkModesTxt)) { | ||||
|         if (ImGui::Combo(CONCAT("##_network_sink_mode_", stringId), &modeId, sinkModesTxt)) { | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["protocol"] = modeId; | ||||
|             config.conf[stringId]["mode"] = modeId; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
| @@ -153,33 +167,33 @@ public: | ||||
|  | ||||
|         ImGui::LeftLabel("Samplerate"); | ||||
|         ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo(CONCAT("##_network_sink_sr_", _streamName), &srId, sampleRatesTxt.c_str())) { | ||||
|             sampleRate = sampleRates[srId]; | ||||
|             _stream->setSampleRate(sampleRate); | ||||
|         if (ImGui::Combo(CONCAT("##_network_sink_sr_", stringId), &srId, samplerates.txt)) { | ||||
|             sampleRate = samplerates.value(srId); | ||||
|             entry->setSamplerate(sampleRate); | ||||
|             packer.setSampleCount(sampleRate / 60); | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["sampleRate"] = sampleRate; | ||||
|             config.conf[stringId]["samplerate"] = sampleRate; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", _streamName), &stereo)) { | ||||
|         if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", stringId), &stereo)) { | ||||
|             stop(); | ||||
|             start(); | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["stereo"] = stereo; | ||||
|             config.conf[stringId]["stereo"] = stereo; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         if (listening && ImGui::Button(CONCAT("Stop##_network_sink_stop_", _streamName), ImVec2(menuWidth, 0))) { | ||||
|         if (listening && ImGui::Button(CONCAT("Stop##_network_sink_stop_", stringId), ImVec2(menuWidth, 0))) { | ||||
|             stopServer(); | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["listening"] = false; | ||||
|             config.conf[stringId]["running"] = false; | ||||
|             config.release(true); | ||||
|         } | ||||
|         else if (!listening && ImGui::Button(CONCAT("Start##_network_sink_stop_", _streamName), ImVec2(menuWidth, 0))) { | ||||
|         else if (!listening && ImGui::Button(CONCAT("Start##_network_sink_stop_", stringId), ImVec2(menuWidth, 0))) { | ||||
|             startServer(); | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["listening"] = true; | ||||
|             config.conf[stringId]["running"] = true; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
| @@ -271,47 +285,40 @@ private: | ||||
|         _this->listener->acceptAsync(clientHandler, _this); | ||||
|     } | ||||
|  | ||||
|     SinkManager::Stream* _stream; | ||||
|     // DSP | ||||
|     dsp::buffer::Packer<dsp::stereo_t> packer; | ||||
|     dsp::convert::StereoToMono s2m; | ||||
|     dsp::sink::Handler<float> monoSink; | ||||
|     dsp::sink::Handler<dsp::stereo_t> stereoSink; | ||||
|  | ||||
|     std::string _streamName; | ||||
|  | ||||
|     int srId = 0; | ||||
|     bool running = false; | ||||
|     OptionList<std::string, SinkMode> modes; | ||||
|     OptionList<int, double> samplerates; | ||||
|  | ||||
|     char hostname[1024]; | ||||
|     int port = 4242; | ||||
|  | ||||
|     int modeId = 1; | ||||
|  | ||||
|     std::vector<unsigned int> sampleRates; | ||||
|     std::string sampleRatesTxt; | ||||
|     unsigned int sampleRate = 48000; | ||||
|     int port = 7355; | ||||
|     SinkMode mode = SINK_MODE_TCP; | ||||
|     int modeId; | ||||
|     int sampleRate = 48000; | ||||
|     int srId; | ||||
|     bool stereo = false; | ||||
|     bool running = false; | ||||
|  | ||||
|     int16_t* netBuf; | ||||
|  | ||||
|     net::Listener listener; | ||||
|     net::Conn conn; | ||||
|     std::mutex connMtx; | ||||
| }; | ||||
|  | ||||
| class NetworkSinkModule : public ModuleManager::Instance { | ||||
| class NetworkSinkModule : public ModuleManager::Instance, SinkProvider { | ||||
| public: | ||||
|     NetworkSinkModule(std::string name) { | ||||
|         this->name = name; | ||||
|         provider.create = create_sink; | ||||
|         provider.ctx = this; | ||||
|  | ||||
|         sigpath::sinkManager.registerSinkProvider("Network", provider); | ||||
|         // Register self as provider | ||||
|         sigpath::streamManager.registerSinkProvider("Network", this); | ||||
|     } | ||||
|  | ||||
|     ~NetworkSinkModule() { | ||||
|         // Unregister sink, this will automatically stop and delete all instances of the audio sink | ||||
|         sigpath::sinkManager.unregisterSinkProvider("Network"); | ||||
|         // Unregister self | ||||
|         sigpath::streamManager.unregisterSinkProvider(this); | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
| @@ -328,14 +335,13 @@ public: | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) { | ||||
|         return (SinkManager::Sink*)(new NetworkSink(stream, streamName)); | ||||
|     std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) { | ||||
|         return std::make_unique<NetworkSink>(entry, stream, name, id, stringId); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     SinkManager::SinkProvider provider; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|   | ||||
| @@ -152,6 +152,7 @@ public: | ||||
|  | ||||
|         // Update samplerate from ID | ||||
|         sampleRate = sampleRates[srId]; | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|     } | ||||
|  | ||||
| private: | ||||
| @@ -231,7 +232,6 @@ private: | ||||
|         if (SmGui::Combo(CONCAT("##_audio_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { | ||||
|             std::string dev = _this->devices.key(_this->devId); | ||||
|             _this->select(dev); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             config.acquire(); | ||||
|             config.conf["device"] = dev; | ||||
|             config.release(true); | ||||
| @@ -253,7 +253,6 @@ private: | ||||
|         if (SmGui::Button(CONCAT("Refresh##_audio_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->selectedDevice); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|   | ||||
| @@ -304,7 +304,6 @@ private: | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_hackrf_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) { | ||||
|             _this->selectBySerial(_this->devList[_this->devId]); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             config.acquire(); | ||||
|             config.conf["device"] = _this->selectedSerial; | ||||
|             config.release(true); | ||||
|   | ||||
| @@ -118,6 +118,7 @@ private: | ||||
|  | ||||
|         // Update host samplerate | ||||
|         sampleRate = samplerates.key(srId); | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
| @@ -198,7 +199,6 @@ private: | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_hermes_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { | ||||
|             _this->selectMac(_this->devices.key(_this->devId)); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (!_this->selectedMac.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["device"] = _this->devices.key(_this->devId); | ||||
| @@ -225,7 +225,6 @@ private: | ||||
|             std::string mac = config.conf["device"]; | ||||
|             config.release(); | ||||
|             _this->selectMac(mac); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|   | ||||
| @@ -210,6 +210,7 @@ public: | ||||
|  | ||||
|         // Update samplerate | ||||
|         sampleRate = srList[srId]; | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|  | ||||
|         // Close device | ||||
|         perseus_close(dev); | ||||
| @@ -328,7 +329,6 @@ private: | ||||
|         if (SmGui::Combo(CONCAT("##_airspyhf_dev_sel_", _this->name), &_this->devId, _this->devList.txt)) { | ||||
|             std::string serial = _this->devList.key(_this->devId); | ||||
|             _this->select(serial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             config.acquire(); | ||||
|             config.conf["device"] = serial; | ||||
|             config.release(true); | ||||
|   | ||||
| @@ -244,6 +244,9 @@ private: | ||||
|             bwId = 0; | ||||
|             bandwidth = bandwidths.value(bwId); | ||||
|         } | ||||
|          | ||||
|         // Update core samplerate | ||||
|         core::setInputSampleRate(samplerate); | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
| @@ -348,7 +351,6 @@ private: | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo("##plutosdr_dev_sel", &_this->devId, _this->devices.txt)) { | ||||
|             _this->select(_this->devices.key(_this->devId)); | ||||
|             core::setInputSampleRate(_this->samplerate); | ||||
|             config.acquire(); | ||||
|             config.conf["device"] = _this->devices.key(_this->devId); | ||||
|             config.release(true); | ||||
| @@ -371,7 +373,7 @@ private: | ||||
|         if (SmGui::Button(CONCAT("Refresh##_pluto_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->devDesc); | ||||
|             core::setInputSampleRate(_this->samplerate); | ||||
|  | ||||
|         } | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|   | ||||
| @@ -1,10 +0,0 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(rfnm_source) | ||||
|  | ||||
| file(GLOB SRC "src/*.cpp") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| target_include_directories(rfnm_source PRIVATE "C:/Users/ryzerth/Documents/Code/librfnm/include/librfnm") | ||||
| target_link_directories(rfnm_source PRIVATE "C:/Users/ryzerth/Documents/Code/librfnm/build/Release") | ||||
| target_link_libraries(rfnm_source PRIVATE librfnm) | ||||
| @@ -1,362 +0,0 @@ | ||||
| #include <imgui.h> | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/smgui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <librfnm.h> | ||||
| #include <core.h> | ||||
| #include <utils/optionlist.h> | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "rfnm_source", | ||||
|     /* Description:     */ "RFNM Source Module", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ -1 | ||||
| }; | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| class RFNMSourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     RFNMSourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         sampleRate = 61440000.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; | ||||
|  | ||||
|         // Refresh devices | ||||
|         refresh(); | ||||
|  | ||||
|         // Select first (TODO: Select from config) | ||||
|         select(""); | ||||
|  | ||||
|         sigpath::sourceManager.registerSource("RFNM", &handler); | ||||
|     } | ||||
|  | ||||
|     ~RFNMSourceModule() { | ||||
|          | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
|  | ||||
|     void enable() { | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void refresh() {    | ||||
| #ifndef __ANDROID__ | ||||
|         devices.clear(); | ||||
|         auto list = librfnm::find(librfnm_transport::LIBRFNM_TRANSPORT_USB); | ||||
|         for (const auto& info : list) { | ||||
|             // Format device name | ||||
|             std::string devName = "RFNM "; | ||||
|             devName += info.motherboard.user_readable_name; | ||||
|             devName += " ["; | ||||
|             devName += (char*)info.motherboard.serial_number; | ||||
|             devName += ']'; | ||||
|  | ||||
|             // Save device | ||||
|             devices.define((char*)info.motherboard.serial_number, devName, (char*)info.motherboard.serial_number); | ||||
|         } | ||||
| #else | ||||
|         // Check for device presence | ||||
|         int vid, pid; | ||||
|         devFd = backend::getDeviceFD(vid, pid, backend::RFNM_VIDPIDS); | ||||
|         if (devFd < 0) { return; } | ||||
|  | ||||
|         // Get device info | ||||
|         std::string fakeName = "RFNM USB"; | ||||
|         devices.define(fakeName, fakeName, fakeName); | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     void select(const std::string& serial) { | ||||
|         // If there are no devices, give up | ||||
|         if (devices.empty()) { | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If the serial was not found, select the first available serial | ||||
|         if (!devices.keyExists(serial)) { | ||||
|             select(devices.key(0)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // // Open the device | ||||
|         // librfnm* dev = new librfnm(librfnm_transport::LIBRFNM_TRANSPORT_USB, serial); | ||||
|  | ||||
|         // Define bandwidths | ||||
|         bandwidths.clear(); | ||||
|         bandwidths.define(-1, "Auto", -1); | ||||
|         for (int i = 1; i <= 100; i++) { | ||||
|             char buf[128]; | ||||
|             sprintf(buf, "%d MHz", i); | ||||
|             bandwidths.define(i, buf, i); | ||||
|         } | ||||
|  | ||||
|         // Get gain range | ||||
|         gainMin = -30;//dev->librfnm_s->rx.ch[0].gain_range.min; | ||||
|         gainMax = 60;//dev->librfnm_s->rx.ch[0].gain_range.max; | ||||
|  | ||||
|         // // Close device | ||||
|         // delete dev; | ||||
|  | ||||
|         // Define samplerates | ||||
|         samplerates.clear(); | ||||
|         samplerates.define(61440000, "61.44 MHz", 2); | ||||
|         samplerates.define(122880000, "122.88 MHz", 1); | ||||
|  | ||||
|         // TODO: Load options | ||||
|         srId = samplerates.keyId(61440000); | ||||
|         bwId = bandwidths.nameId("Auto"); | ||||
|         gain = 0; | ||||
|  | ||||
|         // Update samplerate | ||||
|         sampleRate = samplerates.key(srId); | ||||
|  | ||||
|         // Save serial number | ||||
|         selectedSerial = serial; | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
|         RFNMSourceModule* _this = (RFNMSourceModule*)ctx; | ||||
|         core::setInputSampleRate(_this->sampleRate); | ||||
|         flog::info("RFNMSourceModule '{0}': Menu Select!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void menuDeselected(void* ctx) { | ||||
|         RFNMSourceModule* _this = (RFNMSourceModule*)ctx; | ||||
|         flog::info("RFNMSourceModule '{0}': Menu Deselect!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void start(void* ctx) { | ||||
|         RFNMSourceModule* _this = (RFNMSourceModule*)ctx; | ||||
|         if (_this->running) { return; } | ||||
|  | ||||
|         // Open the device | ||||
| #ifndef __ANDROID__ | ||||
|         _this->openDev = new librfnm(librfnm_transport::LIBRFNM_TRANSPORT_USB, _this->selectedSerial); | ||||
| #else | ||||
|         _this->openDev = new librfnm(_this->devFd); | ||||
| #endif | ||||
|  | ||||
|         // Configure the device | ||||
|         _this->openDev->librfnm_s->rx.ch[0].enable = RFNM_CH_ON; | ||||
|         _this->openDev->librfnm_s->rx.ch[0].samp_freq_div_n = _this->samplerates[_this->srId]; | ||||
|         _this->openDev->librfnm_s->rx.ch[0].freq = _this->freq; | ||||
|         _this->openDev->librfnm_s->rx.ch[0].gain = _this->gain; | ||||
|         _this->openDev->librfnm_s->rx.ch[0].rfic_lpf_bw = 100; | ||||
|         _this->openDev->librfnm_s->rx.ch[0].fm_notch = _this->fmNotch ? rfnm_fm_notch::RFNM_FM_NOTCH_ON : rfnm_fm_notch::RFNM_FM_NOTCH_OFF; | ||||
|         _this->openDev->librfnm_s->rx.ch[0].path = _this->openDev->librfnm_s->rx.ch[0].path_preferred; | ||||
|         rfnm_api_failcode fail = _this->openDev->set(LIBRFNM_APPLY_CH0_RX); | ||||
|         if (fail != rfnm_api_failcode::RFNM_API_OK) { | ||||
|             flog::error("Failed to configure device: {}", (int)fail); | ||||
|         } | ||||
|  | ||||
|         // Configure the stream | ||||
|         _this->bufferSize = -1; | ||||
|         _this->openDev->rx_stream(librfnm_stream_format::LIBRFNM_STREAM_FORMAT_CS16, &_this->bufferSize); | ||||
|         if (_this->bufferSize <= 0) { | ||||
|             flog::error("Failed to configure stream"); | ||||
|         } | ||||
|  | ||||
|         // Allocate and queue buffers | ||||
|         flog::debug("BUFFER SIZE: {}", _this->bufferSize); | ||||
|         for (int i = 0; i < LIBRFNM_MIN_RX_BUFCNT; i++) { | ||||
|             _this->rxBuf[i].buf = dsp::buffer::alloc<uint8_t>(_this->bufferSize); | ||||
|             _this->openDev->rx_qbuf(&_this->rxBuf[i]); | ||||
|         } | ||||
|  | ||||
|         // Start worker | ||||
|         _this->workerThread = std::thread(&RFNMSourceModule::worker, _this); | ||||
|          | ||||
|         _this->running = true; | ||||
|         flog::info("RFNMSourceModule '{0}': Start!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void stop(void* ctx) { | ||||
|         RFNMSourceModule* _this = (RFNMSourceModule*)ctx; | ||||
|         if (!_this->running) { return; } | ||||
|         _this->running = false; | ||||
|          | ||||
|         // Stop worker | ||||
|         _this->stream.stopWriter(); | ||||
|         if (_this->workerThread.joinable()) { _this->workerThread.join(); } | ||||
|         _this->stream.clearWriteStop(); | ||||
|  | ||||
|         // Disable channel | ||||
|         _this->openDev->librfnm_s->rx.ch[0].enable = RFNM_CH_ON; | ||||
|         _this->openDev->set(LIBRFNM_APPLY_CH0_RX); | ||||
|  | ||||
|         // Close device | ||||
|         delete _this->openDev; | ||||
|  | ||||
|         // Free buffers | ||||
|         for (int i = 0; i < LIBRFNM_MIN_RX_BUFCNT; i++) { | ||||
|             dsp::buffer::free(_this->rxBuf[i].buf); | ||||
|         } | ||||
|  | ||||
|         flog::info("RFNMSourceModule '{0}': Stop!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         RFNMSourceModule* _this = (RFNMSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             _this->openDev->librfnm_s->rx.ch[0].freq = freq; | ||||
|             rfnm_api_failcode fail = _this->openDev->set(LIBRFNM_APPLY_CH0_RX); | ||||
|             if (fail != rfnm_api_failcode::RFNM_API_OK) { | ||||
|                 flog::error("Failed to tune: {}", (int)fail); | ||||
|             } | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         flog::info("RFNMSourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
|     } | ||||
|  | ||||
|     static void menuHandler(void* ctx) { | ||||
|         RFNMSourceModule* _this = (RFNMSourceModule*)ctx; | ||||
|          | ||||
|         if (_this->running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_rfnm_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { | ||||
|             _this->select(_this->devices.key(_this->devId)); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Combo(CONCAT("##_rfnm_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) { | ||||
|             _this->sampleRate = _this->samplerates.key(_this->srId); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::SameLine(); | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Button(CONCAT("Refresh##_rfnm_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->selectedSerial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         SmGui::LeftLabel("Bandwidth"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(CONCAT("##_rfnm_bw_sel_", _this->name), &_this->bwId, _this->bandwidths.txt)) { | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("Gain"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderInt(CONCAT("##_rfnm_gain_", _this->name), &_this->gain, _this->gainMin, _this->gainMax)) { | ||||
|             if (_this->running) { | ||||
|                 _this->openDev->librfnm_s->rx.ch[0].gain = _this->gain; | ||||
|                 rfnm_api_failcode fail = _this->openDev->set(LIBRFNM_APPLY_CH0_RX); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Checkbox(CONCAT("FM Notch##_rfnm_", _this->name), &_this->fmNotch)) { | ||||
|             if (_this->running) { | ||||
|                 _this->openDev->librfnm_s->rx.ch[0].fm_notch = _this->fmNotch ? rfnm_fm_notch::RFNM_FM_NOTCH_ON : rfnm_fm_notch::RFNM_FM_NOTCH_OFF; | ||||
|                 rfnm_api_failcode fail = _this->openDev->set(LIBRFNM_APPLY_CH0_RX); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void worker() { | ||||
|         librfnm_rx_buf* lrxbuf; | ||||
|         int sampCount = bufferSize/4; | ||||
|  | ||||
|         // TODO: Define number of buffers per swap to maintain 200 fps | ||||
|  | ||||
|         while (true) { | ||||
|             // Receive a buffer | ||||
|             auto fail = openDev->rx_dqbuf(&lrxbuf, LIBRFNM_CH0, 1000); | ||||
|             if (fail == rfnm_api_failcode::RFNM_API_DQBUF_NO_DATA) { | ||||
|                 flog::error("Dequeue buffer didn't have any data"); | ||||
|                 continue; | ||||
|             } | ||||
|             else if (fail) { break; } | ||||
|  | ||||
|             // Convert buffer to CF32 | ||||
|             volk_16i_s32f_convert_32f((float*)stream.writeBuf, (int16_t*)lrxbuf->buf, 32768.0f, sampCount * 2); | ||||
|  | ||||
|             // Reque buffer | ||||
|             openDev->rx_qbuf(lrxbuf); | ||||
|  | ||||
|             // Swap data | ||||
|             if (!stream.swap(sampCount)) { break; } | ||||
|         } | ||||
|  | ||||
|         flog::debug("Worker exiting"); | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     dsp::stream<dsp::complex_t> stream; | ||||
|     double sampleRate; | ||||
|     SourceManager::SourceHandler handler; | ||||
|     bool running = false; | ||||
|     double freq; | ||||
|  | ||||
|     OptionList<std::string, std::string> devices; | ||||
|     OptionList<int, int> bandwidths; | ||||
|     OptionList<int, int> samplerates; | ||||
|     int gainMin = 0; | ||||
|     int gainMax = 0; | ||||
|  | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|     int bwId = 0; | ||||
|     int gain = 0; | ||||
|     bool fmNotch = false; | ||||
|     std::string selectedSerial; | ||||
|     librfnm* openDev; | ||||
|     int bufferSize = -1; | ||||
|     librfnm_rx_buf rxBuf[LIBRFNM_MIN_RX_BUFCNT]; | ||||
|  | ||||
| #ifdef __ANDROID__ | ||||
|     int devFd = 0; | ||||
| #endif | ||||
|  | ||||
|     std::thread workerThread; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     // Nothing here | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new RFNMSourceModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
|     delete (RFNMSourceModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     // Nothing here | ||||
| } | ||||
| @@ -411,6 +411,8 @@ public: | ||||
|             agcSetPoint = config.conf["devices"][selectedName]["agcSetPoint"]; | ||||
|         } | ||||
|  | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|  | ||||
|         // Per device options | ||||
|         if (openDev.hwVer == SDRPLAY_RSP1_ID) { | ||||
|             // No config to load | ||||
| @@ -686,7 +688,6 @@ private: | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##sdrplay_dev", _this->name), &_this->devId, _this->devListTxt.c_str())) { | ||||
|             _this->selectById(_this->devId); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             config.acquire(); | ||||
|             config.conf["device"] = _this->devNameList[_this->devId]; | ||||
|             config.release(true); | ||||
| @@ -710,7 +711,6 @@ private: | ||||
|             if (SmGui::Button(CONCAT("Refresh##sdrplay_refresh", _this->name))) { | ||||
|                 _this->refresh(); | ||||
|                 _this->selectByName(_this->selectedName); | ||||
|                 core::setInputSampleRate(_this->sampleRate); | ||||
|             } | ||||
|  | ||||
|             SmGui::LeftLabel("Bandwidth"); | ||||
|   | ||||
| @@ -9,13 +9,8 @@ | ||||
| #include <gui/smgui.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <codecvt> | ||||
| #include <locale> | ||||
| #include <aaroniartsaapi.h> | ||||
|  | ||||
| #ifndef _WIN32 | ||||
| #include <unistd.h> | ||||
| #endif | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
| @@ -194,6 +189,7 @@ public: | ||||
|  | ||||
|         // Set samplerate | ||||
|         samplerate = sampleRateList.value(srId); | ||||
|         core::setInputSampleRate(samplerate.effective); | ||||
|  | ||||
|         // Close device | ||||
|         AARTSAAPI_CloseDevice(&api, &dev); | ||||
| @@ -333,10 +329,9 @@ private: | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_spectran_dev_", _this->name), &_this->devId, _this->devList.txt)) { | ||||
|             _this->selectSerial(_this->devList.key(_this->devId)); | ||||
|             core::setInputSampleRate(_this->samplerate.effective); | ||||
|              | ||||
|         } | ||||
|          | ||||
|         // TODO: SR sel | ||||
|         if (SmGui::Combo(CONCAT("##_spectran_sr_", _this->name), &_this->srId, _this->sampleRateList.txt)) { | ||||
|             _this->samplerate = _this->sampleRateList.value(_this->srId); | ||||
|             core::setInputSampleRate(_this->samplerate.effective); | ||||
| @@ -552,4 +547,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) { | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
| } | ||||
| @@ -211,6 +211,7 @@ public: | ||||
|  | ||||
|         // Apply samplerate | ||||
|         sampleRate = samplerates.key(srId); | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|     } | ||||
|  | ||||
|     void setBandwidth(double bw) { | ||||
| @@ -334,7 +335,6 @@ private: | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_usrp_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { | ||||
|             _this->select(_this->devices.key(_this->devId)); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (!_this->selectedSer.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["device"] = _this->devices.key(_this->devId); | ||||
| @@ -357,8 +357,10 @@ private: | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Button(CONCAT("Refresh##_usrp_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->selectedSer); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             config.acquire(); | ||||
|             std::string ser = config.conf["device"]; | ||||
|             config.release(); | ||||
|             _this->select(ser); | ||||
|         } | ||||
|  | ||||
|         if (_this->channels.size() > 1) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user