27 Commits

Author SHA1 Message Date
40f1ee5651 Merge pull request #1385 from AlexandreRouma/master
Keep new_sinks branch up to date
2024-04-18 03:14:40 +02:00
c4b0cb37a6 Merge pull request #1315 from AlexandreRouma/master
keep new_sinks branch up to date
2024-02-02 14:51:34 +01:00
cd7dabda51 convert network sink to new stream system 2024-01-30 22:04:36 +01:00
139f63ad25 limit stream configuration to creator 2024-01-30 20:28:32 +01:00
75800e0ca2 fix merge issue 2024-01-30 18:09:59 +01:00
84da183559 Merge pull request #1310 from AlexandreRouma/master
New sink/stream system
2024-01-30 18:08:41 +01:00
0144d8e8ce resolve conflict in radio 2024-01-30 18:08:17 +01:00
7ef88f23a8 resolve conflict in audio_sink 2 2024-01-30 18:06:31 +01:00
c934e41a61 resolve conflict in audio_sink 2024-01-30 18:05:28 +01:00
9dd6c8546d resolve conflict in spectran_http_client.cpp 2024-01-30 18:02:16 +01:00
191f652fc3 fix incorrect formatting #1288 2024-01-22 17:05:50 +01:00
e208511bde more work 2023-12-16 19:57:48 +01:00
348bf75281 Merge pull request #1252 from AlexandreRouma/master
Merging back modern stuff into the new sinks branch
2023-12-14 20:54:04 +01:00
cbf1d6703e more work 2023-08-20 21:06:36 +02:00
03c30f202e Merge pull request #1132 from AlexandreRouma/master
Merge back
2023-07-11 01:15:03 +02:00
25bc9f60ed more work 2023-07-10 03:48:59 +02:00
2e80882ab5 Merge pull request #1130 from AlexandreRouma/master
Merging back master changes
2023-07-09 18:35:44 +02:00
4c584847de more work 2023-07-09 18:34:52 +02:00
2a741932e0 more or less finished the new stream system 2023-07-09 04:09:18 +02:00
ff655caf31 saving work 2023-05-17 03:55:47 +02:00
0277232bdb redesign 2023-04-21 22:44:45 +02:00
2e3e2a7dca Merge pull request #1049 from AlexandreRouma/master
Merging back changes
2023-04-20 09:36:52 +02:00
271a77e4ce More work on the stream system 2023-04-20 09:35:06 +02:00
48e9708d74 added disclaimer 2023-04-10 22:52:50 +02:00
961cd3f133 beginning of documentation 2023-04-03 20:49:27 +02:00
2676190d3a Merge pull request #1035 from AlexandreRouma/master
merge back changes to master
2023-04-03 20:40:25 +02:00
8851735cb8 initial structure 2023-04-03 20:28:17 +02:00
144 changed files with 2337 additions and 15675 deletions

View File

@ -36,13 +36,6 @@ jobs:
working-directory: ${{runner.workspace}}
run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/" ; rm "C:/Program Files/PothosSDR/lib/libusb-1.0.lib" ; cp "libusb_old/MS64/dll/libusb-1.0.lib" "C:/Program Files/PothosSDR/lib/"
- name: Download librtlsdr
run: Invoke-WebRequest -Uri "https://ftp.osmocom.org/binaries/windows/rtl-sdr/rtl-sdr-64bit-20240623.zip" -OutFile ${{runner.workspace}}/rtl-sdr.zip
- name: Patch Pothos with newer librtlsdr version
working-directory: ${{runner.workspace}}
run: 7z x rtl-sdr.zip ; rm "C:/Program Files/PothosSDR/bin/rtlsdr.dll" ; cp "rtl-sdr-64bit-20240623/librtlsdr.dll" "C:/Program Files/PothosSDR/bin/rtlsdr.dll"
- name: Download SDRPlay API
run: Invoke-WebRequest -Uri "https://www.sdrpp.org/SDRplay.zip" -OutFile ${{runner.workspace}}/SDRplay.zip
@ -50,7 +43,7 @@ jobs:
run: 7z x ${{runner.workspace}}/SDRplay.zip -o"C:/Program Files/"
- name: Download codec2
run: git clone https://github.com/drowe67/codec2
run: git clone https://github.com/AlexandreRouma/codec2
- name: Prepare MinGW
run: C:/msys64/msys2_shell.cmd -defterm -here -no-start -mingw64 -c "pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja"
@ -65,23 +58,17 @@ jobs:
run: mkdir "C:/Program Files/codec2" ; mkdir "C:/Program Files/codec2/include" ; mkdir "C:/Program Files/codec2/include/codec2" ; mkdir "C:/Program Files/codec2/lib" ; cd "codec2" ; xcopy "src" "C:/Program Files/codec2/include" ; cd "build" ; xcopy "src" "C:/Program Files/codec2/lib" ; xcopy "codec2" "C:/Program Files/codec2/include/codec2"
- name: Install vcpkg dependencies
run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows libusb:x64-windows spdlog:x64-windows
run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows libusb:x64-windows
- name: Install rtaudio
run: git clone https://github.com/thestk/rtaudio ; cd rtaudio ; git checkout 2f2fca4502d506abc50f6d4473b2836d24cfb1e3 ; mkdir build ; cd build ; cmake .. ; cmake --build . --config Release ; cmake --install .
- name: Install libperseus-sdr
run: git clone https://github.com/AlexandreRouma/libperseus-sdr ; cd libperseus-sdr ; mkdir build ; cd build ; cmake -DCMAKE_BUILD_TYPE=Release "-DLIBUSB_LIBRARIES=C:/Program Files/PothosSDR/lib/libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIRS=C:/Program Files/PothosSDR/include/libusb-1.0" .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr"
- name: Install librfnm
run: git clone https://github.com/AlexandreRouma/librfnm ; cd librfnm ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install .
- name: Install libfobos
run: git clone https://github.com/AlexandreRouma/libfobos ; cd libfobos ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install .
run: git clone https://github.com/AlexandreRouma/libperseus-sdr ; cd libperseus-sdr ; mkdir build ; cd build ; cmake "-DLIBUSB_LIBRARIES=C:/Program Files/PothosSDR/lib/libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIRS=C:/Program Files/PothosSDR/include/libusb-1.0" .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr"
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake -DCOPY_MSVC_REDISTRIBUTABLES=ON "$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 -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=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
@ -98,7 +85,7 @@ jobs:
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
build_macos_intel:
runs-on: macos-13
runs-on: macos-12
steps:
- uses: actions/checkout@v4
@ -107,13 +94,13 @@ jobs:
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Install dependencies
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && pip3 install mako
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool && pip3 install mako
- name: Install volk
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 ../../
@ -125,20 +112,14 @@ jobs:
run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install libperseus
run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && sudo make install && cd ..
- name: Install librfnm
run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
- name: Install libfobos
run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && make install && cd ..
- name: Install more recent librtlsdr
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build
working-directory: ${{runner.workspace}}/build
@ -164,13 +145,13 @@ jobs:
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Install dependencies
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && pip3 install mako --break-system-packages
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool && pip3 install mako --break-system-packages
- name: Install volk
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 ../../
@ -184,18 +165,12 @@ jobs:
# - name: Install libperseus
# run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && make install && cd ..
- name: Install librfnm
run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
- name: Install libfobos
run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
- name: Install more recent librtlsdr
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build
working-directory: ${{runner.workspace}}/build
@ -211,7 +186,7 @@ jobs:
name: sdrpp_macos_arm
path: ${{runner.workspace}}/sdrpp_macos_arm.zip
build_debian_buster_amd64:
build_debian_buster:
runs-on: ubuntu-latest
steps:
@ -233,29 +208,7 @@ jobs:
name: sdrpp_debian_buster_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_buster_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && 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_debian_buster_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bullseye_amd64:
build_debian_bullseye:
runs-on: ubuntu-latest
steps:
@ -277,29 +230,7 @@ jobs:
name: sdrpp_debian_bullseye_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bullseye_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && 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_debian_bullseye_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bookworm_amd64:
build_debian_bookworm:
runs-on: ubuntu-latest
steps:
@ -321,29 +252,7 @@ jobs:
name: sdrpp_debian_bookworm_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bookworm_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bookworm && 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_debian_bookworm_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_sid_amd64:
build_debian_sid:
runs-on: ubuntu-latest
steps:
@ -365,29 +274,7 @@ jobs:
name: sdrpp_debian_sid_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_sid_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && 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_debian_sid_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_focal_amd64:
build_ubuntu_focal:
runs-on: ubuntu-latest
steps:
@ -409,29 +296,7 @@ jobs:
name: sdrpp_ubuntu_focal_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_focal_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && 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_focal_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_jammy_amd64:
build_ubuntu_jammy:
runs-on: ubuntu-latest
steps:
@ -452,37 +317,15 @@ jobs:
with:
name: sdrpp_ubuntu_jammy_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_jammy_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && 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_jammy_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_noble_amd64:
build_ubuntu_mantic:
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
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_mantic && 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
@ -494,73 +337,7 @@ jobs:
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_noble_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_noble_aarch64:
runs-on: ubuntu-24.04-arm
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_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_oracular_amd64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_oracular && 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_oracular_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_oracular_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_oracular && 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_oracular_aarch64
name: sdrpp_ubuntu_mantic_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_raspios_bullseye_armhf:
@ -618,29 +395,7 @@ jobs:
path: ${{runner.workspace}}/sdrpp.apk
create_full_archive:
needs: [
'build_windows',
'build_macos_intel',
'build_macos_arm',
'build_debian_buster_amd64',
'build_debian_buster_aarch64',
'build_debian_bullseye_amd64',
'build_debian_bullseye_aarch64',
'build_debian_bookworm_amd64',
'build_debian_bookworm_aarch64',
'build_debian_sid_amd64',
'build_debian_sid_aarch64',
'build_ubuntu_focal_amd64',
'build_ubuntu_focal_aarch64',
'build_ubuntu_jammy_amd64',
'build_ubuntu_jammy_aarch64',
'build_ubuntu_noble_amd64',
'build_ubuntu_noble_aarch64',
'build_ubuntu_oracular_amd64',
'build_ubuntu_oracular_aarch64',
'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:
@ -654,21 +409,12 @@ jobs:
mv sdrpp_macos_intel/sdrpp_macos_intel.zip sdrpp_all/ &&
mv sdrpp_macos_arm/sdrpp_macos_arm.zip sdrpp_all/ &&
mv sdrpp_debian_buster_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_buster_amd64.deb &&
mv sdrpp_debian_buster_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_buster_aarch64.deb &&
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&
mv sdrpp_debian_bullseye_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_aarch64.deb &&
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&
mv sdrpp_debian_bookworm_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_amd64.deb &&
mv sdrpp_debian_bookworm_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_aarch64.deb &&
mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
mv sdrpp_debian_sid_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_aarch64.deb &&
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
mv sdrpp_ubuntu_focal_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_aarch64.deb &&
mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb &&
mv sdrpp_ubuntu_jammy_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_aarch64.deb &&
mv sdrpp_ubuntu_noble_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_amd64.deb &&
mv sdrpp_ubuntu_noble_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_aarch64.deb &&
mv sdrpp_ubuntu_oracular_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_oracular_amd64.deb &&
mv sdrpp_ubuntu_oracular_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_oracular_aarch64.deb &&
mv sdrpp_ubuntu_mantic_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_mantic_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

6
.gitignore vendored
View File

@ -16,8 +16,4 @@ poggers_decoder
m17_decoder/libcorrect
SDR++.app
android/deps
android/app/assets
source_modules/dragonlabs_source
source_modules/badgesdr_source
decoder_modules/mystery_decoder
decoder_modules/tetra_decoder
android/app/assets

View File

@ -12,20 +12,14 @@ 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_FOBOSSDR_SOURCE "Build FobosSDR Source Module (Dependencies: libfobos)" OFF)
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON)
option(OPT_BUILD_HAROGIC_SOURCE "Build Harogic Source Module (Dependencies: htra_api)" OFF)
option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON)
option(OPT_BUILD_HYDRASDR_SOURCE "Build HydraSDR Source Module (Dependencies: libhydrasdr)" OFF)
option(OPT_BUILD_KCSDR_SOURCE "Build KCSDR Source Module (Dependencies: libkcsdr)" OFF)
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF)
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)
@ -46,15 +40,12 @@ option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: port
# Decoders
option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF)
option(OPT_BUILD_DAB_DECODER "Build the DAB/DAB+ decoder (no dependencies required)" OFF)
option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF)
option(OPT_BUILD_KG_SSTV_DECODER "Build the KG SSTV (KG-STV) decoder module (no dependencies required)" OFF)
option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (Dependencies: codec2)" OFF)
option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dependencies required)" ON)
option(OPT_BUILD_PAGER_DECODER "Build the pager decoder module (no dependencies required)" ON)
option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
option(OPT_BUILD_RYFI_DECODER "RyFi data link decoder" OFF)
option(OPT_BUILD_VOR_RECEIVER "VOR beacon receiver" OFF)
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
# Misc
@ -70,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")
@ -135,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)
@ -147,30 +133,14 @@ if (OPT_BUILD_FILE_SOURCE)
add_subdirectory("source_modules/file_source")
endif (OPT_BUILD_FILE_SOURCE)
if (OPT_BUILD_FOBOSSDR_SOURCE)
add_subdirectory("source_modules/fobossdr_source")
endif (OPT_BUILD_FOBOSSDR_SOURCE)
if (OPT_BUILD_HACKRF_SOURCE)
add_subdirectory("source_modules/hackrf_source")
endif (OPT_BUILD_HACKRF_SOURCE)
if (OPT_BUILD_HAROGIC_SOURCE)
add_subdirectory("source_modules/harogic_source")
endif (OPT_BUILD_HAROGIC_SOURCE)
if (OPT_BUILD_HERMES_SOURCE)
add_subdirectory("source_modules/hermes_source")
endif (OPT_BUILD_HERMES_SOURCE)
if (OPT_BUILD_HYDRASDR_SOURCE)
add_subdirectory("source_modules/hydrasdr_source")
endif (OPT_BUILD_HYDRASDR_SOURCE)
if (OPT_BUILD_KCSDR_SOURCE)
add_subdirectory("source_modules/kcsdr_source")
endif (OPT_BUILD_KCSDR_SOURCE)
if (OPT_BUILD_LIMESDR_SOURCE)
add_subdirectory("source_modules/limesdr_source")
endif (OPT_BUILD_LIMESDR_SOURCE)
@ -187,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)
@ -203,10 +169,6 @@ if (OPT_BUILD_RTL_TCP_SOURCE)
add_subdirectory("source_modules/rtl_tcp_source")
endif (OPT_BUILD_RTL_TCP_SOURCE)
if (OPT_BUILD_SDDC_SOURCE)
add_subdirectory("source_modules/sddc_source")
endif (OPT_BUILD_SDDC_SOURCE)
if (OPT_BUILD_SDRPP_SERVER_SOURCE)
add_subdirectory("source_modules/sdrpp_server_source")
endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
@ -263,10 +225,6 @@ if (OPT_BUILD_ATV_DECODER)
add_subdirectory("decoder_modules/atv_decoder")
endif (OPT_BUILD_ATV_DECODER)
if (OPT_BUILD_DAB_DECODER)
add_subdirectory("decoder_modules/dab_decoder")
endif (OPT_BUILD_DAB_DECODER)
if (OPT_BUILD_FALCON9_DECODER)
add_subdirectory("decoder_modules/falcon9_decoder")
endif (OPT_BUILD_FALCON9_DECODER)
@ -291,14 +249,6 @@ if (OPT_BUILD_RADIO)
add_subdirectory("decoder_modules/radio")
endif (OPT_BUILD_RADIO)
if (OPT_BUILD_RYFI_DECODER)
add_subdirectory("decoder_modules/ryfi_decoder")
endif (OPT_BUILD_RYFI_DECODER)
if (OPT_BUILD_VOR_RECEIVER)
add_subdirectory("decoder_modules/vor_receiver")
endif (OPT_BUILD_VOR_RECEIVER)
if (OPT_BUILD_WEATHER_SAT_DECODER)
add_subdirectory("decoder_modules/weather_sat_decoder")
endif (OPT_BUILD_WEATHER_SAT_DECODER)
@ -352,21 +302,6 @@ target_compile_options(sdrpp PRIVATE ${SDRPP_COMPILER_FLAGS})
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 ()
@ -407,6 +342,4 @@ endif ()
# Create uninstall target
configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY)
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
# Create headers target
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)

View File

@ -10,7 +10,7 @@ android {
minSdkVersion 28
targetSdkVersion 28
versionCode 1
versionName "1.2.1"
versionName "1.1.0"
externalNativeBuild {
cmake {

View File

@ -99,10 +99,7 @@ namespace backend {
glfwWindowHint(GLFW_CLIENT_API, OPENGL_VERSIONS_IS_ES[i] ? GLFW_OPENGL_ES_API : GLFW_OPENGL_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_VERSIONS_MAJOR[i]);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_VERSIONS_MINOR[i]);
#if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 4)
glfwWindowHintString(GLFW_WAYLAND_APP_ID, "sdrpp");
#endif
// Create window with graphics context
monitor = glfwGetPrimaryMonitor();
window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);

View File

@ -147,11 +147,11 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["menuElements"][3]["name"] = "Sinks";
defConfig["menuElements"][3]["open"] = true;
defConfig["menuElements"][4]["name"] = "Frequency Manager";
defConfig["menuElements"][4]["open"] = true;
defConfig["menuElements"][3]["name"] = "Frequency Manager";
defConfig["menuElements"][3]["open"] = true;
defConfig["menuElements"][5]["name"] = "VFO Color";
defConfig["menuElements"][5]["open"] = true;
defConfig["menuElements"][4]["name"] = "VFO Color";
defConfig["menuElements"][4]["open"] = true;
defConfig["menuElements"][6]["name"] = "Band Plan";
defConfig["menuElements"][6]["open"] = true;
@ -173,26 +173,16 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true;
defConfig["moduleInstances"]["File Source"]["module"] = "file_source";
defConfig["moduleInstances"]["File Source"]["enabled"] = true;
defConfig["moduleInstances"]["FobosSDR Source"]["module"] = "fobossdr_source";
defConfig["moduleInstances"]["FobosSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["HackRF Source"]["module"] = "hackrf_source";
defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true;
defConfig["moduleInstances"]["Harogic Source"]["module"] = "harogic_source";
defConfig["moduleInstances"]["Harogic Source"]["enabled"] = true;
defConfig["moduleInstances"]["Hermes Source"]["module"] = "hermes_source";
defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true;
defConfig["moduleInstances"]["HydraSDR Source"]["module"] = "hydrasdr_source";
defConfig["moduleInstances"]["HydraSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["Network Source"]["module"] = "network_source";
defConfig["moduleInstances"]["Network Source"]["enabled"] = true;
defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source";
defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["RFNM Source"]["module"] = "rfnm_source";
defConfig["moduleInstances"]["RFNM Source"]["enabled"] = true;
defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source";
defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source";
defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true;
defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source";
@ -203,12 +193,8 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true;
defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source";
defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true;
defConfig["moduleInstances"]["Spectran HTTP Source"]["module"] = "spectran_http_source";
defConfig["moduleInstances"]["Spectran HTTP Source"]["enabled"] = true;
defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source";
defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true;
defConfig["moduleInstances"]["USRP Source"]["module"] = "usrp_source";
defConfig["moduleInstances"]["USRP Source"]["enabled"] = true;
defConfig["moduleInstances"]["Audio Sink"] = "audio_sink";
defConfig["moduleInstances"]["Network Sink"] = "network_sink";
@ -234,19 +220,12 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["modules"] = json::array();
defConfig["offsets"]["SpyVerter"] = 120000000.0;
defConfig["offsets"]["Ham-It-Up"] = 125000000.0;
defConfig["offsets"]["MMDS S-band (1998MHz)"] = -1998000000.0;
defConfig["offsets"]["DK5AV X-Band"] = -6800000000.0;
defConfig["offsets"]["Ku LNB (9750MHz)"] = -9750000000.0;
defConfig["offsets"]["Ku LNB (10700MHz)"] = -10700000000.0;
defConfig["selectedOffset"] = "None";
defConfig["manualOffset"] = 0.0;
defConfig["offsetMode"] = (int)0; // Off
defConfig["offset"] = 0.0;
defConfig["showMenu"] = true;
defConfig["showWaterfall"] = true;
defConfig["source"] = "";
defConfig["decimation"] = 1;
defConfig["decimationPower"] = 0;
defConfig["iqCorrection"] = false;
defConfig["invertIQ"] = false;
@ -327,18 +306,12 @@ int sdrpp_main(int argc, char* argv[]) {
// Remove unused elements
auto items = core::configManager.conf.items();
auto newConf = core::configManager.conf;
bool configCorrected = false;
for (auto const& item : items) {
if (!defConfig.contains(item.key())) {
flog::info("Unused key in config {0}, repairing", item.key());
newConf.erase(item.key());
configCorrected = true;
core::configManager.conf.erase(item.key());
}
}
if (configCorrected) {
core::configManager.conf = newConf;
}
// Update to new module representation in config if needed
for (auto [_name, inst] : core::configManager.conf["moduleInstances"].items()) {

View File

@ -37,20 +37,14 @@ namespace sdrpp_credits {
const char* hardwareDonators[] = {
"Aaronia AG",
"Airspy",
"Alex 4Z5LV",
"Analog Devices",
"CaribouLabs",
"Deepace",
"Ettus Research",
"Harogic",
"Howard Su",
"MicroPhase",
"Microtelecom",
"MyriadRF",
"Nuand",
"RFNM",
"RFspace",
"RigExpert",
"RTL-SDRblog",
"SDRplay"
};
@ -70,18 +64,15 @@ namespace sdrpp_credits {
"Flinger Films",
"Frank Werner (HB9FXQ)",
"gringogrigio",
"Jandro",
"Jeff Moe",
"Joe Cupano",
"KD1SQ",
"Kezza",
"Krys Kamieniecki",
"Lee Donaghy",
"Lee (KD1SQ)",
"Lee KD1SQ",
".lozenge. (Hank Hill)",
"Martin Herren (HB9FXX)",
"NeoVilsonWong",
"Nitin (VU2JEK)",
"ON4MU",
"Passion-Radio.com",
"Paul Maine",

View File

@ -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();

View File

@ -1,6 +1,5 @@
#pragma once
#include <volk/volk.h>
#include <string.h>
namespace dsp::buffer {
template<class T>

View File

@ -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;

View File

@ -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() {}

View File

@ -1,6 +1,5 @@
#pragma once
#include <volk/volk.h>
#include "../buffer/buffer.h"
namespace dsp {
template<class T>

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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();

View File

@ -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);
}

View File

@ -5,120 +5,113 @@
#include <gui/main_window.h>
#include <gui/style.h>
#include <signal_path/signal_path.h>
#include <utils/optionlist.h>
#include <gui/dialogs/dialog_box.h>
namespace sourcemenu {
int offsetMode = 0;
int sourceId = 0;
EventHandler<std::string> sourcesChangedHandler;
EventHandler<std::string> sourceUnregisterHandler;
OptionList<std::string, std::string> sources;
std::string selectedSource;
int decimId = 0;
OptionList<int, int> decimations;
double customOffset = 0.0;
double effectiveOffset = 0.0;
int decimationPower = 0;
bool iqCorrection = false;
bool invertIQ = false;
int offsetId = 0;
double manualOffset = 0.0;
std::string selectedOffset;
double effectiveOffset = 0.0;
OptionList<std::string, double> offsets;
std::map<std::string, double> namedOffsets;
EventHandler<std::string> sourceRegisteredHandler;
EventHandler<std::string> sourceUnregisterHandler;
EventHandler<std::string> sourceUnregisteredHandler;
bool showAddOffsetDialog = false;
char newOffsetName[1024];
double newOffset = 0.0;
std::vector<std::string> sourceNames;
std::string sourceNamesTxt;
std::string selectedSource;
bool showDelOffsetDialog = false;
std::string delOffsetName = "";
// Offset IDs
enum {
OFFSET_ID_NONE,
OFFSET_ID_MANUAL,
OFFSET_ID_CUSTOM_BASE
OFFSET_MODE_NONE,
OFFSET_MODE_CUSTOM,
OFFSET_MODE_SPYVERTER,
OFFSET_MODE_HAM_IT_UP,
OFFSET_MODE_MMDS_SB_1998,
OFFSET_MODE_DK5AV_XB,
OFFSET_MODE_KU_LNB_9750,
OFFSET_MODE_KU_LNB_10700,
_OFFSET_MODE_COUNT
};
void updateOffset() {
// Compute the effective offset
switch (offsetId) {
case OFFSET_ID_NONE:
effectiveOffset = 0;
break;
case OFFSET_ID_MANUAL:
effectiveOffset = manualOffset;
break;
default:
effectiveOffset = namedOffsets[offsets.name(offsetId)];
break;
}
const char* offsetModesTxt = "None\0"
"Custom\0"
"SpyVerter\0"
"Ham-It-Up\0"
"MMDS S-band (1998MHz)\0"
"DK5AV X-Band\0"
"Ku LNB (9750MHz)\0"
"Ku LNB (10700MHz)\0";
// Apply it
const char* decimationStages = "None\0"
"2\0"
"4\0"
"8\0"
"16\0"
"32\0"
"64\0";
void updateOffset() {
if (offsetMode == OFFSET_MODE_CUSTOM) { effectiveOffset = customOffset; }
else if (offsetMode == OFFSET_MODE_SPYVERTER) {
effectiveOffset = 120000000;
} // 120MHz Up-conversion
else if (offsetMode == OFFSET_MODE_HAM_IT_UP) {
effectiveOffset = 125000000;
} // 125MHz Up-conversion
else if (offsetMode == OFFSET_MODE_MMDS_SB_1998) {
effectiveOffset = -1998000000;
} // 1.998GHz Down-conversion
else if (offsetMode == OFFSET_MODE_DK5AV_XB) {
effectiveOffset = -6800000000;
} // 6.8GHz Down-conversion
else if (offsetMode == OFFSET_MODE_KU_LNB_9750) {
effectiveOffset = -9750000000;
} // 9.750GHz Down-conversion
else if (offsetMode == OFFSET_MODE_KU_LNB_10700) {
effectiveOffset = -10700000000;
} // 10.7GHz Down-conversion
else {
effectiveOffset = 0;
}
sigpath::sourceManager.setTuningOffset(effectiveOffset);
}
void selectOffsetById(int id) {
// Update the offset mode
offsetId = id;
selectedOffset = offsets.name(id);
// Update the offset
updateOffset();
}
void selectOffsetByName(const std::string& name) {
// If the name doesn't exist, select 'None'
if (!offsets.nameExists(name)) {
selectOffsetById(OFFSET_ID_NONE);
return;
}
// Select using the ID associated with the name
selectOffsetById(offsets.nameId(name));
}
void refreshSources() {
// Get sources
auto sourceNames = sigpath::sourceManager.getSourceNames();
// Define source options
sources.clear();
sourceNames = sigpath::sourceManager.getSourceNames();
sourceNamesTxt.clear();
for (auto name : sourceNames) {
sources.define(name, name, name);
sourceNamesTxt += name;
sourceNamesTxt += '\0';
}
}
void selectSource(std::string name) {
// If there is no source, give up
if (sources.empty()) {
sourceId = 0;
if (sourceNames.empty()) {
selectedSource.clear();
return;
}
auto it = std::find(sourceNames.begin(), sourceNames.end(), name);
if (it == sourceNames.end()) {
selectSource(sourceNames[0]);
return;
}
sourceId = std::distance(sourceNames.begin(), it);
selectedSource = sourceNames[sourceId];
sigpath::sourceManager.selectSource(sourceNames[sourceId]);
}
// If a source with the given name doesn't exist, select the first source instead
if (!sources.valueExists(name)) {
selectSource(sources.value(0));
void onSourceRegistered(std::string name, void* ctx) {
refreshSources();
if (selectedSource.empty()) {
sourceId = 0;
selectSource(sourceNames[0]);
return;
}
// Update the GUI variables
sourceId = sources.valueId(name);
selectedSource = name;
// Select the source module
sigpath::sourceManager.selectSource(name);
}
void onSourcesChanged(std::string name, void* ctx) {
// Update the source list
refreshSources();
// Reselect the current source
selectSource(selectedSource);
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
}
void onSourceUnregister(std::string name, void* ctx) {
@ -127,173 +120,60 @@ namespace sourcemenu {
// TODO: Stop everything
}
void reloadOffsets() {
// Clear list
offsets.clear();
namedOffsets.clear();
void onSourceUnregistered(std::string name, void* ctx) {
refreshSources();
// Define special offset modes
offsets.define("None", OFFSET_ID_NONE);
offsets.define("Manual", OFFSET_ID_MANUAL);
// Acquire the config file
core::configManager.acquire();
// Load custom offsets
auto ofs = core::configManager.conf["offsets"].items();
for (auto& o : ofs) {
namedOffsets[o.key()] = (double)o.value();
if (sourceNames.empty()) {
selectedSource = "";
return;
}
// Define custom offsets
for (auto& [name, offset] : namedOffsets) {
offsets.define(name, offsets.size());
if (name == selectedSource) {
sourceId = std::clamp<int>(sourceId, 0, sourceNames.size() - 1);
selectSource(sourceNames[sourceId]);
return;
}
// Release the config file
core::configManager.release();
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
}
void init() {
// Load offset modes
reloadOffsets();
// Define decimation values
decimations.define(1, "None", 1);
decimations.define(2, "2x", 2);
decimations.define(4, "4x", 4);
decimations.define(8, "8x", 8);
decimations.define(16, "16x", 16);
decimations.define(32, "32x", 32);
decimations.define(64, "64x", 64);
// Acquire the config file
core::configManager.acquire();
// Load other settings
std::string selectedSource = core::configManager.conf["source"];
manualOffset = core::configManager.conf["manualOffset"];
std::string selectedOffset = core::configManager.conf["selectedOffset"];
std::string selected = core::configManager.conf["source"];
customOffset = core::configManager.conf["offset"];
offsetMode = core::configManager.conf["offsetMode"];
decimationPower = core::configManager.conf["decimationPower"];
iqCorrection = core::configManager.conf["iqCorrection"];
invertIQ = core::configManager.conf["invertIQ"];
int decimation = core::configManager.conf["decimation"];
if (decimations.keyExists(decimation)) {
decimId = decimations.keyId(decimation);
}
// Release the config file
core::configManager.release();
// Select the source module
refreshSources();
selectSource(selectedSource);
// Update frontend settings
sigpath::iqFrontEnd.setDCBlocking(iqCorrection);
sigpath::iqFrontEnd.setInvertIQ(invertIQ);
sigpath::iqFrontEnd.setDecimation(decimations.value(decimId));
selectOffsetByName(selectedOffset);
updateOffset();
// Register handlers
sourcesChangedHandler.handler = onSourcesChanged;
refreshSources();
selectSource(selected);
sigpath::iqFrontEnd.setDecimation(1 << decimationPower);
sourceRegisteredHandler.handler = onSourceRegistered;
sourceUnregisterHandler.handler = onSourceUnregister;
sigpath::sourceManager.onSourceRegistered.bindHandler(&sourcesChangedHandler);
sourceUnregisteredHandler.handler = onSourceUnregistered;
sigpath::sourceManager.onSourceRegistered.bindHandler(&sourceRegisteredHandler);
sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler);
sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourcesChangedHandler);
}
sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourceUnregisteredHandler);
void addOffset(const std::string& name, double offset) {
// Acquire the config file
core::configManager.acquire();
// Define a new offset
core::configManager.conf["offsets"][name] = offset;
// Acquire the config file
core::configManager.release(true);
// Reload the offsets
reloadOffsets();
// Attempt to re-select the same one
selectOffsetByName(selectedOffset);
}
void delOffset(const std::string& name) {
// Acquire the config file
core::configManager.acquire();
// Define a new offset
core::configManager.conf["offsets"].erase(name);
// Acquire the config file
core::configManager.release(true);
// Reload the offsets
reloadOffsets();
// Attempt to re-select the same one
selectOffsetByName(selectedOffset);
}
bool addOffsetDialog() {
bool open = true;
gui::mainWindow.lockWaterfallControls = true;
float menuWidth = ImGui::GetContentRegionAvail().x;
const char* id = "Add offset##sdrpp_add_offset_dialog_";
ImGui::OpenPopup(id);
if (ImGui::BeginPopup(id, ImGuiWindowFlags_NoResize)) {
ImGui::LeftLabel("Name");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
ImGui::InputText("##sdrpp_add_offset_name", newOffsetName, 1023);
ImGui::LeftLabel("Offset");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
ImGui::InputDouble("##sdrpp_add_offset_offset", &newOffset);
bool nameExists = offsets.nameExists(newOffsetName);
bool reservedName = !strcmp(newOffsetName, "None") || !strcmp(newOffsetName, "Manual");
bool denyApply = !newOffsetName[0] || nameExists || reservedName;
if (nameExists) {
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "An offset with the given name already exists.");
}
else if (reservedName) {
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "The given name is reserved.");
}
if (denyApply) { style::beginDisabled(); }
if (ImGui::Button("Apply")) {
addOffset(newOffsetName, newOffset);
open = false;
}
if (denyApply) { style::endDisabled(); }
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
open = false;
}
ImGui::EndPopup();
}
return open;
core::configManager.release();
}
void draw(void* ctx) {
float itemWidth = ImGui::GetContentRegionAvail().x;
float lineHeight = ImGui::GetTextLineHeightWithSpacing();
float spacing = lineHeight - ImGui::GetTextLineHeight();
bool running = gui::mainWindow.sdrIsRunning();
if (running) { style::beginDisabled(); }
ImGui::SetNextItemWidth(itemWidth);
if (ImGui::Combo("##source", &sourceId, sources.txt)) {
std::string newSource = sources.value(sourceId);
selectSource(newSource);
if (ImGui::Combo("##source", &sourceId, sourceNamesTxt.c_str())) {
selectSource(sourceNames[sourceId]);
core::configManager.acquire();
core::configManager.conf["source"] = newSource;
core::configManager.conf["source"] = sourceNames[sourceId];
core::configManager.release(true);
}
@ -316,45 +196,21 @@ namespace sourcemenu {
}
ImGui::LeftLabel("Offset mode");
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX() - 2.0f*(lineHeight + 1.5f*spacing));
if (ImGui::Combo("##_sdrpp_offset", &offsetId, offsets.txt)) {
selectOffsetById(offsetId);
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##_sdrpp_offset_mode", &offsetMode, offsetModesTxt)) {
updateOffset();
core::configManager.acquire();
core::configManager.conf["selectedOffset"] = offsets.key(offsetId);
core::configManager.conf["offsetMode"] = offsetMode;
core::configManager.release(true);
}
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - spacing);
if (offsetId < OFFSET_ID_CUSTOM_BASE) { ImGui::BeginDisabled(); }
if (ImGui::Button("-##_sdrpp_offset_del_", ImVec2(lineHeight + 0.5f*spacing, 0))) {
delOffsetName = selectedOffset;
showDelOffsetDialog = true;
}
if (offsetId < OFFSET_ID_CUSTOM_BASE) { ImGui::EndDisabled(); }
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - spacing);
if (ImGui::Button("+##_sdrpp_offset_add_", ImVec2(lineHeight + 0.5f*spacing, 0))) {
strcpy(newOffsetName, "New Offset");
showAddOffsetDialog = true;
}
// Offset delete confirmation
if (ImGui::GenericDialog("sdrpp_del_offset_confirm", showDelOffsetDialog, GENERIC_DIALOG_BUTTONS_YES_NO, []() {
ImGui::Text("Deleting offset named \"%s\". Are you sure?", delOffsetName.c_str());
}) == GENERIC_DIALOG_BUTTON_YES) {
delOffset(delOffsetName);
}
// Offset add diaglog
if (showAddOffsetDialog) { showAddOffsetDialog = addOffsetDialog(); }
ImGui::LeftLabel("Offset");
ImGui::FillWidth();
if (offsetId == OFFSET_ID_MANUAL) {
if (ImGui::InputDouble("##freq_offset", &manualOffset, 1.0, 100.0)) {
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
if (offsetMode == OFFSET_MODE_CUSTOM) {
if (ImGui::InputDouble("##freq_offset", &customOffset, 1.0, 100.0)) {
updateOffset();
core::configManager.acquire();
core::configManager.conf["manualOffset"] = manualOffset;
core::configManager.conf["offset"] = customOffset;
core::configManager.release(true);
}
}
@ -366,11 +222,11 @@ namespace sourcemenu {
if (running) { style::beginDisabled(); }
ImGui::LeftLabel("Decimation");
ImGui::FillWidth();
if (ImGui::Combo("##source_decim", &decimId, decimations.txt)) {
sigpath::iqFrontEnd.setDecimation(decimations.value(decimId));
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##source_decim", &decimationPower, decimationStages)) {
sigpath::iqFrontEnd.setDecimation(1 << decimationPower);
core::configManager.acquire();
core::configManager.conf["decimation"] = decimations.key(decimId);
core::configManager.conf["decimationPower"] = decimationPower;
core::configManager.release(true);
}
if (running) { style::endDisabled(); }

View 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();
}
}
};

View File

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

View File

@ -3,7 +3,6 @@
#include <gui/style.h>
#include <gui/gui.h>
#include <backend.h>
#include <utils/hrfreq.h>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
@ -91,7 +90,6 @@ void FrequencySelect::moveCursorToDigit(int i) {
void FrequencySelect::draw() {
auto window = ImGui::GetCurrentWindow();
auto io = ImGui::GetIO();
widgetPos = ImGui::GetWindowContentRegionMin();
ImVec2 cursorPos = ImGui::GetCursorPos();
widgetPos.x += window->Pos.x + cursorPos.x;
@ -134,7 +132,7 @@ void FrequencySelect::draw() {
ImVec2 mousePos = ImGui::GetMousePos();
bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
int mw = io.MouseWheel;
int mw = ImGui::GetIO().MouseWheel;
bool onDigit = false;
bool hovered = false;
@ -176,7 +174,7 @@ void FrequencySelect::draw() {
moveCursorToDigit(i + 1);
}
auto chars = io.InputQueueCharacters;
auto chars = ImGui::GetIO().InputQueueCharacters;
// For each keyboard characters, type it
for (int j = 0; j < chars.Size; j++) {
@ -196,34 +194,6 @@ void FrequencySelect::draw() {
}
}
digitHovered = hovered;
if (isInArea(mousePos, digitTopMins[0], digitBottomMaxs[11])) {
bool shortcutKey = io.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl);
bool ctrlOnly = (io.KeyMods == ImGuiKeyModFlags_Ctrl);
bool shiftOnly = (io.KeyMods == ImGuiKeyModFlags_Shift);
bool copy = ((shortcutKey && ImGui::IsKeyPressed(ImGuiKey_C)) || (ctrlOnly && ImGui::IsKeyPressed(ImGuiKey_Insert)));
bool paste = ((shortcutKey && ImGui::IsKeyPressed(ImGuiKey_V)) || (shiftOnly && ImGui::IsKeyPressed(ImGuiKey_Insert)));
if (copy) {
// Convert the freqency to a string
std::string freqStr = hrfreq::toString(frequency);
// Write it to the clipboard
ImGui::SetClipboardText(freqStr.c_str());
}
if (paste) {
// Attempt to parse the clipboard as a number
const char* clip = ImGui::GetClipboardText();
// If the clipboard is not empty, attempt to parse it
if (clip) {
double newFreq;
if (hrfreq::fromString(clip, newFreq)) {
setFrequency(abs(newFreq));
frequencyChanged = true;
}
}
}
}
}
uint64_t freq = 0;

View File

@ -42,7 +42,6 @@ public:
class Instance {
public:
virtual ~Instance() {}
virtual void postInit() = 0;
virtual void enable() = 0;
virtual void disable() = 0;

View File

@ -5,4 +5,5 @@ namespace sigpath {
VFOManager vfoManager;
SourceManager sourceManager;
SinkManager sinkManager;
StreamManager streamManager;
};

View File

@ -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;
};

View File

@ -85,7 +85,7 @@ void SourceManager::tune(double freq) {
return;
}
// TODO: No need to always retune the hardware in Panadapter mode
selectedHandler->tuneHandler(abs(((tuneMode == TuningMode::NORMAL) ? freq : ifFreq) + tuneOffset), selectedHandler->ctx);
selectedHandler->tuneHandler(((tuneMode == TuningMode::NORMAL) ? freq : ifFreq) + tuneOffset, selectedHandler->ctx);
onRetune.emit(freq);
currentFreq = freq;
}

View 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;
}

View 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;
};

View File

@ -1,120 +0,0 @@
#include "hrfreq.h"
#include <utils/flog.h>
namespace hrfreq {
std::string toString(double freq) {
// Determine the scale
int maxDecimals = 0;
const char* suffix = "Hz";
if (freq >= 1e9) {
freq /= 1e9;
maxDecimals = 9;
suffix = "GHz";
}
else if (freq >= 1e6) {
freq /= 1e6;
maxDecimals = 6;
suffix = "MHz";
}
else if (freq >= 1e3) {
freq /= 1e3;
maxDecimals = 3;
suffix = "KHz";
}
// Convert to string (TODO: Not sure if limiting the decimals rounds)
char numBuf[128];
int numLen = sprintf(numBuf, "%0.*lf", maxDecimals, freq);
// If there is a decimal point, remove the useless zeros
if (maxDecimals) {
for (int i = numLen-1; i >= 0; i--) {
bool dot = (numBuf[i] == '.');
if (numBuf[i] != '0' && !dot) { break; }
numBuf[i] = 0;
if (dot) { break; }
}
}
// Concat the suffix
char finalBuf[128];
sprintf(finalBuf, "%s%s", numBuf, suffix);
// Return the final string
return finalBuf;
}
bool isNumeric(char c) {
return std::isdigit(c) || c == '+' || c == '-' || c == '.' || c == ',';
}
bool fromString(const std::string& str, double& freq) {
// Skip non-numeric characters
int i = 0;
char c;
for (; i < str.size(); i++) {
if (isNumeric(str[i])) { break; }
}
// Extract the numeric part
std::string numeric;
for (; i < str.size(); i++) {
// Get the character
c = str[i];
// If it's a letter, stop
if (std::isalpha(c)) { break; }
// If isn't numeric, skip it
if (!isNumeric(c)) { continue; }
// If it's a comma, skip it for now. This enforces a dot as a decimal point
if (c == ',') { continue; }
// Add the character to the numeric string
numeric += c;
}
// Attempt to parse the numeric part
double num;
try {
num = std::stod(numeric);
}
catch (const std::exception& e) {
flog::error("Failed to parse numeric part: '{}'", numeric);
return false;
}
// If no more text is available, assume the numeric part gives a frequency in Hz
if (i == str.size()) {
flog::warn("No unit given, assuming it's Hz");
freq = num;
return true;
}
// Scale the numeric value depending on the first scale character
char scale = std::toupper(str[i]);
switch (scale) {
case 'G':
num *= 1e9;
break;
case 'M':
num *= 1e6;
break;
case 'K':
num *= 1e3;
break;
case 'H':
break;
default:
flog::warn("Unknown frequency scale: '{}'", scale);
break;
}
// Return the frequency
freq = num;
return true; // TODO
}
}

View File

@ -1,19 +0,0 @@
#pragma once
#include <string>
namespace hrfreq {
/**
* Convert a frequency to a human-readable string.
* @param freq Frequency in Hz.
* @return Human-readable representation of the frequency.
*/
std::string toString(double freq);
/**
* Convert a human-readable representation of a frequency to a frequency value.
* @param str String containing the human-readable frequency.
* @param freq Value to write the decoded frequency to.
* @return True on success, false otherwise.
*/
bool fromString(const std::string& str, double& freq);
}

View File

@ -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];
}

View File

@ -1,3 +1,3 @@
#pragma once
#define VERSION_STR "1.2.1"
#define VERSION_STR "1.1.0"

View File

@ -1,65 +0,0 @@
#pragma once
#include <dsp/processor.h>
#include <dsp/math/fast_atan2.h>
#include <dsp/math/hz_to_rads.h>
#include <dsp/math/normalize_phase.h>
namespace dsp::demod {
class Amplitude : public Processor<complex_t, float> {
using base_type = Processor<complex_t, float>;
public:
Amplitude() {}
Amplitude(stream<complex_t>* in, double deviation) { init(in, deviation); }
Amplitude(stream<complex_t>* in, double deviation, double samplerate) { init(in, deviation, samplerate); }
virtual void init(stream<complex_t>* in, double deviation) {
_invDeviation = 1.0 / deviation;
base_type::init(in);
}
virtual void init(stream<complex_t>* in, double deviation, double samplerate) {
init(in, math::hzToRads(deviation, samplerate));
}
void setDeviation(double deviation) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_invDeviation = 1.0 / deviation;
}
void setDeviation(double deviation, double samplerate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_invDeviation = 1.0 / math::hzToRads(deviation, samplerate);
}
inline int process(int count, complex_t* in, float* out) {
volk_32fc_magnitude_32f(out, (lv_32fc_t*)in, count);
return count;
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
phase = 0.0f;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
float _invDeviation;
float phase = 0.0f;
};
}

View File

@ -1,176 +0,0 @@
617 + 6 -> I
304 + 6 -> II
616 + 6 -> III
305 + 6 -> IV
304
305
306
307
308
309
310
311
312
616
617
618
619
620
621
622
623
624
305
306
307
308
309
310
311
312
313
617
618
619
620
621
622
623
624
0
304
305
306
307
308
309
310
311
312
616
617
618
619
620
621
622
623
624
305
306
307
308
309
310
311
312
313
617
618
619
620
621
622
623
624
0
304
305
306
307
308
309
310
311
312
616
617
618
619
620
621
622
623
624
305
306
307
308
309
310
311
312
313
617
618
619
620
621
622
623
624
0
304
305
306
307
308
309
310
311
312
616
617
618
619
620
621
622
623
624
305
306
307
308
309
310
311
312
313
617
618
619
620
621
622
623
624
0
304
305
306
307
308
309
310
311
312
616
617
618
619
620
621
622
623
624

View File

@ -0,0 +1,63 @@
#pragma once
#include <dsp/loop/pll.h>
#include "chrominance_filter.h"
// TODO: Should be 60 but had to try something
#define BURST_START (63+CHROMA_FIR_DELAY)
#define BURST_END (BURST_START+28)
#define A_PHASE ((135.0/180.0)*FL_M_PI)
#define B_PHASE ((-135.0/180.0)*FL_M_PI)
namespace dsp::loop {
class ChromaPLL : public PLL {
using base_type = PLL;
public:
ChromaPLL() {}
ChromaPLL(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) {
base_type::init(in, bandwidth, initFreq, initPhase, minFreq, maxFreq);
}
inline int process(int count, complex_t* in, complex_t* out, bool aphase = false) {
// Process the pre-burst section
for (int i = 0; i < BURST_START; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advancePhase();
}
// Process the burst itself
if (aphase) {
for (int i = BURST_START; i < BURST_END; i++) {
complex_t outVal = in[i] * math::phasor(-pcl.phase);
out[i] = outVal;
pcl.advance(math::normalizePhase(outVal.phase() - A_PHASE));
}
}
else {
for (int i = BURST_START; i < BURST_END; i++) {
complex_t outVal = in[i] * math::phasor(-pcl.phase);
out[i] = outVal;
pcl.advance(math::normalizePhase(outVal.phase() - B_PHASE));
}
}
// Process the post-burst section
for (int i = BURST_END; i < count; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advancePhase();
}
return count;
}
inline int processBlank(int count, complex_t* in, complex_t* out) {
for (int i = 0; i < count; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advancePhase();
}
return count;
}
};
}

View File

@ -0,0 +1,239 @@
#pragma once
#include <dsp/types.h>
inline const dsp::complex_t CHROMA_FIR[] = {
{-0.000005461290583903, -0.000011336784355655},
{ 0.000020060944485414, 0.000009851315045203},
{-0.000034177222729438, 0.000007245841504981},
{ 0.000027694034878705, -0.000033114740542635},
{-0.000001217597841648, 0.000039141482370942},
{-0.000008324593371228, -0.000011315001355976},
{-0.000038085228233509, -0.000010585909953738},
{ 0.000114833396071141, -0.000047778708840608},
{-0.000115428390169113, 0.000205816198882814},
{-0.000055467806072871, -0.000356692479491626},
{ 0.000349316846854190, 0.000326162940234916},
{-0.000558465829929114, -0.000048001521408724},
{ 0.000488176200631416, -0.000319593757302922},
{-0.000169437838021935, 0.000501610900725908},
{-0.000131793335799502, -0.000373003580727547},
{ 0.000166817395492786, 0.000105930895534474},
{ 0.000030499908326112, -0.000003048682668943},
{-0.000174999505027919, 0.000168008090089458},
{ 0.000054431163395030, -0.000385174790951272},
{ 0.000215876012859739, 0.000372695852521209},
{-0.000325534912280750, -0.000130173041693966},
{ 0.000154951430569290, -0.000045395998708328},
{ 0.000054324657659002, -0.000076028700470037},
{ 0.000015664427565764, 0.000348002612845696},
{-0.000345943017888332, -0.000402175417043307},
{ 0.000568731727879741, 0.000112347863435682},
{-0.000416485880859085, 0.000211750352828909},
{ 0.000087462353623011, -0.000188197153014309},
{-0.000032082305030264, -0.000136804226080664},
{ 0.000379089999045955, 0.000303466839685362},
{-0.000726760198519770, -0.000007022279302816},
{ 0.000619888661818195, -0.000476871323359809},
{-0.000151885493742993, 0.000595641190573181},
{-0.000100626407015494, -0.000227947144491108},
{-0.000201935458823941, -0.000107628631934340},
{ 0.000680260922139900, -0.000120771182888852},
{-0.000666108629277491, 0.000744775901128973},
{ 0.000067236591919755, -0.001044125966364420},
{ 0.000447037274751822, 0.000651912509450913},
{-0.000262675893448686, -0.000082499729563337},
{-0.000349821460486320, 0.000132102793530818},
{ 0.000507024815168287, -0.000837598610490618},
{ 0.000163814255478652, 0.001346530693477834},
{-0.000970457632383793, -0.000968411010101160},
{ 0.000974834882891140, 0.000116507082762032},
{-0.000225464280571542, 0.000137131865995708},
{-0.000211542240694642, 0.000563783548428947},
{-0.000414412310798766, -0.001309793399193736},
{ 0.001497010004594478, 0.001021907858926259},
{-0.001752019159639658, 0.000116536066154131},
{ 0.000872822027879430, -0.000783952720205569},
{-0.000032439446797970, 0.000184988059956734},
{ 0.000446259382722895, 0.000833040920509238},
{-0.001741577737284306, -0.000764423771425237},
{ 0.002306569133792772, -0.000593352416441601},
{-0.001336084746214192, 0.001744394557524181},
{-0.000015810020735495, -0.001342809547658260},
{ 0.000007636494885364, 0.000009498318627546},
{ 0.001403876768349702, 0.000326101441888391},
{-0.002351020828600226, 0.001098649819278302},
{ 0.001389314639579544, -0.002746943712072884},
{ 0.000526319899588909, 0.002635084366837732},
{-0.001109526585744687, -0.000950323796527721},
{-0.000307792427984886, -0.000013203419520794},
{ 0.001737955094951111, -0.001247368808692850},
{-0.000974502437588420, 0.003352512117661680},
{-0.001462571137390936, -0.003635296917435679},
{ 0.002783459090201693, 0.001604420226187745},
{-0.001471518558760170, 0.000211117948702137},
{-0.000575340825070194, 0.000601820846100026},
{ 0.000302090333345692, -0.003088058972305493},
{ 0.002496092353182990, 0.003912508340989065},
{-0.004645661091012423, -0.001630427298020200},
{ 0.003556824805628799, -0.001209822327859352},
{-0.000744999556260706, 0.001143238699138109},
{ 0.000144278726929409, 0.001638049051599065},
{-0.003025291044450178, -0.003226370992887968},
{ 0.006047866290490120, 0.000927406808799887},
{-0.005338456415106141, 0.003008811999350399},
{ 0.001642959659014839, -0.003972384205231079},
{ 0.000273874932822212, 0.000977326273749033},
{ 0.002315022846573390, 0.001695671268241410},
{-0.006240953957978884, 0.000207330368698293},
{ 0.006164252120861735, -0.005177351717451013},
{-0.001560310257561104, 0.007437030759707700},
{-0.002131333814462852, -0.004317129694157112},
{ 0.000280518918541908, 0.000134405998842553},
{ 0.004612116481180659, -0.001024468120657814},
{-0.005599300279638699, 0.006828277067771868},
{ 0.000228879728552504, -0.010675998154712657},
{ 0.005692081512980654, 0.007582243186569848},
{-0.005100500569859509, -0.001364751685737153},
{-0.000902490398043454, 0.000385770160220703},
{ 0.003673858819546609, -0.006701685283451640},
{ 0.002079056046131593, 0.012568579063417429},
{-0.010730008156911677, -0.009826454574016218},
{ 0.012092401380903161, 0.000921764172237851},
{-0.004714530989129091, 0.003151948807627123},
{-0.001055930168838909, 0.003228576712467020},
{-0.004343270165991213, -0.011924332879354394},
{ 0.016499994418955999, 0.010255324919126899},
{-0.021047239750251585, 0.002309419513135448},
{ 0.011855513874047341, -0.011604071033866310},
{-0.000777842281358575, 0.005916341648175263},
{ 0.004380939277688377, 0.007397670455730446},
{-0.021891594662401131, -0.008509480947490166},
{ 0.032787638290674201, -0.009950745850861956},
{-0.021022579272463194, 0.030030850567389102},
{-0.001508145650189953, -0.027571914870304640},
{ 0.004056649693022923, 0.004624901687718579},
{ 0.025728742586666287, 0.004824671348397606},
{-0.058002700931665603, 0.030198618296813803},
{ 0.043631619628438784, -0.096308304333327280},
{ 0.033451363423624300, 0.136687079396426990},
{-0.129387018420204200, -0.101540513046619400},
{ 0.172881344826560730, -0.000000000000005297},
{-0.129387018420198010, 0.101540513046627330},
{ 0.033451363423615862, -0.136687079396429050},
{ 0.043631619628444723, 0.096308304333324601},
{-0.058002700931667456, -0.030198618296810247},
{ 0.025728742586665992, -0.004824671348399184},
{ 0.004056649693022639, -0.004624901687718827},
{-0.001508145650188251, 0.027571914870304734},
{-0.021022579272465047, -0.030030850567387805},
{ 0.032787638290674812, 0.009950745850859947},
{-0.021891594662400610, 0.008509480947491507},
{ 0.004380939277687923, -0.007397670455730714},
{-0.000777842281358940, -0.005916341648175215},
{ 0.011855513874048058, 0.011604071033865578},
{-0.021047239750251731, -0.002309419513134139},
{ 0.016499994418955360, -0.010255324919127926},
{-0.004343270165990471, 0.011924332879354665},
{-0.001055930168839110, -0.003228576712466955},
{-0.004714530989129287, -0.003151948807626830},
{ 0.012092401380903103, -0.000921764172238603},
{-0.010730008156911072, 0.009826454574016881},
{ 0.002079056046130817, -0.012568579063417559},
{ 0.003673858819547020, 0.006701685283451416},
{-0.000902490398043478, -0.000385770160220647},
{-0.005100500569859424, 0.001364751685737466},
{ 0.005692081512980187, -0.007582243186570198},
{ 0.000228879728553163, 0.010675998154712643},
{-0.005599300279639117, -0.006828277067771524},
{ 0.004612116481180722, 0.001024468120657532},
{ 0.000280518918541900, -0.000134405998842571},
{-0.002131333814462586, 0.004317129694157243},
{-0.001560310257561563, -0.007437030759707604},
{ 0.006164252120862052, 0.005177351717450635},
{-0.006240953957978898, -0.000207330368697911},
{ 0.002315022846573286, -0.001695671268241552},
{ 0.000273874932822152, -0.000977326273749050},
{ 0.001642959659015084, 0.003972384205230976},
{-0.005338456415106324, -0.003008811999350072},
{ 0.006047866290490063, -0.000927406808800258},
{-0.003025291044449980, 0.003226370992888153},
{ 0.000144278726929308, -0.001638049051599074},
{-0.000744999556260777, -0.001143238699138063},
{ 0.003556824805628873, 0.001209822327859134},
{-0.004645661091012323, 0.001630427298020484},
{ 0.002496092353182751, -0.003912508340989219},
{ 0.000302090333345882, 0.003088058972305475},
{-0.000575340825070231, -0.000601820846099991},
{-0.001471518558760183, -0.000211117948702046},
{ 0.002783459090201593, -0.001604420226187919},
{-0.001462571137390710, 0.003635296917435769},
{-0.000974502437588628, -0.003352512117661619},
{ 0.001737955094951189, 0.001247368808692742},
{-0.000307792427984885, 0.000013203419520814},
{-0.001109526585744628, 0.000950323796527789},
{ 0.000526319899588746, -0.002635084366837765},
{ 0.001389314639579712, 0.002746943712072799},
{-0.002351020828600294, -0.001098649819278158},
{ 0.001403876768349682, -0.000326101441888477},
{ 0.000007636494885364, -0.000009498318627546},
{-0.000015810020735412, 0.001342809547658261},
{-0.001336084746214299, -0.001744394557524099},
{ 0.002306569133792808, 0.000593352416441460},
{-0.001741577737284259, 0.000764423771425344},
{ 0.000446259382722843, -0.000833040920509266},
{-0.000032439446797982, -0.000184988059956732},
{ 0.000872822027879478, 0.000783952720205515},
{-0.001752019159639665, -0.000116536066154024},
{ 0.001497010004594416, -0.001021907858926351},
{-0.000414412310798685, 0.001309793399193761},
{-0.000211542240694677, -0.000563783548428934},
{-0.000225464280571550, -0.000137131865995694},
{ 0.000974834882891133, -0.000116507082762092},
{-0.000970457632383734, 0.000968411010101219},
{ 0.000163814255478569, -0.001346530693477844},
{ 0.000507024815168339, 0.000837598610490586},
{-0.000349821460486328, -0.000132102793530797},
{-0.000262675893448681, 0.000082499729563353},
{ 0.000447037274751782, -0.000651912509450940},
{ 0.000067236591919819, 0.001044125966364416},
{-0.000666108629277537, -0.000744775901128932},
{ 0.000680260922139908, 0.000120771182888810},
{-0.000201935458823935, 0.000107628631934352},
{-0.000100626407015480, 0.000227947144491114},
{-0.000151885493743030, -0.000595641190573172},
{ 0.000619888661818225, 0.000476871323359771},
{-0.000726760198519770, 0.000007022279302861},
{ 0.000379089999045936, -0.000303466839685386},
{-0.000032082305030256, 0.000136804226080666},
{ 0.000087462353623023, 0.000188197153014303},
{-0.000416485880859098, -0.000211750352828883},
{ 0.000568731727879734, -0.000112347863435717},
{-0.000345943017888307, 0.000402175417043329},
{ 0.000015664427565742, -0.000348002612845697},
{ 0.000054324657659007, 0.000076028700470034},
{ 0.000154951430569292, 0.000045395998708319},
{-0.000325534912280742, 0.000130173041693986},
{ 0.000215876012859716, -0.000372695852521222},
{ 0.000054431163395054, 0.000385174790951269},
{-0.000174999505027930, -0.000168008090089447},
{ 0.000030499908326113, 0.000003048682668941},
{ 0.000166817395492779, -0.000105930895534485},
{-0.000131793335799479, 0.000373003580727555},
{-0.000169437838021966, -0.000501610900725898},
{ 0.000488176200631435, 0.000319593757302892},
{-0.000558465829929111, 0.000048001521408758},
{ 0.000349316846854170, -0.000326162940234938},
{-0.000055467806072849, 0.000356692479491629},
{-0.000115428390169126, -0.000205816198882806},
{ 0.000114833396071144, 0.000047778708840601},
{-0.000038085228233508, 0.000010585909953741},
{-0.000008324593371228, 0.000011315001355977},
{-0.000001217597841650, -0.000039141482370942},
{ 0.000027694034878707, 0.000033114740542633},
{-0.000034177222729439, -0.000007245841504979},
{ 0.000020060944485413, -0.000009851315045204},
{-0.000005461290583903, 0.000011336784355656},
};
#define CHROMA_FIR_SIZE (sizeof(CHROMA_FIR)/sizeof(dsp::complex_t))
#define CHROMA_FIR_DELAY ((CHROMA_FIR_SIZE-1)/2)

View File

@ -1,131 +0,0 @@
#pragma once
#include <dsp/types.h>
const dsp::complex_t CHROMA_BANDPASS[123] = {
{ -0.000007675039564594f, -0.000017362992335168f },
{ 0.000050180791439308f, -0.000005054021864311f },
{ -0.000022529111707761f, 0.000102942513429095f },
{ -0.000157609487484146f, -0.000092618697641464f },
{ 0.000205649042029007f, -0.000181710515677257f },
{ 0.000143445458895462f, 0.000331994546004200f },
{ -0.000414693079508517f, 0.000038265188132615f },
{ 0.000090081630021837f, -0.000395731646002122f },
{ 0.000257705918065856f, 0.000154354504676150f },
{ -0.000064051192147575f, 0.000055648228186439f },
{ 0.000089938060647145f, 0.000213032074676941f },
{ -0.000604775098099200f, 0.000050706635726124f },
{ 0.000223309865890358f, -0.000944433958755193f },
{ 0.001049943574694384f, 0.000640863688898729f },
{ -0.000983491651119595f, 0.000840133365053179f },
{ -0.000417178588714773f, -0.001011686459999295f },
{ 0.000616677332283103f, -0.000046513429902547f },
{ 0.000018549463752019f, -0.000075619948809012f },
{ 0.000734408386201158f, 0.000456742966201638f },
{ -0.001192460562555901f, 0.001001510577200253f },
{ -0.000729137747758392f, -0.001811046261815935f },
{ 0.001878272869910273f, -0.000125879189667096f },
{ -0.000312873903977849f, 0.001230889889574772f },
{ -0.000142534831707354f, -0.000090307321579771f },
{ -0.000942796972567241f, 0.000778470227412111f },
{ -0.000945381510920278f, -0.002406055808135091f },
{ 0.003537159230775561f, -0.000207350791625892f },
{ -0.000956199555190230f, 0.003634225577771235f },
{ -0.002543835202533561f, -0.001641705037372486f },
{ 0.001064108471592447f, -0.000863770138941644f },
{ -0.000335799601479829f, -0.000876091753216939f },
{ 0.003390761989356699f, -0.000170321604912419f },
{ -0.001408130728751909f, 0.005175554625981795f },
{ -0.005203055300834108f, -0.003419861284250694f },
{ 0.004342719678657084f, -0.003465264906764298f },
{ 0.001143432997855297f, 0.003059520699490539f },
{ 0.000304096484476364f, -0.000012725974706621f },
{ -0.001193870642975282f, 0.004247469277548632f },
{ -0.006681021498855877f, -0.004471771356204969f },
{ 0.007965721969864534f, -0.006247895626072559f },
{ 0.003365883969059717f, 0.009241201835481184f },
{ -0.006835562188141396f, 0.000228798228738161f },
{ 0.000409900284971528f, -0.001412838961851673f },
{ -0.004331406608345981f, -0.002951876085350234f },
{ 0.009290089917766562f, -0.007161958719089258f },
{ 0.005418326020709935f, 0.015272361365960607f },
{ -0.017077565432843410f, 0.000428641984774326f },
{ 0.003850771342644978f, -0.012869517593577566f },
{ 0.004380859690202961f, 0.003039552423897447f },
{ 0.004761181766399753f, -0.003607421240356480f },
{ 0.005926935731028822f, 0.017160134858844222f },
{ -0.028153584885925551f, 0.000471042980325370f },
{ 0.009655944938035437f, -0.031314555422639050f },
{ 0.023930146568136038f, 0.016901617811072800f },
{ -0.012998853255109976f, 0.009678807314399702f },
{ 0.002043176559434885f, 0.006079907699564680f },
{ -0.036686455817128191f, 0.000306882557812233f },
{ 0.021529138474771701f, -0.067800343150283604f },
{ 0.085421344938160879f, 0.061409588050754214f },
{ -0.108166660998898100f, 0.079141989828113088f },
{ -0.047617308971534079f, -0.145721049254261960f },
{ 0.160079041453427080f, -0.000000000000000427f },
{ -0.047617308971533295f, 0.145721049254262240f },
{ -0.108166660998898530f, -0.079141989828112505f },
{ 0.085421344938160546f, -0.061409588050754672f },
{ 0.021529138474772065f, 0.067800343150283493f },
{ -0.036686455817128191f, -0.000306882557812037f },
{ 0.002043176559434853f, -0.006079907699564691f },
{ -0.012998853255110026f, -0.009678807314399631f },
{ 0.023930146568135951f, -0.016901617811072928f },
{ 0.009655944938035604f, 0.031314555422638994f },
{ -0.028153584885925554f, -0.000471042980325220f },
{ 0.005926935731028730f, -0.017160134858844253f },
{ 0.004761181766399772f, 0.003607421240356455f },
{ 0.004380859690202943f, -0.003039552423897470f },
{ 0.003850771342645046f, 0.012869517593577545f },
{ -0.017077565432843413f, -0.000428641984774235f },
{ 0.005418326020709854f, -0.015272361365960637f },
{ 0.009290089917766600f, 0.007161958719089209f },
{ -0.004331406608345964f, 0.002951876085350257f },
{ 0.000409900284971536f, 0.001412838961851670f },
{ -0.006835562188141398f, -0.000228798228738125f },
{ 0.003365883969059667f, -0.009241201835481201f },
{ 0.007965721969864567f, 0.006247895626072517f },
{ -0.006681021498855855f, 0.004471771356205005f },
{ -0.001193870642975304f, -0.004247469277548626f },
{ 0.000304096484476364f, 0.000012725974706619f },
{ 0.001143432997855281f, -0.003059520699490545f },
{ 0.004342719678657102f, 0.003465264906764274f },
{ -0.005203055300834089f, 0.003419861284250722f },
{ -0.001408130728751936f, -0.005175554625981787f },
{ 0.003390761989356700f, 0.000170321604912401f },
{ -0.000335799601479825f, 0.000876091753216940f },
{ 0.001064108471592452f, 0.000863770138941638f },
{ -0.002543835202533552f, 0.001641705037372499f },
{ -0.000956199555190250f, -0.003634225577771230f },
{ 0.003537159230775563f, 0.000207350791625874f },
{ -0.000945381510920265f, 0.002406055808135096f },
{ -0.000942796972567245f, -0.000778470227412106f },
{ -0.000142534831707354f, 0.000090307321579771f },
{ -0.000312873903977856f, -0.001230889889574770f },
{ 0.001878272869910274f, 0.000125879189667086f },
{ -0.000729137747758382f, 0.001811046261815939f },
{ -0.001192460562555906f, -0.001001510577200246f },
{ 0.000734408386201156f, -0.000456742966201642f },
{ 0.000018549463752019f, 0.000075619948809012f },
{ 0.000616677332283103f, 0.000046513429902543f },
{ -0.000417178588714767f, 0.001011686459999298f },
{ -0.000983491651119600f, -0.000840133365053174f },
{ 0.001049943574694380f, -0.000640863688898734f },
{ 0.000223309865890363f, 0.000944433958755192f },
{ -0.000604775098099200f, -0.000050706635726121f },
{ 0.000089938060647144f, -0.000213032074676941f },
{ -0.000064051192147576f, -0.000055648228186438f },
{ 0.000257705918065856f, -0.000154354504676151f },
{ 0.000090081630021839f, 0.000395731646002121f },
{ -0.000414693079508517f, -0.000038265188132613f },
{ 0.000143445458895461f, -0.000331994546004200f },
{ 0.000205649042029008f, 0.000181710515677256f },
{ -0.000157609487484145f, 0.000092618697641465f },
{ -0.000022529111707761f, -0.000102942513429094f },
{ 0.000050180791439308f, 0.000005054021864311f },
{ -0.000007675039564594f, 0.000017362992335168f }
};
#define CHROMA_BANDPASS_SIZE (sizeof(CHROMA_BANDPASS)/sizeof(dsp::complex_t))
#define CHROMA_BANDPASS_DELAY (CHROMA_BANDPASS_SIZE/2)

View File

@ -5,33 +5,6 @@
#include <dsp/multirate/polyphase_bank.h>
#include <dsp/math/step.h>
#define LINE_SIZE 945
#define SYNC_LEN 70
#define SYNC_SIDE_LEN 17
#define SYNC_L_START (LINE_SIZE - SYNC_SIDE_LEN)
#define SYNC_R_START (SYNC_LEN/2)
#define SYNC_R_END (SYNC_R_START + (SYNC_LEN/2) + SYNC_SIDE_LEN)
#define SYNC_HALF_LEN ((SYNC_LEN/2) + SYNC_SIDE_LEN)
#define EQUAL_LEN 35
#define HBLANK_START SYNC_LEN
#define HBLANK_END 155
#define HBLANK_LEN (HBLANK_END - HBLANK_START + 1)
#define SYNC_LEVEL (-0.428)
#define COLORBURST_START 84
#define COLORBURST_LEN 33
#define MAX_LOCK 1000
dsp::complex_t PHASE_REF[2] = {
{ -0.707106781186547f, 0.707106781186547f },
{ -0.707106781186547f, -0.707106781186547f }
};
class LineSync : public dsp::Processor<float, float> {
using base_type = dsp::Processor<float, float>;
public:
@ -54,17 +27,41 @@ public:
_interpPhaseCount = interpPhaseCount;
_interpTapCount = interpTapCount;
pcl.init(_muGain, _omegaGain, 0.0, 0.0, 1.0, _omega, _omega * (1.0 - omegaRelLimit), _omega * (1.0 + omegaRelLimit));
generateInterpTaps();
buffer = dsp::buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
// TODO: Needs tuning, so do the gains
maxPeriod = (int32_t)(1.0001 * (float)(1 << 30));
minPeriod = (int32_t)(0.9999 * (float)(1 << 30));
base_type::init(in);
}
void setOmegaGain(double omegaGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_omegaGain = omegaGain;
pcl.setCoefficients(_muGain, _omegaGain);
}
void setMuGain(double muGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_muGain = muGain;
pcl.setCoefficients(_muGain, _omegaGain);
}
void setOmegaRelLimit(double omegaRelLimit) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_omegaRelLimit = omegaRelLimit;
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
}
void setSyncLevel(float level) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
syncLevel = level;
}
void setInterpParams(int interpPhaseCount, int interpTapCount) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
@ -84,7 +81,8 @@ public:
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
offset = 0;
phase = 0;
pcl.phase = 0.0f;
pcl.freq = _omega;
base_type::tempStart();
}
@ -95,120 +93,61 @@ public:
// Copy data to work buffer
memcpy(bufStart, base_type::_in->readBuf, count * sizeof(float));
// Process samples while they are available
if (test2) {
test2 = false;
offset += 5;
}
// Process all samples
while (offset < count) {
// While the offset is negative, out put zeros
while (offset < 0 && pixel < LINE_SIZE) {
// Output a zero
base_type::out.writeBuf[pixel++] = 0.0f;
// Calculate new output value
int phase = std::clamp<int>(floorf(pcl.phase * (float)_interpPhaseCount), 0, _interpPhaseCount - 1);
float outVal;
volk_32f_x2_dot_prod_32f(&outVal, &buffer[offset], interpBank.phases[phase], _interpTapCount);
base_type::out.writeBuf[outCount++] = outVal;
// Increment the phase
phase += period;
offset += (phase >> 30);
phase &= 0x3FFFFFFF;
}
// Process as much of a line as possible
while (offset < count && pixel < LINE_SIZE) {
// Compute the output sample
volk_32f_x2_dot_prod_32f(&base_type::out.writeBuf[pixel++], &buffer[offset], interpBank.phases[(phase >> 23) & 0x7F], _interpTapCount);
// Increment the phase
phase += period;
offset += (phase >> 30);
phase &= 0x3FFFFFFF;
}
// If the line is done, process it
if (pixel == LINE_SIZE) {
// Compute averages. (TODO: Try faster method)
// If the end of the line is reached, process it and determin error
float error = 0;
if (outCount >= 720) {
// Compute averages.
float left = 0.0f, right = 0.0f;
int lc = 0, rc = 0;
for (int i = SYNC_L_START; i < LINE_SIZE; i++) {
for (int i = (720-17); i < 720; i++) {
left += base_type::out.writeBuf[i];
lc++;
}
for (int i = 0; i < SYNC_R_START; i++) {
for (int i = 0; i < 27; i++) {
left += base_type::out.writeBuf[i];
lc++;
}
for (int i = SYNC_R_START; i < SYNC_R_END; i++) {
for (int i = 27; i < (54+17); i++) {
right += base_type::out.writeBuf[i];
rc++;
}
left *= (1.0f/44.0f);
right *= (1.0f/44.0f);
// If the sync is present, compute error
if ((left < syncLevel && right < syncLevel) && !forceLock) {
error = (left + syncBias - right);
locked = true;
}
else {
locked = false;
}
// Compute the error
float error = (left - right) * (1.0f/((float)SYNC_HALF_LEN));
// Compute the change in phase and frequency due to the error
float periodDelta = error * _omegaGain;
float phaseDelta = error * _muGain;
// Normalize the phase delta (TODO: Make faster)
while (phaseDelta <= -1.0f) {
phaseDelta += 1.0f;
offset--;
}
while (phaseDelta >= 1.0f) {
phaseDelta -= 1.0f;
offset++;
}
// Update the period (TODO: Clamp error*omegaGain to prevent weird shit with corrupt samples)
period += (int32_t)(periodDelta * (float)(1 << 30));
period = std::clamp<uint32_t>(period, minPeriod, maxPeriod);
// Update the phase
phase += (int32_t)(phaseDelta * (float)(1 << 30));
// Normalize the phase
uint32_t overflow = phase >> 30;
if (overflow) {
if (error < 0) {
offset -= 4 - overflow;
}
else {
offset += overflow;
}
}
phase &= 0x3FFFFFFF;
// Find the lowest value
float lowest = INFINITY;
int lowestId = -1;
for (int i = 0; i < LINE_SIZE; i++) {
float val = base_type::out.writeBuf[i];
if (val < lowest) {
lowest = val;
lowestId = i;
}
}
// Check the the line is in lock
bool lineLocked = (lowestId < SYNC_R_END || lowestId >= SYNC_L_START);
// Update the lock status based on the line lock
if (!lineLocked && locked) {
locked--;
}
else if (lineLocked && locked < MAX_LOCK) {
locked++;
}
// If not locked, attempt to lock by forcing the sync to happen at the right spot
// TODO: This triggers waaaay too easily at low SNR
if (!locked && fastLock) {
offset += lowestId - SYNC_R_START;
locked = MAX_LOCK / 2;
if (++counter >= 100) {
counter = 0;
//flog::warn("Left: {}, Right: {}, Error: {}, Freq: {}, Phase: {}", left, right, error, pcl.freq, pcl.phase);
}
// Output line
if (!base_type::out.swap(LINE_SIZE)) { break; }
pixel = 0;
if (!base_type::out.swap(outCount)) { break; }
outCount = 0;
}
}
// Get the offset ready for the next buffer
// Advance symbol offset and phase
pcl.advance(error);
float delta = floorf(pcl.phase);
offset += delta;
pcl.phase -= delta;
}
offset -= count;
// Update delay buffer
@ -216,15 +155,16 @@ public:
// Swap if some data was generated
base_type::_in->flush();
return 0;
return outCount;
}
float syncBias = 0;
bool locked = false;
bool test2 = false;
uint32_t period = (0x800072F3 >> 1);//(1 << 31) + 1;
float syncBias = 0.0f;
bool forceLock = false;
int locked = 0;
bool fastLock = true;
int counter = 0;
protected:
void generateInterpTaps() {
@ -235,6 +175,7 @@ protected:
}
dsp::multirate::PolyphaseBank<float> interpBank;
dsp::loop::PhaseControlLoop<double, false> pcl;
double _omega;
double _omegaGain;
@ -242,14 +183,11 @@ protected:
double _omegaRelLimit;
int _interpPhaseCount;
int _interpTapCount;
float* buffer;
float* bufStart;
uint32_t phase = 0;
uint32_t maxPeriod;
uint32_t minPeriod;
float syncLevel = -0.03f;
int offset = 0;
int pixel = 0;
int outCount = 0;
float* buffer;
float* bufStart;
float syncLevel = -0.03f;
};

View File

@ -16,13 +16,9 @@
#include <dsp/filter/fir.h>
#include <dsp/taps/from_array.h>
#include "amplitude.h"
#include <dsp/demod/am.h>
#include <dsp/loop/fast_agc.h>
#include "chrominance_filter.h"
#include "filters.h"
#include <dsp/math/normalize_phase.h>
#include <fstream>
#include "chroma_pll.h"
#define CONCAT(a, b) ((std::string(a) + b).c_str())
@ -33,26 +29,24 @@ SDRPP_MOD_INFO{/* Name: */ "atv_decoder",
/* Max instances */ -1
};
#define SAMPLE_RATE (625.0f * (float)LINE_SIZE * 25.0f)
#define SAMPLE_RATE (625.0f * 720.0f * 25.0f)
class ATVDecoderModule : public ModuleManager::Instance {
public:
ATVDecoderModule(std::string name) : img(768, 576) {
ATVDecoderModule(std::string name) : img(720, 625) {
this->name = name;
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 7000000.0f, SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE, true);
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 8000000.0f, SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE, true);
agc.init(vfo->output, 1.0f, 1e6, 0.001f, 1.0f);
demod.init(&agc.out, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
//demod.init(vfo->output, dsp::demod::AM<float>::CARRIER, 8000000.0f, 50.0 / SAMPLE_RATE, 50.0 / SAMPLE_RATE, 0.0f, SAMPLE_RATE);
demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
sync.init(&demod.out, 1.0f, 1e-6, 1.0, 0.05);
sink.init(&sync.out, handler, this);
r2c.init(NULL);
chromaTaps = dsp::taps::fromArray(CHROMA_FIR_SIZE, CHROMA_FIR);
fir.init(NULL, chromaTaps);
pll.init(NULL, 0.01, 0.0, dsp::math::hzToRads(4433618.75, SAMPLE_RATE), dsp::math::hzToRads(4433618.75*0.90, SAMPLE_RATE), dsp::math::hzToRads(4433618.75*1.1, SAMPLE_RATE));
file = std::ofstream("chromasub_diff.bin", std::ios::binary | std::ios::out);
agc.start();
demod.start();
sync.start();
sink.start();
@ -64,10 +58,7 @@ class ATVDecoderModule : public ModuleManager::Instance {
if (vfo) {
sigpath::vfoManager.deleteVFO(vfo);
}
agc.stop();
demod.stop();
sync.stop();
sink.stop();
gui::menu.removeEntry(name);
}
@ -83,8 +74,6 @@ class ATVDecoderModule : public ModuleManager::Instance {
bool isEnabled() { return enabled; }
std::ofstream file;
private:
static void menuHandler(void *ctx) {
ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
@ -93,215 +82,142 @@ class ATVDecoderModule : public ModuleManager::Instance {
style::beginDisabled();
}
// Ideal width for testing: 750pixels
ImGui::FillWidth();
_this->img.draw();
ImGui::TextUnformatted("Horizontal Sync:");
ImGui::SameLine();
if (_this->sync.locked > 750) {
ImGui::LeftLabel("Sync");
ImGui::FillWidth();
ImGui::SliderFloat("##syncLvl", &_this->sync_level, -2, 2);
ImGui::LeftLabel("Min");
ImGui::FillWidth();
ImGui::SliderFloat("##minLvl", &_this->minLvl, -1.0, 1.0);
ImGui::LeftLabel("Span");
ImGui::FillWidth();
ImGui::SliderFloat("##spanLvl", &_this->spanLvl, 0, 1.0);
ImGui::LeftLabel("Sync Bias");
ImGui::FillWidth();
ImGui::SliderFloat("##syncBias", &_this->sync.syncBias,-0.1, 0.1);
if (ImGui::Button("Test2")) {
_this->sync.test2 = true;
}
if (ImGui::Button("Switch frame")) {
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
}
if (_this->sync.locked) {
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Locked");
}
else {
ImGui::TextUnformatted("Not locked");
}
ImGui::TextUnformatted("Vertical Sync:");
ImGui::SameLine();
if (_this->vlock > 15) {
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Locked");
}
else {
ImGui::TextUnformatted("Not locked");
}
ImGui::Checkbox("Fast Lock", &_this->sync.fastLock);
ImGui::Checkbox("Color Mode", &_this->colorMode);
ImGui::Checkbox("Force Lock", &_this->sync.forceLock);
if (!_this->enabled) {
style::endDisabled();
}
if (ImGui::Button("Close Debug")) {
_this->file.close();
}
ImGui::Text("Gain: %f", _this->gain);
ImGui::Text("Offset: %f", _this->offset);
ImGui::Text("Subcarrier: %f", _this->subcarrierFreq);
}
uint32_t pp = 0;
static void handler(float *data, int count, void *ctx) {
ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
// Correct the offset
volk_32f_s32f_add_32f(data, data, _this->offset, count);
// Convert line to complex
_this->r2c.process(720, data, _this->r2c.out.writeBuf);
// Correct the gain
volk_32f_s32f_multiply_32f(data, data, _this->gain, count);
// Isolate the chroma subcarrier
_this->fir.process(720, _this->r2c.out.writeBuf, _this->fir.out.writeBuf);
// Compute the sync levels
float syncLLevel = 0.0f;
float syncRLevel = 0.0f;
volk_32f_accumulator_s32f(&syncLLevel, data, EQUAL_LEN);
volk_32f_accumulator_s32f(&syncRLevel, &data[EQUAL_LEN], SYNC_LEN - EQUAL_LEN);
syncLLevel *= 1.0f / EQUAL_LEN;
syncRLevel *= 1.0f / (SYNC_LEN - EQUAL_LEN);
float syncLevel = (syncLLevel + syncRLevel) * 0.5f; // TODO: It's technically correct but if the sizes were different it wouldn't be
// Run chroma carrier through the PLL
_this->pll.process(720, _this->fir.out.writeBuf, _this->pll.out.writeBuf, ((_this->ypos%2)==1) ^ _this->evenFrame);
// Compute the blanking level
float blankLevel = 0.0f;
volk_32f_accumulator_s32f(&blankLevel, &data[HBLANK_START], HBLANK_LEN);
blankLevel /= (float)HBLANK_LEN;
// Render line to the image without color
int lypos = _this->ypos - 1;
if (lypos < 0) { lypos = 624; }
uint32_t* lastLine = &((uint32_t *)_this->img.buffer)[(lypos < 313) ? (lypos*720*2) : ((((lypos - 313)*2)+1)*720) ];
uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[(_this->ypos < 313) ? (_this->ypos*720*2) : ((((_this->ypos - 313)*2)+1)*720) ];
// Run the offset control loop
_this->offset -= (blankLevel / _this->gain)*0.001;
_this->offset = std::clamp<float>(_this->offset, -1.0f, 1.0f);
_this->gain -= (blankLevel - syncLevel + SYNC_LEVEL)*0.01f;
_this->gain = std::clamp<float>(_this->gain, 0.1f, 10.0f);
//uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[_this->ypos*720];
// Detect the sync type
uint16_t shortSync = (syncLLevel < 0.5f*SYNC_LEVEL) && (syncRLevel > 0.5f*SYNC_LEVEL) && (blankLevel > 0.5f*SYNC_LEVEL);
uint16_t longSync = (syncLLevel < 0.5f*SYNC_LEVEL) && (syncRLevel < 0.5f*SYNC_LEVEL) && (blankLevel < 0.5f*SYNC_LEVEL);
// Save sync type to history
_this->syncHistory = (_this->syncHistory << 2) | (longSync << 1) | shortSync;
// If the line has a colorburst, decode it
dsp::complex_t* buf1 = _this->r2c.out.readBuf;
dsp::complex_t* buf2 = _this->r2c.out.writeBuf;
if (true) {
// Convert the line into complex
_this->r2c.process(count, data, buf1);
// Extract the chroma subcarrier (TODO: Optimise by running only where needed)
for (int i = COLORBURST_START; i < count-(CHROMA_BANDPASS_DELAY+1); i++) {
volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&buf2[i], (lv_32fc_t*)&buf1[i - CHROMA_BANDPASS_DELAY], (lv_32fc_t*)CHROMA_BANDPASS, CHROMA_BANDPASS_SIZE);
}
// Down convert the chroma subcarrier (TODO: Optimise by running only where needed)
lv_32fc_t startPhase = { 1.0f, 0.0f };
lv_32fc_t phaseDelta = { sinf(_this->subcarrierFreq), cosf(_this->subcarrierFreq) };
#if VOLK_VERSION >= 030100
volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)&buf2[COLORBURST_START], (lv_32fc_t*)&buf2[COLORBURST_START], &phaseDelta, &startPhase, count - COLORBURST_START);
#else
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)&buf2[COLORBURST_START], (lv_32fc_t*)&buf2[COLORBURST_START], phaseDelta, &startPhase, count - COLORBURST_START);
#endif
// Compute the phase of the burst
dsp::complex_t burstAvg = { 0.0f, 0.0f };
volk_32fc_accumulator_s32fc((lv_32fc_t*)&burstAvg, (lv_32fc_t*)&buf2[COLORBURST_START], COLORBURST_LEN);
float burstAmp = burstAvg.amplitude();
if (burstAmp*(1.0f/(float)COLORBURST_LEN) < 0.02f) {
printf("%d\n", _this->line);
}
burstAvg *= (1.0f / (burstAmp*burstAmp));
burstAvg = burstAvg.conj();
// Normalize the chroma data (TODO: Optimise by running only where needed)
volk_32fc_s32fc_multiply_32fc((lv_32fc_t*)&buf2[COLORBURST_START], (lv_32fc_t*)&buf2[COLORBURST_START], *((lv_32fc_t*)&burstAvg), count - COLORBURST_START);
// Compute the frequency error of the burst
float phase = buf2[COLORBURST_START].phase();
float error = 0.0f;
for (int i = COLORBURST_START+1; i < COLORBURST_START+COLORBURST_LEN; i++) {
float cphase = buf2[i].phase();
error += dsp::math::normalizePhase(cphase - phase);
phase = cphase;
}
error *= (1.0f / (float)(COLORBURST_LEN-1));
// Update the subcarrier freq
_this->subcarrierFreq += error*0.0001f;
for (int i = 0; i < count; i++) {
int imval = std::clamp<float>((data[i] - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
// uint32_t re = std::clamp<float>((_this->pll.out.writeBuf[i].re - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
// uint32_t im = std::clamp<float>((_this->pll.out.writeBuf[i].im - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
// currentLine[i] = 0xFF000000 | (im << 8) | re;
currentLine[i] = 0xFF000000 | (imval << 16) | (imval << 8) | imval;
}
// Render the line if it's visible
if (_this->ypos >= 34 && _this->ypos <= 34+576-1) {
uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[(_this->ypos - 34)*768];
if (_this->colorMode) {
for (int i = 155; i < (155+768); i++) {
int imval1 = std::clamp<float>(fabsf(buf2[i-155+COLORBURST_START].re*5.0f) * 255.0f, 0, 255);
int imval2 = std::clamp<float>(fabsf(buf2[i-155+COLORBURST_START].im*5.0f) * 255.0f, 0, 255);
currentLine[i-155] = 0xFF000000 | (imval2 << 8) | imval1;
}
// Vertical scan logic
_this->ypos++;
bool rollover = _this->ypos >= 625;
if (rollover) {
{
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
}
else {
for (int i = 155; i < (155+768); i++) {
int imval = std::clamp<float>(data[i-155+COLORBURST_START] * 255.0f, 0, 255);
currentLine[i-155] = 0xFF000000 | (imval << 16) | (imval << 8) | imval;
}
}
}
// Compute whether to rollover
bool rollToOdd = (_this->ypos == 624);
bool rollToEven = (_this->ypos == 623);
// Compute the field sync
bool syncToOdd = (_this->syncHistory == 0b0101011010010101);
bool syncToEven = (_this->syncHistory == 0b0001011010100101);
// Process the sync (NOTE: should start with 0b01, but for some reason I don't see a sync?)
if (rollToOdd || syncToOdd) {
// Update the vertical lock state
bool disagree = (rollToOdd ^ syncToOdd);
if (disagree && _this->vlock > 0) {
_this->vlock--;
}
else if (!disagree && _this->vlock < 20) {
_this->vlock++;
}
// Start the odd field
_this->ypos = 1;
_this->line++;
}
else if (rollToEven || syncToEven) {
// Update the vertical lock state
bool disagree = (rollToEven ^ syncToEven);
if (disagree && _this->vlock > 0) {
_this->vlock--;
}
else if (!disagree && _this->vlock < 20) {
_this->vlock++;
}
// Start the even field
_this->ypos = 0;
_this->line = 0;
// Swap the video buffer
_this->img.swap();
}
else {
_this->ypos += 2;
_this->line++;
// Measure vsync levels
float sync0 = 0.0f, sync1 = 0.0f;
for (int i = 0; i < 306; i++) {
sync0 += data[i];
}
for (int i = (720/2); i < ((720/2)+306); i++) {
sync1 += data[i];
}
sync0 *= (1.0f/305.0f);
sync1 *= (1.0f/305.0f);
// Save sync detection to history
_this->syncHistory >>= 2;
_this->syncHistory |= (((uint16_t)(sync1 < _this->sync_level)) << 9) | (((uint16_t)(sync0 < _this->sync_level)) << 8);
// Trigger vsync in case one is detected
// TODO: Also sync with odd field
if (!rollover && _this->syncHistory == 0b0000011111) {
{
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
}
_this->ypos = 0;
_this->img.swap();
}
}
// NEW SYNC:
float offset = 0.0f;
float gain = 1.0f;
uint16_t syncHistory = 0;
int line = 0;
int ypos = 0;
int vlock = 0;
float subcarrierFreq = 0.0f;
std::string name;
bool enabled = true;
VFOManager::VFO *vfo = NULL;
//dsp::demod::Quadrature demod;
dsp::loop::FastAGC<dsp::complex_t> agc;
dsp::demod::Amplitude demod;
//dsp::demod::AM<float> demod;
dsp::demod::Quadrature demod;
LineSync sync;
dsp::sink::Handler<float> sink;
dsp::convert::RealToComplex r2c;
dsp::tap<dsp::complex_t> chromaTaps;
dsp::filter::FIR<dsp::complex_t, dsp::complex_t> fir;
dsp::loop::ChromaPLL pll;
int ypos = 0;
bool colorMode = false;
bool evenFrame = false;
std::mutex evenFrameMtx;
float sync_level = -0.06f;
int sync_count = 0;
int short_sync = 0;
float minLvl = 0.0f;
float spanLvl = 1.0f;
bool lockedLines = 0;
uint16_t syncHistory = 0;
ImGui::ImageDisplay img;
};

View File

@ -1,37 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(dab_decoder)
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
include(${SDRPP_MODULE_CMAKE})
target_include_directories(dab_decoder PRIVATE "src/")
if (MSVC)
# Lib path
target_include_directories(dab_decoder PRIVATE "C:/Program Files/codec2/include/")
target_link_directories(dab_decoder PRIVATE "C:/Program Files/codec2/lib")
target_link_libraries(dab_decoder PRIVATE libcodec2)
elseif (ANDROID)
target_include_directories(dab_decoder PUBLIC
/sdr-kit/${ANDROID_ABI}/include/codec2
)
target_link_libraries(dab_decoder PUBLIC
/sdr-kit/${ANDROID_ABI}/lib/libcodec2.so
)
else ()
find_package(PkgConfig)
pkg_check_modules(LIBCODEC2 REQUIRED codec2)
target_include_directories(dab_decoder PRIVATE ${LIBCODEC2_INCLUDE_DIRS})
target_link_directories(dab_decoder PRIVATE ${LIBCODEC2_LIBRARY_DIRS})
target_link_libraries(dab_decoder PRIVATE ${LIBCODEC2_LIBRARIES})
# Include it because for some reason pkgconfig doesn't look here?
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
target_include_directories(dab_decoder PRIVATE "/usr/local/include")
endif()
endif ()

View File

@ -1,280 +0,0 @@
#pragma once
#include <dsp/processor.h>
#include <utils/flog.h>
#include <fftw3.h>
#include "dab_phase_sym.h"
namespace dab {
class CyclicSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
public:
CyclicSync() {}
// TODO: The default AGC rate is probably way too fast, plot out the avgCorr to see how much it moves
CyclicSync(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) { init(in, symbolLength, cyclicPrefixLength, samplerate, agcRate); }
void init(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) {
// Computer the number of samples for the symbol and its cyclic prefix
symbolSamps = round(samplerate * symbolLength);
prefixSamps = round(samplerate * cyclicPrefixLength);
// Allocate and clear the delay buffer
delayBuf = dsp::buffer::alloc<dsp::complex_t>(STREAM_BUFFER_SIZE + 64000);
dsp::buffer::clear(delayBuf, symbolSamps);
// Allocate and clear the history buffer
histBuf = dsp::buffer::alloc<dsp::complex_t>(prefixSamps);
dsp::buffer::clear(histBuf, prefixSamps);
// Compute the delay input addresses
delayBufInput = &delayBuf[symbolSamps];
// Compute the correlation AGC configuration
this->agcRate = agcRate;
agcRateInv = 1.0f - agcRate;
base_type::init(in);
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
base_type::tempStart();
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
// Copy the data into the normal delay buffer
memcpy(delayBufInput, base_type::_in->readBuf, count * sizeof(dsp::complex_t));
// Flush the input stream
base_type::_in->flush();
// Do cross-correlation
for (int i = 0; i < count; i++) {
// Get the current history slot
dsp::complex_t* slot = &histBuf[histId++];
// Wrap around the history slot index (TODO: Check that the history buffer's length is correct)
histId %= prefixSamps;
// Kick out last value from the correlation
corr -= *slot;
// Save input value and compute the new prodct
dsp::complex_t val = delayBuf[i];
dsp::complex_t prod = val.conj()*delayBuf[i+symbolSamps];
// Add the new value to the correlation
*slot = prod;
// Add the new value to the history buffer
corr += prod;
// Compute sample amplitude
float rcorr = corr.amplitude();
// If a high enough peak is reached, reset the symbol counter
if (rcorr > avgCorr && rcorr > peakCorr) { // Note keeping an average level might not be needed
peakCorr = rcorr;
peakLCorr = lastCorr;
samplesSincePeak = 0;
}
// If this is the sample right after the peak, save it
if (samplesSincePeak == 1) {
peakRCorr = rcorr;
}
// Write the sample to the output
out.writeBuf[samplesSincePeak++] = val;
// If the end of the symbol is reached, send it off
if (samplesSincePeak >= symbolSamps) {
if (!out.swap(symbolSamps)) {
return -1;
}
samplesSincePeak = 0;
peakCorr = 0;
}
// Update the average correlation
lastCorr = rcorr;
// Update the average correlation value
avgCorr = agcRate*rcorr + agcRateInv*avgCorr;
}
// Move unused data
memmove(delayBuf, &delayBuf[count], symbolSamps * sizeof(dsp::complex_t));
return count;
}
protected:
int symbolSamps;
int prefixSamps;
int histId = 0;
dsp::complex_t* histBuf;
dsp::complex_t* delayBuf;
dsp::complex_t* delayBufInput;
dsp::complex_t corr = { 0.0f, 0.0f };
int samplesSincePeak = 0;
float lastCorr = 0.0f;
float peakCorr = 0.0f;
float peakLCorr = 0.0f;
float peakRCorr = 0.0f;
// Note only required for DAB
float avgCorr = 0.0f;
float agcRate;
float agcRateInv;
};
class FrameFreqSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
public:
FrameFreqSync() {}
FrameFreqSync(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) { init(in, agcRate); }
void init(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) {
// Allocate buffers
amps = dsp::buffer::alloc<float>(2048);
conjRef = dsp::buffer::alloc<dsp::complex_t>(2048);
corrIn = (dsp::complex_t*)fftwf_alloc_complex(2048);
corrOut = (dsp::complex_t*)fftwf_alloc_complex(2048);
// Copy the phase reference
memcpy(conjRef, DAB_PHASE_SYM_CONJ, 2048 * sizeof(dsp::complex_t));
// Plan the FFT computation
plan = fftwf_plan_dft_1d(2048, (fftwf_complex*)corrIn, (fftwf_complex*)corrOut, FFTW_FORWARD, FFTW_ESTIMATE);
// Compute the correlation AGC configuration
this->agcRate = agcRate;
agcRateInv = 1.0f - agcRate;
base_type::init(in);
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
base_type::tempStart();
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
// Apply frequency shift
lv_32fc_t phase = lv_cmake(1.0f, 0.0f);
lv_32fc_t phaseDelta = lv_cmake(cos(offset), sin(offset));
#if VOLK_VERSION >= 030100
volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
#else
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
#endif
// Compute the amplitude amplitude of all samples
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)_in->readBuf, 2048);
// Compute the average signal level by adding up all values
float level = 0.0f;
volk_32f_accumulator_s32f(&level, amps, 2048);
// Detect a frame sync condition
if (level < avgLvl * 0.5f) {
// Reset symbol counter
sym = 1;
// Update the average level
avgLvl = agcRate*level + agcRateInv*avgLvl;
// Flush the input stream and return
base_type::_in->flush();
return count;
}
// Update the average level
avgLvl = agcRate*level + agcRateInv*avgLvl;
// Handle phase reference
if (sym == 1) {
// Output the symbols (DEBUG ONLY)
memcpy(corrIn, _in->readBuf, 2048 * sizeof(dsp::complex_t));
fftwf_execute(plan);
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048);
int outCount = 0;
dsp::complex_t pi4 = { cos(3.1415926535*0.25), sin(3.1415926535*0.25) };
for (int i = -767; i < 768; i++) {
if (!i) { continue; }
int cid0 = ((i-1) >= 0) ? (i-1) : 2048+(i-1);
int cid1 = (i >= 0) ? i : 2048+i;;
out.writeBuf[outCount++] = pi4 * (corrOut[cid1] * corrOut[cid0].conj()) * (1.0f/(amps[cid0]*amps[cid0]));
}
out.swap(outCount);
// Multiply the samples with the conjugated phase reference signal
volk_32fc_x2_multiply_32fc((lv_32fc_t*)corrIn, (lv_32fc_t*)_in->readBuf, (lv_32fc_t*)conjRef, 2048);
// Compute the FFT of the product
fftwf_execute(plan);
// Compute the amplitude of the bins
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048);
// Locate highest power bin
uint32_t peakId;
volk_32f_index_max_32u(&peakId, amps, 2048);
// Obtain the value of the bins next to the peak
float peakL = amps[(peakId + 2047) % 2048];
float peakR = amps[(peakId + 1) % 2048];
// Compute the integer frequency offset
float offInt = (peakId < 1024) ? (float)peakId : ((float)peakId - 2048.0f);
// Compute the frequency offset in rad/samp
float off = 3.1415926535f * (offInt + ((peakR - peakL) / (peakR + peakL))) * (1.0f / 1024.0f);
// Run control loop
offset -= 0.1f*off;
flog::debug("Offset: {} Hz, Error: {} Hz, Avg Level: {}", offset * (0.5f/3.1415926535f)*2.048e6, off * (0.5f/3.1415926535f)*2.048e6, avgLvl);
}
// Increment the symbol counter
sym++;
// Flush the input stream and return
base_type::_in->flush();
return count;
}
protected:
fftwf_plan plan;
float* amps;
dsp::complex_t* conjRef;
dsp::complex_t* corrIn;
dsp::complex_t* corrOut;
int sym;
float offset = 0.0f;
float avgLvl = 0.0f;
float agcRate;
float agcRateInv;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,163 +0,0 @@
#include <imgui.h>
#include <config.h>
#include <core.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <filesystem>
#include <dsp/stream.h>
#include <dsp/buffer/reshaper.h>
#include <dsp/multirate/rational_resampler.h>
#include <dsp/sink/handler_sink.h>
#include <fstream>
#include <chrono>
#include "dab_dsp.h"
#include <gui/widgets/constellation_diagram.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
/* Name: */ "dab_decoder",
/* Description: */ "DAB/DAB+ Decoder for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
#define INPUT_SAMPLE_RATE 2.048e6
#define VFO_BANDWIDTH 1.6e6
class M17DecoderModule : public ModuleManager::Instance {
public:
M17DecoderModule(std::string name) {
this->name = name;
file = std::ofstream("sync4.f32", std::ios::out | std::ios::binary);
// Load config
config.acquire();
config.release(true);
// Initialize VFO
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, VFO_BANDWIDTH, INPUT_SAMPLE_RATE, VFO_BANDWIDTH, VFO_BANDWIDTH, true);
vfo->setSnapInterval(250);
// Initialize DSP here
csync.init(vfo->output, 1e-3, 246e-6, INPUT_SAMPLE_RATE);
ffsync.init(&csync.out);
ns.init(&ffsync.out, handler, this);
// Start DSO Here
csync.start();
ffsync.start();
ns.start();
gui::menu.registerEntry(name, menuHandler, this, this);
}
~M17DecoderModule() {
gui::menu.removeEntry(name);
// Stop DSP Here
if (enabled) {
csync.stop();
ffsync.stop();
ns.stop();
sigpath::vfoManager.deleteVFO(vfo);
}
sigpath::sinkManager.unregisterStream(name);
}
void postInit() {}
void enable() {
double bw = gui::waterfall.getBandwidth();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), VFO_BANDWIDTH, INPUT_SAMPLE_RATE, VFO_BANDWIDTH, VFO_BANDWIDTH, true);
vfo->setSnapInterval(250);
// Set Input of demod here
csync.setInput(vfo->output);
// Start DSP here
csync.start();
ffsync.start();
ns.start();
enabled = true;
}
void disable() {
// Stop DSP here
csync.stop();
ffsync.stop();
ns.stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static void menuHandler(void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvail().x;
if (!_this->enabled) { style::beginDisabled(); }
_this->constDiagram.draw();
if (!_this->enabled) { style::endDisabled(); }
}
std::ofstream file;
static void handler(dsp::complex_t* data, int count, void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
//_this->file.write((char*)data, count * sizeof(dsp::complex_t));
dsp::complex_t* buf = _this->constDiagram.acquireBuffer();
memcpy(buf, data, 1024 * sizeof(dsp::complex_t));
_this->constDiagram.releaseBuffer();
}
std::string name;
bool enabled = true;
dab::CyclicSync csync;
dab::FrameFreqSync ffsync;
dsp::sink::Handler<dsp::complex_t> ns;
ImGui::ConstellationDiagram constDiagram;
// DSP Chain
VFOManager::VFO* vfo;
};
MOD_EXPORT void _INIT_() {
// Create default recording directory
json def = json({});
config.setPath(core::args["root"].s() + "/dab_decoder_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new M17DecoderModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (M17DecoderModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -1,34 +0,0 @@
0123456789
--- ---
0*4
1*5
2*6
1*5
2*6 = L + 3*7 - 0*4
3*7
2*6
3*7 = L + 4*8 - 1*5
4*8
3*7
4*8 = L + 5*9 - 2*6
5*9
0*5
1*6
2*7
1*6
2*7
3*8
2*7
3*8
4*9
=> Use same technique to cache the interpolation results

View File

@ -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;

View File

@ -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"; }

View File

@ -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"; }

View File

@ -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"; }

View File

@ -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"; }

View File

@ -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"; }

View File

@ -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"; }

View File

@ -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"; }

View File

@ -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"; }

View File

@ -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;

View File

@ -1,8 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(ryfi_decoder)
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
include(${SDRPP_MODULE_CMAKE})
target_include_directories(ryfi_decoder PRIVATE "src/")

View File

@ -1,139 +0,0 @@
#include <imgui.h>
#include <config.h>
#include <core.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <filesystem>
#include <dsp/routing/splitter.h>
#include <dsp/buffer/reshaper.h>
#include <dsp/sink/handler_sink.h>
#include <gui/widgets/folder_select.h>
#include <gui/widgets/constellation_diagram.h>
#include "ryfi/receiver.h"
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
/* Name: */ "ryfi_decoder",
/* Description: */ "RyFi decoder for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
#define INPUT_BANDWIDTH 600e3
#define INPUT_SAMPLE_RATE 1000e3
#define INPUT_BAUDRATE 500e3
#define SYMBOL_DIAG_RATE 30
#define SYMBOL_DIAG_COUNT 1024
class RyFiDecoderModule : public ModuleManager::Instance {
public:
RyFiDecoderModule(std::string name) {
this->name = name;
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_BANDWIDTH, INPUT_SAMPLE_RATE, INPUT_BANDWIDTH, INPUT_BANDWIDTH, true);
rx.init(vfo->output, INPUT_BAUDRATE, INPUT_SAMPLE_RATE);
reshape.init(rx.softOut, SYMBOL_DIAG_COUNT, (INPUT_BAUDRATE / SYMBOL_DIAG_RATE) - SYMBOL_DIAG_COUNT);
symSink.init(&reshape.out, symSinkHandler, this);
rx.onPacket.bind(&RyFiDecoderModule::packetHandler, this);
rx.start();
reshape.start();
symSink.start();
gui::menu.registerEntry(name, menuHandler, this, this);
}
~RyFiDecoderModule() {
rx.stop();
reshape.stop();
symSink.stop();
sigpath::vfoManager.deleteVFO(vfo);
gui::menu.removeEntry(name);
}
void postInit() {}
void enable() {
double bw = gui::waterfall.getBandwidth();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), INPUT_BANDWIDTH, INPUT_SAMPLE_RATE, INPUT_BANDWIDTH, INPUT_BANDWIDTH, true);
rx.setInput(vfo->output);
rx.start();
reshape.start();
symSink.start();
enabled = true;
}
void disable() {
rx.stop();
reshape.stop();
symSink.stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
void packetHandler(ryfi::Packet pkt) {
flog::debug("Got a {} byte packet!", pkt.size());
}
static void menuHandler(void* ctx) {
RyFiDecoderModule* _this = (RyFiDecoderModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvail().x;
if (!_this->enabled) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
_this->constDiagram.draw();
if (!_this->enabled) { style::endDisabled(); }
}
static void symSinkHandler(dsp::complex_t* data, int count, void* ctx) {
RyFiDecoderModule* _this = (RyFiDecoderModule*)ctx;
dsp::complex_t* buf = _this->constDiagram.acquireBuffer();
memcpy(buf, data, 1024 * sizeof(dsp::complex_t));
_this->constDiagram.releaseBuffer();
}
std::string name;
bool enabled = true;
// DSP Chain
VFOManager::VFO* vfo;
ryfi::Receiver rx;
dsp::buffer::Reshaper<dsp::complex_t> reshape;
dsp::sink::Handler<dsp::complex_t> symSink;
ImGui::ConstellationDiagram constDiagram;
};
MOD_EXPORT void _INIT_() {
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new RyFiDecoderModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (RyFiDecoderModule*)instance;
}
MOD_EXPORT void _END_() {
}

View File

@ -1,74 +0,0 @@
#include "conv_codec.h"
namespace ryfi {
ConvEncoder::ConvEncoder(dsp::stream<uint8_t>* in) {
// Create the convolutional encoder instance
conv = correct_convolutional_create(2, 7, correct_conv_r12_7_polynomial);
// Init the base class
base_type::init(in);
}
ConvEncoder::~ConvEncoder() {
// Destroy the convolutional encoder instance
correct_convolutional_destroy(conv);
}
int ConvEncoder::encode(const uint8_t* in, uint8_t* out, int count) {
// Run convolutional encoder on the data
return correct_convolutional_encode(conv, in, count, out);
}
int ConvEncoder::run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count);
base_type::_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
ConvDecoder::ConvDecoder(dsp::stream<dsp::complex_t>* in) {
// Create the convolutional encoder instance
conv = correct_convolutional_create(2, 7, correct_conv_r12_7_polynomial);
// Allocate the soft symbol buffer
soft = dsp::buffer::alloc<uint8_t>(STREAM_BUFFER_SIZE);
// Init the base class
base_type::init(in);
}
ConvDecoder::~ConvDecoder() {
// Destroy the convolutional encoder instance
correct_convolutional_destroy(conv);
// Free the soft symbol buffer
dsp::buffer::free(soft);
}
int ConvDecoder::decode(const dsp::complex_t* in, uint8_t* out, int count) {
// Convert to uint8
const float* _in = (const float*)in;
count *= 2;
for (int i = 0; i < count; i++) {
soft[i] = std::clamp<int>((_in[i] * 127.0f) + 128.0f, 0, 255);
}
// Run convolutional decoder on the data
return correct_convolutional_decode_soft(conv, soft, count, out);
}
int ConvDecoder::run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = decode(base_type::_in->readBuf, base_type::out.writeBuf, count);
base_type::_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
}

View File

@ -1,71 +0,0 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "dsp/processor.h"
extern "C" {
#include "correct.h"
}
namespace ryfi {
/**
* RyFi Convolutional Encoder.
*/
class ConvEncoder : public dsp::Processor<uint8_t, uint8_t> {
using base_type = dsp::Processor<uint8_t, uint8_t>;
public:
/**
* Create a convolutional encoder specifying an input stream.
* @param in Input stream.
*/
ConvEncoder(dsp::stream<uint8_t>* in = NULL);
// Destructor
~ConvEncoder();
/**
* Encode data.
* @param in Input bytes.
* @param out Output bits.
* @param count Number of input bytes.
* @return Number of output bits.
*/
int encode(const uint8_t* in, uint8_t* out, int count);
private:
int run();
correct_convolutional* conv;
};
/**
* RyFi Convolutional Decoder.
*/
class ConvDecoder : public dsp::Processor<dsp::complex_t, uint8_t> {
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
public:
/**
* Create a convolutional encoder specifying an input stream.
* @param in Input stream.
*/
ConvDecoder(dsp::stream<dsp::complex_t>* in = NULL);
// Destructor
~ConvDecoder();
/**
* Decode soft symbols.
* @param in Input soft symbols.
* @param out Output bytes.
* @param count Number of input bytes.
* @return Number of output bits.
*/
int decode(const dsp::complex_t* in, uint8_t* out, int count);
private:
int run();
correct_convolutional* conv;
uint8_t* soft = NULL;
};
}

View File

@ -1,37 +0,0 @@
#include "frame.h"
namespace ryfi {
int Frame::serialize(uint8_t* bytes) const {
// Write the counter
bytes[0] = (counter >> 8) & 0xFF;
bytes[1] = counter & 0xFF;
// Write the first packet pointer
bytes[2] = (firstPacket >> 8) & 0xFF;
bytes[3] = firstPacket & 0xFF;
// Write the last packet pointer
bytes[4] = (lastPacket >> 8) & 0xFF;
bytes[5] = lastPacket & 0xFF;
// Write the data
memcpy(&bytes[6], content, FRAME_DATA_SIZE);
// Return the length of a serialized frame
return FRAME_SIZE;
}
void Frame::deserialize(const uint8_t* bytes, Frame& frame) {
// Read the counter
frame.counter = (((uint16_t)bytes[0]) << 8) | ((uint16_t)bytes[1]);
// Read the first packet pointer
frame.firstPacket = (((uint16_t)bytes[2]) << 8) | ((uint16_t)bytes[3]);
// Read the last packet pointer
frame.lastPacket = (((uint16_t)bytes[4]) << 8) | ((uint16_t)bytes[5]);
// Read the data
memcpy(frame.content, &bytes[6], FRAME_DATA_SIZE);
}
}

View File

@ -1,42 +0,0 @@
#pragma once
#include <stdint.h>
#include "rs_codec.h"
namespace ryfi {
enum PacketOffset {
PKT_OFFS_NONE = 0xFFFF
};
struct Frame {
/**
* Serialize the frame to bytes.
* @param bytes Buffer to write the serialized frame to.
*/
int serialize(uint8_t* bytes) const;
/**
* Deserialize a frame from bytes.
* @param bytes Buffer to deserialize the frame from.
* @param frame Object that will contain the deserialize frame.
*/
static void deserialize(const uint8_t* bytes, Frame& frame);
// Size of a serialized frame
static inline const int FRAME_SIZE = RS_BLOCK_DEC_SIZE*RS_BLOCK_COUNT;
// Size of the data area of the frame
static inline const int FRAME_DATA_SIZE = FRAME_SIZE - 6;
// Steadily increasing counter.
uint16_t counter = 0;
// Byte offset of the first packet in the frame.
uint16_t firstPacket = 0;
// Byte offset of the last packet in the frame.
uint16_t lastPacket = 0;
// Data area of the frame.
uint8_t content[FRAME_DATA_SIZE];
};
}

View File

@ -1,137 +0,0 @@
#include "framing.h"
namespace ryfi {
dsp::complex_t QPSK_SYMBOLS[4] = {
{ -0.070710678118f, -0.070710678118f },
{ -0.070710678118f, 0.070710678118f },
{ 0.070710678118f, -0.070710678118f },
{ 0.070710678118f, 0.070710678118f },
};
Framer::Framer(dsp::stream<uint8_t>* in) {
// Generate the sync symbols
int k = 0;
for (int i = 62; i >= 0; i -= 2) {
syncSyms[k++] = QPSK_SYMBOLS[(SYNC_WORD >> i) & 0b11];
}
// Initialize base class
base_type::init(in);
}
int Framer::encode(const uint8_t* in, dsp::complex_t* out, int count) {
// Copy sync symbols
memcpy(out, syncSyms, SYNC_SYMS*sizeof(dsp::complex_t));
// Modulate the rest of the bits
dsp::complex_t* dataOut = &out[SYNC_SYMS];
int dataSyms = count / 2;
for (int i = 0; i < dataSyms; i++) {
uint8_t bits = (in[i >> 2] >> (6 - 2*(i & 0b11))) & 0b11;
dataOut[i] = QPSK_SYMBOLS[bits];
}
// Compute and return the total number of symbols
return SYNC_SYMS + dataSyms;
}
int Framer::run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count);
base_type::_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
Deframer::Deframer(dsp::stream<dsp::complex_t> *in) {
// Compute sync word rotations
// 0: 00 01 11 10
// 90: 10 00 01 11
// 180: 11 10 00 01
// 270: 01 11 10 00
// For 0 and 180 it's the sync and its complement
syncRots[ROT_0_DEG] = SYNC_WORD;
syncRots[ROT_180_DEG] = ~SYNC_WORD;
// For 90 and 270 its the quadrature and its complement
uint64_t quad;
for (int i = 62; i >= 0; i -= 2) {
// Get the symbol
uint8_t sym = (SYNC_WORD >> i) & 0b11;
// Rotate it 90 degrees
uint8_t rsym;
switch (sym) {
case 0b00: rsym = 0b10; break;
case 0b01: rsym = 0b00; break;
case 0b11: rsym = 0b01; break;
case 0b10: rsym = 0b11; break;
}
// Push it into the quadrature
quad = (quad << 2) | rsym;
}
syncRots[ROT_90_DEG] = quad;
syncRots[ROT_270_DEG] = ~quad;
base_type::init(in);
}
int Deframer::run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
dsp::complex_t* in = base_type::_in->readBuf;
for (int i = 0; i < count; i++) {
if (recv) {
// Copy the symbol to the output and rotate it approprieate
base_type::out.writeBuf[outCount++] = in[i] * symRot;
// Check if we're done receiving the frame, send it out
if (!(--recv)) {
if (!base_type::out.swap(outCount)) {
base_type::_in->flush();
return -1;
}
}
}
else {
// Get the raw symbol
dsp::complex_t fsym = in[i];
// Decode the symbol
uint8_t sym = ((fsym.re > 0) ? 0b10 : 0b00) | ((fsym.im > 0) ? 0b01 : 0b00);
// Push it to the shift register
shift = (shift << 2) | sym;
// Find the rotation starting with the last known one
for (int i = 0; i < 4; i++) {
// Get the test rotation
int testRot = (knownRot+i) & 0b11;
// Check if the hamming distance is close enough
int dist;
if (distance(shift, syncRots[testRot]) < 6) {
// Save the new rotation
knownRot = testRot;
// Start reading in symbols for the frame
symRot = symRots[knownRot];
recv = 8168; // TODO: Don't hardcode!
outCount = 0;
}
}
}
}
base_type::_in->flush();
return count;
}
}

View File

@ -1,87 +0,0 @@
#pragma once
#include "dsp/processor.h"
#include <stdint.h>
#include <stddef.h>
namespace ryfi {
// Synchronization word.
inline const uint64_t SYNC_WORD = 0x341CC540819D8963;
// Number of synchronization bits.
inline const int SYNC_BITS = 64;
// Number of synchronization symbols.
inline const int SYNC_SYMS = SYNC_BITS / 2;
// Possible constellation rotations
enum {
ROT_0_DEG = 0,
ROT_90_DEG = 1,
ROT_180_DEG = 2,
ROT_270_DEG = 3
};
/**
* RyFi Framer.
*/
class Framer : public dsp::Processor<uint8_t, dsp::complex_t> {
using base_type = dsp::Processor<uint8_t, dsp::complex_t>;
public:
/**
* Create a framer specifying an input stream.
* @param in Input stream.
*/
Framer(dsp::stream<uint8_t>* in = NULL);
/**
* Encode a frame to symbols adding a sync word.
*/
int encode(const uint8_t* in, dsp::complex_t* out, int count);
private:
int run();
dsp::complex_t syncSyms[SYNC_SYMS];
};
class Deframer : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
public:
/**
* Create a deframer specifying an input stream.
* @param in Input stream.
*/
Deframer(dsp::stream<dsp::complex_t> *in = NULL);
private:
int run();
inline static constexpr int distance(uint64_t a, uint64_t b) {
int dist = 0;
for (int i = 0; i < 64; i++) {
dist += ((a & 1) != (b & 1));
a >>= 1;
b >>= 1;
}
return dist;
}
// Frame reading counters
int recv = 0;
int outCount = 0;
// Rotation handling
int knownRot = 0;
uint64_t syncRots[4];
dsp::complex_t symRot;
const dsp::complex_t symRots[4] = {
{ 1.0f, 0.0f }, // 0 deg
{ 0.0f, -1.0f }, // 90 deg
{ -1.0f, 0.0f }, // 180 deg
{ 0.0f, 1.0f }, // 270 deg
};
// Shift register
uint64_t shift;
};
}

View File

@ -1,126 +0,0 @@
#include "packet.h"
#include "string.h"
#include <stdexcept>
namespace ryfi {
Packet::Packet() {}
Packet::Packet(uint8_t* content, int size) {
// Check that the size isn't too large
if (size > MAX_CONTENT_SIZE) {
throw std::runtime_error("Content size is too large to fit in a packet");
}
// Allocate the buffer
allocate(size);
// Copy over the content
memcpy(_content, content, size);
}
Packet::Packet(const Packet& b) {
// Reallocate the buffer
allocate(b._size);
// Copy over the content
memcpy(_content, b._content, b._size);
}
Packet::Packet(Packet&& b) {
// Move members
_content = b._content;
_size = b._size;
// Destroy old object
b._content = NULL;
b._size = 0;
}
Packet::~Packet() {
// Delete the content
if (_content) { delete[] _content; }
}
Packet& Packet::operator=(const Packet& b) {
// Reallocate the buffer
allocate(b._size);
// Copy over the content
memcpy(_content, b._content, b._size);
// Return self
return *this;
}
Packet& Packet::operator=(Packet&& b) {
// Move members
_content = b._content;
_size = b._size;
// Destroy old object
b._content = NULL;
b._size = 0;
// Return self
return *this;
}
Packet::operator bool() const {
return _size > 0;
}
int Packet::size() const {
// Return the size
return _size;
}
const uint8_t* Packet::data() const {
// Return the size
return _content;
}
void Packet::setContent(uint8_t* content, int size) {
// Check that the size isn't too large
if (size > MAX_CONTENT_SIZE) {
throw std::runtime_error("Content size is too large to fit in a packet");
}
// Reallocate the buffer
allocate(size);
// Copy over the content
memcpy(_content, content, size);
}
int Packet::serializedSize() const {
// Two size bytes + Size of the content
return _size + 2;
}
int Packet::serialize(uint8_t* bytes) const {
// Write the size in big-endian
bytes[0] = (_size >> 8) & 0xFF;
bytes[1] = _size & 0xFF;
// Copy the content of the packet
memcpy(&bytes[2], _content, _size);
// Return the serialized size
return serializedSize();
}
void Packet::allocate(int newSize) {
// If the size hasn't changed, do nothing
if (newSize == _size) { return; }
// Free the old buffer
if (_content) { delete[] _content; };
// Update the size
_size = newSize;
// Allocate the buffer
_content = new uint8_t[newSize];
}
}

View File

@ -1,89 +0,0 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
namespace ryfi {
/**
* RyFi Protocol Packet.
*/
class Packet {
public:
// Default constructor
Packet();
/**
* Create a packet from its content.
* @param content Content of the packet.
* @param size Number of bytes of content.
*/
Packet(uint8_t* content, int size);
// Copy constructor
Packet(const Packet& b);
// Move constructor
Packet(Packet&& b);
// Destructor
~Packet();
// Copy assignment operator
Packet& operator=(const Packet& b);
// Move assignment operator
Packet& operator=(Packet&& b);
// Cast to bool operator
operator bool() const;
/**
* Get the size of the content of the packet.
* @return Size of the content of the packet.
*/
int size() const;
/**
* Get the content of the packet. The pointer is only valid until reallocation or deletion.
* @return Content of the packet.
*/
const uint8_t* data() const;
/**
* Set the content of the packet.
* @param content Content of the packet.
* @param size Number of bytes of content.
*/
void setContent(uint8_t* content, int size);
/**
* Get the size of the serialized packet.
* @return Size of the serialized packet.
*/
int serializedSize() const;
/**
* Serialize the packet to bytes.
* @param bytes Buffer to which to write the serialized packet.
* @return Size of the serialized packet.
*/
int serialize(uint8_t* bytes) const;
/**
* Deserialize a packet from bytes.
* TODO
*/
static bool deserialize(uint8_t* bytes, int size, Packet& pkt);
// Maximum size of the content of the packet.
static inline const int MAX_CONTENT_SIZE = 0xFFFF;
// Maximum size of the serialized packet.
static inline const int MAX_SERIALIZED_SIZE = MAX_CONTENT_SIZE + 2;
private:
void allocate(int newSize);
uint8_t* _content = NULL;
int _size = 0;
};
}

View File

@ -1,194 +0,0 @@
#include "receiver.h"
#include "utils/flog.h"
namespace ryfi {
Receiver::Receiver() {}
Receiver::Receiver(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate) {
init(in, baudrate, samplerate);
}
Receiver::~Receiver() {
// Stop everything
stop();
}
void Receiver::init(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate) {
// Initialize the DSP
demod.init(in, baudrate, samplerate, 31, 0.6, 0.1f, 0.005f, 1e-6, 0.01);
doubler.init(&demod.out);
softOut = &doubler.outA;
deframer.setInput(&doubler.outB);
conv.setInput(&deframer.out);
rs.setInput(&conv.out);
}
void Receiver::setInput(dsp::stream<dsp::complex_t>* in) {
demod.setInput(in);
}
void Receiver::start() {
// Do nothing if already running
if (running) { return; }
// Start the worker thread
workerThread = std::thread(&Receiver::worker, this);
// Start the DSP
demod.start();
doubler.start();
deframer.start();
conv.start();
rs.start();
// Update the running state
running = true;
}
void Receiver::stop() {
// Do nothing if not running
if (!running) { return; }
// Stop the worker thread
rs.out.stopReader();
if (workerThread.joinable()) { workerThread.join(); }
rs.out.clearReadStop();
// Stop the DSP
demod.stop();
doubler.stop();
deframer.stop();
conv.stop();
rs.stop();
// Update the running state
running = false;
}
void Receiver::worker() {
Frame frame;
uint16_t lastCounter = 0;
uint8_t* pktBuffer = new uint8_t[Packet::MAX_CONTENT_SIZE];
int pktExpected = 0;
int pktRead = 0;
int valid = 0;
while (true) {
// Read a frame
int count = rs.out.read();
if (count <= 0) { break; }
// Deserialize the frame
Frame::deserialize(rs.out.readBuf, frame);
valid++;
// Flush the stream
rs.out.flush();
//flog::info("Frame[{}]: FirstPacket={}, LastPacket={}", frame.counter, frame.firstPacket, frame.lastPacket);
// Compute the expected frame counter
uint16_t expectedCounter = lastCounter + 1;
lastCounter = frame.counter;
// If the frames aren't consecutive
int frameRead = 0;
if (frame.counter != expectedCounter) {
flog::warn("Lost at least {} frames after {} valid frames", ((int)frame.counter - (int)expectedCounter + 0x10000) % 0x10000, valid);
// Cancel the partial packet if there was one
pktExpected = 0;
pktRead = 0;
valid = 1;
// If this frame is not an idle frame or continuation frame
if (frame.firstPacket != PKT_OFFS_NONE) {
// If the offset of the first packet is not plausible
if (frame.firstPacket > Frame::FRAME_DATA_SIZE-2) {
flog::warn("Packet had non-plausible offset: {}", frameRead);
// Skip the frame
continue;
}
// Skip to the end of the packet
frameRead = frame.firstPacket;
}
}
// If there is no partial packet and the frame doesn't contain a packet start, skip it
if (!pktExpected && frame.firstPacket == PKT_OFFS_NONE) { continue; }
// Extract packets from the frame
bool firstPacket = true;
bool lastPacket = false;
while (frameRead < Frame::FRAME_DATA_SIZE) {
// If there is a partial packet read as much as possible from it
if (pktExpected) {
// Compute how many bytes of the packet are available in the frame
int readable = std::min<int>(pktExpected - pktRead, Frame::FRAME_DATA_SIZE - frameRead);
//flog::debug("Reading {} bytes", readable);
// Write them to the packet
memcpy(&pktBuffer[pktRead], &frame.content[frameRead], readable);
pktRead += readable;
frameRead += readable;
// If the packet is read entirely
if (pktRead >= pktExpected) {
// Create the packet object
Packet pkt(pktBuffer, pktExpected);
// Send off the packet
onPacket(pkt);
// Prepare for the next packet
pktRead = 0;
pktExpected = 0;
// If this was the last packet of the frame
if (lastPacket || frame.firstPacket == PKT_OFFS_NONE) {
// Skip the rest of the frame
frameRead = Frame::FRAME_DATA_SIZE;
continue;
}
}
// Go to next packet
continue;
}
// If the packet offset is not plausible
if (Frame::FRAME_DATA_SIZE - frameRead < 2) {
flog::warn("Packet had non-plausible offset: {}", frameRead);
// Skip the rest of the frame and the packet
frameRead = Frame::FRAME_DATA_SIZE;
pktExpected = 0;
pktRead = 0;
continue;
}
// If this is the first packet, use the frame info to skip possible left over data
if (firstPacket) {
frameRead = frame.firstPacket;
firstPacket = false;
}
// Check if this is the last packet
lastPacket = (frameRead == frame.lastPacket);
// Parse the packet size
pktExpected = ((uint16_t)frame.content[frameRead]) << 8;
pktExpected |= (uint16_t)frame.content[frameRead+1];
//flog::debug("Starting to read a {} byte packet at offset {}", pktExpected, frameRead);
// Skip to the packet content
frameRead += 2;
}
}
delete[] pktBuffer;
}
}

View File

@ -1,69 +0,0 @@
#pragma once
#include "utils/new_event.h"
#include "dsp/demod/psk.h"
#include "dsp/routing/doubler.h"
#include "packet.h"
#include "frame.h"
#include "rs_codec.h"
#include "conv_codec.h"
#include "framing.h"
#include <mutex>
namespace ryfi {
class Receiver {
public:
Receiver();
/**
* Create a transmitter.
* @param in Baseband input.
* @param baudrate Baudrate to use over the air.
* @param samplerate Samplerate of the baseband.
*/
Receiver(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate);
/**
* Create a transmitter.
* @param in Baseband input.
* @param baudrate Baudrate to use over the air.
* @param samplerate Samplerate of the baseband.
*/
void init(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate);
/**
* Set the input stream.
* @param in Baseband input.
*/
void setInput(dsp::stream<dsp::complex_t>* in);
// Destructor
~Receiver();
/**
* Start the transmitter's DSP.
*/
void start();
/**
* Stop the transmitter's DSP.
*/
void stop();
dsp::stream<dsp::complex_t>* softOut;
NewEvent<Packet> onPacket;
private:
void worker();
// DSP
dsp::demod::PSK<4> demod;
dsp::routing::Doubler<dsp::complex_t> doubler;
Deframer deframer;
ConvDecoder conv;
RSDecoder rs;
bool running = false;
std::thread workerThread;
};
}

View File

@ -1,169 +0,0 @@
#include "rs_codec.h"
namespace ryfi {
RSEncoder::RSEncoder(dsp::stream<uint8_t>* in) {
// Create the convolutional encoder instance
rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 1, 1, 32);
// Init the base class
base_type::init(in);
}
RSEncoder::~RSEncoder() {
// Destroy the convolutional encoder instance
correct_reed_solomon_destroy(rs);
}
int RSEncoder::encode(const uint8_t* in, uint8_t* out, int count) {
// Check the size
assert(count == RS_BLOCK_COUNT*RS_BLOCK_DEC_SIZE);
// Go through each block
uint8_t block[RS_BLOCK_ENC_SIZE];
for (int i = 0; i < RS_BLOCK_COUNT; i++) {
// Encode block
correct_reed_solomon_encode(rs, &in[i*RS_BLOCK_DEC_SIZE], RS_BLOCK_DEC_SIZE, block);
// Interleave into the frame
int k = 0;
for (int j = i; j < RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT; j += RS_BLOCK_COUNT) {
out[j] = block[k++];
}
}
// Scramble
for (int i = 0; i < RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE; i++) {
out[i] ^= RS_SCRAMBLER_SEQ[i];
}
return RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE;
}
int RSEncoder::run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count);
base_type::_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
RSDecoder::RSDecoder(dsp::stream<uint8_t>* in) {
// Create the convolutional encoder instance
rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 1, 1, 32);
// Init the base class
base_type::init(in);
}
RSDecoder::~RSDecoder() {
// Destroy the convolutional encoder instance
correct_reed_solomon_destroy(rs);
}
int RSDecoder::decode(uint8_t* in, uint8_t* out, int count) {
// Check the size
assert(count == RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE);
// Descramble (TODO: Don't do it in-place)
for (int i = 0; i < RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE; i++) {
in[i] ^= RS_SCRAMBLER_SEQ[i];
}
// Go through each block
uint8_t block[RS_BLOCK_ENC_SIZE];
for (int i = 0; i < RS_BLOCK_COUNT; i++) {
// Deinterleave out of the frame
int k = 0;
for (int j = i; j < count; j += RS_BLOCK_COUNT) {
block[k++] = in[j];
}
// Decode block and return if decoding fails
int res = correct_reed_solomon_decode(rs, block, RS_BLOCK_ENC_SIZE, &out[i*RS_BLOCK_DEC_SIZE]);
if (res < 0) { return 0; }
}
return RS_BLOCK_COUNT*RS_BLOCK_DEC_SIZE;
}
int RSDecoder::run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = decode(base_type::_in->readBuf, base_type::out.writeBuf, count);
base_type::_in->flush();
if (count && !out.swap(count)) { return -1; }
return count;
}
const uint8_t RS_SCRAMBLER_SEQ[RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT] = {
0x75, 0x05, 0x7C, 0xCE, 0xF1, 0xD0, 0x6C, 0xF6, 0xFA, 0x65, 0xF6, 0xFC, 0xE0, 0x0A, 0x82, 0x17,
0x6C, 0xBE, 0x76, 0xA0, 0xD6, 0x46, 0x12, 0x2E, 0xDE, 0xB5, 0xF7, 0xAD, 0xCB, 0x51, 0x63, 0x47,
0x27, 0x30, 0x7E, 0x43, 0xD1, 0xA1, 0xCB, 0x10, 0x08, 0x49, 0xDF, 0x86, 0xD4, 0xC4, 0xD7, 0x3C,
0x6D, 0x03, 0x07, 0x37, 0x5B, 0xB3, 0xCD, 0x79, 0x6F, 0x1E, 0xBA, 0xC5, 0x6E, 0xC3, 0x8C, 0x7A,
0x25, 0x99, 0x61, 0x54, 0x5A, 0x96, 0x57, 0x9B, 0xE0, 0x60, 0x5B, 0x09, 0x6D, 0x8B, 0x2D, 0x9D,
0x15, 0x9D, 0x0E, 0xBF, 0x57, 0xFB, 0x9C, 0x49, 0x82, 0x2C, 0x48, 0x59, 0x92, 0x47, 0x79, 0x17,
0x16, 0x74, 0xEA, 0xEA, 0xBB, 0xC5, 0x72, 0x32, 0x17, 0xD1, 0xB3, 0xDE, 0xEB, 0x15, 0xC7, 0x55,
0x8A, 0xF2, 0x88, 0xC2, 0x33, 0xA6, 0x17, 0x8B, 0xD4, 0x77, 0x22, 0x00, 0x63, 0x47, 0x45, 0x5F,
0x36, 0x35, 0x58, 0x8B, 0x88, 0xEC, 0xCA, 0xC4, 0x60, 0x53, 0x9E, 0xBD, 0xB2, 0xF5, 0x51, 0x46,
0x34, 0x9A, 0x07, 0x25, 0x3F, 0xF5, 0x65, 0x63, 0x77, 0x3C, 0x5A, 0xFA, 0x4E, 0x0C, 0xF7, 0x1B,
0x82, 0xAB, 0x73, 0x06, 0x7F, 0xB7, 0xC6, 0x6B, 0xBF, 0xB1, 0x46, 0xF3, 0x01, 0x91, 0xB1, 0xFF,
0x5C, 0x6F, 0xF9, 0x43, 0x0E, 0x6A, 0x70, 0x89, 0x0B, 0xEA, 0x8C, 0xD4, 0x1B, 0x51, 0x01, 0x31,
0x71, 0x2E, 0xDF, 0x24, 0xC1, 0xD5, 0xDB, 0x0E, 0xF5, 0xEB, 0x78, 0x79, 0x39, 0x5B, 0xAD, 0xC3,
0xA9, 0xA6, 0x60, 0x30, 0xA2, 0x9A, 0x7B, 0xA0, 0xF4, 0xAA, 0xC5, 0x57, 0xB3, 0x16, 0xF9, 0xB5,
0x79, 0x20, 0xC1, 0x88, 0x9A, 0x00, 0x43, 0xB2, 0xC6, 0x84, 0x8D, 0x03, 0xF2, 0xD8, 0x90, 0x7A,
0x21, 0x37, 0x7E, 0xF7, 0x75, 0xE5, 0xFB, 0xC9, 0xDC, 0xAB, 0x4B, 0xBC, 0x35, 0x38, 0xB9, 0x3A,
0x53, 0x89, 0x7E, 0xD5, 0x94, 0x12, 0x2D, 0x9B, 0x91, 0x90, 0x1D, 0x4D, 0x0E, 0xE0, 0x93, 0xF3,
0xC1, 0xA1, 0x9B, 0x73, 0x27, 0x22, 0x41, 0x27, 0xEE, 0x2A, 0xD7, 0x45, 0xBC, 0x8F, 0x9B, 0xA2,
0x36, 0x11, 0x16, 0x37, 0x1A, 0xF1, 0x2E, 0x71, 0xCF, 0x86, 0x89, 0x83, 0x5A, 0xF1, 0x24, 0x6C,
0x56, 0x71, 0x53, 0xE4, 0xD2, 0xCB, 0xCA, 0x86, 0x1E, 0xA0, 0xD5, 0x83, 0x3B, 0xEF, 0x09, 0x09,
0xC2, 0x07, 0x53, 0x86, 0xE6, 0x8A, 0xC6, 0x70, 0xFB, 0x91, 0x43, 0xCB, 0x91, 0x6E, 0xA9, 0xBC,
0x31, 0x42, 0x61, 0x0C, 0x88, 0xB8, 0x2C, 0xED, 0xD8, 0xE6, 0xA3, 0xEC, 0xAC, 0xB9, 0x45, 0x5E,
0x2C, 0x73, 0x3F, 0x2E, 0x06, 0xE0, 0xBF, 0x73, 0xDD, 0x2E, 0x45, 0x50, 0x6C, 0x53, 0x55, 0xF0,
0x7F, 0x6E, 0x61, 0xFA, 0xA0, 0x7A, 0x1C, 0xF0, 0xBD, 0xAC, 0x48, 0x61, 0x03, 0x6B, 0xED, 0x54,
0x2A, 0x27, 0x94, 0xF6, 0xF9, 0x6A, 0x04, 0x08, 0x0B, 0x3C, 0xC3, 0x30, 0x66, 0x01, 0xFB, 0xDC,
0xC9, 0x65, 0x03, 0x83, 0x7D, 0x0A, 0xDF, 0xA5, 0x04, 0x14, 0xE4, 0xF2, 0x4C, 0x01, 0xDF, 0x04,
0xD2, 0x80, 0xB9, 0x9B, 0xD9, 0x5E, 0xF8, 0x2A, 0x93, 0x8D, 0x8C, 0x09, 0x9B, 0x38, 0xEC, 0x3B,
0xC4, 0x29, 0x90, 0x7C, 0x65, 0x3A, 0xF2, 0x4B, 0x69, 0xD3, 0x63, 0x9B, 0x40, 0x95, 0xC3, 0xFB,
0x67, 0x54, 0x40, 0x9B, 0x26, 0x9F, 0x52, 0xFE, 0xD8, 0xD0, 0x24, 0x9C, 0x5C, 0xD4, 0xEF, 0xDE,
0x28, 0x66, 0x75, 0x04, 0xCB, 0xA4, 0xC0, 0xB9, 0x4B, 0xC9, 0x20, 0x4B, 0x56, 0xC7, 0x86, 0xC5,
0x39, 0x45, 0x18, 0xA7, 0x48, 0x14, 0x1A, 0x51, 0xCA, 0xD0, 0xC0, 0x15, 0xDD, 0xC1, 0x28, 0x4A,
0x7A, 0xD2, 0x10, 0xEA, 0x83, 0xD3, 0x3A, 0xEF, 0x48, 0x29, 0x41, 0xA4, 0xD4, 0x57, 0xA6, 0x1D,
0x76, 0x24, 0x93, 0x58, 0x7E, 0xB7, 0xDD, 0x0B, 0xF2, 0xCE, 0x71, 0x55, 0xF5, 0xAB, 0x8C, 0xC8,
0x70, 0x59, 0x73, 0x69, 0x9D, 0x29, 0x5E, 0x59, 0xF4, 0xB2, 0xC4, 0x97, 0x75, 0xF0, 0x65, 0x1B,
0x66, 0x5F, 0xA4, 0x33, 0x5C, 0xC7, 0xBF, 0x45, 0xE6, 0x20, 0xC0, 0xBD, 0xAD, 0xAE, 0x9F, 0x97,
0x05, 0xD8, 0x04, 0x2B, 0x0A, 0x46, 0xE8, 0xB8, 0xCB, 0x00, 0xE2, 0x7C, 0x70, 0x1B, 0x49, 0xDE,
0x81, 0xEB, 0x24, 0xAC, 0x1B, 0x3E, 0x09, 0xFB, 0xAC, 0xB7, 0xF2, 0xD1, 0xB2, 0x78, 0xF3, 0xAC,
0xC7, 0x6A, 0xA2, 0x07, 0x4C, 0xED, 0x61, 0xAD, 0x04, 0x7F, 0x45, 0x83, 0x59, 0x31, 0x27, 0xF0,
0x16, 0x6B, 0x0C, 0xAA, 0xD4, 0xD1, 0xCB, 0x1C, 0x51, 0x41, 0x0D, 0x2F, 0x8F, 0xF9, 0xF9, 0x7F,
0x22, 0x89, 0x46, 0xF4, 0xB8, 0x93, 0x98, 0x9E, 0x3E, 0x23, 0xF1, 0x6E, 0x64, 0x08, 0xB6, 0xC9,
0x6E, 0x53, 0x53, 0xED, 0xAD, 0x21, 0xCD, 0x1A, 0xF0, 0x45, 0xFC, 0x14, 0x00, 0xEA, 0xF7, 0x42,
0xEE, 0xDA, 0x58, 0x0D, 0x85, 0xBC, 0x74, 0xFB, 0x73, 0x78, 0xB5, 0x5E, 0x5E, 0x6F, 0x6F, 0x7E,
0x39, 0xC2, 0x05, 0x50, 0xDB, 0x3D, 0xB8, 0xF3, 0x8F, 0x80, 0xEC, 0x46, 0x29, 0x39, 0x89, 0xF3,
0x55, 0x9C, 0x6A, 0x5F, 0x7C, 0xD9, 0x7C, 0x13, 0xE4, 0x56, 0x5E, 0xE9, 0x60, 0x19, 0xE2, 0x7D,
0xC4, 0x41, 0x92, 0x8D, 0xDA, 0x21, 0x58, 0x20, 0xE9, 0xA8, 0x4C, 0x16, 0x34, 0x99, 0xAC, 0xB7,
0x30, 0xBD, 0x39, 0x19, 0xAC, 0x9B, 0x4B, 0x27, 0xFA, 0x32, 0xC1, 0x48, 0xA1, 0x80, 0x34, 0x36,
0x1E, 0xFB, 0x92, 0x43, 0x35, 0x72, 0x2D, 0xEF, 0xD2, 0xF2, 0xFC, 0xC2, 0x85, 0xAB, 0x59, 0x40,
0x8D, 0x9D, 0x1A, 0x1F, 0xE2, 0x92, 0x87, 0xA2, 0xF9, 0x2C, 0x78, 0xE4, 0xC3, 0x26, 0x56, 0x07,
0xB3, 0x78, 0xAF, 0x79, 0x3D, 0x88, 0xF4, 0xAD, 0x66, 0x7C, 0x07, 0x58, 0x98, 0x82, 0x1A, 0x26,
0xF7, 0xFD, 0xCE, 0xFF, 0x75, 0xED, 0xAB, 0xBD, 0xAE, 0x6D, 0x5C, 0x28, 0x91, 0xF3, 0xB7, 0x5C,
0x27, 0x05, 0xEC, 0x3B, 0xE3, 0xDD, 0x93, 0x24, 0x7F, 0xAD, 0x14, 0xAA, 0x49, 0x61, 0x8F, 0x96,
0x1F, 0xAA, 0xB2, 0xEE, 0xA8, 0x24, 0x41, 0x7C, 0xDC, 0xF1, 0x28, 0x26, 0xE6, 0x7F, 0x98, 0x20,
0x50, 0x5F, 0x90, 0x21, 0x8A, 0x09, 0x26, 0x59, 0xD0, 0x07, 0x2F, 0xE1, 0x35, 0x4D, 0x0B, 0x20,
0xB2, 0xD5, 0xDD, 0xB5, 0xAC, 0x1B, 0xFE, 0xD9, 0xE3, 0x35, 0xF1, 0xB8, 0x3F, 0x3D, 0xFC, 0x0B,
0x5A, 0x57, 0xA9, 0x92, 0x2B, 0xC8, 0x3E, 0xC2, 0xAA, 0xEF, 0xB9, 0x98, 0x2C, 0xA8, 0xAB, 0xF6,
0xA1, 0xBF, 0xBC, 0x8D, 0x97, 0xA2, 0x74, 0xD9, 0xE5, 0x99, 0x85, 0x81, 0x15, 0xB0, 0xE7, 0x8B,
0x48, 0x86, 0xF4, 0x94, 0x9C, 0x62, 0x82, 0xD1, 0x2C, 0x24, 0x4B, 0xAC, 0x7A, 0xB8, 0x4E, 0x4A,
0xD2, 0xF6, 0xAA, 0xED, 0xE0, 0x9C, 0x98, 0xD2, 0xDF, 0xC1, 0xBC, 0xBF, 0x55, 0x7D, 0x40, 0xB5,
0xDE, 0xD4, 0x25, 0xBB, 0x81, 0xF4, 0x07, 0x1D, 0xE7, 0x3C, 0xB4, 0x62, 0xC9, 0x55, 0x0A, 0x3A,
0xD5, 0xCE, 0x97, 0xED, 0x30, 0x76, 0x76, 0x51, 0xBC, 0x8C, 0xE4, 0x54, 0xBE, 0xB7, 0xB5, 0xCD,
0xF8, 0x76, 0x37, 0x53, 0x2C, 0x9F, 0xE4, 0xC7, 0xEB, 0xF5, 0x8D, 0x23, 0x8A, 0xDA, 0xD1, 0xA9,
0xD8, 0x4C, 0x53, 0xF3, 0x49, 0xA7, 0x1A, 0x5D, 0xE5, 0x03, 0x49, 0x52, 0xD3, 0xE2, 0x1F, 0xA5,
0x35, 0x9C, 0xBB, 0x0B, 0xC7, 0x0D, 0xA4, 0x65, 0x54, 0x8B, 0x39, 0xF1, 0x3B, 0x67, 0x21, 0x71,
0x10, 0xE7, 0x76, 0xC4, 0xA8, 0xC2, 0x9D, 0x93, 0xC6, 0x51, 0xBA, 0x23
};
}

View File

@ -1,82 +0,0 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "dsp/processor.h"
extern "C" {
#include "correct.h"
}
namespace ryfi {
// Size of an encoded reed-solomon block.
inline const int RS_BLOCK_ENC_SIZE = 255;
// Size of a decoded reed-solomon block.
inline const int RS_BLOCK_DEC_SIZE = 223;
// Number of reed-solomon blocks.
inline const int RS_BLOCK_COUNT = 4;
// Scrambler sequence
extern const uint8_t RS_SCRAMBLER_SEQ[RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT];
/**
* RyFi Reed-Solomon Encoder.
*/
class RSEncoder : public dsp::Processor<uint8_t, uint8_t> {
using base_type = dsp::Processor<uint8_t, uint8_t>;
public:
/**
* Create a reed-solomon encoder specifying an input stream.
* @param in Input stream
*/
RSEncoder(dsp::stream<uint8_t>* in = NULL);
// Destructor
~RSEncoder();
/**
* Encode data.
* @param in Input bytes.
* @param out Output bytes.
* @param count Number of input bytes.
* @return Number of output bytes.
*/
int encode(const uint8_t* in, uint8_t* out, int count);
private:
int run();
correct_reed_solomon* rs;
};
/**
* RyFi Reed-Solomon Decoder.
*/
class RSDecoder : public dsp::Processor<uint8_t, uint8_t> {
using base_type = dsp::Processor<uint8_t, uint8_t>;
public:
/**
* Create a reed-solomon decoder specifying an input stream.
* @param in Input stream
*/
RSDecoder(dsp::stream<uint8_t>* in = NULL);
// Destructor
~RSDecoder();
/**
* Decode data.
* @param in Input bytes.
* @param out Output bytes.
* @param count Number of input bytes.
* @return Number of output bytes.
*/
int decode(uint8_t* in, uint8_t* out, int count);
private:
int run();
correct_reed_solomon* rs;
};
}

View File

@ -1,177 +0,0 @@
#include "transmitter.h"
namespace ryfi {
Transmitter::Transmitter(double baudrate, double samplerate) {
// Initialize the DSP
rs.setInput(&in);
conv.setInput(&rs.out);
framer.setInput(&conv.out);
resamp.init(&framer.out, baudrate, samplerate);
rrcTaps = dsp::taps::rootRaisedCosine<float>(511, 0.6, baudrate, samplerate);
// Normalize the taps
float tot = 0.0f;
for (int i = 0; i < rrcTaps.size; i++) {
tot += rrcTaps.taps[i];
}
for (int i = 0; i < rrcTaps.size; i++) {
rrcTaps.taps[i] /= tot;
}
rrc.init(&resamp.out, rrcTaps);
out = &rrc.out;
}
Transmitter::~Transmitter() {
// Stop everything
stop();
}
void Transmitter::start() {
// Do nothing if already running
if (running) { return; }
// Start the worker thread
workerThread = std::thread(&Transmitter::worker, this);
// Start the DSP
rs.start();
conv.start();
framer.start();
resamp.start();
rrc.start();
// Update the running state
running = true;
}
void Transmitter::stop() {
// Do nothing if not running
if (!running) { return; }
// Stop the worker thread
in.stopWriter();
if (workerThread.joinable()) { workerThread.join(); }
in.clearWriteStop();
// Stop the DSP
rs.stop();
conv.stop();
framer.stop();
resamp.stop();
rrc.stop();
// Update the running state
running = false;
}
bool Transmitter::send(const Packet& pkt) {
// Acquire the packet queue
std::lock_guard<std::mutex> lck(packetsMtx);
// If there are too many packets queued up, drop the packet
if (packets.size() >= MAX_QUEUE_SIZE) { return false; }
// Push the packet onto the queue
packets.push(pkt);
}
bool Transmitter::txFrame(const Frame& frame) {
// Serialize the frame
int count = frame.serialize(in.writeBuf);
// Send it off
return in.swap(count);
}
Packet Transmitter::popPacket() {
// Acquire the packet queue
std::unique_lock<std::mutex> lck(packetsMtx);
// If no packets are available, return empty packet
if (!packets.size()) { return Packet(); }
// Pop the front packet and return it
Packet pkt = packets.front();
packets.pop();
return pkt;
}
void Transmitter::worker() {
Frame frame;
Packet pkt;
uint16_t counter = 0;
int pktToWrite = 0;
int pktWritten = 0;
uint8_t* pktBuffer = new uint8_t[Packet::MAX_SERIALIZED_SIZE];
while (true) {
// Initialize the frame
frame.counter = counter++;
frame.firstPacket = PKT_OFFS_NONE;
frame.lastPacket = PKT_OFFS_NONE;
int frameOffset = 0;
// Fill the frame with as much packet data as possible
while (frameOffset < sizeof(Frame::content)) {
// If there is no packet in the process of being sent
if (!pktWritten) {
// If there is not enough space for the size of the packet
if ((sizeof(Frame::content) - frameOffset) < 2) {
// Fill the rest of the frame with noise and send it
for (int i = frameOffset; i < sizeof(Frame::content); i++) { frame.content[i] = rand(); }
break;
}
// Get the next packet
pkt = popPacket();
// If there was an available packet
if (pkt) {
// Serialize the packet
pktToWrite = pkt.serializedSize();
pkt.serialize(pktBuffer);
}
}
// If none was available
if (!pkt) {
// Fill the rest of the frame with noise and send it
for (int i = frameOffset; i < sizeof(Frame::content); i++) { frame.content[i] = rand(); }
break;
}
// If this is the beginning of the packet
if (!pktWritten) {
//flog::debug("Starting to write a {} byte packet at offset {}", pktToWrite-2, frameOffset);
// If this is the first packet of the frame, update its offset
if (frame.firstPacket == PKT_OFFS_NONE) { frame.firstPacket = frameOffset; }
// Update the last packet pointer
frame.lastPacket = frameOffset;
}
// Compute the amount of data writeable to the frame
int writeable = std::min<int>(pktToWrite - pktWritten, sizeof(Frame::content) - frameOffset);
// Copy the data to the frame
memcpy(&frame.content[frameOffset], &pktBuffer[pktWritten], writeable);
pktWritten += writeable;
frameOffset += writeable;
// If the packet is done being sent
if (pktWritten >= pktToWrite) {
// Prepare for a new packet
pktToWrite = 0;
pktWritten = 0;
}
}
// Send the frame
if (!txFrame(frame)) { break; }
}
delete[] pktBuffer;
}
}

View File

@ -1,69 +0,0 @@
#pragma once
#include "dsp/multirate/rational_resampler.h"
#include "dsp/taps/root_raised_cosine.h"
#include "dsp/filter/fir.h"
#include "packet.h"
#include "frame.h"
#include "rs_codec.h"
#include "conv_codec.h"
#include "framing.h"
#include <queue>
#include <mutex>
namespace ryfi {
class Transmitter {
public:
/**
* Create a transmitter.
* @param baudrate Baudrate to use over the air.
* @param samplerate Samplerate of the baseband.
*/
Transmitter(double baudrate, double samplerate);
// Destructor
~Transmitter();
/**
* Start the transmitter's DSP.
*/
void start();
/**
* Stop the transmitter's DSP.
*/
void stop();
/**
* Send a packet.
* @param pkg Packet to send.
* @return True if the packet was send, false if it was dropped.
*/
bool send(const Packet& pkt);
// Baseband output
dsp::stream<dsp::complex_t>* out;
static inline const int MAX_QUEUE_SIZE = 32;
private:
bool txFrame(const Frame& frame);
Packet popPacket();
void worker();
// Packet queue
std::mutex packetsMtx;
std::queue<Packet> packets;
// DSP
dsp::stream<uint8_t> in;
RSEncoder rs;
ConvEncoder conv;
Framer framer;
dsp::multirate::RationalResampler<dsp::complex_t> resamp;
dsp::tap<float> rrcTaps;
dsp::filter::FIR<dsp::complex_t, float> rrc;
bool running = false;
std::thread workerThread;
};
}

View File

@ -1,8 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(vor_receiver)
file(GLOB_RECURSE SRC "src/*.cpp")
include(${SDRPP_MODULE_CMAKE})
target_include_directories(vor_receiver PRIVATE "src/")

View File

@ -1,128 +0,0 @@
#include <imgui.h>
#include <config.h>
#include <core.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <filesystem>
#include <dsp/buffer/reshaper.h>
#include <dsp/sink/handler_sink.h>
#include <gui/widgets/constellation_diagram.h>
#include "vor_decoder.h"
#include <fstream>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
/* Name: */ "vor_receiver",
/* Description: */ "VOR Receiver for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
#define INPUT_SAMPLE_RATE VOR_IN_SR
class VORReceiverModule : public ModuleManager::Instance {
public:
VORReceiverModule(std::string name) {
this->name = name;
// Load config
config.acquire();
// TODO: Load config
config.release();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, true);
decoder = new vor::Decoder(vfo->output, 1);
decoder->onBearing.bind(&VORReceiverModule::onBearing, this);
decoder->start();
gui::menu.registerEntry(name, menuHandler, this, this);
}
~VORReceiverModule() {
decoder->stop();
sigpath::vfoManager.deleteVFO(vfo);
gui::menu.removeEntry(name);
delete decoder;
}
void postInit() {}
void enable() {
double bw = gui::waterfall.getBandwidth();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, true);
decoder->setInput(vfo->output);
decoder->start();
enabled = true;
}
void disable() {
decoder->stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static void menuHandler(void* ctx) {
VORReceiverModule* _this = (VORReceiverModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvail().x;
if (!_this->enabled) { style::beginDisabled(); }
ImGui::Text("Bearing: %f°", _this->bearing);
ImGui::Text("Quality: %0.1f%%", _this->quality);
if (!_this->enabled) { style::endDisabled(); }
}
void onBearing(float nbearing, float nquality) {
bearing = (180.0f * nbearing / FL_M_PI);
quality = nquality * 100.0f;
}
std::string name;
bool enabled = true;
// DSP Chain
VFOManager::VFO* vfo;
vor::Decoder* decoder;
float bearing = 0.0f, quality = 0.0f;
};
MOD_EXPORT void _INIT_() {
// Create default recording directory
std::string root = (std::string)core::args["root"];
json def = json({});
config.setPath(root + "/vor_receiver_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new VORReceiverModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (VORReceiverModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -1,50 +0,0 @@
#include "vor_decoder.h"
#define STDDEV_NORM_FACTOR 1.813799364234218f // 2.0f * FL_M_PI / sqrt(12)
namespace vor {
Decoder::Decoder(dsp::stream<dsp::complex_t>* in, double integrationTime) {
rx.init(in);
reshape.init(&rx.out, round(1000.0 * integrationTime), 0);
symSink.init(&reshape.out, dataHandler, this);
}
Decoder::~Decoder() {
// TODO
}
void Decoder::setInput(dsp::stream<dsp::complex_t>* in) {
rx.setInput(in);
}
void Decoder::start() {
rx.start();
reshape.start();
symSink.start();
}
void Decoder::stop() {
rx.stop();
reshape.stop();
symSink.stop();
}
void Decoder::dataHandler(float* data, int count, void* ctx) {
// Get the instance from context
Decoder* _this = (Decoder*)ctx;
// Compute the mean and standard deviation of the
float mean, stddev;
volk_32f_stddev_and_mean_32f_x2(&stddev, &mean, data, count);
// Compute the signal quality
float quality = std::max<float>(1.0f - (stddev / STDDEV_NORM_FACTOR), 0.0f);
// Convert the phase difference to a compass heading
mean = -mean;
if (mean < 0) { mean = 2.0f*FL_M_PI + mean; }
// Call the handler
_this->onBearing(mean, quality);
}
}

View File

@ -1,49 +0,0 @@
#include "vor_receiver.h"
#include <dsp/buffer/reshaper.h>
#include <dsp/sink/handler_sink.h>
#include <utils/new_event.h>
namespace vor {
// Note: hard coded to 22KHz samplerate
class Decoder {
public:
/**
* Create an instance of a VOR decoder.
* @param in Input IQ stream at 22 KHz sampling rate.
* @param integrationTime Integration time of the bearing data in seconds.
*/
Decoder(dsp::stream<dsp::complex_t>* in, double integrationTime);
// Destructor
~Decoder();
/**
* Set the input stream.
* @param in Input IQ stream at 22 KHz sampling rate.
*/
void setInput(dsp::stream<dsp::complex_t>* in);
/**
* Start the decoder.
*/
void start();
/**
* Stop the decoder.
*/
void stop();
/**
* handler(bearing, signalQuality);
*/
NewEvent<float, float> onBearing;
private:
static void dataHandler(float* data, int count, void* ctx);
// DSP
Receiver rx;
dsp::buffer::Reshaper<float> reshape;
dsp::sink::Handler<float> symSink;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,106 +0,0 @@
#include <dsp/sink.h>
#include <dsp/types.h>
#include <dsp/demod/am.h>
#include <dsp/demod/quadrature.h>
#include <dsp/convert/real_to_complex.h>
#include <dsp/channel/frequency_xlator.h>
#include <dsp/filter/fir.h>
#include <dsp/math/delay.h>
#include <dsp/math/conjugate.h>
#include <dsp/channel/rx_vfo.h>
#include "vor_fm_filter.h"
#include <utils/wav.h>
#define VOR_IN_SR 25e3
namespace vor {
class Receiver : public dsp::Processor<dsp::complex_t, float> {
using base_type = dsp::Processor<dsp::complex_t, float>;
public:
Receiver() {}
Receiver(dsp::stream<dsp::complex_t>* in) { init(in); }
~Receiver() {
if (!base_type::_block_init) { return; }
base_type::stop();
dsp::taps::free(fmfTaps);
}
void init(dsp::stream<dsp::complex_t>* in) {
amd.init(NULL, dsp::demod::AM<float>::CARRIER, VOR_IN_SR, 50.0f / VOR_IN_SR, 5.0f / VOR_IN_SR, 100.0f / VOR_IN_SR, VOR_IN_SR);
amr2c.init(NULL);
fmr2c.init(NULL);
fmx.init(NULL, -9960, VOR_IN_SR);
fmfTaps = dsp::taps::fromArray(FM_TAPS_COUNT, fm_taps);
fmf.init(NULL, fmfTaps);
fmd.init(NULL, 600, VOR_IN_SR);
amde.init(NULL, FM_TAPS_COUNT / 2);
amv.init(NULL, VOR_IN_SR, 1000, 30, 30);
fmv.init(NULL, VOR_IN_SR, 1000, 30, 30);
base_type::init(in);
}
int process(dsp::complex_t* in, float* out, int count) {
// Demodulate the AM outer modulation
volk_32fc_magnitude_32f(amd.out.writeBuf, (lv_32fc_t*)in, count);
amr2c.process(count, amd.out.writeBuf, amr2c.out.writeBuf);
// Isolate the FM subcarrier
fmx.process(count, amr2c.out.writeBuf, fmx.out.writeBuf);
fmf.process(count, fmx.out.writeBuf, fmx.out.writeBuf);
// Demodulate the FM subcarrier
fmd.process(count, fmx.out.writeBuf, fmd.out.writeBuf);
fmr2c.process(count, fmd.out.writeBuf, fmr2c.out.writeBuf);
// Delay the AM signal by the same amount as the FM one
amde.process(count, amr2c.out.writeBuf, amr2c.out.writeBuf);
// Isolate the 30Hz component on both the AM and FM channels
int rcount = amv.process(count, amr2c.out.writeBuf, amv.out.writeBuf);
fmv.process(count, fmr2c.out.writeBuf, fmv.out.writeBuf);
// If no data was returned, we're done for this round
if (!rcount) { return 0; }
// Conjugate FM reference
volk_32fc_conjugate_32fc((lv_32fc_t*)fmv.out.writeBuf, (lv_32fc_t*)fmv.out.writeBuf, rcount);
// Multiply both together
volk_32fc_x2_multiply_32fc((lv_32fc_t*)amv.out.writeBuf, (lv_32fc_t*)amv.out.writeBuf, (lv_32fc_t*)fmv.out.writeBuf, rcount);
// Compute angle
volk_32fc_s32f_atan2_32f(out, (lv_32fc_t*)amv.out.writeBuf, 1.0f, rcount);
return rcount;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
int outCount = process(base_type::_in->readBuf, base_type::out.writeBuf, count);
// Swap if some data was generated
base_type::_in->flush();
if (outCount) {
if (!base_type::out.swap(outCount)) { return -1; }
}
return outCount;
}
private:
dsp::demod::AM<float> amd;
dsp::convert::RealToComplex amr2c;
dsp::convert::RealToComplex fmr2c;
dsp::channel::FrequencyXlator fmx;
dsp::tap<float> fmfTaps;
dsp::filter::FIR<dsp::complex_t, float> fmf;
dsp::demod::Quadrature fmd;
dsp::math::Delay<dsp::complex_t> amde;
dsp::channel::RxVFO amv;
dsp::channel::RxVFO fmv;
};
}

View File

@ -6,14 +6,13 @@ cd /root
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-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 libspdlog-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/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
@ -26,30 +25,10 @@ make install
ldconfig
cd ..
# Install librfnm
git clone https://github.com/AlexandreRouma/librfnm
cd librfnm
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
cd ../../
# Install libfobos
git clone https://github.com/AlexandreRouma/libfobos
cd libfobos
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
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 -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
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 ..

View File

@ -6,14 +6,13 @@ cd /root
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-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 libspdlog-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/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
@ -26,30 +25,10 @@ make install
ldconfig
cd ..
# Install librfnm
git clone https://github.com/AlexandreRouma/librfnm
cd librfnm
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
cd ../../
# Install libfobos
git clone https://github.com/AlexandreRouma/libfobos
cd libfobos
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
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 -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
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 ..

View File

@ -6,14 +6,13 @@ cd /root
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-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 libspdlog-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/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
@ -26,30 +25,10 @@ make install
ldconfig
cd ..
# Install librfnm
git clone https://github.com/AlexandreRouma/librfnm
cd librfnm
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
cd ../../
# Install libfobos
git clone https://github.com/AlexandreRouma/libfobos
cd libfobos
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
cd ../../
cd SDRPlusPlus
mkdir build
cd build
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
make VERBOSE=1 -j2
cd ..

View File

@ -6,14 +6,13 @@ cd /root
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 libspdlog-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/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
@ -26,30 +25,10 @@ make install
ldconfig
cd ..
# Install librfnm
git clone https://github.com/AlexandreRouma/librfnm
cd librfnm
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
cd ../../
# Install libfobos
git clone https://github.com/AlexandreRouma/libfobos
cd libfobos
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
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 -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
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 ..

View File

@ -12,14 +12,13 @@ apt update
# Install dependencies and tools
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libairspy-dev \
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
libcodec2-dev libudev-dev autoconf libtool xxd libspdlog-dev
libcodec2-dev libudev-dev autoconf libtool xxd
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/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
@ -52,26 +51,6 @@ make install
ldconfig
cd ..
# Install librfnm
git clone https://github.com/AlexandreRouma/librfnm
cd librfnm
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
cd ../../
# Install libfobos
git clone https://github.com/AlexandreRouma/libfobos
cd libfobos
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
cd ../../
# Fix missing .pc file for codec2
echo 'prefix=/usr/' >> /usr/share/pkgconfig/codec2.pc
echo 'libdir=/usr/include/x86_64-linux-gnu/' >> /usr/share/pkgconfig/codec2.pc
@ -87,7 +66,7 @@ echo 'Cflags: -I/usr/include/codec2' >> /usr/share/pkgconfig/codec2.pc
cd SDRPlusPlus
mkdir build
cd build
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
make VERBOSE=1 -j2
# Generate package

View File

@ -6,14 +6,13 @@ cd /root
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-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 libspdlog-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/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
@ -26,30 +25,10 @@ make install
ldconfig
cd ..
# Install librfnm
git clone https://github.com/AlexandreRouma/librfnm
cd librfnm
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
cd ../../
# Install libfobos
git clone https://github.com/AlexandreRouma/libfobos
cd libfobos
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
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 -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
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 ..

View File

@ -6,14 +6,13 @@ cd /root
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-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 libspdlog-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/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
@ -26,30 +25,10 @@ make install
ldconfig
cd ..
# Install librfnm
git clone https://github.com/AlexandreRouma/librfnm
cd librfnm
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
cd ../../
# Install libfobos
git clone https://github.com/AlexandreRouma/libfobos
cd libfobos
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
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 -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
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 ..

View File

@ -1,4 +1,4 @@
FROM ubuntu:noble
FROM ubuntu:mantic
ENV DEBIAN_FRONTEND=noninteractive
COPY do_build.sh /root
RUN chmod +x /root/do_build.sh

View File

@ -6,14 +6,13 @@ cd /root
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 libspdlog-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/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
@ -26,30 +25,10 @@ make install
ldconfig
cd ..
# Install librfnm
git clone https://github.com/AlexandreRouma/librfnm
cd librfnm
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
cd ../../
# Install libfobos
git clone https://github.com/AlexandreRouma/libfobos
cd libfobos
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
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 -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
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 ..

View File

@ -1,4 +0,0 @@
FROM ubuntu:oracular
ENV DEBIAN_FRONTEND=noninteractive
COPY do_build.sh /root
RUN chmod +x /root/do_build.sh

View File

@ -1,56 +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 libspdlog-dev
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/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 ..
# Install librfnm
git clone https://github.com/AlexandreRouma/librfnm
cd librfnm
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
cd ../../
# Install libfobos
git clone https://github.com/AlexandreRouma/libfobos
cd libfobos
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j2
make install
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 -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
make VERBOSE=1 -j2
cd ..
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev'

View File

@ -28,8 +28,6 @@ bundle_is_not_to_be_installed() {
if [ "$1" = "Security" ]; then echo 1; fi
if [ "$1" = "AppleFSCompression" ]; then echo 1; fi
if [ "$1" = "libsdrplay_api.so.3.14" ]; then echo 1; fi
if [ "$1" = "libsdrplay_api.so.3.15" ]; then echo 1; fi
if [ "$1" = "libxml2.2.dylib" ]; then echo 1; fi
}
# ========================= FOR INTERNAL USE ONLY =========================
@ -228,4 +226,4 @@ bundle_sign() {
fi
codesign --force --deep -s - $1
}
}

View File

@ -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.1$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

View File

@ -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.1 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 =========================
@ -35,14 +35,11 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/airspyhf_source/airspyhf_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/bladerf_source/bladerf_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/file_source/file_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/fobossdr_source/fobossdr_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hackrf_source/hackrf_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hermes_source/hermes_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/limesdr_source/limesdr_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/network_source/network_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/perseus_source/perseus_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/plutosdr_source/plutosdr_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rfnm_source/rfnm_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rfspace_source/rfspace_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_sdr_source/rtl_sdr_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_tcp_source/rtl_tcp_source.dylib

View File

@ -24,9 +24,6 @@ cp 'C:/Program Files/PothosSDR/bin/bladeRF.dll' sdrpp_windows_x64/
cp $build_dir/source_modules/file_source/Release/file_source.dll sdrpp_windows_x64/modules/
cp $build_dir/source_modules/fobossdr_source/Release/fobossdr_source.dll sdrpp_windows_x64/modules/
cp 'C:/Program Files/RigExpert/Fobos/bin/fobos.dll' sdrpp_windows_x64/
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/
@ -35,8 +32,6 @@ cp $build_dir/source_modules/hermes_source/Release/hermes_source.dll sdrpp_windo
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/network_source/Release/network_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/
@ -44,11 +39,6 @@ cp $build_dir/source_modules/plutosdr_source/Release/plutosdr_source.dll sdrpp_w
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/rfnm_source/Release/rfnm_source.dll sdrpp_windows_x64/modules/
cp 'C:/Program Files/RFNM/bin/rfnm.dll' sdrpp_windows_x64/
cp 'C:/Program Files/RFNM/bin/spdlog.dll' sdrpp_windows_x64/
cp 'C:/Program Files/RFNM/bin/fmt.dll' sdrpp_windows_x64/
cp $build_dir/source_modules/rfspace_source/Release/rfspace_source.dll sdrpp_windows_x64/modules/
cp $build_dir/source_modules/rtl_sdr_source/Release/rtl_sdr_source.dll sdrpp_windows_x64/modules/

View File

@ -168,9 +168,10 @@ public:
writer.setSamplerate(samplerate);
// Open file
std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband";
std::string vfoName = (recMode == RECORDER_MODE_AUDIO) ? selectedStreamName : "";
std::string extension = ".wav";
std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, recMode, vfoName) + extension);
std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, type, vfoName) + extension);
if (!writer.open(expandedPath)) {
flog::error("Failed to open file for recording: {0}", expandedPath);
return;
@ -248,6 +249,7 @@ private:
}
ImGui::Columns(1, CONCAT("EndRecorderModeColumns##_", _this->name), false);
ImGui::EndGroup();
if (_this->recording) { style::endDisabled(); }
// Recording path
if (_this->folderSelect.render("##_recorder_fold_" + _this->name)) {
@ -282,11 +284,8 @@ private:
config.release(true);
}
if (_this->recording) { style::endDisabled(); }
// Show additional audio options
if (_this->recMode == RECORDER_MODE_AUDIO) {
if (_this->recording) { style::beginDisabled(); }
ImGui::LeftLabel("Stream");
ImGui::FillWidth();
if (ImGui::Combo(CONCAT("##_recorder_stream_", _this->name), &_this->streamId, _this->audioStreams.txt)) {
@ -295,7 +294,6 @@ private:
config.conf[_this->name]["audioStream"] = _this->audioStreams.key(_this->streamId);
config.release(true);
}
if (_this->recording) { style::endDisabled(); }
_this->updateAudioMeter(_this->audioLvl);
ImGui::FillWidth();
@ -451,7 +449,7 @@ private:
{ RADIO_IFACE_MODE_RAW, "RAW" }
};
std::string genFileName(std::string templ, int mode, std::string name) {
std::string genFileName(std::string templ, std::string type, std::string name) {
// Get data
time_t now = time(0);
tm* ltm = localtime(&now);
@ -461,9 +459,6 @@ private:
freq += gui::waterfall.vfos[name]->generalOffset;
}
// Select the recording type string
std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband";
// Format to string
char freqStr[128];
char hourStr[128];
@ -472,7 +467,7 @@ private:
char dayStr[128];
char monStr[128];
char yearStr[128];
const char* modeStr = (recMode == RECORDER_MODE_AUDIO) ? "Unknown" : "IQ";
const char* modeStr = "Unknown";
sprintf(freqStr, "%.0lfHz", freq);
sprintf(hourStr, "%02d", ltm->tm_hour);
sprintf(minStr, "%02d", ltm->tm_min);
@ -481,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

View File

@ -3,7 +3,6 @@
#include <gui/gui.h>
#include <gui/style.h>
#include <signal_path/signal_path.h>
#include <chrono>
SDRPP_MOD_INFO{
/* Name: */ "scanner",

View File

@ -41,13 +41,14 @@ To create a desktop shortcut, rightclick the exe and select `Send to -> Desktop
Download the latest release from [the Releases page](https://github.com/AlexandreRouma/SDRPlusPlus/releases) and extract to the directory of your choice.
Then, use apt to install it:
Then, run:
```sh
sudo apt install path/to/the/sdrpp_debian_amd64.deb
sudo apt install libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev
sudo dpkg -i sdrpp_debian_amd64.deb
```
**IMPORTANT: You must install the drivers for your SDR. Follow instructions from your manufacturer as to how to do this on your particular distro.**
If `libvolk2-dev` is not available, use `libvolk1-dev`.
### Arch-based
@ -324,16 +325,12 @@ Modules in beta are still included in releases for the most part but not enabled
| audio_source | Working | rtaudio | OPT_BUILD_AUDIO_SOURCE | ✅ | ✅ | ✅ |
| bladerf_source | Working | libbladeRF | OPT_BUILD_BLADERF_SOURCE | ⛔ | ✅ (not Debian Buster) | ✅ |
| file_source | Working | - | OPT_BUILD_FILE_SOURCE | ✅ | ✅ | ✅ |
| fobossdr_source | Working | libfobos | OPT_BUILD_FOBOSSDR_SOURCE | ✅ | ✅ | ✅ |
| hackrf_source | Working | libhackrf | OPT_BUILD_HACKRF_SOURCE | ✅ | ✅ | ✅ |
| harogic_source | Beta | htra_api | OPT_BUILD_HAROGIC_SOURCE | ⛔ | ⛔ | ✅ |
| hermes_source | Beta | - | OPT_BUILD_HERMES_SOURCE | ✅ | ✅ | ✅ |
| kcsdr_source | Unfinished | libkcsdr | OPT_BUILD_KCSDR_SOURCE | ⛔ | ⛔ | ⛔ |
| limesdr_source | Working | liblimesuite | OPT_BUILD_LIMESDR_SOURCE | ⛔ | ✅ | ✅ |
| network_source | Beta | - | OPT_BUILD_NETWORK_SOURCE | ✅ | ✅ | |
| 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 | ✅ | ✅ | ✅ |
@ -341,9 +338,9 @@ Modules in beta are still included in releases for the most part but not enabled
| sdrpp_server_source | Working | - | OPT_BUILD_SDRPP_SERVER_SOURCE | ✅ | ✅ | ✅ |
| soapy_source | Deprecated | soapysdr | OPT_BUILD_SOAPY_SOURCE | ⛔ | ⛔ | ⛔ |
| spectran_source | Unfinished | RTSA Suite | OPT_BUILD_SPECTRAN_SOURCE | ⛔ | ⛔ | ⛔ |
| spectran_http_source | Beta | - | OPT_BUILD_SPECTRAN_HTTP_SOURCE | ✅ | ✅ | |
| spectran_http_source | Beta | - | OPT_BUILD_SPECTRAN_HTTP_SOURCE | ✅ | ✅ | |
| spyserver_source | Working | - | OPT_BUILD_SPYSERVER_SOURCE | ✅ | ✅ | ✅ |
| usrp_source | Beta | libuhd | OPT_BUILD_USRP_SOURCE | ⛔ | ⛔ | |
| usrp_source | Beta | libuhd | OPT_BUILD_USRP_SOURCE | ⛔ | ⛔ | |
## Sinks
@ -352,22 +349,20 @@ Modules in beta are still included in releases for the most part but not enabled
| android_audio_sink | Working | - | OPT_BUILD_ANDROID_AUDIO_SINK | ⛔ | ✅ | ✅ (Android only) |
| audio_sink | Working | rtaudio | OPT_BUILD_AUDIO_SINK | ✅ | ✅ | ✅ |
| network_sink | Working | - | OPT_BUILD_NETWORK_SINK | ✅ | ✅ | ✅ |
| new_portaudio_sink | Working | portaudio | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
| portaudio_sink | Working | portaudio | OPT_BUILD_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
| new_portaudio_sink | Beta | portaudio | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
| portaudio_sink | Beta | portaudio | OPT_BUILD_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
## Decoders
| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default |
|---------------------|------------|--------------|-------------------------------|:---------------:|:----------------:|:---------------------------:|
| atv_decoder | Unfinished | - | OPT_BUILD_ATV_DECODER | ⛔ | ⛔ | ⛔ |
| dab_decoder | Unfinished | - | OPT_BUILD_DAB_DECODER | ⛔ | ⛔ | ⛔ |
| falcon9_decoder | Unfinished | ffplay | OPT_BUILD_FALCON9_DECODER | ⛔ | ⛔ | ⛔ |
| kgsstv_decoder | Unfinished | - | OPT_BUILD_KGSSTV_DECODER | ⛔ | ⛔ | ⛔ |
| m17_decoder | Working | - | OPT_BUILD_M17_DECODER | ⛔ | ✅ | ⛔ |
| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ |
| pager_decoder | Unfinished | - | OPT_BUILD_PAGER_DECODER | ⛔ | ⛔ | ⛔ |
| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ |
| radio | Unfinished | - | OPT_BUILD_VOR_RECEIVER | ⛔ | ⛔ | ⛔ |
| weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ |
## Misc
@ -420,8 +415,8 @@ If you still have an issue, please open an issue about it or ask on the discord.
# Contributing
Feel free to submit band plans via the GitHub issue tracker.
For code changes, please create a feature request instead.
Feel free to submit pull requests and report bugs via the GitHub issue tracker.
I will soon publish a contributing.md listing the code style to use.
# Credits
@ -441,18 +436,15 @@ For code changes, please create a feature request instead.
* Flinger Films
* [Frank Werner (HB9FXQ)](https://twitter.com/HB9FXQ)
* gringogrigio
* Jandro
* Jeff Moe
* Joe Cupano
* KD1SQ
* Kezza
* Krys Kamieniecki
* Lee Donaghy
* Lee (KD1SQ)
* Lee KD1SQ
* .lozenge. (Hank Hill)
* Martin Herren (HB9FXX)
* NeoVilsonWong
* Nitin (VU2JEK)
* ON4MU
* [Passion-Radio.com](https://passion-radio.com/)
* Paul Maine

View File

@ -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"
}
]
}

View File

@ -2,8 +2,8 @@
"name": "France",
"country_name": "France",
"country_code": "FR",
"author_name": "Fred F4EED, Armand31",
"author_url": "http://f4eed.wordpress.com, https://github.com/Armand31",
"author_name": "Fred F4EED",
"author_url": "http://f4eed.wordpress.com",
"bands": [
{
"name": "137KHz - Radioamateur",
@ -355,7 +355,7 @@
"end": 54000000
},
{
"name": "Radiodiffusion - Bande FM",
"name": "Bande FM - Radiodif.",
"type": "broadcast",
"start": 80000000,
"end": 108000000
@ -396,12 +396,6 @@
"start": 162362500,
"end": 162587500
},
{
"name": "Radiodiffusion - Bande DAB",
"type": "broadcast",
"start": 174000000,
"end": 223000000
},
{
"name": "Military Aviation",
"type": "military",
@ -414,12 +408,6 @@
"start": 240000000,
"end": 270000000
},
{
"name": "Police (TETRAPOL)",
"type": "military",
"start": 380000000,
"end": 400000000
},
{
"name": "70cm - Radioamateur",
"type": "amateur",
@ -432,24 +420,12 @@
"start": 446000000,
"end": 446200000
},
{
"name": "TNT (DVB-T)",
"type": "broadcast",
"start": 470000000,
"end": 694000000
},
{
"name": "23cm - Radioamateur",
"type": "amateur",
"start": 1240000000,
"end": 1300000000
},
{
"name": "Radiodiffusion - Bande DAB",
"type": "broadcast",
"start": 1452000000,
"end": 1492000000
},
{
"name": "13cm - Radioamateur",
"type": "amateur",

View File

@ -1,231 +0,0 @@
{
"name": "Ireland",
"country_name": "Republic Of Ireland",
"country_code": "IE",
"author_name": "Oskar Dudek",
"author_url": "",
"bands": [
{
"name": "2200m Ham Band",
"type": "amateur",
"start": 135700,
"end": 137800
},
{
"name": "Long wave",
"type": "broadcast",
"start": 148500,
"end": 282500
},
{
"name": "AM broadcast",
"type": "broadcast",
"start": 531000,
"end": 1602000
},
{
"name": "160m ham band",
"type": "amateur",
"start": 1810000,
"end": 2000000
},
{
"name": "120m SW broadcast",
"type": "broadcast",
"start": 2300000,
"end": 2495000
},
{
"name": "90m SW Broadcast",
"type": "broadcast",
"start": 3200000,
"end": 3400000
},
{
"name": "80m ham band",
"type": "amateur",
"start": 3500000,
"end": 3800000
},
{
"name": "75m SW Broadcast",
"type": "broadcast",
"start": 3900000,
"end": 4000000
},
{
"name": "60m SW Broadcast",
"type": "broadcast",
"start": 4750000,
"end": 5060000
},
{
"name": "60m ham band",
"type": "amateur",
"start": 5351500,
"end": 5366500
},
{
"name": "49m SW Broadcast",
"type": "broadcast",
"start": 5900000,
"end": 6200000
},
{
"name": "40m ham band",
"type": "amateur",
"start": 7000000,
"end": 7200000
},
{
"name": "40m SW Broadcast",
"type": "broadcast",
"start": 7200000,
"end": 7450000
},
{
"name": "31m SW Broadcast",
"type": "broadcast",
"start": 9400000,
"end": 9900000
},
{
"name": "30m ham band",
"type": "amateur",
"start": 10100000,
"end": 10150000
},
{
"name": "25m SW Broadcast",
"type": "broadcast",
"start": 11600000,
"end": 12100000
},
{
"name": "22m SW Broadcast",
"type": "broadcast",
"start": 13570000,
"end": 13870000
},
{
"name": "20m ham band",
"type": "amateur",
"start": 14000000,
"end": 14350000
},
{
"name": "19m SW Broadcast",
"type": "broadcast",
"start": 15100000,
"end": 15800000
},
{
"name": "17m ham band",
"type": "amateur",
"start": 18068000,
"end": 18168000
},
{
"name": "16m SW Broadcast",
"type": "broadcast",
"start": 17480000,
"end": 17900000
},
{
"name": "15m SW Broadcast",
"type": "broadcast",
"start": 18900000,
"end": 19020000
},
{
"name": "15m ham band",
"type": "amateur",
"start": 21000000,
"end": 21450000
},
{
"name": "13m SW Broadcast",
"type": "broadcast",
"start": 21450000,
"end": 21850000
},
{
"name": "12m ham band",
"type": "amateur",
"start": 24890000,
"end": 24990000
},
{
"name": "11m SW Broadcast",
"type": "broadcast",
"start": 25670000,
"end": 26100000
},
{
"name": "CB",
"type": "amateur",
"start": 26965000,
"end": 27405000
},
{
"name": "10m ham band",
"type": "amateur",
"start": 28000000,
"end": 29700000
},
{
"name": "FM Broadcast",
"type": "broadcast",
"start": 87500000,
"end": 108000000
},
{
"name": "Airband VOR/ILS",
"type": "aviation",
"start": 108000000,
"end": 117900000
},
{
"name": "Airband Voice",
"type": "aviation",
"start": 118000000,
"end": 137000000
},
{
"name": "Polar orbiting satellites",
"type": "satellite",
"start": 137000000,
"end": 138000000
},
{
"name": "6m ham band",
"type": "amateur",
"start": 50000000,
"end": 52000000
},
{
"name": "4m ham band",
"type": "amateur",
"start": 70000000,
"end": 70500000
},
{
"name": "2m ham band",
"type": "amateur",
"start": 144000000,
"end": 146000000
},
{
"name": "70cm ham band",
"type": "amateur",
"start": 430000000,
"end": 440000000
},
{
"name": "ADS-B",
"type": "aviation",
"start": 1089000000,
"end": 1091000000
}
]
}

View File

@ -1,549 +0,0 @@
{
"name": "Republic of Korea",
"country_name": "Republic of Korea",
"country_code": "KR",
"author_name": "SeoyeonBae",
"author_url": "https://github.com/bsy0317",
"bands": [
{
"name": "Radio Navigation",
"type": "aviation",
"start": 8300,
"end": 14000
},
{
"name": "Coastal Telegraph",
"type": "marine",
"start": 14000,
"end": 19950
},
{
"name": "Standard Frequency Time Signal",
"type": "utility",
"start": 19950,
"end": 20250
},
{
"name": "Coastal Telegraph",
"type": "marine",
"start": 20250,
"end": 70000
},
{
"name": "Radio Navigation",
"type": "navigation",
"start": 70000,
"end": 160000
},
{
"name": "Aviation Radio Navigation",
"type": "aviation",
"start": 160000,
"end": 285000
},
{
"name": "Aviation Maritime Radiobeacon",
"type": "aviation",
"start": 285000,
"end": 325000
},
{
"name": "Aviation Radio Navigation",
"type": "aviation",
"start": 325000,
"end": 472000
},
{
"name": "Amateur",
"type": "amateur",
"start": 472000,
"end": 479000
},
{
"name": "International Distress Safety Call",
"type": "marine",
"start": 479000,
"end": 505000
},
{
"name": "Maritime Telegraph",
"type": "marine",
"start": 505000,
"end": 526500
},
{
"name": "Standard Broadcast",
"type": "broadcast",
"start": 526500,
"end": 1606500
},
{
"name": "Radiobuoy",
"type": "navigation",
"start": 1606500,
"end": 1800000
},
{
"name": "Amateur Station",
"type": "amateur",
"start": 1800000,
"end": 1825000
},
{
"name": "Radiobuoy Control LORAN",
"type": "radiolocation",
"start": 1825000,
"end": 2000000
},
{
"name": "Radiobuoy",
"type": "fixed",
"start": 2000000,
"end": 2065000
},
{
"name": "Distress Call",
"type": "marine",
"start": 2065000,
"end": 2107000
},
{
"name": "International Distress Search and Rescue",
"type": "mobile",
"start": 2173500,
"end": 2190500
},
{
"name": "Road Management",
"type": "fixed",
"start": 2194000,
"end": 2495000
},
{
"name": "Standard Frequency Time Signal",
"type": "utility",
"start": 2495000,
"end": 2505000
},
{
"name": "Ship Station Telephone",
"type": "fixed",
"start": 2505000,
"end": 2850000
},
{
"name": "Aviation Mobile R",
"type": "aviation",
"start": 2850000,
"end": 3025000
},
{
"name": "Aviation Mobile OR",
"type": "aviation",
"start": 3025000,
"end": 3155000
},
{
"name": "Aviation Mobile R",
"type": "aviation",
"start": 3400000,
"end": 3500000
},
{
"name": "Amateur Station",
"type": "amateur",
"start": 3500000,
"end": 3550000
},
{
"name": "Experimental Station",
"type": "fixed",
"start": 3550000,
"end": 3790000
},
{
"name": "Amateur Station",
"type": "amateur",
"start": 3790000,
"end": 3800000
},
{
"name": "Shortwave Broadcast",
"type": "broadcast",
"start": 3900000,
"end": 3950000
},
{
"name": "Standard Frequency Time Signal",
"type": "utility",
"start": 3995000,
"end": 4005000
},
{
"name": "Ship Station Telephone",
"type": "marine",
"start": 4005000,
"end": 4063000
},
{
"name": "Oceanographic Data",
"type": "marine",
"start": 4063000,
"end": 4065000
},
{
"name": "Ship Station Duplex Telephone",
"type": "marine",
"start": 4065000,
"end": 4146000
},
{
"name": "Ship Station Simplex Telephone",
"type": "marine",
"start": 4146000,
"end": 4152000
},
{
"name": "Ship Station Wideband Telegraph Fax",
"type": "marine",
"start": 4152000,
"end": 4172000
},
{
"name": "Ship Station Narrowband",
"type": "marine",
"start": 4172000,
"end": 4181750
},
{
"name": "Ship Station A1A Morse Code Communication",
"type": "marine",
"start": 4186750,
"end": 4202250
},
{
"name": "Radiolocation",
"type": "radiolocation",
"start": 4438000,
"end": 4488000
},
{
"name": "Calling Response",
"type": "fixed",
"start": 4488000,
"end": 4650000
},
{
"name": "Aviation Mobile R",
"type": "aviation",
"start": 4650000,
"end": 4850000
},
{
"name": "Standard Frequency Time Signal",
"type": "utility",
"start": 4995000,
"end": 5005000
},
{
"name": "Search Rescue",
"type": "aviation",
"start": 5480000,
"end": 5730000
},
{
"name": "Broadcast",
"type": "broadcast",
"start": 5900000,
"end": 5950000
},
{
"name": "Shortwave Broadcast",
"type": "broadcast",
"start": 5950000,
"end": 6200000
},
{
"name": "Amateur Station",
"type": "amateur",
"start": 7000000,
"end": 7100000
},
{
"name": "Amateur Station",
"type": "amateur",
"start": 7100000,
"end": 7200000
},
{
"name": "Shortwave Broadcast",
"type": "broadcast",
"start": 7200000,
"end": 7450000
},
{
"name": "Standard Frequency Time Signal",
"type": "utility",
"start": 7995000,
"end": 8005000
},
{
"name": "Broadcast",
"type": "broadcast",
"start": 9400000,
"end": 9500000
},
{
"name": "Shortwave Broadcast",
"type": "broadcast",
"start": 9500000,
"end": 9900000
},
{
"name": "Standard Frequency Time Signal",
"type": "utility",
"start": 9995000,
"end": 10005000
},
{
"name": "Amateur Station",
"type": "amateur",
"start": 10100000,
"end": 10150000
},
{
"name": "Aviation Mobile",
"type": "aviation",
"start": 10150000,
"end": 11600000
},
{
"name": "Broadcast",
"type": "broadcast",
"start": 11600000,
"end": 11650000
},
{
"name": "Shortwave Broadcast",
"type": "broadcast",
"start": 11650000,
"end": 12050000
},
{
"name": "Broadcast",
"type": "broadcast",
"start": 12050000,
"end": 12100000
},
{
"name": "Aviation Mobile",
"type": "aviation",
"start": 13260000,
"end": 13360000
},
{
"name": "Radio Astronomy",
"type": "astronomy",
"start": 13360000,
"end": 13410000
},
{
"name": "Broadcast",
"type": "broadcast",
"start": 13570000,
"end": 13600000
},
{
"name": "Shortwave Broadcast",
"type": "broadcast",
"start": 13600000,
"end": 13800000
},
{
"name": "Broadcast",
"type": "broadcast",
"start": 13800000,
"end": 13870000
},
{
"name": "Amateur Station",
"type": "amateur",
"start": 14000000,
"end": 14350000
},
{
"name": "Aviation Mobile",
"type": "aviation",
"start": 15010000,
"end": 15100000
},
{
"name": "Shortwave Broadcast",
"type": "broadcast",
"start": 15100000,
"end": 15600000
},
{
"name": "Shortwave Broadcast",
"type": "broadcast",
"start": 15600000,
"end": 15800000
},
{
"name": "Shortwave Broadcast",
"type": "broadcast",
"start": 15800000,
"end": 15995000
},
{
"name": "Standard Frequency Time Signal",
"type": "utility",
"start": 15995000,
"end": 16005000
},
{
"name": "Broadcast",
"type": "broadcast",
"start": 18900000,
"end": 19020000
},
{
"name": "Amateur Station",
"type": "amateur",
"start": 21000000,
"end": 21450000
},
{
"name": "Shortwave Broadcast",
"type": "broadcast",
"start": 21450000,
"end": 21850000
},
{
"name": "Amateur Station",
"type": "amateur",
"start": 24890000,
"end": 24990000
},
{
"name": "Shortwave Broadcast",
"type": "broadcast",
"start": 25670000,
"end": 26100000
},
{
"name": "Amateur Station",
"type": "amateur",
"start": 28000000,
"end": 29700000
},
{
"name": "Amateur Station",
"type": "amateur",
"start": 50000000,
"end": 54000000
},
{
"name": "TV Broadcast",
"type": "broadcast",
"start": 54000000,
"end": 72000000
},
{
"name": "Flood Warning",
"type": "broadcast",
"start": 72000000,
"end": 74800000
},
{
"name": "TV Broadcast",
"type": "broadcast",
"start": 76000000,
"end": 88000000
},
{
"name": "FM Broadcast",
"type": "broadcast",
"start": 88000000,
"end": 100000000
},
{
"name": "FM Broadcast",
"type": "broadcast",
"start": 100000000,
"end": 108000000
},
{
"name": "ILS Localizer VOR",
"type": "fixed",
"start": 108000000,
"end": 117975000
},
{
"name": "Amateur Station",
"type": "amateur",
"start": 144000000,
"end": 146000000
},
{
"name": "General Communication",
"type": "fixed",
"start": 146000000,
"end": 148000000
},
{
"name": "Low Power Device",
"type": "fixed",
"start": 162037500,
"end": 174000000
},
{
"name": "TV Broadcast",
"type": "broadcast",
"start": 174000000,
"end": 216000000
},
{
"name": "Low Power Device",
"type": "fixed",
"start": 216000000,
"end": 230000000
},
{
"name": "Low Power Device",
"type": "fixed",
"start": 273000000,
"end": 322000000
},
{
"name": "Personal Radio",
"type": "fixed",
"start": 420000000,
"end": 470000000
},
{
"name": "Public Network",
"type": "broadcast",
"start": 698000000,
"end": 806000000
},
{
"name": "Low Power Device",
"type": "fixed",
"start": 942000000,
"end": 960000000
},
{
"name": "Satellite Mobile Communication",
"type": "fixed",
"start": 15250000000,
"end": 16605000000
},
{
"name": "Mobile Communication",
"type": "mobile",
"start": 25000000000,
"end": 37000000000
}
]
}

View File

@ -1,285 +0,0 @@
{
"name": "Turkey",
"country_name": "Turkey",
"country_code": "TR",
"author_name": "Yunus TA2PEA",
"author_url": "https://github.com/ycanerol",
"bands": [
{
"name": "LW",
"type": "amateur",
"start": 135700,
"end": 137800
},
{
"name": "630m",
"type": "amateur",
"start": 472000,
"end": 479000
},
{
"name": "160m",
"type": "amateur",
"start": 1810000,
"end": 1850000
},
{
"name": "80m",
"type": "amateur",
"start": 3500000,
"end": 3800000
},
{
"name": "60m",
"type": "amateur",
"start": 5351500,
"end": 5366500
},
{
"name": "40m",
"type": "amateur",
"start": 7000000,
"end": 7200000
},
{
"name": "30m",
"type": "amateur",
"start": 10100000,
"end": 10150000
},
{
"name": "20m",
"type": "amateur",
"start": 14000000,
"end": 14350000
},
{
"name": "17m",
"type": "amateur",
"start": 18068000,
"end": 18168000
},
{
"name": "15m",
"type": "amateur",
"start": 21000000,
"end": 21450000
},
{
"name": "12m",
"type": "amateur",
"start": 24890000,
"end": 24990000
},
{
"name": "CB",
"type": "other",
"start": 26565000,
"end": 27405000
},
{
"name": "Pagers",
"type": "amateur",
"start": 27750000,
"end": 28000000
},
{
"name": "10m",
"type": "amateur",
"start": 28000000,
"end": 29700000
},
{
"name": "6m",
"type": "amateur",
"start": 50030000,
"end": 51000000
},
{
"name": "FM",
"type": "broadcast",
"start": 87500000,
"end": 108000000
},
{
"name": "Airband VOR/ILS",
"type": "aviation",
"start": 108000000,
"end": 117975000
},
{
"name": "Airband Voice",
"type": "aviation",
"start": 117975000,
"end": 137000000
},
{
"name": "2m",
"type": "amateur",
"start": 144000000,
"end": 146000000
},
{
"name": "Sayac Okuma",
"type": "other",
"start": 169400000,
"end": 169475000
},
{
"name": "Pagers",
"type": "other",
"start": 167000000,
"end": 167100000
},
{
"name": "Public announcement systems",
"type": "other",
"start": 173882500,
"end": 174000000
},
{
"name": "DVB-T",
"type": "broadcast",
"start": 174000000,
"end": 216000000
},
{
"name": "T-DAB",
"type": "broadcast",
"start": 216000000,
"end": 233000000
},
{
"name": "ILS-Glide Path",
"type": "aviation",
"start": 328600000,
"end": 335400000
},
{
"name": "Public Safety/Emergency",
"type": "other",
"start": 380000000,
"end": 385000000
},
{
"name": "Public Safety/Emergency",
"type": "other",
"start": 390000000,
"end": 395000000
},
{
"name": "70cm",
"type": "amateur",
"start": 430200000,
"end": 430700000
},
{
"name": "70cm-RepeaterRX",
"type": "amateur",
"start": 431550000,
"end": 431825000
},
{
"name": "70cm",
"type": "amateur",
"start": 432000000,
"end": 432975000
},
{
"name": "70cm",
"type": "amateur",
"start": 433400000,
"end": 434000000
},
{
"name": "70cm",
"type": "amateur",
"start": 435000000,
"end": 438000000
},
{
"name": "70cm-RepeaterTX",
"type": "amateur",
"start": 439150000,
"end": 439425000
},
{
"name": "Public announcement systems",
"type": "other",
"start": 445250000,
"end": 445462500
},
{
"name": "PMR446",
"type": "other",
"start": 446006250,
"end": 446196875
},
{
"name": "RFID",
"type": "other",
"start": 865000000,
"end": 868000000
},
{
"name": "RFID",
"type": "other",
"start": 916100000,
"end": 918900000
},
{
"name": "23cm",
"type": "amateur",
"start": 1240000000,
"end": 1300000000
},
{
"name": "DECT",
"type": "other",
"start": 1880000000,
"end": 1900000000
},
{
"name": "5GHz",
"type": "amateur",
"start": 5650000000,
"end": 5670000000
},
{
"name": "5GHz",
"type": "amateur",
"start": 5820000000,
"end": 5850000000
},
{
"name": "3cm",
"type": "amateur",
"start": 104500000000,
"end": 104520000000
},
{
"name": "24GHz",
"type": "amateur",
"start": 24000000000,
"end": 24050000000
},
{
"name": "47GHz",
"type": "amateur",
"start": 47000000000,
"end": 47200000000
},
{
"name": "75GHz",
"type": "amateur",
"start": 75500000000,
"end": 7600000000
},
{
"name": "134GHz",
"type": "amateur",
"start": 134000000000,
"end": 142000000000
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -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(&parameters, 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();
}
}

View File

@ -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);
}
@ -217,19 +231,14 @@ private:
}
void startServer() {
try {
if (modeId == SINK_MODE_TCP) {
listener = net::listen(hostname, port);
if (listener) {
listener->acceptAsync(clientHandler, this);
}
}
else {
conn = net::openUDP("0.0.0.0", port, hostname, port, false);
if (modeId == SINK_MODE_TCP) {
listener = net::listen(hostname, port);
if (listener) {
listener->acceptAsync(clientHandler, this);
}
}
catch (const std::exception& e) {
flog::error("Failed to open socket: {}", e.what());
else {
conn = net::openUDP("0.0.0.0", port, hostname, port, false);
}
}
@ -276,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() {}
@ -333,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_() {

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