1 Commits

Author SHA1 Message Date
c488d72ce2 pushing work for the future new source system 2023-03-04 14:18:52 +01:00
196 changed files with 1929 additions and 20618 deletions

View File

@ -7,8 +7,6 @@ assignees: ''
--- ---
# WARNING: Filling out the template below is NOT optional. Issues not filling out this template will be closed without review.
FIRST: Before reporting any bug, make sure that the bug you are reporting has not been reported before. Also, try to use the [nightly version](https://www.sdrpp.org/nightly) if possible in case I've already fixed the bug. FIRST: Before reporting any bug, make sure that the bug you are reporting has not been reported before. Also, try to use the [nightly version](https://www.sdrpp.org/nightly) if possible in case I've already fixed the bug.
**Hardware** **Hardware**

View File

@ -1,4 +0,0 @@
# Important
Only bandplan, colormaps and themes are accepted. Code pull requests are **NOT welcome**.
Open an issue requesting a feature or discussing a possible bugfix instead.

View File

@ -18,7 +18,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Create Build Environment - name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build run: cmake -E make_directory ${{runner.workspace}}/build
@ -34,20 +34,13 @@ jobs:
- name: Patch Pothos with earlier libusb version - name: Patch Pothos with earlier libusb version
working-directory: ${{runner.workspace}} 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/" 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/"
- 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 - name: Download SDRPlay API
run: Invoke-WebRequest -Uri "https://www.sdrpp.org/SDRplay.zip" -OutFile ${{runner.workspace}}/SDRplay.zip run: Invoke-WebRequest -Uri "https://drive.google.com/uc?id=12UHPMwkfa67A11QZDmpCT4iwHnyJHWuu" -OutFile ${{runner.workspace}}/SDRPlay.zip
- name: Install SDRPlay API - name: Install SDRPlay API
run: 7z x ${{runner.workspace}}/SDRplay.zip -o"C:/Program Files/" run: 7z x ${{runner.workspace}}/SDRPlay.zip -o"C:/Program Files/"
- name: Download codec2 - name: Download codec2
run: git clone https://github.com/AlexandreRouma/codec2 run: git clone https://github.com/AlexandreRouma/codec2
@ -65,23 +58,14 @@ 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" 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 - 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
- name: Install rtaudio - 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 . 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 .
- name: Prepare CMake - name: Prepare CMake
working-directory: ${{runner.workspace}}/build 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_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
- name: Build - name: Build
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
@ -92,53 +76,47 @@ jobs:
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")' run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
- name: Save Archive - name: Save Archive
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: sdrpp_windows_x64 name: sdrpp_windows_x64
path: ${{runner.workspace}}/sdrpp_windows_x64.zip path: ${{runner.workspace}}/sdrpp_windows_x64.zip
build_macos_intel: build_macos:
runs-on: macos-12 runs-on: macos-11
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Create Build Environment - name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build run: cmake -E make_directory ${{runner.workspace}}/build
- name: Update brew repositories
run: brew update
- name: Fix stuff
run: rm -f /usr/local/bin/2to3* /usr/local/bin/idle3* /usr/local/bin/pydoc3* /usr/local/bin/python3* /usr/local/bin/python3-config* && brew reinstall gettext
- name: Install dependencies - 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 rtl-sdr libbladerf codec2 zstd && pip3 install mako
- name: Install volk - 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 ../../ run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install SDRplay API - 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/SDRplay_RSP_API-MacOSX-3.07.3.pkg && sudo installer -pkg SDRplay_RSP_API-MacOSX-3.07.3.pkg -target /
- name: Install libiio - 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 ../../ run: git clone https://github.com/analogdevicesinc/libiio && cd libiio && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install libad9361 - name: Install libad9361
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install LimeSuite - name: Install LimeSuite
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 ../../ run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -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 ..
- 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 - name: Prepare CMake
working-directory: ${{runner.workspace}}/build 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 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_SOAPY_SOURCE=OFF -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_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build - name: Build
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
@ -149,74 +127,17 @@ jobs:
run: cd $GITHUB_WORKSPACE && sh make_macos_bundle.sh ${{runner.workspace}}/build ./SDR++.app && zip -r ${{runner.workspace}}/sdrpp_macos_intel.zip SDR++.app run: cd $GITHUB_WORKSPACE && sh make_macos_bundle.sh ${{runner.workspace}}/build ./SDR++.app && zip -r ${{runner.workspace}}/sdrpp_macos_intel.zip SDR++.app
- name: Save Archive - name: Save Archive
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: sdrpp_macos_intel name: sdrpp_macos_intel
path: ${{runner.workspace}}/sdrpp_macos_intel.zip path: ${{runner.workspace}}/sdrpp_macos_intel.zip
build_macos_arm:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: Create Build Environment
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
- 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 /
- 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 ../../
- name: Install libad9361
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install LimeSuite
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 && 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
- name: Build
working-directory: ${{runner.workspace}}/build
run: make VERBOSE=1 -j3
- name: Create Archive
working-directory: ${{runner.workspace}}
run: cd $GITHUB_WORKSPACE && sh make_macos_bundle.sh ${{runner.workspace}}/build ./SDR++.app && zip -r ${{runner.workspace}}/sdrpp_macos_arm.zip SDR++.app
- name: Save Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_macos_arm
path: ${{runner.workspace}}/sdrpp_macos_arm.zip
build_debian_buster: build_debian_buster:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Create Docker Image - name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build
@ -228,7 +149,7 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive - name: Save Deb Archive
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: sdrpp_debian_buster_amd64 name: sdrpp_debian_buster_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
@ -237,7 +158,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Create Docker Image - name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
@ -250,38 +171,16 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive - name: Save Deb Archive
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: sdrpp_debian_bullseye_amd64 name: sdrpp_debian_bullseye_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bookworm:
runs-on: ubuntu-latest
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_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_sid: build_debian_sid:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Create Docker Image - name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
@ -294,16 +193,38 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive - name: Save Deb Archive
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: sdrpp_debian_sid_amd64 name: sdrpp_debian_sid_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
# build_ubuntu_bionic:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
# - name: Create Docker Image
# run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_bionic && 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@v3
# with:
# name: sdrpp_ubuntu_bionic_amd64
# path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_focal: build_ubuntu_focal:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Create Docker Image - name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
@ -316,7 +237,7 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive - name: Save Deb Archive
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: sdrpp_ubuntu_focal_amd64 name: sdrpp_ubuntu_focal_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
@ -325,7 +246,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Create Docker Image - name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build
@ -338,67 +259,23 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive - name: Save Deb Archive
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: sdrpp_ubuntu_jammy_amd64 name: sdrpp_ubuntu_jammy_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_mantic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
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
- 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_mantic_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_noble:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_noble_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_raspios_bullseye_armhf: build_raspios_bullseye_armhf:
runs-on: ARM runs-on: ARM
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Create Build Environment - name: Create Build Environment
run: rm -rf ${{runner.workspace}}/build && cmake -E make_directory ${{runner.workspace}}/build run: rm -rf ${{runner.workspace}}/build && cmake -E make_directory ${{runner.workspace}}/build
- name: Prepare CMake - name: Prepare CMake
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
run: cmake $GITHUB_WORKSPACE -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_USRP_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON run: cmake $GITHUB_WORKSPACE -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_USRP_SOURCE=ON
- name: Build - name: Build
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
@ -409,7 +286,7 @@ jobs:
run: sh $GITHUB_WORKSPACE/make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev' && mv sdrpp_debian_amd64.deb sdrpp_debian_armhf.deb run: sh $GITHUB_WORKSPACE/make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev' && mv sdrpp_debian_amd64.deb sdrpp_debian_armhf.deb
- name: Save Deb Archive - name: Save Deb Archive
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: sdrpp_raspios_bullseye_armhf name: sdrpp_raspios_bullseye_armhf
path: ${{runner.workspace}}/sdrpp_debian_armhf.deb path: ${{runner.workspace}}/sdrpp_debian_armhf.deb
@ -418,7 +295,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Fetch container - name: Fetch container
working-directory: ${{runner.workspace}} working-directory: ${{runner.workspace}}
@ -436,37 +313,33 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/android/app/build/outputs/apk/debug/app-debug.apk ./ && mv app-debug.apk sdrpp.apk run: docker cp build:/root/SDRPlusPlus/android/app/build/outputs/apk/debug/app-debug.apk ./ && mv app-debug.apk sdrpp.apk
- name: Save APK - name: Save APK
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: sdrpp_android name: sdrpp_android
path: ${{runner.workspace}}/sdrpp.apk path: ${{runner.workspace}}/sdrpp.apk
create_full_archive: create_full_archive:
needs: ['build_windows', 'build_macos_intel', 'build_macos_arm', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_ubuntu_noble', 'build_raspios_bullseye_armhf', 'build_android'] needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_raspios_bullseye_armhf', 'build_android']
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download All Builds - name: Download All Builds
uses: actions/download-artifact@v4 uses: actions/download-artifact@v3
- name: Create Archive - name: Create Archive
run: > run: >
mkdir sdrpp_all && mkdir sdrpp_all &&
mv sdrpp_windows_x64/sdrpp_windows_x64.zip sdrpp_all/ && mv sdrpp_windows_x64/sdrpp_windows_x64.zip sdrpp_all/ &&
mv sdrpp_macos_intel/sdrpp_macos_intel.zip sdrpp_all/ && 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_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_buster_amd64.deb &&
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.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_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb && mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb && mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb && mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb &&
mv sdrpp_ubuntu_mantic_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_mantic_amd64.deb &&
mv sdrpp_ubuntu_noble_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_amd64.deb &&
mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb && mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb &&
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
with: with:
name: sdrpp_all name: sdrpp_all
path: sdrpp_all/ path: sdrpp_all/
@ -478,7 +351,7 @@ jobs:
steps: steps:
- name: Download All Builds - name: Download All Builds
uses: actions/download-artifact@v4 uses: actions/download-artifact@v3
- name: Update Nightly - name: Update Nightly
run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber
@ -487,7 +360,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Install codespell - name: Install codespell
run: sudo apt update -y && sudo apt install -y codespell run: sudo apt update -y && sudo apt install -y codespell
@ -499,7 +372,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run check_clang_format - name: Run check_clang_format
run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true

View File

@ -12,53 +12,42 @@ 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_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_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_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_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF)
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON) 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_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_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON)
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_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_SDRPP_SERVER_SOURCE "Build SDR++ Server 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_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_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) option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON)
option(OPT_BUILD_SDRPP_SERVER_SOURCE "Build SDR++ Server Source Module (no dependencies required)" ON)
option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Dependencies: libsdrplay)" OFF) option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Dependencies: libsdrplay)" OFF)
option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Dependencies: soapysdr)" OFF) option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Dependencies: soapysdr)" ON)
option(OPT_BUILD_SPECTRAN_SOURCE "Build Spectran Source Module (Dependencies: Aaronia RTSA Suite)" OFF) option(OPT_BUILD_SPECTRAN_SOURCE "Build Spectran Source Module (Dependencies: Aaronia RTSA Suite)" OFF)
option(OPT_BUILD_SPECTRAN_HTTP_SOURCE "Build Spectran HTTP Source Module (no dependencies required)" ON) option(OPT_BUILD_SPECTRAN_HTTP_SOURCE "Build Spectran HTTP Source Module (no dependencies required)" ON)
option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON) option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON)
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON)
option(OPT_BUILD_USRP_SOURCE "Build USRP Source Module (libuhd)" OFF) option(OPT_BUILD_USRP_SOURCE "Build USRP Source Module (libuhd)" OFF)
# Sinks # Sinks
option(OPT_BUILD_ANDROID_AUDIO_SINK "Build Android Audio Sink Module (Dependencies: AAudio, only for android)" OFF) option(OPT_BUILD_ANDROID_AUDIO_SINK "Build Android Audio Sink Module (Dependencies: AAudio, only for android)" OFF)
option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Dependencies: rtaudio)" ON) option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Dependencies: rtaudio)" ON)
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF)
option(OPT_BUILD_NETWORK_SINK "Build Audio Sink Module (no dependencies required)" ON) option(OPT_BUILD_NETWORK_SINK "Build Audio Sink Module (no dependencies required)" ON)
option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Dependencies: portaudio)" OFF) option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Dependencies: portaudio)" OFF)
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF)
# Decoders # Decoders
option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF) 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_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_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_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_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_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
option(OPT_BUILD_RYFI_DECODER "RyFi data link decoder" OFF)
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF) option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
# Misc # Misc
option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON) option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON)
option(OPT_BUILD_FREQUENCY_MANAGER "Build the Frequency Manager module" ON) option(OPT_BUILD_FREQUENCY_MANAGER "Build the Frequency Manager module" ON)
option(OPT_BUILD_IQ_EXPORTER "Build the IQ Exporter module" ON)
option(OPT_BUILD_RECORDER "Audio and baseband recorder" ON) option(OPT_BUILD_RECORDER "Audio and baseband recorder" ON)
option(OPT_BUILD_RIGCTL_CLIENT "Rigctl client to make SDR++ act as a panadapter" ON) option(OPT_BUILD_RIGCTL_CLIENT "Rigctl client to make SDR++ act as a panadapter" ON)
option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON) option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON)
@ -68,7 +57,6 @@ option(OPT_BUILD_SCHEDULER "Build the scheduler" OFF)
# Other options # Other options
option(USE_INTERNAL_LIBCORRECT "Use an internal version of libcorrect" ON) 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(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 # Module cmake path
set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake") set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake")
@ -133,10 +121,6 @@ if (OPT_BUILD_AUDIO_SOURCE)
add_subdirectory("source_modules/audio_source") add_subdirectory("source_modules/audio_source")
endif (OPT_BUILD_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) if (OPT_BUILD_BLADERF_SOURCE)
add_subdirectory("source_modules/bladerf_source") add_subdirectory("source_modules/bladerf_source")
endif (OPT_BUILD_BLADERF_SOURCE) endif (OPT_BUILD_BLADERF_SOURCE)
@ -145,45 +129,21 @@ if (OPT_BUILD_FILE_SOURCE)
add_subdirectory("source_modules/file_source") add_subdirectory("source_modules/file_source")
endif (OPT_BUILD_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) if (OPT_BUILD_HACKRF_SOURCE)
add_subdirectory("source_modules/hackrf_source") add_subdirectory("source_modules/hackrf_source")
endif (OPT_BUILD_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) if (OPT_BUILD_HERMES_SOURCE)
add_subdirectory("source_modules/hermes_source") add_subdirectory("source_modules/hermes_source")
endif (OPT_BUILD_HERMES_SOURCE) endif (OPT_BUILD_HERMES_SOURCE)
if (OPT_BUILD_KCSDR_SOURCE)
add_subdirectory("source_modules/kcsdr_source")
endif (OPT_BUILD_KCSDR_SOURCE)
if (OPT_BUILD_LIMESDR_SOURCE) if (OPT_BUILD_LIMESDR_SOURCE)
add_subdirectory("source_modules/limesdr_source") add_subdirectory("source_modules/limesdr_source")
endif (OPT_BUILD_LIMESDR_SOURCE) endif (OPT_BUILD_LIMESDR_SOURCE)
if (OPT_BUILD_NETWORK_SOURCE) if (OPT_BUILD_SDRPP_SERVER_SOURCE)
add_subdirectory("source_modules/network_source") add_subdirectory("source_modules/sdrpp_server_source")
endif (OPT_BUILD_NETWORK_SOURCE) endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
if (OPT_BUILD_PERSEUS_SOURCE)
add_subdirectory("source_modules/perseus_source")
endif (OPT_BUILD_PERSEUS_SOURCE)
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) if (OPT_BUILD_RFSPACE_SOURCE)
add_subdirectory("source_modules/rfspace_source") add_subdirectory("source_modules/rfspace_source")
@ -197,10 +157,6 @@ if (OPT_BUILD_RTL_TCP_SOURCE)
add_subdirectory("source_modules/rtl_tcp_source") add_subdirectory("source_modules/rtl_tcp_source")
endif (OPT_BUILD_RTL_TCP_SOURCE) endif (OPT_BUILD_RTL_TCP_SOURCE)
if (OPT_BUILD_SDRPP_SERVER_SOURCE)
add_subdirectory("source_modules/sdrpp_server_source")
endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
if (OPT_BUILD_SDRPLAY_SOURCE) if (OPT_BUILD_SDRPLAY_SOURCE)
add_subdirectory("source_modules/sdrplay_source") add_subdirectory("source_modules/sdrplay_source")
endif (OPT_BUILD_SDRPLAY_SOURCE) endif (OPT_BUILD_SDRPLAY_SOURCE)
@ -221,6 +177,10 @@ if (OPT_BUILD_SPYSERVER_SOURCE)
add_subdirectory("source_modules/spyserver_source") add_subdirectory("source_modules/spyserver_source")
endif (OPT_BUILD_SPYSERVER_SOURCE) endif (OPT_BUILD_SPYSERVER_SOURCE)
if (OPT_BUILD_PLUTOSDR_SOURCE)
add_subdirectory("source_modules/plutosdr_source")
endif (OPT_BUILD_PLUTOSDR_SOURCE)
if (OPT_BUILD_USRP_SOURCE) if (OPT_BUILD_USRP_SOURCE)
add_subdirectory("source_modules/usrp_source") add_subdirectory("source_modules/usrp_source")
endif (OPT_BUILD_USRP_SOURCE) endif (OPT_BUILD_USRP_SOURCE)
@ -253,10 +213,6 @@ if (OPT_BUILD_ATV_DECODER)
add_subdirectory("decoder_modules/atv_decoder") add_subdirectory("decoder_modules/atv_decoder")
endif (OPT_BUILD_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) if (OPT_BUILD_FALCON9_DECODER)
add_subdirectory("decoder_modules/falcon9_decoder") add_subdirectory("decoder_modules/falcon9_decoder")
endif (OPT_BUILD_FALCON9_DECODER) endif (OPT_BUILD_FALCON9_DECODER)
@ -273,18 +229,10 @@ if (OPT_BUILD_METEOR_DEMODULATOR)
add_subdirectory("decoder_modules/meteor_demodulator") add_subdirectory("decoder_modules/meteor_demodulator")
endif (OPT_BUILD_METEOR_DEMODULATOR) endif (OPT_BUILD_METEOR_DEMODULATOR)
if (OPT_BUILD_PAGER_DECODER)
add_subdirectory("decoder_modules/pager_decoder")
endif (OPT_BUILD_PAGER_DECODER)
if (OPT_BUILD_RADIO) if (OPT_BUILD_RADIO)
add_subdirectory("decoder_modules/radio") add_subdirectory("decoder_modules/radio")
endif (OPT_BUILD_RADIO) endif (OPT_BUILD_RADIO)
if (OPT_BUILD_RYFI_DECODER)
add_subdirectory("decoder_modules/ryfi_decoder")
endif (OPT_BUILD_RYFI_DECODER)
if (OPT_BUILD_WEATHER_SAT_DECODER) if (OPT_BUILD_WEATHER_SAT_DECODER)
add_subdirectory("decoder_modules/weather_sat_decoder") add_subdirectory("decoder_modules/weather_sat_decoder")
endif (OPT_BUILD_WEATHER_SAT_DECODER) endif (OPT_BUILD_WEATHER_SAT_DECODER)
@ -299,10 +247,6 @@ if (OPT_BUILD_FREQUENCY_MANAGER)
add_subdirectory("misc_modules/frequency_manager") add_subdirectory("misc_modules/frequency_manager")
endif (OPT_BUILD_FREQUENCY_MANAGER) endif (OPT_BUILD_FREQUENCY_MANAGER)
if (OPT_BUILD_IQ_EXPORTER)
add_subdirectory("misc_modules/iq_exporter")
endif (OPT_BUILD_IQ_EXPORTER)
if (OPT_BUILD_RECORDER) if (OPT_BUILD_RECORDER)
add_subdirectory("misc_modules/recorder") add_subdirectory("misc_modules/recorder")
endif (OPT_BUILD_RECORDER) endif (OPT_BUILD_RECORDER)
@ -323,12 +267,7 @@ if (OPT_BUILD_SCHEDULER)
add_subdirectory("misc_modules/scheduler") add_subdirectory("misc_modules/scheduler")
endif (OPT_BUILD_SCHEDULER) endif (OPT_BUILD_SCHEDULER)
if (MSVC) add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
else ()
add_executable(sdrpp "src/main.cpp")
endif ()
target_link_libraries(sdrpp PRIVATE sdrpp_core) target_link_libraries(sdrpp PRIVATE sdrpp_core)
# Compiler arguments # Compiler arguments
@ -338,21 +277,6 @@ target_compile_options(sdrpp PRIVATE ${SDRPP_COMPILER_FLAGS})
if (MSVC) 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 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) 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 () endif ()
@ -373,7 +297,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\") add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif () endif ()
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON -DOPT_BUILD_PAGER_DECODER=ON # cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON
# Create module cmake file # Create module cmake file
configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY) configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY)
@ -393,6 +317,4 @@ endif ()
# Create uninstall target # Create uninstall target
configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY) 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) add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
# Create headers target

View File

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

View File

@ -1,6 +1,64 @@
# Pull Requests # Pull Requests
Code pull requests are **NOT welcome**. Please open an issue discussing potential bugfixes or feature requests instead. TODO
# Code Style
## Naming Convention
- Files: `snake_case.h` `snake_case.cpp`
- Namespaces: `CamelCase`
- Classes: `CamelCase`
- Structs: `CamelCase_t`
- Members: `camelCase`
- Enum: `SNAKE_CASE`
- Macros: `SNAKE_CASE`
## Brace Style
```c++
int myFunction() {
if (shortIf) { shortFunctionName(); }
if (longIf) {
longFunction();
otherStuff();
myLongFunction();
}
}
```
Note: If it makes the code cleaner, remember to use the `?` keyword instead of a `if else` statement.
## Pointers
Please use `type* name` for pointers.
## Structure
Headers and their associated C++ files shall be in the same directory. All headers must use `#pragma once` instead of other include guards. Only include files in a header that are being used in that header. Include the rest in the associated C++ file.
# Modules
## Module Naming Convention
All modules names must be `snake_case`. If the module is a source, it must end with `_source`. If it is a sink, it must end with `_sink`.
For example, lets take the module named `cool_source`:
- Directory: `cool_source`
- Class: `CoolSourceModule`
- Binary: `cool_source.<os dynlib extension>`
## Integration into main repository
If the module meets the code quality requirements, it may be added to the official repository. A module that doesn't require any external dependencies that the core doesn't already use may be enabled for build by default. Otherwise, they must be disabled for build by default with a `OPT_BUILD_MODULE_NAME` variable set to `OFF`.
# JSON Formatting
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSON files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses.
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
## Band Frequency Allocation ## Band Frequency Allocation
@ -60,8 +118,8 @@ Please follow this guide to properly format the JSON files for custom color maps
} }
``` ```
# JSON Formatting # Best Practices
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSON files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses. * All additions and/or bug fixes to the core must not add additional dependencies.
* Use VSCode for development, VS seems to cause issues.
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity** * DO NOT use libboost for any code meant for this repository

View File

@ -13,7 +13,6 @@ endif (USE_BUNDLE_DEFAULTS)
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c") file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
add_definitions(-DSDRPP_IS_CORE) add_definitions(-DSDRPP_IS_CORE)
add_definitions(-DFLOG_ANDROID_TAG="SDR++")
if (MSVC) if (MSVC)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif () endif ()
@ -108,6 +107,7 @@ elseif (ANDROID)
) )
target_link_libraries(sdrpp_core PUBLIC target_link_libraries(sdrpp_core PUBLIC
/sdr-kit/${ANDROID_ABI}/lib/libcpu_features.a
/sdr-kit/${ANDROID_ABI}/lib/libvolk.so /sdr-kit/${ANDROID_ABI}/lib/libvolk.so
/sdr-kit/${ANDROID_ABI}/lib/libfftw3f.so /sdr-kit/${ANDROID_ABI}/lib/libfftw3f.so
/sdr-kit/${ANDROID_ABI}/lib/libzstd.so /sdr-kit/${ANDROID_ABI}/lib/libzstd.so

View File

@ -45,7 +45,7 @@ uint8_t *history_buffer_get_slice(history_buffer *buf) { return buf->history[buf
shift_register_t history_buffer_search(history_buffer *buf, const distance_t *distances, shift_register_t history_buffer_search(history_buffer *buf, const distance_t *distances,
unsigned int search_every) { unsigned int search_every) {
shift_register_t bestpath = 0; shift_register_t bestpath;
distance_t leasterror = USHRT_MAX; distance_t leasterror = USHRT_MAX;
// search for a state with the least error // search for a state with the least error
for (shift_register_t state = 0; state < buf->num_states; state += search_every) { for (shift_register_t state = 0; state < buf->num_states; state += search_every) {

View File

@ -88,7 +88,7 @@ int CommandArgsParser::parse(int argc, char* argv[]) {
try { try {
carg.ival = std::stoi(arg); carg.ival = std::stoi(arg);
} }
catch (const std::exception& e) { catch (std::exception e) {
printf("Invalid argument, failed to parse integer\n"); printf("Invalid argument, failed to parse integer\n");
showHelp(); showHelp();
return -1; return -1;
@ -98,7 +98,7 @@ int CommandArgsParser::parse(int argc, char* argv[]) {
try { try {
carg.fval = std::stod(arg); carg.fval = std::stod(arg);
} }
catch (const std::exception& e) { catch (std::exception e) {
printf("Invalid argument, failed to parse float\n"); printf("Invalid argument, failed to parse float\n");
showHelp(); showHelp();
return -1; return -1;

View File

@ -36,8 +36,8 @@ void ConfigManager::load(json def, bool lock) {
file >> conf; file >> conf;
file.close(); file.close();
} }
catch (const std::exception& e) { catch (std::exception e) {
flog::error("Config file '{}' is corrupted, resetting it: {}", path, e.what()); flog::error("Config file '{0}' is corrupted, resetting it", path);
conf = def; conf = def;
save(false); save(false);
} }

View File

@ -117,10 +117,6 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["colorMap"] = "Classic"; defConfig["colorMap"] = "Classic";
defConfig["fftHold"] = false; defConfig["fftHold"] = false;
defConfig["fftHoldSpeed"] = 60; defConfig["fftHoldSpeed"] = 60;
defConfig["fftSmoothing"] = false;
defConfig["fftSmoothingSpeed"] = 100;
defConfig["snrSmoothing"] = false;
defConfig["snrSmoothingSpeed"] = 20;
defConfig["fastFFT"] = false; defConfig["fastFFT"] = false;
defConfig["fftHeight"] = 300; defConfig["fftHeight"] = 300;
defConfig["fftRate"] = 20; defConfig["fftRate"] = 20;
@ -173,24 +169,14 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true; defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true;
defConfig["moduleInstances"]["File Source"]["module"] = "file_source"; defConfig["moduleInstances"]["File Source"]["module"] = "file_source";
defConfig["moduleInstances"]["File Source"]["enabled"] = true; 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"]["module"] = "hackrf_source";
defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true; 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"]["module"] = "hermes_source";
defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true; defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true;
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source"; defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true; 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"]["module"] = "plutosdr_source";
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true; defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["RFNM Source"]["module"] = "rfnm_source";
defConfig["moduleInstances"]["RFNM Source"]["enabled"] = true;
defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source"; defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source";
defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true; defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true;
defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source"; defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source";
@ -201,12 +187,10 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true; defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true;
defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source"; defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source";
defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true; defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true;
defConfig["moduleInstances"]["Spectran HTTP Source"]["module"] = "spectran_http_source"; defConfig["moduleInstances"]["SoapySDR Source"]["module"] = "soapy_source";
defConfig["moduleInstances"]["Spectran HTTP Source"]["enabled"] = true; defConfig["moduleInstances"]["SoapySDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source"; defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source";
defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true; 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"]["Audio Sink"] = "audio_sink";
defConfig["moduleInstances"]["Network Sink"] = "network_sink"; defConfig["moduleInstances"]["Network Sink"] = "network_sink";

View File

@ -12,7 +12,6 @@ namespace sdrpp_credits {
"Howard0su", "Howard0su",
"John Donkersley", "John Donkersley",
"Joshua Kimsey", "Joshua Kimsey",
"Manawyrm",
"Martin Hauke", "Martin Hauke",
"Marvin Sinister", "Marvin Sinister",
"Maxime Biette", "Maxime Biette",
@ -22,6 +21,7 @@ namespace sdrpp_credits {
"Shuyuan Liu", "Shuyuan Liu",
"Syne Ardwin (WI9SYN)", "Syne Ardwin (WI9SYN)",
"Szymon Zakrent", "Szymon Zakrent",
"Tobias Mädel",
"Youssef Touil", "Youssef Touil",
"Zimm" "Zimm"
}; };
@ -37,20 +37,13 @@ namespace sdrpp_credits {
const char* hardwareDonators[] = { const char* hardwareDonators[] = {
"Aaronia AG", "Aaronia AG",
"Airspy", "Airspy",
"Alex 4Z5LV",
"Analog Devices", "Analog Devices",
"CaribouLabs", "CaribouLabs",
"Deepace",
"Ettus Research", "Ettus Research",
"Harogic",
"Howard Su", "Howard Su",
"MicroPhase",
"Microtelecom",
"MyriadRF", "MyriadRF",
"Nuand", "Nuand",
"RFNM",
"RFspace", "RFspace",
"RigExpert",
"RTL-SDRblog", "RTL-SDRblog",
"SDRplay" "SDRplay"
}; };
@ -61,36 +54,25 @@ namespace sdrpp_credits {
"Croccydile", "Croccydile",
"Dale L Puckett (K0HYD)", "Dale L Puckett (K0HYD)",
"Daniele D'Agnelli", "Daniele D'Agnelli",
"David Taylor (GM8ARV)",
"D. Jones", "D. Jones",
"Dexruus",
"EB3FRN", "EB3FRN",
"Eric Johnson", "Eric Johnson",
"Ernest Murphy (NH7L)", "Ernest Murphy (NH7L)",
"Flinger Films", "Flinger Films",
"Frank Werner (HB9FXQ)",
"gringogrigio", "gringogrigio",
"Jeff Moe",
"Joe Cupano", "Joe Cupano",
"KD1SQ",
"Kezza", "Kezza",
"Krys Kamieniecki", "Krys Kamieniecki",
"Lee Donaghy", "Lee Donaghy",
"Lee KD1SQ", "Lee KD1SQ",
".lozenge. (Hank Hill)", ".lozenge. (Hank Hill)",
"Martin Herren (HB9FXX)",
"ON4MU", "ON4MU",
"Passion-Radio.com", "Passion-Radio.com",
"Paul Maine", "Paul Maine",
"Peter Betz",
"Scanner School", "Scanner School",
"Scott Palmer",
"SignalsEverywhere", "SignalsEverywhere",
"Syne Ardwin (WI9SYN)", "Syne Ardwin (WI9SYN)",
"W4IPA", "W4IPA",
"William Arcand (W1WRA)",
"William Pitchford",
"Yves Rougy",
"Zipper" "Zipper"
}; };

View File

@ -9,7 +9,6 @@
namespace dsp { namespace dsp {
class generic_block { class generic_block {
public: public:
virtual ~generic_block() {}
virtual void start() {} virtual void start() {}
virtual void stop() {} virtual void stop() {}
virtual int run() { return -1; } virtual int run() { return -1; }
@ -17,6 +16,8 @@ namespace dsp {
class block : public generic_block { class block : public generic_block {
public: public:
virtual void init() {}
virtual ~block() { virtual ~block() {
if (!_block_init) { return; } if (!_block_init) { return; }
stop(); stop();

View File

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

View File

@ -67,6 +67,10 @@ namespace dsp::buffer {
sizes[writeCur] = count; sizes[writeCur] = count;
writeCur++; writeCur++;
writeCur = ((writeCur) % TEST_BUFFER_SIZE); writeCur = ((writeCur) % TEST_BUFFER_SIZE);
// if (((writeCur - readCur + TEST_BUFFER_SIZE) % TEST_BUFFER_SIZE) >= (TEST_BUFFER_SIZE-2)) {
// flog::warn("Overflow");
// }
} }
cnd.notify_all(); cnd.notify_all();
_in->flush(); _in->flush();

View File

@ -93,7 +93,7 @@ namespace dsp {
void disableBlock(Processor<T, T>* block, Func onOutputChange) { void disableBlock(Processor<T, T>* block, Func onOutputChange) {
// Check that the block is part of the chain // Check that the block is part of the chain
if (!blockExists(block)) { if (!blockExists(block)) {
throw std::runtime_error("[chain] Tried to disable a block that isn't part of the chain"); throw std::runtime_error("[chain] Tried to enable a block that isn't part of the chain");
} }
// If already disabled, don't do anything // If already disabled, don't do anything
@ -163,12 +163,10 @@ namespace dsp {
private: private:
Processor<T, T>* blockBefore(Processor<T, T>* block) { Processor<T, T>* blockBefore(Processor<T, T>* block) {
// TODO: This is wrong and must be fixed when I get more time
for (auto& ln : links) { for (auto& ln : links) {
if (ln == block) { return NULL; } if (ln == block) { return NULL; }
if (states[ln]) { return ln; } if (states[ln]) { return ln; }
} }
return NULL;
} }
Processor<T, T>* blockAfter(Processor<T, T>* block) { Processor<T, T>* blockAfter(Processor<T, T>* block) {

View File

@ -41,11 +41,7 @@ namespace dsp::channel {
} }
inline int process(int count, const complex_t* in, complex_t* out) { inline int process(int count, const complex_t* in, complex_t* out) {
#if VOLK_VERSION >= 030100
volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, &phaseDelta, &phase, count);
#else
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, phaseDelta, &phase, count); volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, phaseDelta, &phase, count);
#endif
return count; return count;
} }

View File

@ -12,10 +12,6 @@ namespace dsp::compression {
void init(stream<complex_t>* in, PCMType pcmType) { void init(stream<complex_t>* in, PCMType pcmType) {
_pcmType = pcmType; _pcmType = pcmType;
// Set the output buffer size to the max size of a complex buffer + 8 bytes for the header
out.setBufferSize(STREAM_BUFFER_SIZE*sizeof(complex_t) + 8);
base_type::init(in); base_type::init(in);
} }

View File

@ -49,7 +49,6 @@ namespace dsp::demod {
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate); audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
alFir.init(NULL, audioFirTaps); alFir.init(NULL, audioFirTaps);
arFir.init(NULL, audioFirTaps); arFir.init(NULL, audioFirTaps);
xlator.init(NULL, -57000.0, samplerate);
rdsResamp.init(NULL, samplerate, 5000.0); rdsResamp.init(NULL, samplerate, 5000.0);
lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE); lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE);
@ -57,9 +56,9 @@ namespace dsp::demod {
r = buffer::alloc<float>(STREAM_BUFFER_SIZE); r = buffer::alloc<float>(STREAM_BUFFER_SIZE);
lprDelay.out.free(); lprDelay.out.free();
lmrDelay.out.free();
arFir.out.free(); arFir.out.free();
alFir.out.free(); alFir.out.free();
xlator.out.free();
rdsResamp.out.free(); rdsResamp.out.free();
base_type::init(in); base_type::init(in);
@ -93,7 +92,6 @@ namespace dsp::demod {
alFir.setTaps(audioFirTaps); alFir.setTaps(audioFirTaps);
arFir.setTaps(audioFirTaps); arFir.setTaps(audioFirTaps);
xlator.setOffset(-57000.0, samplerate);
rdsResamp.setInSamplerate(samplerate); rdsResamp.setInSamplerate(samplerate);
reset(); reset();
@ -141,7 +139,7 @@ namespace dsp::demod {
base_type::tempStart(); base_type::tempStart();
} }
inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, complex_t* rdsout = NULL) { inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, float* rdsout = NULL) {
// Demodulate // Demodulate
demod.process(count, in, demod.out.writeBuf); demod.process(count, in, demod.out.writeBuf);
if (_stereo) { if (_stereo) {
@ -154,24 +152,24 @@ namespace dsp::demod {
// Delay // Delay
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf); lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, lmrDelay.out.writeBuf); lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// conjugate PLL output to down convert twice the L-R signal // conjugate PLL output to down convert twice the L-R signal
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf); math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.out.writeBuf); math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.out.writeBuf); math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
// Do RDS demod // Do RDS demod
if (_rdsOut) { if (_rdsOut) {
// Translate to 0Hz // Since the PLL output is no longer needed after this, use it as the output
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf); math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
// Resample to the output samplerate volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout); rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
} }
// Convert output back to real for further processing // Convert output back to real for further processing
convert::ComplexToReal::process(count, lmrDelay.out.writeBuf, lmr); convert::ComplexToReal::process(count, rtoc.out.writeBuf, lmr);
// Amplify by 2x // Amplify by 2x
volk_32f_s32f_multiply_32f(lmr, lmr, 2.0f, count); volk_32f_s32f_multiply_32f(lmr, lmr, 2.0f, count);
@ -195,11 +193,24 @@ namespace dsp::demod {
// Convert to complex // Convert to complex
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf); rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
// Translate to 0Hz // Filter out pilot and run through PLL
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf); pilotFir.process(count, rtoc.out.writeBuf, pilotFir.out.writeBuf);
pilotPLL.process(count, pilotFir.out.writeBuf, pilotPLL.out.writeBuf);
// Resample to the output samplerate // Delay
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout); lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// conjugate PLL output to down convert twice the L-R signal
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
// Since the PLL output is no longer needed after this, use it as the output
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
} }
// Filter if needed // Filter if needed
@ -229,7 +240,7 @@ namespace dsp::demod {
return count; return count;
} }
stream<complex_t> rdsOut; stream<float> rdsOut;
protected: protected:
double _deviation; double _deviation;
@ -242,14 +253,13 @@ namespace dsp::demod {
tap<complex_t> pilotFirTaps; tap<complex_t> pilotFirTaps;
filter::FIR<complex_t, complex_t> pilotFir; filter::FIR<complex_t, complex_t> pilotFir;
convert::RealToComplex rtoc; convert::RealToComplex rtoc;
channel::FrequencyXlator xlator;
loop::PLL pilotPLL; loop::PLL pilotPLL;
math::Delay<float> lprDelay; math::Delay<float> lprDelay;
math::Delay<complex_t> lmrDelay; math::Delay<complex_t> lmrDelay;
tap<float> audioFirTaps; tap<float> audioFirTaps;
filter::FIR<float, float> arFir; filter::FIR<float, float> arFir;
filter::FIR<float, float> alFir; filter::FIR<float, float> alFir;
multirate::RationalResampler<dsp::complex_t> rdsResamp; multirate::RationalResampler<float> rdsResamp;
float* lmr; float* lmr;
float* l; float* l;

View File

@ -3,8 +3,6 @@
#include "quadrature.h" #include "quadrature.h"
#include "../filter/fir.h" #include "../filter/fir.h"
#include "../taps/low_pass.h" #include "../taps/low_pass.h"
#include "../taps/high_pass.h"
#include "../taps/band_pass.h"
#include "../convert/mono_to_stereo.h" #include "../convert/mono_to_stereo.h"
namespace dsp::demod { namespace dsp::demod {
@ -19,26 +17,22 @@ namespace dsp::demod {
~FM() { ~FM() {
if (!base_type::_block_init) { return; } if (!base_type::_block_init) { return; }
base_type::stop(); base_type::stop();
dsp::taps::free(filterTaps); dsp::taps::free(lpfTaps);
} }
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass, bool highPass) { void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass) {
_samplerate = samplerate; _samplerate = samplerate;
_bandwidth = bandwidth; _bandwidth = bandwidth;
_lowPass = lowPass; _lowPass = lowPass;
_highPass = highPass;
demod.init(NULL, bandwidth / 2.0, _samplerate); demod.init(NULL, bandwidth / 2.0, _samplerate);
loadDummyTaps(); lpfTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
fir.init(NULL, filterTaps); lpf.init(NULL, lpfTaps);
// Initialize taps
updateFilter(lowPass, highPass);
if constexpr (std::is_same_v<T, float>) { if constexpr (std::is_same_v<T, float>) {
demod.out.free(); demod.out.free();
} }
fir.out.free(); lpf.out.free();
base_type::init(in); base_type::init(in);
} }
@ -49,7 +43,9 @@ namespace dsp::demod {
base_type::tempStop(); base_type::tempStop();
_samplerate = samplerate; _samplerate = samplerate;
demod.setDeviation(_bandwidth / 2.0, _samplerate); demod.setDeviation(_bandwidth / 2.0, _samplerate);
updateFilter(_lowPass, _highPass); dsp::taps::free(lpfTaps);
lpfTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
lpf.setTaps(lpfTaps);
base_type::tempStart(); base_type::tempStart();
} }
@ -58,20 +54,19 @@ namespace dsp::demod {
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
if (bandwidth == _bandwidth) { return; } if (bandwidth == _bandwidth) { return; }
_bandwidth = bandwidth; _bandwidth = bandwidth;
std::lock_guard<std::mutex> lck2(lpfMtx);
demod.setDeviation(_bandwidth / 2.0, _samplerate); demod.setDeviation(_bandwidth / 2.0, _samplerate);
updateFilter(_lowPass, _highPass); dsp::taps::free(lpfTaps);
lpfTaps = dsp::taps::lowPass(_bandwidth / 2, (_bandwidth / 2) * 0.1, _samplerate);
lpf.setTaps(lpfTaps);
} }
void setLowPass(bool lowPass) { void setLowPass(bool lowPass) {
assert(base_type::_block_init); assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
updateFilter(lowPass, _highPass); std::lock_guard<std::mutex> lck2(lpfMtx);
} _lowPass = lowPass;
lpf.reset();
void setHighPass(bool highPass) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
updateFilter(_lowPass, highPass);
} }
void reset() { void reset() {
@ -79,23 +74,23 @@ namespace dsp::demod {
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop(); base_type::tempStop();
demod.reset(); demod.reset();
fir.reset(); lpf.reset();
base_type::tempStart(); base_type::tempStart();
} }
inline int process(int count, dsp::complex_t* in, T* out) { inline int process(int count, dsp::complex_t* in, T* out) {
if constexpr (std::is_same_v<T, float>) { if constexpr (std::is_same_v<T, float>) {
demod.process(count, in, out); demod.process(count, in, out);
if (filtering) { if (_lowPass) {
std::lock_guard<std::mutex> lck(filterMtx); std::lock_guard<std::mutex> lck(lpfMtx);
fir.process(count, out, out); lpf.process(count, out, out);
} }
} }
if constexpr (std::is_same_v<T, stereo_t>) { if constexpr (std::is_same_v<T, stereo_t>) {
demod.process(count, in, demod.out.writeBuf); demod.process(count, in, demod.out.writeBuf);
if (filtering) { if (_lowPass) {
std::lock_guard<std::mutex> lck(filterMtx); std::lock_guard<std::mutex> lck(lpfMtx);
fir.process(count, demod.out.writeBuf, demod.out.writeBuf); lpf.process(count, demod.out.writeBuf, demod.out.writeBuf);
} }
convert::MonoToStereo::process(count, demod.out.writeBuf, out); convert::MonoToStereo::process(count, demod.out.writeBuf, out);
} }
@ -114,50 +109,13 @@ namespace dsp::demod {
} }
private: private:
void updateFilter(bool lowPass, bool highPass) {
std::lock_guard<std::mutex> lck(filterMtx);
// Update values
_lowPass = lowPass;
_highPass = highPass;
filtering = (lowPass || highPass);
// Free filter taps
dsp::taps::free(filterTaps);
// Generate filter depending on low and high pass settings
if (_lowPass && _highPass) {
filterTaps = dsp::taps::bandPass<float>(300.0, _bandwidth / 2.0, 100.0, _samplerate);
}
else if (_highPass) {
filterTaps = dsp::taps::highPass(300.0, 100.0, _samplerate);
}
else if (_lowPass) {
filterTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
}
else {
loadDummyTaps();
}
// Set filter to use new taps
fir.setTaps(filterTaps);
fir.reset();
}
void loadDummyTaps() {
float dummyTap = 1.0f;
filterTaps = dsp::taps::fromArray<float>(1, &dummyTap);
}
double _samplerate; double _samplerate;
double _bandwidth; double _bandwidth;
bool _lowPass; bool _lowPass;
bool _highPass;
bool filtering;
Quadrature demod; Quadrature demod;
tap<float> filterTaps; tap<float> lpfTaps;
filter::FIR<float, float> fir; filter::FIR<float, float> lpf;
std::mutex filterMtx; std::mutex lpfMtx;
}; };
} }

View File

@ -110,7 +110,7 @@ namespace dsp::demod {
else if (_mode == Mode::LSB) { else if (_mode == Mode::LSB) {
return -_bandwidth / 2.0; return -_bandwidth / 2.0;
} }
else { else if (_mode == Mode::DSB) {
return 0.0; return 0.0;
} }
} }

View File

@ -41,10 +41,10 @@ namespace dsp::filter {
// Move existing data to make transition seemless // Move existing data to make transition seemless
if (_taps.size < oldTC) { if (_taps.size < oldTC) {
memmove(buffer, &buffer[oldTC - _taps.size], (_taps.size - 1) * sizeof(D)); memcpy(buffer, &buffer[oldTC - _taps.size], (_taps.size - 1) * sizeof(D));
} }
else if (_taps.size > oldTC) { else if (_taps.size > oldTC) {
memmove(&buffer[_taps.size - oldTC], buffer, (oldTC - 1) * sizeof(D)); memcpy(&buffer[_taps.size - oldTC], buffer, (oldTC - 1) * sizeof(D));
buffer::clear<D>(buffer, _taps.size - oldTC); buffer::clear<D>(buffer, _taps.size - oldTC);
} }

View File

@ -65,11 +65,6 @@ namespace dsp::loop {
if constexpr(CLAMP_PHASE) { clampPhase(); } if constexpr(CLAMP_PHASE) { clampPhase(); }
} }
inline void advancePhase() {
phase += freq;
if constexpr(CLAMP_PHASE) { clampPhase(); }
}
T freq; T freq;
T phase; T phase;

View File

@ -2,6 +2,5 @@
#include "../multirate/rrc_interpolator.h" #include "../multirate/rrc_interpolator.h"
namespace dsp::mod { namespace dsp::mod {
// TODO: Check if resample before RRC is better than using the RRC taps as a filter (bandwidth probably not correct for alias-free resampling)
typedef multirate::RRCInterpolator<complex_t> PSK; typedef multirate::RRCInterpolator<complex_t> PSK;
} }

View File

@ -5,7 +5,7 @@
#include "../math/hz_to_rads.h" #include "../math/hz_to_rads.h"
namespace dsp::mod { namespace dsp::mod {
class Quadrature : public Processor<float, complex_t> { class Quadrature : Processor<float, complex_t> {
using base_type = Processor<float, complex_t>; using base_type = Processor<float, complex_t>;
public: public:
Quadrature() {} Quadrature() {}

View File

@ -83,6 +83,8 @@ namespace dsp::multirate {
int interp = OutSR / gcd; int interp = OutSR / gcd;
int decim = InSR / gcd; int decim = InSR / gcd;
flog::warn("interp: {0}, decim: {1}", interp, decim);
// Configure resampler // Configure resampler
double tapSamplerate = _symbolrate * (double)interp; double tapSamplerate = _symbolrate * (double)interp;
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount * interp, _rrcBeta, _symbolrate, tapSamplerate); rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount * interp, _rrcBeta, _symbolrate, tapSamplerate);

View File

@ -10,8 +10,6 @@ namespace dsp {
Operator(stream<A>* a, stream<B>* b) { init(a, b); } Operator(stream<A>* a, stream<B>* b) { init(a, b); }
virtual ~Operator() {}
virtual void init(stream<A>* a, stream<B>* b) { virtual void init(stream<A>* a, stream<B>* b) {
_a = a; _a = a;
_b = b; _b = b;

View File

@ -11,7 +11,6 @@
namespace dsp { namespace dsp {
class untyped_stream { class untyped_stream {
public: public:
virtual ~untyped_stream() {}
virtual bool swap(int size) { return false; } virtual bool swap(int size) { return false; }
virtual int read() { return -1; } virtual int read() { return -1; }
virtual void flush() {} virtual void flush() {}

View File

@ -15,7 +15,7 @@ namespace dsp::taps {
if (oddTapCount && !(count % 2)) { count++; } if (oddTapCount && !(count % 2)) { count++; }
return windowedSinc<T>(count, (bandStop - bandStart) / 2.0, sampleRate, [=](double n, double N) { return windowedSinc<T>(count, (bandStop - bandStart) / 2.0, sampleRate, [=](double n, double N) {
if constexpr (std::is_same_v<T, float>) { if constexpr (std::is_same_v<T, float>) {
return 2.0f * cosf(offsetOmega * (float)n) * window::nuttall(n, N); return cosf(offsetOmega * (float)n) * window::nuttall(n, N);
} }
if constexpr (std::is_same_v<T, complex_t>) { if constexpr (std::is_same_v<T, complex_t>) {
// The offset is negative to flip the taps. Complex bandpass are asymetric // The offset is negative to flip the taps. Complex bandpass are asymetric

View File

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

View File

@ -82,7 +82,7 @@ namespace dsp {
inline float fastAmplitude() { inline float fastAmplitude() {
float re_abs = fabsf(re); float re_abs = fabsf(re);
float im_abs = fabsf(im); float im_abs = fabsf(re);
if (re_abs > im_abs) { return re_abs + 0.4f * im_abs; } if (re_abs > im_abs) { return re_abs + 0.4f * im_abs; }
return im_abs + 0.4f * re_abs; return im_abs + 0.4f * re_abs;
} }
@ -125,4 +125,4 @@ namespace dsp {
float l; float l;
float r; float r;
}; };
} }

View File

@ -433,9 +433,6 @@ void MainWindow::draw() {
showCredits = false; showCredits = false;
} }
// Reset waterfall lock
lockWaterfallControls = showCredits;
// Handle menu resize // Handle menu resize
ImVec2 winSize = ImGui::GetWindowSize(); ImVec2 winSize = ImGui::GetWindowSize();
ImVec2 mousePos = ImGui::GetMousePos(); ImVec2 mousePos = ImGui::GetMousePos();
@ -466,10 +463,9 @@ void MainWindow::draw() {
} }
} }
// Process menu keybinds
displaymenu::checkKeybinds();
// Left Column // Left Column
lockWaterfallControls = false;
if (showMenu) { if (showMenu) {
ImGui::Columns(3, "WindowColumns", false); ImGui::Columns(3, "WindowColumns", false);
ImGui::SetColumnWidth(0, menuWidth); ImGui::SetColumnWidth(0, menuWidth);
@ -580,20 +576,8 @@ void MainWindow::draw() {
if (wheel != 0 && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) { if (wheel != 0 && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) {
double nfreq; double nfreq;
if (vfo != NULL) { if (vfo != NULL) {
// Select factor depending on modifier keys nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (vfo->snapInterval * wheel);
double interval; nfreq = roundl(nfreq / vfo->snapInterval) * vfo->snapInterval;
if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) {
interval = vfo->snapInterval * 10.0;
}
else if (ImGui::IsKeyDown(ImGuiKey_LeftAlt)) {
interval = vfo->snapInterval * 0.1;
}
else {
interval = vfo->snapInterval;
}
nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (interval * wheel);
nfreq = roundl(nfreq / interval) * interval;
} }
else { else {
nfreq = gui::waterfall.getCenterFrequency() - (gui::waterfall.getViewBandwidth() * wheel / 20.0); nfreq = gui::waterfall.getCenterFrequency() - (gui::waterfall.getViewBandwidth() * wheel / 20.0);

View File

@ -8,7 +8,6 @@
#include <signal_path/signal_path.h> #include <signal_path/signal_path.h>
#include <gui/style.h> #include <gui/style.h>
#include <utils/optionlist.h> #include <utils/optionlist.h>
#include <algorithm>
namespace displaymenu { namespace displaymenu {
bool showWaterfall; bool showWaterfall;
@ -19,44 +18,50 @@ namespace displaymenu {
std::string colorMapAuthor = ""; std::string colorMapAuthor = "";
int selectedWindow = 0; int selectedWindow = 0;
int fftRate = 20; int fftRate = 20;
int fftSizeId = 0;
int uiScaleId = 0; int uiScaleId = 0;
bool restartRequired = false; bool restartRequired = false;
bool fftHold = false; bool fftHold = false;
int fftHoldSpeed = 60; int fftHoldSpeed = 60;
bool fftSmoothing = false;
int fftSmoothingSpeed = 100;
bool snrSmoothing = false;
int snrSmoothingSpeed = 20;
OptionList<int, int> fftSizes;
OptionList<float, float> uiScales; 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[] = { const IQFrontEnd::FFTWindow fftWindowList[] = {
IQFrontEnd::FFTWindow::RECTANGULAR, IQFrontEnd::FFTWindow::RECTANGULAR,
IQFrontEnd::FFTWindow::BLACKMAN, IQFrontEnd::FFTWindow::BLACKMAN,
IQFrontEnd::FFTWindow::NUTTALL IQFrontEnd::FFTWindow::NUTTALL
}; };
void updateFFTSpeeds() { void updateFFTHoldSpeed() {
gui::waterfall.setFFTHoldSpeed((float)fftHoldSpeed / ((float)fftRate * 10.0f)); gui::waterfall.setFFTHoldSpeed(fftHoldSpeed / (fftRate * 10.0f));
gui::waterfall.setFFTSmoothingSpeed(std::min<float>((float)fftSmoothingSpeed / (float)(fftRate * 10.0f), 1.0f));
gui::waterfall.setSNRSmoothingSpeed(std::min<float>((float)snrSmoothingSpeed / (float)(fftRate * 10.0f), 1.0f));
} }
void init() { 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 = core::configManager.conf["showWaterfall"];
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall(); showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
std::string colormapName = core::configManager.conf["colorMap"]; std::string colormapName = core::configManager.conf["colorMap"];
@ -78,12 +83,15 @@ namespace displaymenu {
fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"]; fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"];
gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate); gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate);
fftSizeId = fftSizes.valueId(65536); fftSizeId = 3;
int size = core::configManager.conf["fftSize"]; int fftSize = core::configManager.conf["fftSize"];
if (fftSizes.keyExists(size)) { for (int i = 0; i < 7; i++) {
fftSizeId = fftSizes.keyId(size); if (fftSize == FFTSizes[i]) {
fftSizeId = i;
break;
}
} }
sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId)); sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]);
fftRate = core::configManager.conf["fftRate"]; fftRate = core::configManager.conf["fftRate"];
sigpath::iqFrontEnd.setFFTRate(fftRate); sigpath::iqFrontEnd.setFFTRate(fftRate);
@ -96,13 +104,7 @@ namespace displaymenu {
fftHold = core::configManager.conf["fftHold"]; fftHold = core::configManager.conf["fftHold"];
fftHoldSpeed = core::configManager.conf["fftHoldSpeed"]; fftHoldSpeed = core::configManager.conf["fftHoldSpeed"];
gui::waterfall.setFFTHold(fftHold); gui::waterfall.setFFTHold(fftHold);
fftSmoothing = core::configManager.conf["fftSmoothing"]; updateFFTHoldSpeed();
fftSmoothingSpeed = core::configManager.conf["fftSmoothingSpeed"];
gui::waterfall.setFFTSmoothing(fftSmoothing);
snrSmoothing = core::configManager.conf["snrSmoothing"];
snrSmoothingSpeed = core::configManager.conf["snrSmoothingSpeed"];
gui::waterfall.setSNRSmoothing(snrSmoothing);
updateFFTSpeeds();
// Define and load UI scales // Define and load UI scales
uiScales.define(1.0f, "100%", 1.0f); uiScales.define(1.0f, "100%", 1.0f);
@ -112,24 +114,15 @@ namespace displaymenu {
uiScaleId = uiScales.valueId(style::uiScale); uiScaleId = uiScales.valueId(style::uiScale);
} }
void setWaterfallShown(bool shown) {
showWaterfall = shown;
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
core::configManager.acquire();
core::configManager.conf["showWaterfall"] = showWaterfall;
core::configManager.release(true);
}
void checkKeybinds() {
if (ImGui::IsKeyPressed(ImGuiKey_Home, false)) {
setWaterfallShown(!showWaterfall);
}
}
void draw(void* ctx) { void draw(void* ctx) {
float menuWidth = ImGui::GetContentRegionAvail().x; float menuWidth = ImGui::GetContentRegionAvail().x;
if (ImGui::Checkbox("Show Waterfall##_sdrpp", &showWaterfall)) { bool homePressed = ImGui::IsKeyPressed(ImGuiKey_Home, false);
setWaterfallShown(showWaterfall); if (ImGui::Checkbox("Show Waterfall##_sdrpp", &showWaterfall) || homePressed) {
if (homePressed) { showWaterfall = !showWaterfall; }
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
core::configManager.acquire();
core::configManager.conf["showWaterfall"] = showWaterfall;
core::configManager.release(true);
} }
if (ImGui::Checkbox("Full Waterfall Update##_sdrpp", &fullWaterfallUpdate)) { if (ImGui::Checkbox("Full Waterfall Update##_sdrpp", &fullWaterfallUpdate)) {
@ -151,47 +144,16 @@ namespace displaymenu {
core::configManager.conf["fftHold"] = fftHold; core::configManager.conf["fftHold"] = fftHold;
core::configManager.release(true); core::configManager.release(true);
} }
ImGui::SameLine();
ImGui::LeftLabel("FFT Hold Speed");
ImGui::FillWidth(); ImGui::FillWidth();
if (ImGui::InputInt("##sdrpp_fft_hold_speed", &fftHoldSpeed)) { if (ImGui::InputInt("##sdrpp_fft_hold_speed", &fftHoldSpeed)) {
updateFFTSpeeds(); updateFFTHoldSpeed();
core::configManager.acquire(); core::configManager.acquire();
core::configManager.conf["fftHoldSpeed"] = fftHoldSpeed; core::configManager.conf["fftHoldSpeed"] = fftHoldSpeed;
core::configManager.release(true); core::configManager.release(true);
} }
if (ImGui::Checkbox("FFT Smoothing##_sdrpp", &fftSmoothing)) {
gui::waterfall.setFFTSmoothing(fftSmoothing);
core::configManager.acquire();
core::configManager.conf["fftSmoothing"] = fftSmoothing;
core::configManager.release(true);
}
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::InputInt("##sdrpp_fft_smoothing_speed", &fftSmoothingSpeed)) {
fftSmoothingSpeed = std::max<int>(fftSmoothingSpeed, 1);
updateFFTSpeeds();
core::configManager.acquire();
core::configManager.conf["fftSmoothingSpeed"] = fftSmoothingSpeed;
core::configManager.release(true);
}
if (ImGui::Checkbox("SNR Smoothing##_sdrpp", &snrSmoothing)) {
gui::waterfall.setSNRSmoothing(snrSmoothing);
core::configManager.acquire();
core::configManager.conf["snrSmoothing"] = snrSmoothing;
core::configManager.release(true);
}
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::InputInt("##sdrpp_snr_smoothing_speed", &snrSmoothingSpeed)) {
snrSmoothingSpeed = std::max<int>(snrSmoothingSpeed, 1);
updateFFTSpeeds();
core::configManager.acquire();
core::configManager.conf["snrSmoothingSpeed"] = snrSmoothingSpeed;
core::configManager.release(true);
}
ImGui::LeftLabel("High-DPI Scaling"); ImGui::LeftLabel("High-DPI Scaling");
ImGui::FillWidth(); ImGui::FillWidth();
if (ImGui::Combo("##sdrpp_ui_scale", &uiScaleId, uiScales.txt)) { if (ImGui::Combo("##sdrpp_ui_scale", &uiScaleId, uiScales.txt)) {
@ -206,7 +168,7 @@ namespace displaymenu {
if (ImGui::InputInt("##sdrpp_fft_rate", &fftRate, 1, 10)) { if (ImGui::InputInt("##sdrpp_fft_rate", &fftRate, 1, 10)) {
fftRate = std::max<int>(1, fftRate); fftRate = std::max<int>(1, fftRate);
sigpath::iqFrontEnd.setFFTRate(fftRate); sigpath::iqFrontEnd.setFFTRate(fftRate);
updateFFTSpeeds(); updateFFTHoldSpeed();
core::configManager.acquire(); core::configManager.acquire();
core::configManager.conf["fftRate"] = fftRate; core::configManager.conf["fftRate"] = fftRate;
core::configManager.release(true); core::configManager.release(true);
@ -214,10 +176,10 @@ namespace displaymenu {
ImGui::LeftLabel("FFT Size"); ImGui::LeftLabel("FFT Size");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, fftSizes.txt)) { if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, FFTSizesStr)) {
sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId)); sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]);
core::configManager.acquire(); core::configManager.acquire();
core::configManager.conf["fftSize"] = fftSizes.key(fftSizeId); core::configManager.conf["fftSize"] = FFTSizes[fftSizeId];
core::configManager.release(true); core::configManager.release(true);
} }
@ -248,4 +210,4 @@ namespace displaymenu {
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Restart required."); ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Restart required.");
} }
} }
} }

View File

@ -2,6 +2,5 @@
namespace displaymenu { namespace displaymenu {
void init(); void init();
void checkKeybinds();
void draw(void* ctx); void draw(void* ctx);
} }

View File

@ -39,7 +39,7 @@ namespace module_manager_menu {
ImVec2 btnSize = ImVec2(lheight, lheight - 1); ImVec2 btnSize = ImVec2(lheight, lheight - 1);
ImVec2 textOff = ImVec2(3.0f * style::uiScale, -5.0f * style::uiScale); ImVec2 textOff = ImVec2(3.0f * style::uiScale, -5.0f * style::uiScale);
if (ImGui::BeginTable("Module Manager Table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200.0f * style::uiScale))) { if (ImGui::BeginTable("Module Manager Table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200))) {
ImGui::TableSetupColumn("Name"); ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Type"); ImGui::TableSetupColumn("Type");
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, cellWidth); ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, cellWidth);

View File

@ -15,10 +15,6 @@ namespace sourcemenu {
bool iqCorrection = false; bool iqCorrection = false;
bool invertIQ = false; bool invertIQ = false;
EventHandler<std::string> sourceRegisteredHandler;
EventHandler<std::string> sourceUnregisterHandler;
EventHandler<std::string> sourceUnregisteredHandler;
std::vector<std::string> sourceNames; std::vector<std::string> sourceNames;
std::string sourceNamesTxt; std::string sourceNamesTxt;
std::string selectedSource; std::string selectedSource;
@ -99,10 +95,10 @@ namespace sourcemenu {
} }
sourceId = std::distance(sourceNames.begin(), it); sourceId = std::distance(sourceNames.begin(), it);
selectedSource = sourceNames[sourceId]; selectedSource = sourceNames[sourceId];
sigpath::sourceManager.selectSource(sourceNames[sourceId]); sigpath::sourceManager.select(sourceNames[sourceId]);
} }
void onSourceRegistered(std::string name, void* ctx) { void onSourceRegistered(std::string name) {
refreshSources(); refreshSources();
if (selectedSource.empty()) { if (selectedSource.empty()) {
@ -114,13 +110,13 @@ namespace sourcemenu {
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource)); sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
} }
void onSourceUnregister(std::string name, void* ctx) { void onSourceUnregister(std::string name) {
if (name != selectedSource) { return; } if (name != selectedSource) { return; }
// TODO: Stop everything // TODO: Stop everything
} }
void onSourceUnregistered(std::string name, void* ctx) { void onSourceUnregistered(std::string name) {
refreshSources(); refreshSources();
if (sourceNames.empty()) { if (sourceNames.empty()) {
@ -153,12 +149,9 @@ namespace sourcemenu {
selectSource(selected); selectSource(selected);
sigpath::iqFrontEnd.setDecimation(1 << decimationPower); sigpath::iqFrontEnd.setDecimation(1 << decimationPower);
sourceRegisteredHandler.handler = onSourceRegistered; sigpath::sourceManager.onSourceRegistered.bind(onSourceRegistered);
sourceUnregisterHandler.handler = onSourceUnregister; sigpath::sourceManager.onSourceUnregister.bind(onSourceUnregister);
sourceUnregisteredHandler.handler = onSourceUnregistered; sigpath::sourceManager.onSourceUnregistered.bind(onSourceUnregistered);
sigpath::sourceManager.onSourceRegistered.bindHandler(&sourceRegisteredHandler);
sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler);
sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourceUnregisteredHandler);
core::configManager.release(); core::configManager.release();
} }
@ -179,7 +172,7 @@ namespace sourcemenu {
if (running) { style::endDisabled(); } if (running) { style::endDisabled(); }
sigpath::sourceManager.showSelectedMenu(); sigpath::sourceManager.showMenu();
if (ImGui::Checkbox("IQ Correction##_sdrpp_iq_corr", &iqCorrection)) { if (ImGui::Checkbox("IQ Correction##_sdrpp_iq_corr", &iqCorrection)) {
sigpath::iqFrontEnd.setDCBlocking(iqCorrection); sigpath::iqFrontEnd.setDCBlocking(iqCorrection);

View File

@ -1,5 +1,4 @@
#pragma once #pragma once
#include <stdint.h>
#include <imgui.h> #include <imgui.h>
#include <string> #include <string>
#include <vector> #include <vector>
@ -145,4 +144,4 @@ namespace SmGui {
// Config configs // Config configs
void ForceSyncForNext(); void ForceSyncForNext();
} }

View File

@ -3,7 +3,6 @@
#include <gui/style.h> #include <gui/style.h>
#include <gui/gui.h> #include <gui/gui.h>
#include <backend.h> #include <backend.h>
#include <utils/hrfreq.h>
#ifndef IMGUI_DEFINE_MATH_OPERATORS #ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS
@ -91,7 +90,6 @@ void FrequencySelect::moveCursorToDigit(int i) {
void FrequencySelect::draw() { void FrequencySelect::draw() {
auto window = ImGui::GetCurrentWindow(); auto window = ImGui::GetCurrentWindow();
auto io = ImGui::GetIO();
widgetPos = ImGui::GetWindowContentRegionMin(); widgetPos = ImGui::GetWindowContentRegionMin();
ImVec2 cursorPos = ImGui::GetCursorPos(); ImVec2 cursorPos = ImGui::GetCursorPos();
widgetPos.x += window->Pos.x + cursorPos.x; widgetPos.x += window->Pos.x + cursorPos.x;
@ -134,7 +132,7 @@ void FrequencySelect::draw() {
ImVec2 mousePos = ImGui::GetMousePos(); ImVec2 mousePos = ImGui::GetMousePos();
bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right); bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
int mw = io.MouseWheel; int mw = ImGui::GetIO().MouseWheel;
bool onDigit = false; bool onDigit = false;
bool hovered = false; bool hovered = false;
@ -176,7 +174,7 @@ void FrequencySelect::draw() {
moveCursorToDigit(i + 1); moveCursorToDigit(i + 1);
} }
auto chars = io.InputQueueCharacters; auto chars = ImGui::GetIO().InputQueueCharacters;
// For each keyboard characters, type it // For each keyboard characters, type it
for (int j = 0; j < chars.Size; j++) { for (int j = 0; j < chars.Size; j++) {
@ -196,34 +194,6 @@ void FrequencySelect::draw() {
} }
} }
digitHovered = hovered; 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; uint64_t freq = 0;

View File

@ -62,33 +62,6 @@ inline void printAndScale(double freq, char* buf) {
} }
} }
inline void doZoom(int offset, int width, int inSize, int outSize, float* in, float* out) {
// NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX
if (offset < 0) {
offset = 0;
}
if (width > 524288) {
width = 524288;
}
float factor = (float)width / (float)outSize;
float sFactor = ceilf(factor);
float uFactor;
float id = offset;
float maxVal;
int sId;
for (int i = 0; i < outSize; i++) {
maxVal = -INFINITY;
sId = (int)id;
uFactor = (sId + sFactor > inSize) ? sFactor - ((sId + sFactor) - inSize) : sFactor;
for (int j = 0; j < uFactor; j++) {
if (in[sId + j] > maxVal) { maxVal = in[sId + j]; }
}
out[i] = maxVal;
id += factor;
}
}
namespace ImGui { namespace ImGui {
WaterFall::WaterFall() { WaterFall::WaterFall() {
fftMin = -70.0; fftMin = -70.0;
@ -613,7 +586,7 @@ namespace ImGui {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize; drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2); drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, &rawFFTs[((i + currentFFTLine) % waterfallHeight) * rawFFTSize], tempData); doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[((i + currentFFTLine) % waterfallHeight) * rawFFTSize], tempData);
for (int j = 0; j < dataWidth; j++) { for (int j = 0; j < dataWidth; j++) {
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange; pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))]; waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
@ -716,7 +689,6 @@ namespace ImGui {
void WaterFall::onResize() { void WaterFall::onResize() {
std::lock_guard<std::recursive_mutex> lck(latestFFTMtx); std::lock_guard<std::recursive_mutex> lck(latestFFTMtx);
std::lock_guard<std::mutex> lck2(smoothingBufMtx);
// return if widget is too small // return if widget is too small
if (widgetSize.x < 100 || widgetSize.y < 100) { if (widgetSize.x < 100 || widgetSize.y < 100) {
return; return;
@ -768,23 +740,14 @@ namespace ImGui {
} }
latestFFTHold = new float[dataWidth]; latestFFTHold = new float[dataWidth];
// Reallocate smoothing buffer
if (fftSmoothing) {
if (smoothingBuf) { delete[] smoothingBuf; }
smoothingBuf = new float[dataWidth];
for (int i = 0; i < dataWidth; i++) {
smoothingBuf[i] = -1000.0f;
}
}
if (waterfallVisible) { if (waterfallVisible) {
delete[] waterfallFb; delete[] waterfallFb;
waterfallFb = new uint32_t[dataWidth * waterfallHeight]; waterfallFb = new uint32_t[dataWidth * waterfallHeight];
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t)); memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
} }
for (int i = 0; i < dataWidth; i++) { for (int i = 0; i < dataWidth; i++) {
latestFFT[i] = -1000.0f; // Hide everything latestFFT[i] = -1000.0; // Hide everything
latestFFTHold[i] = -1000.0f; latestFFTHold[i] = -1000.0;
} }
fftAreaMin = ImVec2(widgetPos.x + (50.0f * style::uiScale), widgetPos.y + (9.0f * style::uiScale)); fftAreaMin = ImVec2(widgetPos.x + (50.0f * style::uiScale), widgetPos.y + (9.0f * style::uiScale));
@ -894,7 +857,7 @@ namespace ImGui {
int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2); int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
if (waterfallVisible) { if (waterfallVisible) {
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT); doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT);
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t)); memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
float pixel; float pixel;
float dataRange = waterfallMax - waterfallMin; float dataRange = waterfallMax - waterfallMin;
@ -906,29 +869,13 @@ namespace ImGui {
waterfallUpdate = true; waterfallUpdate = true;
} }
else { else {
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, rawFFTs, latestFFT); doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs, latestFFT);
fftLines = 1; fftLines = 1;
} }
// Apply smoothing if enabled
if (fftSmoothing && latestFFT != NULL && smoothingBuf != NULL && fftLines != 0) {
std::lock_guard<std::mutex> lck2(smoothingBufMtx);
volk_32f_s32f_multiply_32f(latestFFT, latestFFT, fftSmoothingAlpha, dataWidth);
volk_32f_s32f_multiply_32f(smoothingBuf, smoothingBuf, fftSmoothingBeta, dataWidth);
volk_32f_x2_add_32f(smoothingBuf, latestFFT, smoothingBuf, dataWidth);
memcpy(latestFFT, smoothingBuf, dataWidth * sizeof(float));
}
if (selectedVFO != "" && vfos.size() > 0) { if (selectedVFO != "" && vfos.size() > 0) {
float dummy; float dummy;
if (snrSmoothing) { calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, selectedVFOSNR);
float newSNR = 0.0f;
calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, newSNR);
selectedVFOSNR = (snrSmoothingBeta*selectedVFOSNR) + (snrSmoothingAlpha*newSNR);
}
else {
calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, selectedVFOSNR);
}
} }
// If FFT hold is enabled, update it // If FFT hold is enabled, update it
@ -1163,45 +1110,6 @@ namespace ImGui {
fftHoldSpeed = speed; fftHoldSpeed = speed;
} }
void WaterFall::setFFTSmoothing(bool enabled) {
std::lock_guard<std::mutex> lck(smoothingBufMtx);
fftSmoothing = enabled;
// Free buffer if not null
if (smoothingBuf) {delete[] smoothingBuf; }
// If disabled, stop here
if (!enabled) {
smoothingBuf = NULL;
return;
}
// Allocate and copy existing FFT into it
smoothingBuf = new float[dataWidth];
if (latestFFT) {
std::lock_guard<std::recursive_mutex> lck2(latestFFTMtx);
memcpy(smoothingBuf, latestFFT, dataWidth * sizeof(float));
}
else {
memset(smoothingBuf, 0, dataWidth * sizeof(float));
}
}
void WaterFall::setFFTSmoothingSpeed(float speed) {
std::lock_guard<std::mutex> lck(smoothingBufMtx);
fftSmoothingAlpha = speed;
fftSmoothingBeta = 1.0f - speed;
}
void WaterFall::setSNRSmoothing(bool enabled) {
snrSmoothing = enabled;
}
void WaterFall::setSNRSmoothingSpeed(float speed) {
snrSmoothingAlpha = speed;
snrSmoothingBeta = 1.0f - speed;
}
float* WaterFall::acquireLatestFFT(int& width) { float* WaterFall::acquireLatestFFT(int& width) {
latestFFTMtx.lock(); latestFFTMtx.lock();
if (!latestFFT) { if (!latestFFT) {

View File

@ -90,6 +90,33 @@ namespace ImGui {
float* getFFTBuffer(); float* getFFTBuffer();
void pushFFT(); void pushFFT();
inline void doZoom(int offset, int width, int outWidth, float* data, float* out) {
// NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX
if (offset < 0) {
offset = 0;
}
if (width > 524288) {
width = 524288;
}
float factor = (float)width / (float)outWidth;
float sFactor = ceilf(factor);
float uFactor;
float id = offset;
float maxVal;
int sId;
for (int i = 0; i < outWidth; i++) {
maxVal = -INFINITY;
sId = (int)id;
uFactor = (sId + sFactor > rawFFTSize) ? sFactor - ((sId + sFactor) - rawFFTSize) : sFactor;
for (int j = 0; j < uFactor; j++) {
if (data[sId + j] > maxVal) { maxVal = data[sId + j]; }
}
out[i] = maxVal;
id += factor;
}
}
void updatePallette(float colors[][3], int colorCount); void updatePallette(float colors[][3], int colorCount);
void updatePalletteFromArray(float* colors, int colorCount); void updatePalletteFromArray(float* colors, int colorCount);
@ -142,12 +169,6 @@ namespace ImGui {
void setFFTHold(bool hold); void setFFTHold(bool hold);
void setFFTHoldSpeed(float speed); void setFFTHoldSpeed(float speed);
void setFFTSmoothing(bool enabled);
void setFFTSmoothingSpeed(float speed);
void setSNRSmoothing(bool enabled);
void setSNRSmoothingSpeed(float speed);
float* acquireLatestFFT(int& width); float* acquireLatestFFT(int& width);
void releaseLatestFFT(); void releaseLatestFFT();
@ -161,7 +182,7 @@ namespace ImGui {
bool mouseInFFT = false; bool mouseInFFT = false;
bool mouseInWaterfall = false; bool mouseInWaterfall = false;
float selectedVFOSNR = 0.0f; float selectedVFOSNR = NAN;
bool centerFrequencyLocked = false; bool centerFrequencyLocked = false;
@ -249,7 +270,6 @@ namespace ImGui {
std::recursive_mutex buf_mtx; std::recursive_mutex buf_mtx;
std::recursive_mutex latestFFTMtx; std::recursive_mutex latestFFTMtx;
std::mutex texMtx; std::mutex texMtx;
std::mutex smoothingBufMtx;
float vRange; float vRange;
@ -284,9 +304,8 @@ namespace ImGui {
//std::vector<std::vector<float>> rawFFTs; //std::vector<std::vector<float>> rawFFTs;
int rawFFTSize; int rawFFTSize;
float* rawFFTs = NULL; float* rawFFTs = NULL;
float* latestFFT = NULL; float* latestFFT;
float* latestFFTHold = NULL; float* latestFFTHold;
float* smoothingBuf = NULL;
int currentFFTLine = 0; int currentFFTLine = 0;
int fftLines = 0; int fftLines = 0;
@ -306,14 +325,6 @@ namespace ImGui {
bool fftHold = false; bool fftHold = false;
float fftHoldSpeed = 0.3f; float fftHoldSpeed = 0.3f;
bool fftSmoothing = false;
float fftSmoothingAlpha = 0.5;
float fftSmoothingBeta = 0.5;
bool snrSmoothing = false;
float snrSmoothingAlpha = 0.5;
float snrSmoothingBeta = 0.5;
// UI Select elements // UI Select elements
bool fftResizeSelect = false; bool fftResizeSelect = false;
bool freqScaleSelect = false; bool freqScaleSelect = false;

View File

@ -33,7 +33,7 @@ ModuleManager::Module_t ModuleManager::loadModule(std::string path) {
#else #else
mod.handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); mod.handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (mod.handle == NULL) { if (mod.handle == NULL) {
flog::error("Couldn't load {0}: {1}", path, dlerror()); flog::error("Couldn't load {0}.", path);
mod.handle = NULL; mod.handle = NULL;
return mod; return mod;
} }
@ -183,4 +183,4 @@ void ModuleManager::doPostInitAll() {
flog::info("Running post-init for {0}", name); flog::info("Running post-init for {0}", name);
inst.instance->postInit(); inst.instance->postInit();
} }
} }

View File

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

View File

@ -2,7 +2,7 @@
#include <utils/flog.h> #include <utils/flog.h>
bool ModuleComManager::registerInterface(std::string moduleName, std::string name, void (*handler)(int code, void* in, void* out, void* ctx), void* ctx) { bool ModuleComManager::registerInterface(std::string moduleName, std::string name, void (*handler)(int code, void* in, void* out, void* ctx), void* ctx) {
std::lock_guard<std::recursive_mutex> lck(mtx); std::lock_guard<std::mutex> lck(mtx);
if (interfaces.find(name) != interfaces.end()) { if (interfaces.find(name) != interfaces.end()) {
flog::error("Tried creating module interface with an existing name: {0}", name); flog::error("Tried creating module interface with an existing name: {0}", name);
return false; return false;
@ -16,7 +16,7 @@ bool ModuleComManager::registerInterface(std::string moduleName, std::string nam
} }
bool ModuleComManager::unregisterInterface(std::string name) { bool ModuleComManager::unregisterInterface(std::string name) {
std::lock_guard<std::recursive_mutex> lck(mtx); std::lock_guard<std::mutex> lck(mtx);
if (interfaces.find(name) == interfaces.end()) { if (interfaces.find(name) == interfaces.end()) {
flog::error("Tried to erase module interface with unknown name: {0}", name); flog::error("Tried to erase module interface with unknown name: {0}", name);
return false; return false;
@ -26,13 +26,13 @@ bool ModuleComManager::unregisterInterface(std::string name) {
} }
bool ModuleComManager::interfaceExists(std::string name) { bool ModuleComManager::interfaceExists(std::string name) {
std::lock_guard<std::recursive_mutex> lck(mtx); std::lock_guard<std::mutex> lck(mtx);
if (interfaces.find(name) == interfaces.end()) { return false; } if (interfaces.find(name) == interfaces.end()) { return false; }
return true; return true;
} }
std::string ModuleComManager::getModuleName(std::string name) { std::string ModuleComManager::getModuleName(std::string name) {
std::lock_guard<std::recursive_mutex> lck(mtx); std::lock_guard<std::mutex> lck(mtx);
if (interfaces.find(name) == interfaces.end()) { if (interfaces.find(name) == interfaces.end()) {
flog::error("Tried to call unknown module interface: {0}", name); flog::error("Tried to call unknown module interface: {0}", name);
return ""; return "";
@ -41,7 +41,7 @@ std::string ModuleComManager::getModuleName(std::string name) {
} }
bool ModuleComManager::callInterface(std::string name, int code, void* in, void* out) { bool ModuleComManager::callInterface(std::string name, int code, void* in, void* out) {
std::lock_guard<std::recursive_mutex> lck(mtx); std::lock_guard<std::mutex> lck(mtx);
if (interfaces.find(name) == interfaces.end()) { if (interfaces.find(name) == interfaces.end()) {
flog::error("Tried to call unknown module interface: {0}", name); flog::error("Tried to call unknown module interface: {0}", name);
return false; return false;

View File

@ -18,6 +18,6 @@ public:
bool callInterface(std::string name, int code, void* in, void* out); bool callInterface(std::string name, int code, void* in, void* out);
private: private:
std::recursive_mutex mtx; std::mutex mtx;
std::map<std::string, ModuleComInterface> interfaces; std::map<std::string, ModuleComInterface> interfaces;
}; };

View File

@ -146,7 +146,7 @@ namespace server {
// Load sourceId from config // Load sourceId from config
sourceId = 0; sourceId = 0;
if (sourceList.keyExists(sourceName)) { sourceId = sourceList.keyId(sourceName); } if (sourceList.keyExists(sourceName)) { sourceId = sourceList.keyId(sourceName); }
sigpath::sourceManager.selectSource(sourceList[sourceId]); sigpath::sourceManager.select(sourceList[sourceId]);
// TODO: Use command line option // TODO: Use command line option
std::string host = (std::string)core::args["addr"]; std::string host = (std::string)core::args["addr"];
@ -230,7 +230,7 @@ namespace server {
// Compress data if needed and fill out header fields // Compress data if needed and fill out header fields
if (compression) { if (compression) {
bb_pkt_hdr->type = PACKET_TYPE_BASEBAND_COMPRESSED; bb_pkt_hdr->type = PACKET_TYPE_BASEBAND_COMPRESSED;
bb_pkt_hdr->size = sizeof(PacketHeader) + (uint32_t)ZSTD_compressCCtx(cctx, &bbuf[sizeof(PacketHeader)], SERVER_MAX_PACKET_SIZE-sizeof(PacketHeader), data, count, 1); bb_pkt_hdr->size = sizeof(PacketHeader) + (uint32_t)ZSTD_compressCCtx(cctx, &bbuf[sizeof(PacketHeader)], SERVER_MAX_PACKET_SIZE, data, count, 1);
} }
else { else {
bb_pkt_hdr->type = PACKET_TYPE_BASEBAND; bb_pkt_hdr->type = PACKET_TYPE_BASEBAND;
@ -280,8 +280,7 @@ namespace server {
} }
} }
else if (cmd == COMMAND_START) { else if (cmd == COMMAND_START) {
sigpath::sourceManager.start(); running = sigpath::sourceManager.start();
running = true;
} }
else if (cmd == COMMAND_STOP) { else if (cmd == COMMAND_STOP) {
sigpath::sourceManager.stop(); sigpath::sourceManager.stop();
@ -309,14 +308,14 @@ namespace server {
SmGui::FillWidth(); SmGui::FillWidth();
SmGui::ForceSync(); SmGui::ForceSync();
if (SmGui::Combo("##sdrpp_server_src_sel", &sourceId, sourceList.txt)) { if (SmGui::Combo("##sdrpp_server_src_sel", &sourceId, sourceList.txt)) {
sigpath::sourceManager.selectSource(sourceList[sourceId]); sigpath::sourceManager.select(sourceList[sourceId]);
core::configManager.acquire(); core::configManager.acquire();
core::configManager.conf["source"] = sourceList.key(sourceId); core::configManager.conf["source"] = sourceList.key(sourceId);
core::configManager.release(true); core::configManager.release(true);
} }
if (running) { SmGui::EndDisabled(); } if (running) { SmGui::EndDisabled(); }
sigpath::sourceManager.showSelectedMenu(); sigpath::sourceManager.showMenu();
} }
void renderUI(SmGui::DrawList* dl, std::string diffId, SmGui::DrawListElem diffValue) { void renderUI(SmGui::DrawList* dl, std::string diffId, SmGui::DrawListElem diffValue) {

View File

@ -1,106 +1,186 @@
#include <server.h> #include "source.h"
#include <signal_path/source.h>
#include <utils/flog.h> #include <utils/flog.h>
#include <signal_path/signal_path.h>
#include <core.h>
SourceManager::SourceManager() { void SourceManager::registerSource(const std::string& name, Source* source) {
} std::lock_guard<std::recursive_mutex> lck(mtx);
void SourceManager::registerSource(std::string name, SourceHandler* handler) { // Check arguments
if (source || name.empty()) {
flog::error("Invalid argument to register source", name);
return;
}
// Check that a source with that name doesn't already exist
if (sources.find(name) != sources.end()) { if (sources.find(name) != sources.end()) {
flog::error("Tried to register new source with existing name: {0}", name); flog::error("Tried to register source with existing name: {}", name);
return; return;
} }
sources[name] = handler;
onSourceRegistered.emit(name); // Add source to map
sources[name] = source;
// Add source to lists
sourceNames.push_back(name);
onSourceRegistered(name);
} }
void SourceManager::unregisterSource(std::string name) { void SourceManager::unregisterSource(const std::string& name) {
std::lock_guard<std::recursive_mutex> lck(mtx);
// Check that a source with that name exists
if (sources.find(name) == sources.end()) { if (sources.find(name) == sources.end()) {
flog::error("Tried to unregister non existent source: {0}", name); flog::error("Tried to unregister a non-existent source: {}", name);
return; return;
} }
onSourceUnregister.emit(name);
if (name == selectedName) { // Notify event listeners of the imminent deletion
if (selectedHandler != NULL) { onSourceUnregister(name);
sources[selectedName]->deselectHandler(sources[selectedName]->ctx);
} // Delete from lists
sigpath::iqFrontEnd.setInput(&nullSource); sourceNames.erase(std::find(sourceNames.begin(), sourceNames.end(), name));
selectedHandler = NULL;
}
sources.erase(name); sources.erase(name);
onSourceUnregistered.emit(name);
// Notify event listeners of the deletion
onSourceUnregistered(name);
} }
std::vector<std::string> SourceManager::getSourceNames() { const std::vector<std::string>& SourceManager::getSourceNames() {
std::vector<std::string> names; std::lock_guard<std::recursive_mutex> lck(mtx);
for (auto const& [name, src] : sources) { names.push_back(name); } return sourceNames;
return names;
} }
void SourceManager::selectSource(std::string name) { void SourceManager::select(const std::string& name) {
std::lock_guard<std::recursive_mutex> lck(mtx);
// make sure that source isn't currently selected
if (selectedSourceName == name) { return; }
// Deselect current source
deselect();
// Check that a source with that name exists
if (sources.find(name) == sources.end()) { if (sources.find(name) == sources.end()) {
flog::error("Tried to select non existent source: {0}", name); flog::error("Tried to select a non-existent source: {}", name);
return; return;
} }
if (selectedHandler != NULL) {
sources[selectedName]->deselectHandler(sources[selectedName]->ctx); // Select the source
} selectedSourceName = name;
selectedHandler = sources[name]; selectedSource = sources[name];
selectedHandler->selectHandler(selectedHandler->ctx);
selectedName = name; // Call the selected source
if (core::args["server"].b()) { selectedSource->select();
server::setInput(selectedHandler->stream);
} // Retune to make sure the source has the latest frequency
else { tune(frequency);
sigpath::iqFrontEnd.setInput(selectedHandler->stream);
}
// Set server input here
} }
void SourceManager::showSelectedMenu() { const std::string& SourceManager::getSelected() {
if (selectedHandler == NULL) { std::lock_guard<std::recursive_mutex> lck(mtx);
return; return selectedSourceName;
}
selectedHandler->menuHandler(selectedHandler->ctx);
} }
void SourceManager::start() { bool SourceManager::start() {
if (selectedHandler == NULL) { std::lock_guard<std::recursive_mutex> lck(mtx);
return;
} // Check if not already running
selectedHandler->startHandler(selectedHandler->ctx); if (running) { return true; }
// Call source if selected and save if started
running = (!selectedSource) ? false : selectedSource->start();
return running;
} }
void SourceManager::stop() { void SourceManager::stop() {
if (selectedHandler == NULL) { std::lock_guard<std::recursive_mutex> lck(mtx);
return;
} // Check if running
selectedHandler->stopHandler(selectedHandler->ctx); if (!running) { return; }
// Call source if selected and save state
if (selectedSource) { selectedSource->stop(); }
running = false;
}
bool SourceManager::isRunning() {
std::lock_guard<std::recursive_mutex> lck(mtx);
return running;
} }
void SourceManager::tune(double freq) { void SourceManager::tune(double freq) {
if (selectedHandler == NULL) { std::lock_guard<std::recursive_mutex> lck(mtx);
return;
// Save frequency
frequency = freq;
// Call source if selected
if (selectedSource) {
selectedSource->tune(((mode == TUNING_MODE_NORMAL) ? freq : ifFrequency) + offset);
} }
// TODO: No need to always retune the hardware in Panadapter mode
selectedHandler->tuneHandler(((tuneMode == TuningMode::NORMAL) ? freq : ifFreq) + tuneOffset, selectedHandler->ctx);
onRetune.emit(freq);
currentFreq = freq;
} }
void SourceManager::showMenu() {
std::lock_guard<std::recursive_mutex> lck(mtx);
// Call source if selected
if (selectedSource) { selectedSource->showMenu(); }
}
double SourceManager::getSamplerate() {
std::lock_guard<std::recursive_mutex> lck(mtx);
return samplerate;
}
// =========== TODO: These functions should not happen in this class ===========
void SourceManager::setTuningOffset(double offset) { void SourceManager::setTuningOffset(double offset) {
tuneOffset = offset; std::lock_guard<std::recursive_mutex> lck(mtx);
tune(currentFreq);
// Update offset
this->offset = offset;
// Retune to take affect
tune(frequency);
} }
void SourceManager::setTuningMode(TuningMode mode) { void SourceManager::setTuningMode(TuningMode mode) {
tuneMode = mode; std::lock_guard<std::recursive_mutex> lck(mtx);
tune(currentFreq);
// Update mode
this->mode = mode;
// Retune to take affect
tune(frequency);
} }
void SourceManager::setPanadapterIF(double freq) { void SourceManager::setPanadpterIF(double freq) {
ifFreq = freq; std::lock_guard<std::recursive_mutex> lck(mtx);
tune(currentFreq);
// Update offset
ifFrequency = freq;
// Return to take affect if in panadapter mode
if (mode == TUNING_MODE_PANADAPTER) { tune(frequency); }
}
// =============================================================================
void SourceManager::deselect() {
std::lock_guard<std::recursive_mutex> lck(mtx);
// Call source if selected
if (selectedSource) { selectedSource->deselect(); }
// Mark as deselected
selectedSourceName.clear();
selectedSource = NULL;
}
void SourceManager::setSamplerate(double samplerate) {
std::lock_guard<std::recursive_mutex> lck(mtx);
// Save samplerate and emit event
this->samplerate = samplerate;
onSamplerateChanged(samplerate);
} }

View File

@ -1,56 +1,153 @@
#pragma once #pragma once
#include <string> #include <string>
#include <vector> #include <functional>
#include <map> #include <map>
#include <dsp/stream.h> #include <mutex>
#include <dsp/types.h> #include <dsp/types.h>
#include <dsp/stream.h>
#include <utils/event.h> #include <utils/event.h>
enum TuningMode {
TUNING_MODE_NORMAL,
TUNING_MODE_PANADAPTER
};
class Source;
class SourceManager { class SourceManager {
friend Source;
public: public:
SourceManager(); /**
* Register a source.
* @param name Name of the source.
* @param source Pointer to the source instance.
*/
void registerSource(const std::string& name, Source* source);
struct SourceHandler { /**
dsp::stream<dsp::complex_t>* stream; * Unregister a source.
void (*menuHandler)(void* ctx); * @param name Name of the source.
void (*selectHandler)(void* ctx); */
void (*deselectHandler)(void* ctx); void unregisterSource(const std::string& name);
void (*startHandler)(void* ctx);
void (*stopHandler)(void* ctx);
void (*tuneHandler)(double freq, void* ctx);
void* ctx;
};
enum TuningMode { /**
NORMAL, * Get a list of source names.
PANADAPTER * @return List of source names.
}; */
const std::vector<std::string>& getSourceNames();
void registerSource(std::string name, SourceHandler* handler); /**
void unregisterSource(std::string name); * Select a source.
void selectSource(std::string name); * @param name Name of the source.
void showSelectedMenu(); */
void start(); void select(const std::string& name);
/**
* Get the name of the currently selected source.
* @return Name of the source or empty if no source is selected.
*/
const std::string& getSelected();
/**
* Start the radio.
* @return True if the radio started successfully, false if not.
*/
bool start();
/**
* Stop the radio.
*/
void stop(); void stop();
/**
* Check if the radio is running.
* @return True if the radio is running, false if not.
*/
bool isRunning();
/**
* Tune the radio.
* @param freq Frequency in Hz.
*/
void tune(double freq); void tune(double freq);
/**
* Tune the radio.
* @param freq Frequency to tune the radio to.
*/
void showMenu();
/**
* Get the current samplerate of the radio.
* @return Samplerate in Hz.
*/
double getSamplerate();
// =========== TODO: These functions should not happen in this class ===========
/**
* Set offset to add to the tuned frequency.
* @param offset Offset in Hz.
*/
void setTuningOffset(double offset); void setTuningOffset(double offset);
/**
* Set tuning mode.
* @param mode Tuning mode.
*/
void setTuningMode(TuningMode mode); void setTuningMode(TuningMode mode);
void setPanadapterIF(double freq);
std::vector<std::string> getSourceNames(); /**
* Set panadapter mode IF frequency.
* @param freq IF frequency in Hz.
*/
void setPanadpterIF(double freq);
// =============================================================================
// Emitted after a new source has been registered.
Event<std::string> onSourceRegistered; Event<std::string> onSourceRegistered;
// Emitted when a source is about to be unregistered.
Event<std::string> onSourceUnregister; Event<std::string> onSourceUnregister;
// Emitted after a source has been unregistered.
Event<std::string> onSourceUnregistered; Event<std::string> onSourceUnregistered;
// Emitted when the samplerate of the incoming IQ has changed.
Event<double> onSamplerateChanged;
// Emitted when the source manager is instructed to tune the radio.
Event<double> onRetune; Event<double> onRetune;
private: private:
std::map<std::string, SourceHandler*> sources; void deselect();
std::string selectedName; void setSamplerate(double samplerate);
SourceHandler* selectedHandler = NULL;
double tuneOffset; std::vector<std::string> sourceNames;
double currentFreq; std::map<std::string, Source*> sources;
double ifFreq = 0.0;
TuningMode tuneMode = TuningMode::NORMAL; std::string selectedSourceName = "";
dsp::stream<dsp::complex_t> nullSource; Source* selectedSource = NULL;
bool running = false;
double samplerate = 1e6;
double frequency = 100e6;
double offset = 0;
double ifFrequency = 8.830e6;
TuningMode mode = TUNING_MODE_NORMAL;
std::recursive_mutex mtx;
};
class Source {
public:
virtual void showMenu() {}
virtual void select() = 0;
virtual void deselect() {}
virtual bool start() = 0;
virtual void stop() = 0;
virtual void tune(double freq) {}
dsp::stream<dsp::complex_t> stream;
}; };

View File

@ -1,43 +1,51 @@
#pragma once #pragma once
#include <vector> #include <functional>
#include <utils/flog.h> #include <stdexcept>
#include <mutex>
#include <map>
template <class T> typedef int HandlerID;
struct EventHandler {
EventHandler() {}
EventHandler(void (*handler)(T, void*), void* ctx) {
this->handler = handler;
this->ctx = ctx;
}
void (*handler)(T, void*); template <typename... Args>
void* ctx;
};
template <class T>
class Event { class Event {
using Handler = std::function<void(Args...)>;
public: public:
Event() {} HandlerID bind(Handler handler) {
~Event() {} std::lock_guard<std::mutex> lck(mtx);
HandlerID id = genID();
void emit(T value) { handlers[id] = handler;
for (auto const& handler : handlers) { return id;
handler->handler(value, handler->ctx);
}
} }
void bindHandler(EventHandler<T>* handler) { template<typename MHandler, class T>
handlers.push_back(handler); HandlerID bind(MHandler handler, T* ctx) {
return bind([=](Args... args){
(ctx->*handler)(args...);
});
} }
void unbindHandler(EventHandler<T>* handler) { void unbind(HandlerID id) {
if (std::find(handlers.begin(), handlers.end(), handler) == handlers.end()) { std::lock_guard<std::mutex> lck(mtx);
flog::error("Tried to remove a non-existent event handler"); if (handlers.find(id) == handlers.end()) {
return; throw std::runtime_error("Could not unbind handler, unknown ID");
}
handlers.erase(id);
}
void operator()(Args... args) {
std::lock_guard<std::mutex> lck(mtx);
for (const auto& [desc, handler] : handlers) {
handler(args...);
} }
handlers.erase(std::remove(handlers.begin(), handlers.end(), handler), handlers.end());
} }
private: private:
std::vector<EventHandler<T>*> handlers; HandlerID genID() {
int id;
for (id = 1; handlers.find(id) != handlers.end(); id++);
return id;
}
std::map<HandlerID, Handler> handlers;
std::mutex mtx;
}; };

View File

@ -169,7 +169,7 @@ namespace flog {
fprintf(outStream, "] %s\n", out.c_str()); fprintf(outStream, "] %s\n", out.c_str());
#elif defined(__ANDROID__) #elif defined(__ANDROID__)
// Print format string // Print format string
__android_log_print(TYPE_PRIORITIES[type], FLOG_ANDROID_TAG, COLOR_WHITE "[%02d/%02d/%02d %02d:%02d:%02d.%03d] [%s%s" COLOR_WHITE "] %s\n", __android_log_buf_print(LOG_ID_DEFAULT, TYPE_PRIORITIES[type], FLOG_ANDROID_TAG, COLOR_WHITE "[%02d/%02d/%02d %02d:%02d:%02d.%03d] [%s%s" COLOR_WHITE "] %s\n",
nowc->tm_mday, nowc->tm_mon + 1, nowc->tm_year + 1900, nowc->tm_hour, nowc->tm_min, nowc->tm_sec, 0, TYPE_COLORS[type], TYPE_STR[type], out.c_str()); nowc->tm_mday, nowc->tm_mon + 1, nowc->tm_year + 1900, nowc->tm_hour, nowc->tm_min, nowc->tm_sec, 0, TYPE_COLORS[type], TYPE_STR[type], out.c_str());
#else #else
// Print format string // Print format string

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

@ -1,7 +1,6 @@
#include "net.h" #include "net.h"
#include <string.h> #include <string.h>
#include <codecvt> #include <codecvt>
#include <stdexcept>
#ifdef _WIN32 #ifdef _WIN32
#define WOULD_BLOCK (WSAGetLastError() == WSAEWOULDBLOCK) #define WOULD_BLOCK (WSAGetLastError() == WSAEWOULDBLOCK)
@ -86,14 +85,14 @@ namespace net {
addr.sin_port = htons(port); addr.sin_port = htons(port);
} }
std::string Address::getIPStr() const { std::string Address::getIPStr() {
char buf[128]; char buf[128];
IP_t ip = getIP(); IP_t ip = getIP();
sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
return buf; return buf;
} }
IP_t Address::getIP() const { IP_t Address::getIP() {
return htonl(addr.sin_addr.s_addr); return htonl(addr.sin_addr.s_addr);
} }
@ -101,7 +100,7 @@ namespace net {
addr.sin_addr.s_addr = htonl(ip); addr.sin_addr.s_addr = htonl(ip);
} }
int Address::getPort() const { int Address::getPort() {
return htons(addr.sin_port); return htons(addr.sin_port);
} }
@ -138,16 +137,7 @@ namespace net {
} }
int Socket::send(const uint8_t* data, size_t len, const Address* dest) { int Socket::send(const uint8_t* data, size_t len, const Address* dest) {
// Send data return sendto(sock, (const char*)data, len, 0, (sockaddr*)(dest ? &dest->addr : (raddr ? &raddr->addr : NULL)), sizeof(sockaddr_in));
int err = sendto(sock, (const char*)data, len, 0, (sockaddr*)(dest ? &dest->addr : (raddr ? &raddr->addr : NULL)), sizeof(sockaddr_in));
// On error, close socket
if (err <= 0 && !WOULD_BLOCK) {
close();
return err;
}
return err;
} }
int Socket::sendstr(const std::string& str, const Address* dest) { int Socket::sendstr(const std::string& str, const Address* dest) {
@ -169,8 +159,8 @@ namespace net {
// Set timeout // Set timeout
timeval tv; timeval tv;
tv.tv_sec = timeout / 1000; tv.tv_sec = 0;
tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000; tv.tv_usec = timeout * 1000;
// Wait for data // Wait for data
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL); int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
@ -234,8 +224,8 @@ namespace net {
// Define timeout // Define timeout
timeval tv; timeval tv;
tv.tv_sec = timeout / 1000; tv.tv_sec = 0;
tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000; tv.tv_usec = timeout * 1000;
// Wait for data or error // Wait for data or error
if (timeout != NONBLOCKING) { if (timeout != NONBLOCKING) {
@ -298,7 +288,6 @@ namespace net {
// Save data // Save data
for (auto iface = addresses; iface; iface = iface->ifa_next) { for (auto iface = addresses; iface; iface = iface->ifa_next) {
if (!iface->ifa_addr || !iface->ifa_netmask) { continue; }
if (iface->ifa_addr->sa_family != AF_INET) { continue; } if (iface->ifa_addr->sa_family != AF_INET) { continue; }
InterfaceInfo info; InterfaceInfo info;
info.address = ntohl(*(uint32_t*)&iface->ifa_addr->sa_data[2]); info.address = ntohl(*(uint32_t*)&iface->ifa_addr->sa_data[2]);
@ -384,25 +373,13 @@ namespace net {
return connect(Address(host, port)); return connect(Address(host, port));
} }
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr, bool allowBroadcast) { std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr) {
// Init library if needed // Init library if needed
init(); init();
// Create socket // Create socket
SockHandle_t s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); SockHandle_t s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
// If the remote address is multicast, allow multicast connections
#ifdef _WIN32
const char enable = allowBroadcast;
#else
int enable = allowBroadcast;
#endif
if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int)) < 0) {
closeSocket(s);
throw std::runtime_error("Could not enable broadcast on socket");
return NULL;
}
// Bind socket to local port // Bind socket to local port
if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) { if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) {
closeSocket(s); closeSocket(s);
@ -414,15 +391,15 @@ namespace net {
return std::make_shared<Socket>(s, &raddr); return std::make_shared<Socket>(s, &raddr);
} }
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr, bool allowBroadcast) { std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr) {
return openudp(Address(rhost, rport), laddr, allowBroadcast); return openudp(Address(rhost, rport), laddr);
} }
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport, bool allowBroadcast) { std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport) {
return openudp(raddr, Address(lhost, lport), allowBroadcast); return openudp(raddr, Address(lhost, lport));
} }
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int lport, bool allowBroadcast) { std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int lport) {
return openudp(Address(rhost, rport), Address(lhost, lport), allowBroadcast); return openudp(Address(rhost, rport), Address(lhost, lport));
} }
} }

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
#include <string>
#include <mutex> #include <mutex>
#include <memory> #include <memory>
#include <map> #include <map>
@ -67,13 +66,13 @@ namespace net {
* Get the IP address. * Get the IP address.
* @return IP address in standard string format. * @return IP address in standard string format.
*/ */
std::string getIPStr() const; std::string getIPStr();
/** /**
* Get the IP address. * Get the IP address.
* @return IP address in host byte order. * @return IP address in host byte order.
*/ */
IP_t getIP() const; IP_t getIP();
/** /**
* Set the IP address. * Set the IP address.
@ -85,7 +84,7 @@ namespace net {
* Get the TCP/UDP port. * Get the TCP/UDP port.
* @return TCP/UDP port number. * @return TCP/UDP port number.
*/ */
int getPort() const; int getPort();
/** /**
* Set the TCP/UDP port. * Set the TCP/UDP port.
@ -246,37 +245,37 @@ namespace net {
/** /**
* Create UDP socket. * Create UDP socket.
* @param raddr Remote address. Set to a multicast address to allow multicast. * @param raddr Remote address.
* @param laddr Local address to bind the socket to. * @param laddr Local address to bind the socket to.
* @return Socket instance on success, Throws runtime_error otherwise. * @return Socket instance on success, Throws runtime_error otherwise.
*/ */
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr, bool allowBroadcast = false); std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr);
/** /**
* Create UDP socket. * Create UDP socket.
* @param rhost Remote hostname or IP address. Set to a multicast address to allow multicast. * @param rhost Remote hostname or IP address.
* @param rport Remote port. * @param rport Remote port.
* @param laddr Local address to bind the socket to. * @param laddr Local address to bind the socket to.
* @return Socket instance on success, Throws runtime_error otherwise. * @return Socket instance on success, Throws runtime_error otherwise.
*/ */
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr, bool allowBroadcast = false); std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr);
/** /**
* Create UDP socket. * Create UDP socket.
* @param raddr Remote address. Set to a multicast or broadcast address to allow multicast. * @param raddr Remote address.
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any). * @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically). * @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
* @return Socket instance on success, Throws runtime_error otherwise. * @return Socket instance on success, Throws runtime_error otherwise.
*/ */
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost = "0.0.0.0", int lport = 0, bool allowBroadcast = false); std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost = "0.0.0.0", int lport = 0);
/** /**
* Create UDP socket. * Create UDP socket.
* @param rhost Remote hostname or IP address. Set to a multicast or broadcast address to allow multicast. * @param rhost Remote hostname or IP address.
* @param rport Remote port. * @param rport Remote port.
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any). * @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically). * @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
* @return Socket instance on success, Throws runtime_error otherwise. * @return Socket instance on success, Throws runtime_error otherwise.
*/ */
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0, bool allowBroadcast = false); std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0);
} }

View File

@ -1,7 +1,6 @@
#include <utils/networking.h> #include <utils/networking.h>
#include <assert.h> #include <assert.h>
#include <utils/flog.h> #include <utils/flog.h>
#include <stdexcept>
namespace net { namespace net {
@ -320,7 +319,7 @@ namespace net {
} }
entry.handler(std::move(client), entry.ctx); entry.handler(std::move(client), entry.ctx);
} }
catch (const std::exception& e) { catch (std::exception e) {
listening = false; listening = false;
return; return;
} }

View File

@ -1,52 +0,0 @@
#pragma once
#include <functional>
#include <stdexcept>
#include <mutex>
#include <map>
typedef int HandlerID;
template <typename... Args>
class NewEvent {
public:
using Handler = std::function<void(Args...)>;
HandlerID bind(const Handler& handler) {
std::lock_guard<std::mutex> lck(mtx);
HandlerID id = genID();
handlers[id] = handler;
return id;
}
template<typename MHandler, class T>
HandlerID bind(MHandler handler, T* ctx) {
return bind([=](Args... args){
(ctx->*handler)(args...);
});
}
void unbind(HandlerID id) {
std::lock_guard<std::mutex> lck(mtx);
if (handlers.find(id) == handlers.end()) {
throw std::runtime_error("Could not unbind handler, unknown ID");
}
handlers.erase(id);
}
void operator()(Args... args) {
std::lock_guard<std::mutex> lck(mtx);
for (const auto& [desc, handler] : handlers) {
handler(args...);
}
}
private:
HandlerID genID() {
int id;
for (id = 1; handlers.find(id) != handlers.end(); id++);
return id;
}
std::map<HandlerID, Handler> handlers;
std::mutex mtx;
};

View File

@ -8,7 +8,7 @@ class OptionList {
public: public:
OptionList() { updateText(); } OptionList() { updateText(); }
void define(const K& key, const std::string& name, const T& value) { void define(K key, std::string name, T value) {
if (keyExists(key)) { throw std::runtime_error("Key already exists"); } if (keyExists(key)) { throw std::runtime_error("Key already exists"); }
if (nameExists(name)) { throw std::runtime_error("Name already exists"); } if (nameExists(name)) { throw std::runtime_error("Name already exists"); }
if (valueExists(value)) { throw std::runtime_error("Value already exists"); } if (valueExists(value)) { throw std::runtime_error("Value already exists"); }
@ -18,27 +18,27 @@ public:
updateText(); updateText();
} }
void define(const std::string& name, const T& value) { void define(std::string name, T value) {
define(name, name, value); define(name, name, value);
} }
void undefine(int id) { void undefined(int id) {
keys.erase(keys.begin() + id); keys.erase(keys.begin() + id);
names.erase(names.begin() + id); names.erase(names.begin() + id);
values.erase(values.begin() + id); values.erase(values.begin() + id);
updateText(); updateText();
} }
void undefineKey(const K& key) { void undefineKey(K key) {
undefine(keyId(key)); undefined(keyId(key));
} }
void undefineName(const std::string& name) { void undefineName(std::string name) {
undefine(nameId(name)); undefined(nameId(name));
} }
void undefineValue(const T& value) { void undefineValue(T value) {
undefine(valueId(value)); undefined(valueId(value));
} }
void clear() { void clear() {
@ -48,61 +48,61 @@ public:
updateText(); updateText();
} }
int size() const { int size() {
return keys.size(); return keys.size();
} }
bool empty() const { bool empty() {
return keys.empty(); return keys.empty();
} }
bool keyExists(const K& key) const { bool keyExists(K key) {
if (std::find(keys.begin(), keys.end(), key) != keys.end()) { return true; } if (std::find(keys.begin(), keys.end(), key) != keys.end()) { return true; }
return false; return false;
} }
bool nameExists(const std::string& name) const { bool nameExists(std::string name) {
if (std::find(names.begin(), names.end(), name) != names.end()) { return true; } if (std::find(names.begin(), names.end(), name) != names.end()) { return true; }
return false; return false;
} }
bool valueExists(const T& value) const { bool valueExists(T value) {
if (std::find(values.begin(), values.end(), value) != values.end()) { return true; } if (std::find(values.begin(), values.end(), value) != values.end()) { return true; }
return false; return false;
} }
int keyId(const K& key) const { int keyId(K key) {
auto it = std::find(keys.begin(), keys.end(), key); auto it = std::find(keys.begin(), keys.end(), key);
if (it == keys.end()) { throw std::runtime_error("Key doesn't exists"); } if (it == keys.end()) { throw std::runtime_error("Key doesn't exists"); }
return std::distance(keys.begin(), it); return std::distance(keys.begin(), it);
} }
int nameId(const std::string& name) const { int nameId(std::string name) {
auto it = std::find(names.begin(), names.end(), name); auto it = std::find(names.begin(), names.end(), name);
if (it == names.end()) { throw std::runtime_error("Name doesn't exists"); } if (it == names.end()) { throw std::runtime_error("Name doesn't exists"); }
return std::distance(names.begin(), it); return std::distance(names.begin(), it);
} }
int valueId(const T& value) const { int valueId(T value) {
auto it = std::find(values.begin(), values.end(), value); auto it = std::find(values.begin(), values.end(), value);
if (it == values.end()) { throw std::runtime_error("Value doesn't exists"); } if (it == values.end()) { throw std::runtime_error("Value doesn't exists"); }
return std::distance(values.begin(), it); return std::distance(values.begin(), it);
} }
inline const K& key(int id) const { K key(int id) {
return keys[id]; return keys[id];
} }
inline const std::string& name(int id) const { std::string name(int id) {
return names[id]; return names[id];
} }
inline const T& value(int id) const { T value(int id) {
return values[id]; return values[id];
} }
inline const T& operator[](int& id) const { T operator[](int& id) {
return values[id]; return value(id);
} }
const char* txt = NULL; const char* txt = NULL;

View File

@ -257,7 +257,6 @@ namespace net::http {
// Deserialize // Deserialize
req.deserialize(respData); req.deserialize(respData);
return 0; // Might wanna return size instead
} }
int Client::sendResponseHeader(ResponseHeader& resp) { int Client::sendResponseHeader(ResponseHeader& resp) {
@ -275,7 +274,6 @@ namespace net::http {
// Deserialize // Deserialize
resp.deserialize(respData); resp.deserialize(respData);
return 0; // Might wanna return size instead
} }
int Client::sendChunkHeader(ChunkHeader& chdr) { int Client::sendChunkHeader(ChunkHeader& chdr) {

View File

@ -7,14 +7,6 @@ namespace riff {
const char* LIST_SIGNATURE = "LIST"; const char* LIST_SIGNATURE = "LIST";
const size_t RIFF_LABEL_SIZE = 4; const size_t RIFF_LABEL_SIZE = 4;
// Writer::Writer(const Writer&& b) {
// //file = std::move(b.file);
// }
Writer::~Writer() {
close();
}
bool Writer::open(std::string path, const char form[4]) { bool Writer::open(std::string path, const char form[4]) {
std::lock_guard<std::recursive_mutex> lck(mtx); std::lock_guard<std::recursive_mutex> lck(mtx);
@ -99,9 +91,9 @@ namespace riff {
file.write((char*)&desc.hdr.size, sizeof(desc.hdr.size)); file.write((char*)&desc.hdr.size, sizeof(desc.hdr.size));
file.seekp(pos); file.seekp(pos);
// If parent chunk, increment its size by the size of the sub-chunk plus the size of its header) // If parent chunk, increment its size
if (!chunks.empty()) { if (!chunks.empty()) {
chunks.top().hdr.size += desc.hdr.size + sizeof(ChunkHeader); chunks.top().hdr.size += desc.hdr.size;
} }
} }

View File

@ -20,10 +20,6 @@ namespace riff {
class Writer { class Writer {
public: public:
Writer() {}
// Writer(const Writer&& b);
~Writer();
bool open(std::string path, const char form[4]); bool open(std::string path, const char form[4]);
bool isOpen(); bool isOpen();
void close(); void close();
@ -44,23 +40,4 @@ namespace riff {
std::ofstream file; std::ofstream file;
std::stack<ChunkDesc> chunks; std::stack<ChunkDesc> chunks;
}; };
// class Reader {
// public:
// Reader();
// Reader(const Reader&& b);
// ~Reader();
// bool open(std::string path);
// bool isOpen();
// void close();
// const std::string& form();
// private:
// std::string _form;
// std::recursive_mutex mtx;
// std::ofstream file;
// };
} }

View File

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

View File

@ -1,63 +0,0 @@
#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

@ -1,239 +0,0 @@
#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,193 +0,0 @@
#pragma once
#include <dsp/processor.h>
#include <dsp/loop/phase_control_loop.h>
#include <dsp/taps/windowed_sinc.h>
#include <dsp/multirate/polyphase_bank.h>
#include <dsp/math/step.h>
class LineSync : public dsp::Processor<float, float> {
using base_type = dsp::Processor<float, float>;
public:
LineSync() {}
LineSync(dsp::stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) { init(in, omega, omegaGain, muGain, omegaRelLimit, interpPhaseCount, interpTapCount); }
~LineSync() {
if (!base_type::_block_init) { return; }
base_type::stop();
dsp::multirate::freePolyphaseBank(interpBank);
dsp::buffer::free(buffer);
}
void init(dsp::stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
_omega = omega;
_omegaGain = omegaGain;
_muGain = muGain;
_omegaRelLimit = omegaRelLimit;
_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];
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);
base_type::tempStop();
_interpPhaseCount = interpPhaseCount;
_interpTapCount = interpTapCount;
dsp::multirate::freePolyphaseBank(interpBank);
dsp::buffer::free(buffer);
generateInterpTaps();
buffer = dsp::buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
offset = 0;
pcl.phase = 0.0f;
pcl.freq = _omega;
base_type::tempStart();
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
// Copy data to work buffer
memcpy(bufStart, base_type::_in->readBuf, count * sizeof(float));
if (test2) {
test2 = false;
offset += 5;
}
// Process all samples
while (offset < count) {
// 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;
// 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;
for (int i = (720-17); i < 720; i++) {
left += base_type::out.writeBuf[i];
}
for (int i = 0; i < 27; i++) {
left += base_type::out.writeBuf[i];
}
for (int i = 27; i < (54+17); i++) {
right += base_type::out.writeBuf[i];
}
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;
}
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(outCount)) { break; }
outCount = 0;
}
// Advance symbol offset and phase
pcl.advance(error);
float delta = floorf(pcl.phase);
offset += delta;
pcl.phase -= delta;
}
offset -= count;
// Update delay buffer
memmove(buffer, &buffer[count], (_interpTapCount - 1) * sizeof(float));
// Swap if some data was generated
base_type::_in->flush();
return outCount;
}
bool locked = false;
bool test2 = false;
float syncBias = 0.0f;
bool forceLock = false;
int counter = 0;
protected:
void generateInterpTaps() {
double bw = 0.5 / (double)_interpPhaseCount;
dsp::tap<float> lp = dsp::taps::windowedSinc<float>(_interpPhaseCount * _interpTapCount, dsp::math::hzToRads(bw, 1.0), dsp::window::nuttall, _interpPhaseCount);
interpBank = dsp::multirate::buildPolyphaseBank<float>(_interpPhaseCount, lp);
dsp::taps::free(lp);
}
dsp::multirate::PolyphaseBank<float> interpBank;
dsp::loop::PhaseControlLoop<double, false> pcl;
double _omega;
double _omegaGain;
double _muGain;
double _omegaRelLimit;
int _interpPhaseCount;
int _interpTapCount;
int offset = 0;
int outCount = 0;
float* buffer;
float* bufStart;
float syncLevel = -0.03f;
};

View File

@ -10,15 +10,6 @@
#include <dsp/demod/quadrature.h> #include <dsp/demod/quadrature.h>
#include <dsp/sink/handler_sink.h> #include <dsp/sink/handler_sink.h>
#include "linesync.h"
#include <dsp/loop/pll.h>
#include <dsp/convert/real_to_complex.h>
#include <dsp/filter/fir.h>
#include <dsp/taps/from_array.h>
#include "chrominance_filter.h"
#include "chroma_pll.h"
#define CONCAT(a, b) ((std::string(a) + b).c_str()) #define CONCAT(a, b) ((std::string(a) + b).c_str())
@ -26,8 +17,7 @@ SDRPP_MOD_INFO{/* Name: */ "atv_decoder",
/* Description: */ "ATV decoder for SDR++", /* Description: */ "ATV decoder for SDR++",
/* Author: */ "Ryzerth", /* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0, /* Version: */ 0, 1, 0,
/* Max instances */ -1 /* Max instances */ -1};
};
#define SAMPLE_RATE (625.0f * 720.0f * 25.0f) #define SAMPLE_RATE (625.0f * 720.0f * 25.0f)
@ -39,16 +29,9 @@ class ATVDecoderModule : public ModuleManager::Instance {
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 8000000.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);
demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f); demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
sync.init(&demod.out, 1.0f, 1e-6, 1.0, 0.05); sink.init(&demod.out, handler, this);
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));
demod.start(); demod.start();
sync.start();
sink.start(); sink.start();
gui::menu.registerEntry(name, menuHandler, this, this); gui::menu.registerEntry(name, menuHandler, this, this);
@ -64,13 +47,9 @@ class ATVDecoderModule : public ModuleManager::Instance {
void postInit() {} void postInit() {}
void enable() { void enable() { enabled = true; }
enabled = true;
}
void disable() { void disable() { enabled = false; }
enabled = false;
}
bool isEnabled() { return enabled; } bool isEnabled() { return enabled; }
@ -82,8 +61,6 @@ class ATVDecoderModule : public ModuleManager::Instance {
style::beginDisabled(); style::beginDisabled();
} }
// Ideal width for testing: 750pixels
ImGui::FillWidth(); ImGui::FillWidth();
_this->img.draw(); _this->img.draw();
@ -99,28 +76,6 @@ class ATVDecoderModule : public ModuleManager::Instance {
ImGui::FillWidth(); ImGui::FillWidth();
ImGui::SliderFloat("##spanLvl", &_this->spanLvl, 0, 1.0); 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::Checkbox("Force Lock", &_this->sync.forceLock);
if (!_this->enabled) { if (!_this->enabled) {
style::endDisabled(); style::endDisabled();
} }
@ -129,67 +84,70 @@ class ATVDecoderModule : public ModuleManager::Instance {
static void handler(float *data, int count, void *ctx) { static void handler(float *data, int count, void *ctx) {
ATVDecoderModule *_this = (ATVDecoderModule *)ctx; ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
// Convert line to complex uint8_t *buf = (uint8_t *)_this->img.buffer;
_this->r2c.process(720, data, _this->r2c.out.writeBuf); float val;
float imval;
// Isolate the chroma subcarrier int pos = 0;
_this->fir.process(720, _this->r2c.out.writeBuf, _this->fir.out.writeBuf);
// Run chroma carrier through the PLL
_this->pll.process(720, _this->fir.out.writeBuf, _this->pll.out.writeBuf, ((_this->ypos%2)==1) ^ _this->evenFrame);
// 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) ];
//uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[_this->ypos*720];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
int imval = std::clamp<float>((data[i] - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255); val = data[i];
// uint32_t re = std::clamp<float>((_this->pll.out.writeBuf[i].re - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255); // Sync
// uint32_t im = std::clamp<float>((_this->pll.out.writeBuf[i].im - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255); if (val < _this->sync_level) {
// currentLine[i] = 0xFF000000 | (im << 8) | re; _this->sync_count++;
currentLine[i] = 0xFF000000 | (imval << 16) | (imval << 8) | imval;
}
// Vertical scan logic
_this->ypos++;
bool rollover = _this->ypos >= 625;
if (rollover) {
{
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
} }
_this->ypos = 0; else {
_this->img.swap(); if (_this->sync_count >= 300) {
} _this->short_sync = 0;
}
// Measure vsync levels else if (_this->sync_count >= 33) {
float sync0 = 0.0f, sync1 = 0.0f; if (_this->short_sync == 5) {
for (int i = 0; i < 306; i++) { _this->even_field = false;
sync0 += data[i]; _this->ypos = 0;
} _this->img.swap();
for (int i = (720/2); i < ((720/2)+306); i++) { buf = (uint8_t *)_this->img.buffer;
sync1 += data[i]; }
} else if (_this->short_sync == 4) {
sync0 *= (1.0f/305.0f); _this->even_field = true;
sync1 *= (1.0f/305.0f); _this->ypos = 0;
}
// Save sync detection to history _this->xpos = 0;
_this->syncHistory >>= 2; _this->short_sync = 0;
_this->syncHistory |= (((uint16_t)(sync1 < _this->sync_level)) << 9) | (((uint16_t)(sync0 < _this->sync_level)) << 8); }
else if (_this->sync_count >= 15) {
// Trigger vsync in case one is detected _this->short_sync++;
// TODO: Also sync with odd field }
if (!rollover && _this->syncHistory == 0b0000011111) { _this->sync_count = 0;
{ }
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
// Draw
imval = std::clamp<float>((val - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
if (_this->even_field) {
pos = ((720 * _this->ypos * 2) + _this->xpos) * 4;
}
else {
pos = ((720 * (_this->ypos * 2 + 1)) + _this->xpos) * 4;
}
buf[pos] = imval;
buf[pos + 1] = imval;
buf[pos + 2] = imval;
buf[pos + 3] = imval;
// Image logic
_this->xpos++;
if (_this->xpos >= 720) {
_this->ypos++;
_this->xpos = 0;
}
if (_this->ypos >= 312) {
_this->ypos = 0;
_this->xpos = 0;
_this->even_field = !_this->even_field;
if (_this->even_field) {
_this->img.swap();
buf = (uint8_t *)_this->img.buffer;
}
} }
_this->ypos = 0;
_this->img.swap();
} }
} }
@ -198,27 +156,19 @@ class ATVDecoderModule : public ModuleManager::Instance {
VFOManager::VFO *vfo = NULL; VFOManager::VFO *vfo = NULL;
dsp::demod::Quadrature demod; dsp::demod::Quadrature demod;
LineSync sync;
dsp::sink::Handler<float> sink; dsp::sink::Handler<float> sink;
dsp::convert::RealToComplex r2c;
dsp::tap<dsp::complex_t> chromaTaps; int xpos = 0;
dsp::filter::FIR<dsp::complex_t, dsp::complex_t> fir;
dsp::loop::ChromaPLL pll;
int ypos = 0; int ypos = 0;
bool even_field = false;
bool evenFrame = false; float sync_level = -0.3f;
std::mutex evenFrameMtx;
float sync_level = -0.06f;
int sync_count = 0; int sync_count = 0;
int short_sync = 0; int short_sync = 0;
float minLvl = 0.0f; float minLvl = 0.0f;
float spanLvl = 1.0f; float spanLvl = 1.0f;
bool lockedLines = 0;
uint16_t syncHistory = 0;
ImGui::ImageDisplay img; 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

@ -214,10 +214,10 @@ private:
if (ImGui::Checkbox(CONCAT("Show Reference Lines##m17_showlines_", _this->name), &_this->showLines)) { if (ImGui::Checkbox(CONCAT("Show Reference Lines##m17_showlines_", _this->name), &_this->showLines)) {
if (_this->showLines) { if (_this->showLines) {
_this->diag.lines.push_back(-1.0); _this->diag.lines.push_back(-0.75f);
_this->diag.lines.push_back(-1.0/3.0); _this->diag.lines.push_back(-0.25f);
_this->diag.lines.push_back(1.0/3.0); _this->diag.lines.push_back(0.25f);
_this->diag.lines.push_back(1.0); _this->diag.lines.push_back(0.75f);
} }
else { else {
_this->diag.lines.clear(); _this->diag.lines.clear();

View File

@ -57,13 +57,10 @@ public:
if (config.conf[name].contains("brokenModulation")) { if (config.conf[name].contains("brokenModulation")) {
brokenModulation = config.conf[name]["brokenModulation"]; brokenModulation = config.conf[name]["brokenModulation"];
} }
if (config.conf[name].contains("oqpsk")) {
oqpsk = config.conf[name]["oqpsk"];
}
config.release(); 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); vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 150000, INPUT_SAMPLE_RATE, 150000, 150000, true);
demod.init(vfo->output, 72000.0f, INPUT_SAMPLE_RATE, 33, 0.6f, 0.1f, 0.005f, brokenModulation, oqpsk, 1e-6, 0.01); demod.init(vfo->output, 72000.0f, INPUT_SAMPLE_RATE, 33, 0.6f, 0.1f, 0.005f, brokenModulation, 1e-6, 0.01);
split.init(&demod.out); split.init(&demod.out);
split.bindStream(&symSinkStream); split.bindStream(&symSinkStream);
split.bindStream(&sinkStream); split.bindStream(&sinkStream);
@ -102,7 +99,6 @@ public:
double bw = gui::waterfall.getBandwidth(); double bw = gui::waterfall.getBandwidth();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), 150000, INPUT_SAMPLE_RATE, 150000, 150000, true); vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), 150000, INPUT_SAMPLE_RATE, 150000, 150000, true);
demod.setBrokenModulation(brokenModulation);
demod.setInput(vfo->output); demod.setInput(vfo->output);
demod.start(); demod.start();
@ -155,13 +151,6 @@ private:
config.release(true); config.release(true);
} }
if (ImGui::Checkbox(CONCAT("OQPSK##oqpsk", _this->name), &_this->oqpsk)) {
_this->demod.setOQPSK(_this->oqpsk);
config.acquire();
config.conf[_this->name]["oqpsk"] = _this->oqpsk;
config.release(true);
}
if (!_this->folderSelect.pathIsValid() && _this->enabled) { style::beginDisabled(); } if (!_this->folderSelect.pathIsValid() && _this->enabled) { style::beginDisabled(); }
if (_this->recording) { if (_this->recording) {
@ -256,7 +245,7 @@ private:
uint64_t dataWritten = 0; uint64_t dataWritten = 0;
std::ofstream recFile; std::ofstream recFile;
bool brokenModulation = false; bool brokenModulation = false;
bool oqpsk = false;
int8_t* writeBuffer; int8_t* writeBuffer;
}; };

View File

@ -11,8 +11,8 @@ namespace dsp::demod {
public: public:
Meteor() {} Meteor() {}
Meteor(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, bool oqpsk, double omegaGain, double muGain, double omegaRelLimit = 0.01) { Meteor(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
init(in, symbolrate, samplerate, rrcTapCount, rrcBeta, agcRate, costasBandwidth, brokenModulation, oqpsk, omegaGain, muGain); init(in, symbolrate, samplerate, rrcTapCount, rrcBeta, agcRate, costasBandwidth, brokenModulation, omegaGain, muGain);
} }
~Meteor() { ~Meteor() {
@ -21,12 +21,11 @@ namespace dsp::demod {
taps::free(rrcTaps); taps::free(rrcTaps);
} }
void init(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, bool oqpsk, double omegaGain, double muGain, double omegaRelLimit = 0.01) { void init(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
_symbolrate = symbolrate; _symbolrate = symbolrate;
_samplerate = samplerate; _samplerate = samplerate;
_rrcTapCount = rrcTapCount; _rrcTapCount = rrcTapCount;
_rrcBeta = rrcBeta; _rrcBeta = rrcBeta;
_oqpsk = oqpsk;
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate); rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
rrc.init(NULL, rrcTaps); rrc.init(NULL, rrcTaps);
@ -130,12 +129,6 @@ namespace dsp::demod {
costas.setBrokenModulation(enabled); costas.setBrokenModulation(enabled);
} }
void setOQPSK(bool enabled) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_oqpsk = enabled;
}
void reset() { void reset() {
assert(base_type::_block_init); assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
@ -151,18 +144,6 @@ namespace dsp::demod {
rrc.process(count, in, out); rrc.process(count, in, out);
agc.process(count, out, out); agc.process(count, out, out);
costas.process(count, out, out); costas.process(count, out, out);
if (_oqpsk) {
// Single sample delay + deinterleave
for (int i = 0; i < count; i++) {
float tmp = out[i].im;
out[i].im = lastI;
lastI = tmp;
}
// TODO: Additional 1/24th sample delay
}
return recov.process(count, out, out); return recov.process(count, out, out);
} }
@ -185,8 +166,6 @@ namespace dsp::demod {
double _samplerate; double _samplerate;
int _rrcTapCount; int _rrcTapCount;
double _rrcBeta; double _rrcBeta;
float lastI = 0.0f;
bool _oqpsk = false;
tap<float> rrcTaps; tap<float> rrcTaps;
filter::FIR<complex_t, float> rrc; filter::FIR<complex_t, float> rrc;

View File

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

View File

@ -1,11 +0,0 @@
#pragma once
#include <signal_path/vfo_manager.h>
class Decoder {
public:
virtual ~Decoder() {}
virtual void showMenu() {};
virtual void setVFO(VFOManager::VFO* vfo) = 0;
virtual void start() = 0;
virtual void stop() = 0;
};

View File

@ -1,96 +0,0 @@
#pragma once
#include "../decoder.h"
#include <signal_path/vfo_manager.h>
#include <utils/optionlist.h>
#include <gui/widgets/symbol_diagram.h>
#include <gui/style.h>
#include <dsp/sink/handler_sink.h>
#include "flex.h"
class FLEXDecoder : public Decoder {
dsp::stream<float> dummy1;
dsp::stream<uint8_t> dummy2;
public:
FLEXDecoder(const std::string& name, VFOManager::VFO* vfo) : diag(0.6, 1600) {
this->name = name;
this->vfo = vfo;
// Define baudrate options
baudrates.define(1600, "1600 Baud", 1600);
baudrates.define(3200, "3200 Baud", 3200);
baudrates.define(6400, "6400 Baud", 6400);
// Init DSP
vfo->setBandwidthLimits(12500, 12500, true);
vfo->setSampleRate(16000, 12500);
reshape.init(&dummy1, 1600.0, (1600 / 30.0) - 1600.0);
dataHandler.init(&dummy2, _dataHandler, this);
diagHandler.init(&reshape.out, _diagHandler, this);
}
~FLEXDecoder() {
stop();
}
void showMenu() {
ImGui::LeftLabel("Baudrate");
ImGui::FillWidth();
if (ImGui::Combo(("##pager_decoder_flex_br_" + name).c_str(), &brId, baudrates.txt)) {
// TODO
}
ImGui::FillWidth();
diag.draw();
}
void setVFO(VFOManager::VFO* vfo) {
this->vfo = vfo;
vfo->setBandwidthLimits(12500, 12500, true);
vfo->setSampleRate(24000, 12500);
// dsp.setInput(vfo->output);
}
void start() {
flog::debug("FLEX start");
// dsp.start();
reshape.start();
dataHandler.start();
diagHandler.start();
}
void stop() {
flog::debug("FLEX stop");
// dsp.stop();
reshape.stop();
dataHandler.stop();
diagHandler.stop();
}
private:
static void _dataHandler(uint8_t* data, int count, void* ctx) {
FLEXDecoder* _this = (FLEXDecoder*)ctx;
// _this->decoder.process(data, count);
}
static void _diagHandler(float* data, int count, void* ctx) {
FLEXDecoder* _this = (FLEXDecoder*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
std::string name;
VFOManager::VFO* vfo;
dsp::buffer::Reshaper<float> reshape;
dsp::sink::Handler<uint8_t> dataHandler;
dsp::sink::Handler<float> diagHandler;
flex::Decoder decoder;
ImGui::SymbolDiagram diag;
int brId = 0;
OptionList<int, int> baudrates;
};

View File

@ -1,5 +0,0 @@
#include "flex.h"
namespace flex {
// TODO
}

View File

@ -1,11 +0,0 @@
#pragma once
namespace flex {
class Decoder {
public:
// TODO
private:
// TODO
};
}

View File

@ -1,172 +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 <gui/widgets/folder_select.h>
#include <utils/optionlist.h>
#include "decoder.h"
#include "pocsag/decoder.h"
#include "flex/decoder.h"
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
/* Name: */ "pager_decoder",
/* Description: */ "POCSAG and Flex Pager Decoder"
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
enum Protocol {
PROTOCOL_INVALID = -1,
PROTOCOL_POCSAG,
PROTOCOL_FLEX
};
class PagerDecoderModule : public ModuleManager::Instance {
public:
PagerDecoderModule(std::string name) {
this->name = name;
// Define protocols
protocols.define("POCSAG", PROTOCOL_POCSAG);
//protocols.define("FLEX", PROTOCOL_FLEX);
// Initialize VFO with default values
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 12500, 24000, 12500, 12500, true);
vfo->setSnapInterval(1);
// Select the protocol
selectProtocol(PROTOCOL_POCSAG);
gui::menu.registerEntry(name, menuHandler, this, this);
}
~PagerDecoderModule() {
gui::menu.removeEntry(name);
// Stop DSP
if (enabled) {
decoder->stop();
decoder.reset();
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), 12500, 24000, 12500, 12500, true);
vfo->setSnapInterval(1);
decoder->setVFO(vfo);
decoder->start();
enabled = true;
}
void disable() {
decoder->stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
void selectProtocol(Protocol newProto) {
// Cannot change while disabled
if (!enabled) { return; }
// If the protocol hasn't changed, no need to do anything
if (newProto == proto) { return; }
// Delete current decoder
decoder.reset();
// Create a new decoder
switch (newProto) {
case PROTOCOL_POCSAG:
decoder = std::make_unique<POCSAGDecoder>(name, vfo);
break;
case PROTOCOL_FLEX:
decoder = std::make_unique<FLEXDecoder>(name, vfo);
break;
default:
flog::error("Tried to select unknown pager protocol");
return;
}
// Start the new decoder
decoder->start();
// Save selected protocol
proto = newProto;
}
private:
static void menuHandler(void* ctx) {
PagerDecoderModule* _this = (PagerDecoderModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvail().x;
if (!_this->enabled) { style::beginDisabled(); }
ImGui::LeftLabel("Protocol");
ImGui::FillWidth();
if (ImGui::Combo(("##pager_decoder_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
_this->selectProtocol(_this->protocols.value(_this->protoId));
}
if (_this->decoder) { _this->decoder->showMenu(); }
ImGui::Button(("Record##pager_decoder_show_" + _this->name).c_str(), ImVec2(menuWidth, 0));
ImGui::Button(("Show Messages##pager_decoder_show_" + _this->name).c_str(), ImVec2(menuWidth, 0));
if (!_this->enabled) { style::endDisabled(); }
}
std::string name;
bool enabled = true;
Protocol proto = PROTOCOL_INVALID;
int protoId = 0;
OptionList<std::string, Protocol> protocols;
// DSP Chain
VFOManager::VFO* vfo;
std::unique_ptr<Decoder> decoder;
bool showLines = false;
};
MOD_EXPORT void _INIT_() {
// Create default recording directory
json def = json({});
config.setPath(core::args["root"].s() + "/pager_decoder_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new PagerDecoderModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (PagerDecoderModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -1,105 +0,0 @@
#pragma once
#include "../decoder.h"
#include <signal_path/vfo_manager.h>
#include <utils/optionlist.h>
#include <gui/widgets/symbol_diagram.h>
#include <gui/style.h>
#include <dsp/sink/handler_sink.h>
#include "dsp.h"
#include "pocsag.h"
#define BAUDRATE 2400
#define SAMPLERATE (BAUDRATE*10)
class POCSAGDecoder : public Decoder {
public:
POCSAGDecoder(const std::string& name, VFOManager::VFO* vfo) : diag(0.6, BAUDRATE) {
this->name = name;
this->vfo = vfo;
// Define baudrate options
baudrates.define(512, "512 Baud", 512);
baudrates.define(1200, "1200 Baud", 1200);
baudrates.define(2400, "2400 Baud", 2400);
// Init DSP
vfo->setBandwidthLimits(12500, 12500, true);
vfo->setSampleRate(SAMPLERATE, 12500);
dsp.init(vfo->output, SAMPLERATE, BAUDRATE);
reshape.init(&dsp.soft, BAUDRATE, (BAUDRATE / 30.0) - BAUDRATE);
dataHandler.init(&dsp.out, _dataHandler, this);
diagHandler.init(&reshape.out, _diagHandler, this);
// Init decoder
decoder.onMessage.bind(&POCSAGDecoder::messageHandler, this);
}
~POCSAGDecoder() {
stop();
}
void showMenu() {
ImGui::LeftLabel("Baudrate");
ImGui::FillWidth();
if (ImGui::Combo(("##pager_decoder_pocsag_br_" + name).c_str(), &brId, baudrates.txt)) {
// TODO
}
ImGui::FillWidth();
diag.draw();
}
void setVFO(VFOManager::VFO* vfo) {
this->vfo = vfo;
vfo->setBandwidthLimits(12500, 12500, true);
vfo->setSampleRate(24000, 12500);
dsp.setInput(vfo->output);
}
void start() {
dsp.start();
reshape.start();
dataHandler.start();
diagHandler.start();
}
void stop() {
dsp.stop();
reshape.stop();
dataHandler.stop();
diagHandler.stop();
}
private:
static void _dataHandler(uint8_t* data, int count, void* ctx) {
POCSAGDecoder* _this = (POCSAGDecoder*)ctx;
_this->decoder.process(data, count);
}
static void _diagHandler(float* data, int count, void* ctx) {
POCSAGDecoder* _this = (POCSAGDecoder*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
void messageHandler(pocsag::Address addr, pocsag::MessageType type, const std::string& msg) {
flog::debug("[{}]: '{}'", (uint32_t)addr, msg);
}
std::string name;
VFOManager::VFO* vfo;
POCSAGDSP dsp;
dsp::buffer::Reshaper<float> reshape;
dsp::sink::Handler<uint8_t> dataHandler;
dsp::sink::Handler<float> diagHandler;
pocsag::Decoder decoder;
ImGui::SymbolDiagram diag;
int brId = 2;
OptionList<int, int> baudrates;
};

View File

@ -1,76 +0,0 @@
#pragma once
#include <dsp/stream.h>
#include <dsp/buffer/reshaper.h>
#include <dsp/multirate/rational_resampler.h>
#include <dsp/sink/handler_sink.h>
#include <dsp/demod/quadrature.h>
#include <dsp/clock_recovery/mm.h>
#include <dsp/taps/root_raised_cosine.h>
#include <dsp/correction/dc_blocker.h>
#include <dsp/loop/fast_agc.h>
#include <dsp/digital/binary_slicer.h>
#include <dsp/routing/doubler.h>
class POCSAGDSP : public dsp::Processor<dsp::complex_t, uint8_t> {
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
public:
POCSAGDSP() {}
POCSAGDSP(dsp::stream<dsp::complex_t>* in, double samplerate, double baudrate) { init(in, samplerate, baudrate); }
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double baudrate) {
// Save settings
_samplerate = samplerate;
// Configure blocks
demod.init(NULL, -4500.0, samplerate);
float taps[] = { 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f };
shape = dsp::taps::fromArray<float>(10, taps);
fir.init(NULL, shape);
recov.init(NULL, samplerate/baudrate, 1e-4, 1.0, 0.05);
// Free useless buffers
fir.out.free();
recov.out.free();
// Init base
base_type::init(in);
}
int process(int count, dsp::complex_t* in, float* softOut, uint8_t* out) {
count = demod.process(count, in, demod.out.readBuf);
count = fir.process(count, demod.out.readBuf, demod.out.readBuf);
count = recov.process(count, demod.out.readBuf, softOut);
dsp::digital::BinarySlicer::process(count, softOut, out);
return count;
}
void setBaudrate(double baudrate) {
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; }
count = process(count, base_type::_in->readBuf, soft.writeBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
if (count) { if (!soft.swap(count)) { return -1; } }
return count;
}
dsp::stream<float> soft;
private:
dsp::demod::Quadrature demod;
dsp::tap<float> shape;
dsp::filter::FIR<float, float> fir;
dsp::clock_recovery::MM<float> recov;
double _samplerate;
};

View File

@ -1,173 +0,0 @@
#include "pocsag.h"
#include <string.h>
#include <utils/flog.h>
#define POCSAG_FRAME_SYNC_CODEWORD ((uint32_t)(0b01111100110100100001010111011000))
#define POCSAG_IDLE_CODEWORD_DATA ((uint32_t)(0b011110101100100111000))
#define POCSAG_BATCH_BIT_COUNT (POCSAG_BATCH_CODEWORD_COUNT*32)
#define POCSAG_DATA_BITS_PER_CW 20
#define POCSAG_GEN_POLY ((uint32_t)(0b11101101001))
namespace pocsag {
const char NUMERIC_CHARSET[] = {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'*',
'U',
' ',
'-',
']',
'['
};
Decoder::Decoder() {
// Zero out batch
memset(batch, 0, sizeof(batch));
}
void Decoder::process(uint8_t* symbols, int count) {
for (int i = 0; i < count; i++) {
// Get symbol
uint32_t s = symbols[i];
// If not sync, try to acquire sync (TODO: sync confidence)
if (!synced) {
// Append new symbol to sync shift register
syncSR = (syncSR << 1) | s;
// Test for sync
synced = (distance(syncSR, POCSAG_FRAME_SYNC_CODEWORD) <= POCSAG_SYNC_DIST);
// Go to next symbol
continue;
}
// TODO: Flush message on desync
// Append bit to batch
batch[batchOffset >> 5] |= (s << (31 - (batchOffset & 0b11111)));
batchOffset++;
// On end of batch, decode and reset
if (batchOffset >= POCSAG_BATCH_BIT_COUNT) {
decodeBatch();
batchOffset = 0;
synced = false;
memset(batch, 0, sizeof(batch));
}
}
}
int Decoder::distance(uint32_t a, uint32_t b) {
uint32_t diff = a ^ b;
int dist = 0;
for (int i = 0; i < 32; i++) {
dist += (diff >> i ) & 1;
}
return dist;
}
bool Decoder::correctCodeword(Codeword in, Codeword& out) {
return true; // TODO
}
void Decoder::flushMessage() {
if (!msg.empty()) {
// Send out message
onMessage(addr, msgType, msg);
// Reset state
msg.clear();
currChar = 0;
currOffset = 0;
}
}
void printbin(uint32_t cw) {
for (int i = 31; i >= 0; i--) {
printf("%c", ((cw >> i) & 1) ? '1':'0');
}
}
void bitswapChar(char in, char& out) {
out = 0;
for (int i = 0; i < 7; i++) {
out |= ((in >> (6-i)) & 1) << i;
}
}
void Decoder::decodeBatch() {
for (int i = 0; i < POCSAG_BATCH_CODEWORD_COUNT; i++) {
// Get codeword
Codeword cw = batch[i];
// Correct errors. If corrupted, skip
if (!correctCodeword(cw, cw)) { continue; }
// TODO: End message if two consecutive are corrupt
// Get codeword type
CodewordType type = (CodewordType)((cw >> 31) & 1);
if (type == CODEWORD_TYPE_ADDRESS && (cw >> 11) == POCSAG_IDLE_CODEWORD_DATA) {
type = CODEWORD_TYPE_IDLE;
}
// Decode codeword
if (type == CODEWORD_TYPE_IDLE) {
// If a non-empty message is available, send it out and clear
flushMessage();
}
else if (type == CODEWORD_TYPE_ADDRESS) {
// If a non-empty message is available, send it out and clear
flushMessage();
// Decode message type
msgType = MESSAGE_TYPE_ALPHANUMERIC;
// msgType = (MessageType)((cw >> 11) & 0b11);
// Decode address and append lower 8 bits from position
addr = ((cw >> 13) & 0b111111111111111111) << 3;
addr |= (i >> 1);
}
else if (type == CODEWORD_TYPE_MESSAGE) {
// Extract the 20 data bits
uint32_t data = (cw >> 11) & 0b11111111111111111111;
// Decode data depending on message type
if (msgType == MESSAGE_TYPE_NUMERIC) {
// Numeric messages pack 5 characters per message codeword
msg += NUMERIC_CHARSET[(data >> 16) & 0b1111];
msg += NUMERIC_CHARSET[(data >> 12) & 0b1111];
msg += NUMERIC_CHARSET[(data >> 8) & 0b1111];
msg += NUMERIC_CHARSET[(data >> 4) & 0b1111];
msg += NUMERIC_CHARSET[data & 0b1111];
}
else if (msgType == MESSAGE_TYPE_ALPHANUMERIC) {
// Unpack ascii bits 7 at a time (TODO: could be more efficient)
for (int i = 19; i >= 0; i--) {
// Append bit to char
currChar |= ((data >> i) & 1) << (currOffset++);
// When the char is full, append to message
if (currOffset >= 7) {
// TODO: maybe replace with std::isprint
if (currChar) { msg += currChar; }
currChar = 0;
currOffset = 0;
}
}
}
}
}
}
}

View File

@ -1,51 +0,0 @@
#pragma once
#include <string>
#include <stdint.h>
#include <utils/new_event.h>
#define POCSAG_SYNC_DIST 4
#define POCSAG_BATCH_CODEWORD_COUNT 16
namespace pocsag {
enum CodewordType {
CODEWORD_TYPE_IDLE = -1,
CODEWORD_TYPE_ADDRESS = 0,
CODEWORD_TYPE_MESSAGE = 1
};
enum MessageType {
MESSAGE_TYPE_NUMERIC = 0b00,
MESSAGE_TYPE_ALPHANUMERIC = 0b11
};
using Codeword = uint32_t;
using Address = uint32_t;
class Decoder {
public:
Decoder();
void process(uint8_t* symbols, int count);
NewEvent<Address, MessageType, const std::string&> onMessage;
private:
static int distance(uint32_t a, uint32_t b);
bool correctCodeword(Codeword in, Codeword& out);
void flushMessage();
void decodeBatch();
uint32_t syncSR = 0;
bool synced = false;
int batchOffset = 0;
Codeword batch[POCSAG_BATCH_CODEWORD_COUNT];
Address addr;
MessageType msgType;
std::string msg;
char currChar = 0;
int currOffset = 0;
};
}

View File

@ -19,17 +19,15 @@ namespace demod {
// Load config // Load config
_config->acquire(); _config->acquire();
bool modified = false;
if (config->conf[name][getName()].contains("lowPass")) { if (config->conf[name][getName()].contains("lowPass")) {
_lowPass = config->conf[name][getName()]["lowPass"]; _lowPass = config->conf[name][getName()]["lowPass"];
} }
if (config->conf[name][getName()].contains("highPass")) { _config->release(modified);
_highPass = config->conf[name][getName()]["highPass"];
}
_config->release();
// Define structure // Define structure
demod.init(input, getIFSampleRate(), bandwidth, _lowPass, _highPass); demod.init(input, getIFSampleRate(), bandwidth, _lowPass);
} }
void start() { demod.start(); } void start() { demod.start(); }
@ -43,12 +41,6 @@ namespace demod {
_config->conf[name][getName()]["lowPass"] = _lowPass; _config->conf[name][getName()]["lowPass"] = _lowPass;
_config->release(true); _config->release(true);
} }
if (ImGui::Checkbox(("High Pass##_radio_wfm_highpass_" + name).c_str(), &_highPass)) {
demod.setHighPass(_highPass);
_config->acquire();
_config->conf[name][getName()]["highPass"] = _highPass;
_config->release(true);
}
} }
void setBandwidth(double bandwidth) { void setBandwidth(double bandwidth) {
@ -83,7 +75,6 @@ namespace demod {
ConfigManager* _config = NULL; ConfigManager* _config = NULL;
bool _lowPass = true; bool _lowPass = true;
bool _highPass = false;
std::string name; std::string name;
}; };

View File

@ -1,22 +1,22 @@
#pragma once #pragma once
#include "../demod.h" #include "../demod.h"
#include <dsp/demod/broadcast_fm.h> #include <dsp/demod/broadcast_fm.h>
#include "../rds_demod.h" #include <dsp/clock_recovery/mm.h>
#include <dsp/clock_recovery/fd.h>
#include <dsp/taps/root_raised_cosine.h>
#include <dsp/digital/binary_slicer.h>
#include <dsp/digital/manchester_decoder.h>
#include <dsp/digital/differential_decoder.h>
#include <gui/widgets/symbol_diagram.h> #include <gui/widgets/symbol_diagram.h>
#include <fstream> #include <fstream>
#include <rds.h> #include <rds.h>
namespace demod { namespace demod {
enum RDSRegion {
RDS_REGION_EUROPE,
RDS_REGION_NORTH_AMERICA
};
class WFM : public Demodulator { class WFM : public Demodulator {
public: public:
WFM() : diag(0.5, 4096) {} WFM() {}
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) : diag(0.5, 4096) { WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR); init(name, config, input, bandwidth, audioSR);
} }
@ -29,18 +29,10 @@ namespace demod {
this->name = name; this->name = name;
_config = config; _config = config;
// Define RDS regions
rdsRegions.define("eu", "Europe", RDS_REGION_EUROPE);
rdsRegions.define("na", "North America", RDS_REGION_NORTH_AMERICA);
// Register FFT draw handler
fftRedrawHandler.handler = fftRedraw; fftRedrawHandler.handler = fftRedraw;
fftRedrawHandler.ctx = this; fftRedrawHandler.ctx = this;
gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler); gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler);
// Default
std::string rdsRegionStr = "eu";
// Load config // Load config
_config->acquire(); _config->acquire();
bool modified = false; bool modified = false;
@ -53,50 +45,33 @@ namespace demod {
if (config->conf[name][getName()].contains("rds")) { if (config->conf[name][getName()].contains("rds")) {
_rds = config->conf[name][getName()]["rds"]; _rds = config->conf[name][getName()]["rds"];
} }
if (config->conf[name][getName()].contains("rdsInfo")) {
_rdsInfo = config->conf[name][getName()]["rdsInfo"];
}
if (config->conf[name][getName()].contains("rdsRegion")) {
rdsRegionStr = config->conf[name][getName()]["rdsRegion"];
}
_config->release(modified); _config->release(modified);
// Load RDS region // Define structure
if (rdsRegions.keyExists(rdsRegionStr)) {
rdsRegionId = rdsRegions.keyId(rdsRegionStr);
rdsRegion = rdsRegions.value(rdsRegionId);
}
else {
rdsRegion = RDS_REGION_EUROPE;
rdsRegionId = rdsRegions.valueId(rdsRegion);
}
// Init DSP
demod.init(input, bandwidth / 2.0f, getIFSampleRate(), _stereo, _lowPass, _rds); demod.init(input, bandwidth / 2.0f, getIFSampleRate(), _stereo, _lowPass, _rds);
rdsDemod.init(&demod.rdsOut, _rdsInfo); recov.init(&demod.rdsOut, 5000.0 / 2375, omegaGain, muGain, 0.01);
hs.init(&rdsDemod.out, rdsHandler, this); slice.init(&recov.out);
reshape.init(&rdsDemod.soft, 4096, (1187 / 30) - 4096); manch.init(&slice.out);
diagHandler.init(&reshape.out, _diagHandler, this); diff.init(&manch.out, 2);
hs.init(&diff.out, rdsHandler, this);
// Init RDS display
diag.lines.push_back(-0.8);
diag.lines.push_back(0.8);
} }
void start() { void start() {
demod.start(); demod.start();
rdsDemod.start(); recov.start();
slice.start();
manch.start();
diff.start();
hs.start(); hs.start();
reshape.start();
diagHandler.start();
} }
void stop() { void stop() {
demod.stop(); demod.stop();
rdsDemod.stop(); recov.stop();
slice.stop();
manch.stop();
diff.stop();
hs.stop(); hs.stop();
reshape.stop();
diagHandler.stop();
} }
void showMenu() { void showMenu() {
@ -119,129 +94,14 @@ namespace demod {
_config->release(true); _config->release(true);
} }
// TODO: This might break when the entire radio module is disabled // if (_rds) {
if (!_rds) { ImGui::BeginDisabled(); } // if (rdsDecode.countryCodeValid()) { ImGui::Text("Country code: %d", rdsDecode.getCountryCode()); }
if (ImGui::Checkbox(("Advanced RDS Info##_radio_wfm_rds_info_" + name).c_str(), &_rdsInfo)) { // if (rdsDecode.programCoverageValid()) { ImGui::Text("Program coverage: %d", rdsDecode.getProgramCoverage()); }
setAdvancedRds(_rdsInfo); // if (rdsDecode.programRefNumberValid()) { ImGui::Text("Reference number: %d", rdsDecode.getProgramRefNumber()); }
_config->acquire(); // if (rdsDecode.programTypeValid()) { ImGui::Text("Program type: %d", rdsDecode.getProgramType()); }
_config->conf[name][getName()]["rdsInfo"] = _rdsInfo; // if (rdsDecode.PSNameValid()) { ImGui::Text("Program name: [%s]", rdsDecode.getPSName().c_str()); }
_config->release(true); // if (rdsDecode.radioTextValid()) { ImGui::Text("Radiotext: [%s]", rdsDecode.getRadioText().c_str()); }
} // }
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::Combo(("##_radio_wfm_rds_region_" + name).c_str(), &rdsRegionId, rdsRegions.txt)) {
rdsRegion = rdsRegions.value(rdsRegionId);
_config->acquire();
_config->conf[name][getName()]["rdsRegion"] = rdsRegions.key(rdsRegionId);
_config->release(true);
}
if (!_rds) { ImGui::EndDisabled(); }
float menuWidth = ImGui::GetContentRegionAvail().x;
if (_rds && _rdsInfo) {
ImGui::BeginTable(("##radio_wfm_rds_info_tbl_" + name).c_str(), 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders);
if (rdsDecode.piCodeValid()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("PI Code");
ImGui::TableSetColumnIndex(1);
if (rdsRegion == RDS_REGION_NORTH_AMERICA) {
ImGui::Text("0x%04X (%s)", rdsDecode.getPICode(), rdsDecode.getCallsign().c_str());
}
else {
ImGui::Text("0x%04X", rdsDecode.getPICode());
}
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Country Code");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%d", rdsDecode.getCountryCode());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Coverage");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s (%d)", rds::AREA_COVERAGE_TO_STR[rdsDecode.getProgramCoverage()], rdsDecode.getProgramCoverage());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Reference Number");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%d", rdsDecode.getProgramRefNumber());
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("PI Code");
ImGui::TableSetColumnIndex(1);
if (rdsRegion == RDS_REGION_NORTH_AMERICA) {
ImGui::TextUnformatted("0x---- (----)");
}
else {
ImGui::TextUnformatted("0x----");
}
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Country Code");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("--"); // TODO: String
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Coverage");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("------- (--)");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Reference Number");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("--");
}
if (rdsDecode.programTypeValid()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Type");
ImGui::TableSetColumnIndex(1);
if (rdsRegion == RDS_REGION_NORTH_AMERICA) {
ImGui::Text("%s (%d)", rds::PROGRAM_TYPE_US_TO_STR[rdsDecode.getProgramType()], rdsDecode.getProgramType());
}
else {
ImGui::Text("%s (%d)", rds::PROGRAM_TYPE_EU_TO_STR[rdsDecode.getProgramType()], rdsDecode.getProgramType());
}
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Type");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("------- (--)"); // TODO: String
}
if (rdsDecode.musicValid()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Music");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s", rdsDecode.getMusic() ? "Yes":"No");
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Music");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("---");
}
ImGui::EndTable();
ImGui::SetNextItemWidth(menuWidth);
diag.draw();
}
} }
void setBandwidth(double bandwidth) { void setBandwidth(double bandwidth) {
@ -279,24 +139,12 @@ namespace demod {
demod.setStereo(_stereo); demod.setStereo(_stereo);
} }
void setAdvancedRds(bool enabled) {
rdsDemod.setSoftEnabled(enabled);
_rdsInfo = enabled;
}
private: private:
static void rdsHandler(uint8_t* data, int count, void* ctx) { static void rdsHandler(uint8_t* data, int count, void* ctx) {
WFM* _this = (WFM*)ctx; WFM* _this = (WFM*)ctx;
_this->rdsDecode.process(data, count); _this->rdsDecode.process(data, count);
} }
static void _diagHandler(float* data, int count, void* ctx) {
WFM* _this = (WFM*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
static void fftRedraw(ImGui::WaterFall::FFTRedrawArgs args, void* ctx) { static void fftRedraw(ImGui::WaterFall::FFTRedrawArgs args, void* ctx) {
WFM* _this = (WFM*)ctx; WFM* _this = (WFM*)ctx;
if (!_this->_rds) { return; } if (!_this->_rds) { return; }
@ -338,31 +186,23 @@ namespace demod {
} }
dsp::demod::BroadcastFM demod; dsp::demod::BroadcastFM demod;
RDSDemod rdsDemod; dsp::clock_recovery::FD recov;
dsp::digital::BinarySlicer slice;
dsp::digital::ManchesterDecoder manch;
dsp::digital::DifferentialDecoder diff;
dsp::sink::Handler<uint8_t> hs; dsp::sink::Handler<uint8_t> hs;
EventHandler<ImGui::WaterFall::FFTRedrawArgs> fftRedrawHandler; EventHandler<ImGui::WaterFall::FFTRedrawArgs> fftRedrawHandler;
dsp::buffer::Reshaper<float> reshape; rds::RDSDecoder rdsDecode;
dsp::sink::Handler<float> diagHandler;
ImGui::SymbolDiagram diag;
rds::Decoder rdsDecode;
ConfigManager* _config = NULL; ConfigManager* _config = NULL;
bool _stereo = false; bool _stereo = false;
bool _lowPass = true; bool _lowPass = true;
bool _rds = false; bool _rds = false;
bool _rdsInfo = false;
float muGain = 0.01; float muGain = 0.01;
float omegaGain = (0.01*0.01)/4.0; float omegaGain = (0.01*0.01)/4.0;
int rdsRegionId = 0;
RDSRegion rdsRegion = RDS_REGION_EUROPE;
OptionList<std::string, RDSRegion> rdsRegions;
std::string name; std::string name;
}; };
} }

View File

@ -597,16 +597,14 @@ private:
static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) { static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) {
RadioModule* _this = (RadioModule*)ctx; RadioModule* _this = (RadioModule*)ctx;
if (!_this->enabled || !_this->selectedDemod) { return; }
// If no demod is selected, reject the command
if (!_this->selectedDemod) { return; }
// Execute commands // Execute commands
if (code == RADIO_IFACE_CMD_GET_MODE && out) { if (code == RADIO_IFACE_CMD_GET_MODE && out) {
int* _out = (int*)out; int* _out = (int*)out;
*_out = _this->selectedDemodID; *_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; int* _in = (int*)in;
_this->selectDemodByID((DemodID)*_in); _this->selectDemodByID((DemodID)*_in);
} }
@ -614,7 +612,7 @@ private:
float* _out = (float*)out; float* _out = (float*)out;
*_out = _this->bandwidth; *_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; float* _in = (float*)in;
if (_this->bandwidthLocked) { return; } if (_this->bandwidthLocked) { return; }
_this->setBandwidth(*_in); _this->setBandwidth(*_in);
@ -623,7 +621,7 @@ private:
bool* _out = (bool*)out; bool* _out = (bool*)out;
*_out = _this->squelchEnabled; *_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; bool* _in = (bool*)in;
_this->setSquelchEnabled(*_in); _this->setSquelchEnabled(*_in);
} }
@ -631,7 +629,7 @@ private:
float* _out = (float*)out; float* _out = (float*)out;
*_out = _this->squelchLevel; *_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; float* _in = (float*)in;
_this->setSquelchLevel(*_in); _this->setSquelchLevel(*_in);
} }

View File

@ -3,8 +3,6 @@
#include <map> #include <map>
#include <algorithm> #include <algorithm>
#include <utils/flog.h>
namespace rds { namespace rds {
std::map<uint16_t, BlockType> SYNDROMES = { std::map<uint16_t, BlockType> SYNDROMES = {
{ 0b1111011000, BLOCK_TYPE_A }, { 0b1111011000, BLOCK_TYPE_A },
@ -22,98 +20,6 @@ namespace rds {
{ BLOCK_TYPE_D, 0b0110110100 } { BLOCK_TYPE_D, 0b0110110100 }
}; };
std::map<uint16_t, const char*> THREE_LETTER_CALLS = {
{ 0x99A5, "KBW" },
{ 0x99A6, "KCY" },
{ 0x9990, "KDB" },
{ 0x99A7, "KDF" },
{ 0x9950, "KEX" },
{ 0x9951, "KFH" },
{ 0x9952, "KFI" },
{ 0x9953, "KGA" },
{ 0x9991, "KGB" },
{ 0x9954, "KGO" },
{ 0x9955, "KGU" },
{ 0x9956, "KGW" },
{ 0x9957, "KGY" },
{ 0x99AA, "KHQ" },
{ 0x9958, "KID" },
{ 0x9959, "KIT" },
{ 0x995A, "KJR" },
{ 0x995B, "KLO" },
{ 0x995C, "KLZ" },
{ 0x995D, "KMA" },
{ 0x995E, "KMJ" },
{ 0x995F, "KNX" },
{ 0x9960, "KOA" },
{ 0x99AB, "KOB" },
{ 0x9992, "KOY" },
{ 0x9993, "KPQ" },
{ 0x9964, "KQV" },
{ 0x9994, "KSD" },
{ 0x9965, "KSL" },
{ 0x9966, "KUJ" },
{ 0x9995, "KUT" },
{ 0x9967, "KVI" },
{ 0x9968, "KWG" },
{ 0x9996, "KXL" },
{ 0x9997, "KXO" },
{ 0x996B, "KYW" },
{ 0x9999, "WBT" },
{ 0x996D, "WBZ" },
{ 0x996E, "WDZ" },
{ 0x996F, "WEW" },
{ 0x999A, "WGH" },
{ 0x9971, "WGL" },
{ 0x9972, "WGN" },
{ 0x9973, "WGR" },
{ 0x999B, "WGY" },
{ 0x9975, "WHA" },
{ 0x9976, "WHB" },
{ 0x9977, "WHK" },
{ 0x9978, "WHO" },
{ 0x999C, "WHP" },
{ 0x999D, "WIL" },
{ 0x997A, "WIP" },
{ 0x99B3, "WIS" },
{ 0x997B, "WJR" },
{ 0x99B4, "WJW" },
{ 0x99B5, "WJZ" },
{ 0x997C, "WKY" },
{ 0x997D, "WLS" },
{ 0x997E, "WLW" },
{ 0x999E, "WMC" },
{ 0x999F, "WMT" },
{ 0x9981, "WOC" },
{ 0x99A0, "WOI" },
{ 0x9983, "WOL" },
{ 0x9984, "WOR" },
{ 0x99A1, "WOW" },
{ 0x99B9, "WRC" },
{ 0x99A2, "WRR" },
{ 0x99A3, "WSB" },
{ 0x99A4, "WSM" },
{ 0x9988, "WWJ" },
{ 0x9989, "WWL" }
};
std::map<uint16_t, const char*> NAT_LOC_LINKED_STATIONS = {
{ 0xB01, "NPR-1" },
{ 0xB02, "CBC - Radio One" },
{ 0xB03, "CBC - Radio Two" },
{ 0xB04, "Radio-Canada - Première Chaîne" },
{ 0xB05, "Radio-Canada - Espace Musique" },
{ 0xB06, "CBC" },
{ 0xB07, "CBC" },
{ 0xB08, "CBC" },
{ 0xB09, "CBC" },
{ 0xB0A, "NPR-2" },
{ 0xB0B, "NPR-3" },
{ 0xB0C, "NPR-4" },
{ 0xB0D, "NPR-5" },
{ 0xB0E, "NPR-6" }
};
// 9876543210 // 9876543210
const uint16_t LFSR_POLY = 0b0110111001; const uint16_t LFSR_POLY = 0b0110111001;
const uint16_t IN_POLY = 0b1100011011; const uint16_t IN_POLY = 0b1100011011;
@ -122,7 +28,7 @@ namespace rds {
const int DATA_LEN = 16; const int DATA_LEN = 16;
const int POLY_LEN = 10; const int POLY_LEN = 10;
void Decoder::process(uint8_t* symbols, int count) { void RDSDecoder::process(uint8_t* symbols, int count) {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
// Shift in the bit // Shift in the bit
shiftReg = ((shiftReg << 1) & 0x3FFFFFF) | (symbols[i] & 1); shiftReg = ((shiftReg << 1) & 0x3FFFFFF) | (symbols[i] & 1);
@ -148,26 +54,18 @@ namespace rds {
type = (BlockType)((lastType + 1) % _BLOCK_TYPE_COUNT); type = (BlockType)((lastType + 1) % _BLOCK_TYPE_COUNT);
} }
// Save block while correcting errors (NOT YET) <- idk why the "not yet is here", TODO: find why // Save block while correcting errors (NOT YET)
blocks[type] = correctErrors(shiftReg, type, blockAvail[type]); blocks[type] = correctErrors(shiftReg, type, blockAvail[type]);
// If block type is A, decode it directly, otherwise, update continous count // Update continous group count
if (type == BLOCK_TYPE_A) { if (type == BLOCK_TYPE_A) { contGroup = 1; }
decodeBlockA(); else if (type == BLOCK_TYPE_B && lastType == BLOCK_TYPE_A) { contGroup++; }
}
else if (type == BLOCK_TYPE_B) { contGroup = 1; }
else if ((type == BLOCK_TYPE_C || type == BLOCK_TYPE_CP) && lastType == BLOCK_TYPE_B) { contGroup++; } else if ((type == BLOCK_TYPE_C || type == BLOCK_TYPE_CP) && lastType == BLOCK_TYPE_B) { contGroup++; }
else if (type == BLOCK_TYPE_D && (lastType == BLOCK_TYPE_C || lastType == BLOCK_TYPE_CP)) { contGroup++; } else if (type == BLOCK_TYPE_D && (lastType == BLOCK_TYPE_C || lastType == BLOCK_TYPE_CP)) { contGroup++; }
else { else { contGroup = 0; }
// If block B is available, decode it alone.
if (contGroup == 1) {
decodeBlockB();
}
contGroup = 0;
}
// If we've got an entire group, process it // If we've got an entire group, process it
if (contGroup >= 3) { if (contGroup >= 4) {
contGroup = 0; contGroup = 0;
decodeGroup(); decodeGroup();
} }
@ -178,7 +76,7 @@ namespace rds {
} }
} }
uint16_t Decoder::calcSyndrome(uint32_t block) { uint16_t RDSDecoder::calcSyndrome(uint32_t block) {
uint16_t syn = 0; uint16_t syn = 0;
// Calculate the syndrome using a LFSR // Calculate the syndrome using a LFSR
@ -197,7 +95,7 @@ namespace rds {
return syn; return syn;
} }
uint32_t Decoder::correctErrors(uint32_t block, BlockType type, bool& recovered) { uint32_t RDSDecoder::correctErrors(uint32_t block, BlockType type, bool& recovered) {
// Subtract the offset from block // Subtract the offset from block
block ^= (uint32_t)OFFSETS[type]; block ^= (uint32_t)OFFSETS[type];
uint32_t out = block; uint32_t out = block;
@ -226,264 +124,96 @@ namespace rds {
return out; return out;
} }
void Decoder::decodeBlockA() { void RDSDecoder::decodeGroup() {
// Acquire lock std::lock_guard<std::mutex> lck(groupMtx);
std::lock_guard<std::mutex> lck(blockAMtx); auto now = std::chrono::high_resolution_clock::now();
anyGroupLastUpdate = now;
// If it didn't decode properly return // Make sure blocks A and B are available
if (!blockAvail[BLOCK_TYPE_A]) { return; } if (!blockAvail[BLOCK_TYPE_A] || !blockAvail[BLOCK_TYPE_B]) { return; }
// Decode PI code // Decode PI code
piCode = (blocks[BLOCK_TYPE_A] >> 10) & 0xFFFF;
countryCode = (blocks[BLOCK_TYPE_A] >> 22) & 0xF; countryCode = (blocks[BLOCK_TYPE_A] >> 22) & 0xF;
programCoverage = (AreaCoverage)((blocks[BLOCK_TYPE_A] >> 18) & 0xF); programCoverage = (AreaCoverage)((blocks[BLOCK_TYPE_A] >> 18) & 0xF);
programRefNumber = (blocks[BLOCK_TYPE_A] >> 10) & 0xFF; programRefNumber = (blocks[BLOCK_TYPE_A] >> 10) & 0xFF;
callsign = decodeCallsign(piCode);
// Update timeout
blockALastUpdate = std::chrono::high_resolution_clock::now();;
}
void Decoder::decodeBlockB() {
// Acquire lock
std::lock_guard<std::mutex> lck(blockBMtx);
// If it didn't decode properly return (TODO: Make sure this is not needed)
if (!blockAvail[BLOCK_TYPE_B]) { return; }
// Decode group type and version // Decode group type and version
groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF; uint8_t groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF;
groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1); GroupVersion groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1);
// Decode traffic program and program type // Decode traffic program and program type
trafficProgram = (blocks[BLOCK_TYPE_B] >> 20) & 1; trafficProgram = (blocks[BLOCK_TYPE_B] >> 20) & 1;
programType = (ProgramType)((blocks[BLOCK_TYPE_B] >> 15) & 0x1F); programType = (ProgramType)((blocks[BLOCK_TYPE_B] >> 15) & 0x1F);
if (groupType == 0) {
group0LastUpdate = now;
trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1;
music = (blocks[BLOCK_TYPE_B] >> 13) & 1;
uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1;
uint8_t offset = ((blocks[BLOCK_TYPE_B] >> 10) & 0b11);
uint8_t diOffset = 3 - offset;
uint8_t psOffset = offset * 2;
// Update timeout if (groupVer == GROUP_VER_A && blockAvail[BLOCK_TYPE_C]) {
blockBLastUpdate = std::chrono::high_resolution_clock::now(); alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF;
}
void Decoder::decodeGroup0() {
// Acquire lock
std::lock_guard<std::mutex> lck(group0Mtx);
// Decode Block B data
trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1;
music = (blocks[BLOCK_TYPE_B] >> 13) & 1;
uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1;
uint8_t offset = ((blocks[BLOCK_TYPE_B] >> 10) & 0b11);
uint8_t diOffset = 3 - offset;
uint8_t psOffset = offset * 2;
// Decode Block C data
if (groupVer == GROUP_VER_A && blockAvail[BLOCK_TYPE_C]) {
alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF;
}
// Write DI bit to the decoder identification
decoderIdent &= ~(1 << diOffset);
decoderIdent |= (diBit << diOffset);
// Write chars at offset the PSName
if (blockAvail[BLOCK_TYPE_D]) {
programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
// Update timeout
group0LastUpdate = std::chrono::high_resolution_clock::now();
}
void Decoder::decodeGroup2() {
// Acquire lock
std::lock_guard<std::mutex> lck(group2Mtx);
// Get char offset and write chars in the Radiotext
bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1;
uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF;
// Clear text field if the A/B flag changed
if (nAB != rtAB) {
radioText = " ";
}
rtAB = nAB;
// Write char at offset in Radiotext
if (groupVer == GROUP_VER_A) {
uint8_t rtOffset = offset * 4;
if (blockAvail[BLOCK_TYPE_C]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
} }
// Write DI bit to the decoder identification
decoderIdent &= ~(1 << diOffset);
decoderIdent |= (diBit << diOffset);
// Write chars at offset the PSName
if (blockAvail[BLOCK_TYPE_D]) { if (blockAvail[BLOCK_TYPE_D]) {
radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF; programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF; programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
} }
} }
else { else if (groupType == 2) {
uint8_t rtOffset = offset * 2; group2LastUpdate = now;
if (blockAvail[BLOCK_TYPE_D]) { // Get char offset and write chars in the Radiotext
radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF; bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF; uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF;
// Clear text field if the A/B flag changed
if (nAB != rtAB) {
radioText = " ";
} }
} rtAB = nAB;
// Update timeout // Write char at offset in Radiotext
group2LastUpdate = std::chrono::high_resolution_clock::now(); if (groupVer == GROUP_VER_A) {
} uint8_t rtOffset = offset * 4;
if (blockAvail[BLOCK_TYPE_C]) {
void Decoder::decodeGroup10() { radioText[rtOffset] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
// Acquire lock radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
std::lock_guard<std::mutex> lck(group10Mtx); }
if (blockAvail[BLOCK_TYPE_D]) {
// Check if the text needs to be cleared radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
bool ab = (blocks[BLOCK_TYPE_B] >> 14) & 1; radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
if (ab != ptnAB) { }
programTypeName = " ";
}
ptnAB = ab;
// Decode segment address
bool addr = (blocks[BLOCK_TYPE_B] >> 10) & 1;
// Save text depending on address
if (addr) {
if (blockAvail[BLOCK_TYPE_C]) {
programTypeName[4] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
programTypeName[5] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
}
if (blockAvail[BLOCK_TYPE_D]) {
programTypeName[6] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programTypeName[7] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
}
else {
if (blockAvail[BLOCK_TYPE_C]) {
programTypeName[0] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
programTypeName[1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
}
if (blockAvail[BLOCK_TYPE_D]) {
programTypeName[2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programTypeName[3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
}
// Update timeout
group10LastUpdate = std::chrono::high_resolution_clock::now();
}
void Decoder::decodeGroup() {
// Make sure blocks B is available
if (!blockAvail[BLOCK_TYPE_B]) { return; }
// Decode block B
decodeBlockB();
// Decode depending on group type
switch (groupType) {
case 0:
decodeGroup0();
break;
case 2:
decodeGroup2();
break;
case 10:
decodeGroup10();
break;
default:
break;
}
}
std::string Decoder::base26ToCall(uint16_t pi) {
// Determin first better based on offset
bool w = (pi >= 21672);
std::string callsign(w ? "W" : "K");
// Base25 decode the rest
std::string restStr;
int rest = pi - (w ? 21672 : 4096);
while (rest) {
restStr += 'A' + (rest % 26);
rest /= 26;
}
// Pad with As
while (restStr.size() < 3) {
restStr += 'A';
}
// Reorder chars
for (int i = restStr.size() - 1; i >= 0; i--) {
callsign += restStr[i];
}
return callsign;
}
std::string Decoder::decodeCallsign(uint16_t pi) {
if ((pi >> 8) == 0xAF) {
// AFXY -> XY00
return base26ToCall((pi & 0xFF) << 8);
}
else if ((pi >> 12) == 0xA) {
// AXYZ -> X0YZ
return base26ToCall((((pi >> 8) & 0xF) << 12) | (pi & 0xFF));
}
else if (pi >= 0x9950 && pi <= 0x9EFF) {
// 3 letter callsigns
if (THREE_LETTER_CALLS.find(pi) != THREE_LETTER_CALLS.end()) {
return THREE_LETTER_CALLS[pi];
} }
else { else {
return "Not Assigned"; uint8_t rtOffset = offset * 2;
if (blockAvail[BLOCK_TYPE_D]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
} }
} }
else if (pi >= 0x1000 && pi <= 0x994F) {
// Normal encoding
if ((pi & 0xFF) == 0 || ((pi >> 8) & 0xF) == 0) {
return "Not Assigned";
}
else {
return base26ToCall(pi);
}
}
else if (pi >= 0xB000 && pi <= 0xEFFF) {
uint16_t _pi = ((pi >> 12) << 8) | (pi & 0xFF);
if (NAT_LOC_LINKED_STATIONS.find(_pi) != NAT_LOC_LINKED_STATIONS.end()) {
return NAT_LOC_LINKED_STATIONS[_pi];
}
else {
return "Not Assigned";
}
}
else {
return "Not Assigned";
}
} }
bool Decoder::blockAValid() { bool RDSDecoder::anyGroupValid() {
auto now = std::chrono::high_resolution_clock::now(); auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - blockALastUpdate)).count() < RDS_BLOCK_A_TIMEOUT_MS; return (std::chrono::duration_cast<std::chrono::milliseconds>(now - anyGroupLastUpdate)).count() < 5000.0;
} }
bool Decoder::blockBValid() { bool RDSDecoder::group0Valid() {
auto now = std::chrono::high_resolution_clock::now(); auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - blockBLastUpdate)).count() < RDS_BLOCK_B_TIMEOUT_MS; return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group0LastUpdate)).count() < 5000.0;
} }
bool Decoder::group0Valid() { bool RDSDecoder::group2Valid() {
auto now = std::chrono::high_resolution_clock::now(); auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group0LastUpdate)).count() < RDS_GROUP_0_TIMEOUT_MS; return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group2LastUpdate)).count() < 5000.0;
}
bool Decoder::group2Valid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group2LastUpdate)).count() < RDS_GROUP_2_TIMEOUT_MS;
}
bool Decoder::group10Valid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group10LastUpdate)).count() < RDS_GROUP_10_TIMEOUT_MS;
} }
} }

View File

@ -4,12 +4,6 @@
#include <chrono> #include <chrono>
#include <mutex> #include <mutex>
#define RDS_BLOCK_A_TIMEOUT_MS 5000.0
#define RDS_BLOCK_B_TIMEOUT_MS 5000.0
#define RDS_GROUP_0_TIMEOUT_MS 5000.0
#define RDS_GROUP_2_TIMEOUT_MS 5000.0
#define RDS_GROUP_10_TIMEOUT_MS 5000.0
namespace rds { namespace rds {
enum BlockType { enum BlockType {
BLOCK_TYPE_A, BLOCK_TYPE_A,
@ -26,42 +20,22 @@ namespace rds {
}; };
enum AreaCoverage { enum AreaCoverage {
AREA_COVERAGE_INVALID = -1, AREA_COVERAGE_LOCAL,
AREA_COVERAGE_LOCAL = 0, AREA_COVERAGE_INTERNATIONAL,
AREA_COVERAGE_INTERNATIONAL = 1, AREA_COVERAGE_NATIONAL,
AREA_COVERAGE_NATIONAL = 2, AREA_COVERAGE_SUPRA_NATIONAL,
AREA_COVERAGE_SUPRA_NATIONAL = 3, AREA_COVERAGE_REGIONAL1,
AREA_COVERAGE_REGIONAL1 = 4, AREA_COVERAGE_REGIONAL2,
AREA_COVERAGE_REGIONAL2 = 5, AREA_COVERAGE_REGIONAL3,
AREA_COVERAGE_REGIONAL3 = 6, AREA_COVERAGE_REGIONAL4,
AREA_COVERAGE_REGIONAL4 = 7, AREA_COVERAGE_REGIONAL5,
AREA_COVERAGE_REGIONAL5 = 8, AREA_COVERAGE_REGIONAL6,
AREA_COVERAGE_REGIONAL6 = 9, AREA_COVERAGE_REGIONAL7,
AREA_COVERAGE_REGIONAL7 = 10, AREA_COVERAGE_REGIONAL8,
AREA_COVERAGE_REGIONAL8 = 11, AREA_COVERAGE_REGIONAL9,
AREA_COVERAGE_REGIONAL9 = 12, AREA_COVERAGE_REGIONAL10,
AREA_COVERAGE_REGIONAL10 = 13, AREA_COVERAGE_REGIONAL11,
AREA_COVERAGE_REGIONAL11 = 14, AREA_COVERAGE_REGIONAL12
AREA_COVERAGE_REGIONAL12 = 15
};
inline const char* AREA_COVERAGE_TO_STR[] = {
"Local",
"International",
"National",
"Supra-National",
"Regional 1",
"Regional 2",
"Regional 3",
"Regional 4",
"Regional 5",
"Regional 6",
"Regional 7",
"Regional 8",
"Regional 9",
"Regional 10",
"Regional 11",
"Regional 12",
}; };
enum ProgramType { enum ProgramType {
@ -134,76 +108,6 @@ namespace rds {
PROGRAM_TYPE_EU_ALARM = 31 PROGRAM_TYPE_EU_ALARM = 31
}; };
inline const char* PROGRAM_TYPE_EU_TO_STR[] = {
"None",
"News",
"Current Affairs",
"Information",
"Sports",
"Education",
"Drama",
"Culture",
"Science",
"Varied",
"Pop Music",
"Rock Music",
"Easy Listening Music",
"Light Classical",
"Serious Classical",
"Other Music",
"Weather",
"Finance",
"Children Program",
"Social Affairs",
"Religion",
"Phone-in",
"Travel",
"Leisure",
"Jazz Music",
"Country Music",
"National Music",
"Oldies Music",
"Folk Music",
"Documentary",
"Alarm Test",
"Alarm",
};
inline const char* PROGRAM_TYPE_US_TO_STR[] = {
"None",
"News",
"Information",
"Sports",
"Talk",
"Rock",
"Classic Rock",
"Adult Hits",
"Soft Rock",
"Top 40",
"Country",
"Oldies",
"Soft",
"Nostalgia",
"Jazz",
"Classical",
"Rythm and Blues",
"Soft Rythm and Blues",
"Foreign Language",
"Religious Music",
"Religious Talk",
"Personality",
"Public",
"College",
"Unassigned",
"Unassigned",
"Unassigned",
"Unassigned",
"Unassigned",
"Weather",
"Emergency Test",
"Emergency",
};
enum DecoderIdentification { enum DecoderIdentification {
DECODER_IDENT_STEREO = (1 << 0), DECODER_IDENT_STEREO = (1 << 0),
DECODER_IDENT_ARTIFICIAL_HEAD = (1 << 1), DECODER_IDENT_ARTIFICIAL_HEAD = (1 << 1),
@ -211,49 +115,35 @@ namespace rds {
DECODER_IDENT_VARIABLE_PTY = (1 << 0) DECODER_IDENT_VARIABLE_PTY = (1 << 0)
}; };
class Decoder { class RDSDecoder {
public: public:
void process(uint8_t* symbols, int count); void process(uint8_t* symbols, int count);
bool piCodeValid() { std::lock_guard<std::mutex> lck(blockAMtx); return blockAValid(); } bool countryCodeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
uint16_t getPICode() { std::lock_guard<std::mutex> lck(blockAMtx); return piCode; } uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(groupMtx); return countryCode; }
uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(blockAMtx); return countryCode; } bool programCoverageValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(blockAMtx); return programCoverage; } uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(groupMtx); return programCoverage; }
uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(blockAMtx); return programRefNumber; } bool programRefNumberValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
std::string getCallsign() { std::lock_guard<std::mutex> lck(blockAMtx); return callsign; } uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(groupMtx); return programRefNumber; }
bool programTypeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
bool programTypeValid() { std::lock_guard<std::mutex> lck(blockBMtx); return blockBValid(); } ProgramType getProgramType() { std::lock_guard<std::mutex> lck(groupMtx); return programType; }
ProgramType getProgramType() { std::lock_guard<std::mutex> lck(blockBMtx); return programType; }
bool musicValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); } bool musicValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); }
bool getMusic() { std::lock_guard<std::mutex> lck(group0Mtx); return music; } bool getMusic() { std::lock_guard<std::mutex> lck(groupMtx); return music; }
bool PSNameValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); } bool PSNameValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); }
std::string getPSName() { std::lock_guard<std::mutex> lck(group0Mtx); return programServiceName; } std::string getPSName() { std::lock_guard<std::mutex> lck(groupMtx); return programServiceName; }
bool radioTextValid() { std::lock_guard<std::mutex> lck(group2Mtx); return group2Valid(); } bool radioTextValid() { std::lock_guard<std::mutex> lck(groupMtx); return group2Valid(); }
std::string getRadioText() { std::lock_guard<std::mutex> lck(group2Mtx); return radioText; } std::string getRadioText() { std::lock_guard<std::mutex> lck(groupMtx); return radioText; }
bool programTypeNameValid() { std::lock_guard<std::mutex> lck(group10Mtx); return group10Valid(); }
std::string getProgramTypeName() { std::lock_guard<std::mutex> lck(group10Mtx); return programTypeName; }
private: private:
static uint16_t calcSyndrome(uint32_t block); static uint16_t calcSyndrome(uint32_t block);
static uint32_t correctErrors(uint32_t block, BlockType type, bool& recovered); static uint32_t correctErrors(uint32_t block, BlockType type, bool& recovered);
void decodeBlockA();
void decodeBlockB();
void decodeGroup0();
void decodeGroup2();
void decodeGroup10();
void decodeGroup(); void decodeGroup();
static std::string base26ToCall(uint16_t pi); bool anyGroupValid();
static std::string decodeCallsign(uint16_t pi);
bool blockAValid();
bool blockBValid();
bool group0Valid(); bool group0Valid();
bool group2Valid(); bool group2Valid();
bool group10Valid();
// State machine // State machine
uint32_t shiftReg = 0; uint32_t shiftReg = 0;
@ -264,26 +154,17 @@ namespace rds {
uint32_t blocks[_BLOCK_TYPE_COUNT]; uint32_t blocks[_BLOCK_TYPE_COUNT];
bool blockAvail[_BLOCK_TYPE_COUNT]; bool blockAvail[_BLOCK_TYPE_COUNT];
// Block A (All groups) // All groups
std::mutex blockAMtx; std::mutex groupMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> blockALastUpdate{}; // 1970-01-01 std::chrono::time_point<std::chrono::high_resolution_clock> anyGroupLastUpdate;
uint16_t piCode;
uint8_t countryCode; uint8_t countryCode;
AreaCoverage programCoverage; AreaCoverage programCoverage;
uint8_t programRefNumber; uint8_t programRefNumber;
std::string callsign;
// Block B (All groups)
std::mutex blockBMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> blockBLastUpdate{}; // 1970-01-01
uint8_t groupType;
GroupVersion groupVer;
bool trafficProgram; bool trafficProgram;
ProgramType programType; ProgramType programType;
// Group type 0 // Group type 0
std::mutex group0Mtx; std::chrono::time_point<std::chrono::high_resolution_clock> group0LastUpdate;
std::chrono::time_point<std::chrono::high_resolution_clock> group0LastUpdate{}; // 1970-01-01
bool trafficAnnouncement; bool trafficAnnouncement;
bool music; bool music;
uint8_t decoderIdent; uint8_t decoderIdent;
@ -291,16 +172,9 @@ namespace rds {
std::string programServiceName = " "; std::string programServiceName = " ";
// Group type 2 // Group type 2
std::mutex group2Mtx; std::chrono::time_point<std::chrono::high_resolution_clock> group2LastUpdate;
std::chrono::time_point<std::chrono::high_resolution_clock> group2LastUpdate{}; // 1970-01-01
bool rtAB = false; bool rtAB = false;
std::string radioText = " "; std::string radioText = " ";
// Group type 10
std::mutex group10Mtx;
std::chrono::time_point<std::chrono::high_resolution_clock> group10LastUpdate{}; // 1970-01-01
bool ptnAB = false;
std::string programTypeName = " ";
}; };
} }

View File

@ -1,102 +0,0 @@
#pragma once
#include <dsp/processor.h>
#include <dsp/loop/fast_agc.h>
#include <dsp/loop/costas.h>
#include <dsp/taps/band_pass.h>
#include <dsp/filter/fir.h>
#include <dsp/convert/complex_to_real.h>
#include <dsp/clock_recovery/mm.h>
#include <dsp/digital/binary_slicer.h>
#include <dsp/digital/differential_decoder.h>
class RDSDemod : public dsp::Processor<dsp::complex_t, uint8_t> {
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
public:
RDSDemod() {}
RDSDemod(dsp::stream<dsp::complex_t>* in, bool enableSoft) { init(in, enableSoft); }
~RDSDemod() {}
void init(dsp::stream<dsp::complex_t>* in, bool enableSoft) {
// Save config
this->enableSoft = enableSoft;
// Initialize the DSP
agc.init(NULL, 1.0, 1e6, 0.1);
costas.init(NULL, 0.005f);
taps = dsp::taps::bandPass<dsp::complex_t>(0, 2375, 100, 5000);
fir.init(NULL, taps);
double baudfreq = dsp::math::hzToRads(2375.0/2.0, 5000);
costas2.init(NULL, 0.01, 0.0, baudfreq, baudfreq - (baudfreq*0.1), baudfreq + (baudfreq*0.1));
recov.init(NULL, 5000.0 / (2375.0 / 2.0), 1e-6, 0.01, 0.01);
diff.init(NULL, 2);
// Free useless buffers
agc.out.free();
fir.out.free();
costas2.out.free();
recov.out.free();
// Init the rest
base_type::init(in);
}
void setSoftEnabled(bool enable) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
enableSoft = enable;
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
agc.reset();
costas.reset();
fir.reset();
costas2.reset();
recov.reset();
diff.reset();
base_type::tempStart();
}
inline int process(int count, dsp::complex_t* in, float* softOut, uint8_t* hardOut) {
count = agc.process(count, in, costas.out.readBuf);
count = costas.process(count, costas.out.readBuf, costas.out.writeBuf);
count = fir.process(count, costas.out.writeBuf, costas.out.writeBuf);
count = costas2.process(count, costas.out.writeBuf, costas.out.readBuf);
count = dsp::convert::ComplexToReal::process(count, costas.out.readBuf, softOut);
count = recov.process(count, softOut, softOut);
count = dsp::digital::BinarySlicer::process(count, softOut, diff.out.readBuf);
count = diff.process(count, diff.out.readBuf, hardOut);
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = process(count, base_type::_in->readBuf, soft.writeBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
if (enableSoft) {
if (!soft.swap(count)) { return -1; }
}
return count;
}
dsp::stream<float> soft;
private:
bool enableSoft = false;
dsp::loop::FastAGC<dsp::complex_t> agc;
dsp::loop::Costas<2> costas;
dsp::tap<dsp::complex_t> taps;
dsp::filter::FIR<dsp::complex_t, dsp::complex_t> fir;
dsp::loop::Costas<2> costas2;
dsp::clock_recovery::MM<float> recov;
dsp::digital::DifferentialDecoder diff;
};

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

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