mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-07-09 10:35:21 +02:00
Compare commits
2 Commits
new_sinks
...
noise_redu
Author | SHA1 | Date | |
---|---|---|---|
87da47f53d | |||
75050347de |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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**
|
||||||
|
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
@ -1,4 +1,7 @@
|
|||||||
# Important
|
# Important
|
||||||
|
|
||||||
Only bandplan, colormaps and themes are accepted. Code pull requests are **NOT welcome**.
|
Only minor bug fixes and bandplans are accepted.
|
||||||
|
|
||||||
|
Pull requests adding features or any bug fix that requires significant code changes will be automatically rejected.
|
||||||
|
|
||||||
Open an issue requesting a feature or discussing a possible bugfix instead.
|
Open an issue requesting a feature or discussing a possible bugfix instead.
|
217
.github/workflows/build_all.yml
vendored
217
.github/workflows/build_all.yml
vendored
@ -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,13 +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 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
|
||||||
@ -58,17 +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
|
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 "-DLIBUSB_LIBRARIES=C:/Program Files/PothosSDR/lib/libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIRS=C:/Program Files/PothosSDR/include/libusb-1.0" .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr"
|
|
||||||
|
|
||||||
- name: Prepare CMake
|
- name: Prepare CMake
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
run: cmake "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
|
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
|
||||||
@ -79,47 +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 && 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.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.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 && 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 -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
|
||||||
@ -130,68 +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 && 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.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target /
|
|
||||||
|
|
||||||
- name: Install libiio
|
|
||||||
run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
|
||||||
|
|
||||||
- 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 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 -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
|
||||||
|
|
||||||
@ -203,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
|
||||||
@ -212,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
|
||||||
@ -225,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
|
||||||
@ -269,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
|
||||||
@ -291,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
|
||||||
@ -300,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
|
||||||
@ -313,45 +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_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
|
||||||
@ -362,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
|
||||||
@ -371,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}}
|
||||||
@ -389,36 +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_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_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/
|
||||||
@ -430,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
|
||||||
@ -439,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
|
||||||
@ -451,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
|
||||||
|
@ -17,26 +17,24 @@ option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
|
|||||||
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_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_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_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)
|
||||||
@ -44,14 +42,12 @@ option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies:
|
|||||||
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_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)
|
||||||
@ -145,17 +141,9 @@ 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_RFSPACE_SOURCE)
|
if (OPT_BUILD_RFSPACE_SOURCE)
|
||||||
add_subdirectory("source_modules/rfspace_source")
|
add_subdirectory("source_modules/rfspace_source")
|
||||||
@ -169,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)
|
||||||
@ -193,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)
|
||||||
@ -241,10 +229,6 @@ 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)
|
||||||
@ -263,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)
|
||||||
@ -287,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
|
||||||
@ -322,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)
|
||||||
|
@ -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
|
@ -108,6 +108,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
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
@ -181,8 +177,6 @@ int sdrpp_main(int argc, char* argv[]) {
|
|||||||
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
|
defConfig["moduleInstances"]["LimeSDR 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"]["PerseusSDR Source"]["module"] = "perseus_source";
|
|
||||||
defConfig["moduleInstances"]["PerseusSDR 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";
|
||||||
@ -193,6 +187,8 @@ 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"]["SoapySDR Source"]["module"] = "soapy_source";
|
||||||
|
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;
|
||||||
|
|
||||||
|
@ -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"
|
||||||
};
|
};
|
||||||
@ -41,7 +41,6 @@ namespace sdrpp_credits {
|
|||||||
"CaribouLabs",
|
"CaribouLabs",
|
||||||
"Ettus Research",
|
"Ettus Research",
|
||||||
"Howard Su",
|
"Howard Su",
|
||||||
"MicroPhase",
|
|
||||||
"MyriadRF",
|
"MyriadRF",
|
||||||
"Nuand",
|
"Nuand",
|
||||||
"RFspace",
|
"RFspace",
|
||||||
@ -55,7 +54,6 @@ 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",
|
"Dexruus",
|
||||||
"EB3FRN",
|
"EB3FRN",
|
||||||
@ -83,7 +81,6 @@ namespace sdrpp_credits {
|
|||||||
"Syne Ardwin (WI9SYN)",
|
"Syne Ardwin (WI9SYN)",
|
||||||
"W4IPA",
|
"W4IPA",
|
||||||
"William Arcand (W1WRA)",
|
"William Arcand (W1WRA)",
|
||||||
"William Pitchford",
|
|
||||||
"Yves Rougy",
|
"Yves Rougy",
|
||||||
"Zipper"
|
"Zipper"
|
||||||
};
|
};
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ namespace dsp::convert {
|
|||||||
|
|
||||||
StereoToMono(stream<stereo_t>* in) { base_type::init(in); }
|
StereoToMono(stream<stereo_t>* in) { base_type::init(in); }
|
||||||
|
|
||||||
inline int process(int count, const stereo_t* in, float* out) {
|
static inline int process(int count, const stereo_t* in, float* out) {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
out[i] = (in[i].l + in[i].r) / 2.0f;
|
out[i] = (in[i].l + in[i].r) / 2.0f;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
183
core/src/dsp/noise_reduction/audio.h
Normal file
183
core/src/dsp/noise_reduction/audio.h
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../processor.h"
|
||||||
|
#include "../window/nuttall.h"
|
||||||
|
#include <fftw3.h>
|
||||||
|
#include "../convert/stereo_to_mono.h"
|
||||||
|
|
||||||
|
namespace dsp::noise_reduction {
|
||||||
|
class Audio : public Processor<stereo_t, stereo_t> {
|
||||||
|
using base_type = Processor<stereo_t, stereo_t>;
|
||||||
|
public:
|
||||||
|
Audio() {}
|
||||||
|
|
||||||
|
Audio(stream<stereo_t>* in, int bins) { init(in, bins); }
|
||||||
|
|
||||||
|
~Audio() {
|
||||||
|
if (!base_type::_block_init) { return; }
|
||||||
|
base_type::stop();
|
||||||
|
destroyBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(stream<stereo_t>* in, int bins) {
|
||||||
|
_bins = bins;
|
||||||
|
complexBins = (bins / 2) + 1;
|
||||||
|
normFactor = 1.0f / (float)_bins;
|
||||||
|
initBuffers();
|
||||||
|
base_type::init(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBins(int bins) {
|
||||||
|
assert(base_type::_block_init);
|
||||||
|
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||||
|
base_type::tempStop();
|
||||||
|
_bins = bins;
|
||||||
|
complexBins = (bins / 2) + 1;
|
||||||
|
normFactor = 1.0f / (float)_bins;
|
||||||
|
destroyBuffers();
|
||||||
|
initBuffers();
|
||||||
|
base_type::tempStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLevel(float level) {
|
||||||
|
_level = powf(10.0f, level * 0.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
assert(base_type::_block_init);
|
||||||
|
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||||
|
base_type::tempStop();
|
||||||
|
buffer::clear(buffer, _bins - 1);
|
||||||
|
buffer::clear(backFFTIn, _bins);
|
||||||
|
buffer::clear(noisePrint, _bins);
|
||||||
|
base_type::tempStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
int process(int count, const stereo_t* in, stereo_t* out) {
|
||||||
|
// Write new input data to buffer
|
||||||
|
convert::StereoToMono::process(count, in, bufferStart);
|
||||||
|
|
||||||
|
// Iterate the FFT
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
// Apply windows
|
||||||
|
volk_32f_x2_multiply_32f(forwFFTIn, &buffer[i], fftWin, _bins);
|
||||||
|
|
||||||
|
// Do forward FFT
|
||||||
|
fftwf_execute(forwardPlan);
|
||||||
|
|
||||||
|
// Get bin amplitude and square to get power
|
||||||
|
volk_32fc_magnitude_32f(ampBuf, (lv_32fc_t*)forwFFTOut, complexBins);
|
||||||
|
|
||||||
|
// Update noise print using a running average
|
||||||
|
volk_32f_s32f_multiply_32f(scaledAmps, ampBuf, alpha, complexBins);
|
||||||
|
volk_32f_s32f_multiply_32f(noisePrint, noisePrint, beta, complexBins);
|
||||||
|
volk_32f_x2_add_32f(noisePrint, noisePrint, scaledAmps, complexBins);
|
||||||
|
|
||||||
|
// Clamp amplitudes
|
||||||
|
volk_32f_x2_max_32f(ampBuf, ampBuf, noisePrint, complexBins);
|
||||||
|
|
||||||
|
// Compute Wiener (funny) filter
|
||||||
|
volk_32f_x2_subtract_32f(scaledAmps, ampBuf, noisePrint, complexBins);
|
||||||
|
volk_32f_x2_divide_32f(scaledAmps, scaledAmps, ampBuf, complexBins);
|
||||||
|
|
||||||
|
// Apply wiener filter to bins
|
||||||
|
volk_32fc_32f_multiply_32fc((lv_32fc_t*)backFFTIn, (lv_32fc_t*)forwFFTOut, scaledAmps, complexBins);
|
||||||
|
|
||||||
|
// Do reverse FFT and get first element
|
||||||
|
fftwf_execute(backwardPlan);
|
||||||
|
out[i].l = backFFTOut[_bins / 2];
|
||||||
|
out[i].r = backFFTOut[_bins / 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Correct amplitude
|
||||||
|
volk_32f_s32f_multiply_32f((float*)out, (float*)out, normFactor, count*2);
|
||||||
|
|
||||||
|
// Move buffer buffer
|
||||||
|
memmove(buffer, &buffer[count], (_bins - 1) * sizeof(float));
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int run() {
|
||||||
|
int count = base_type::_in->read();
|
||||||
|
if (count < 0) { return -1; }
|
||||||
|
|
||||||
|
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
|
||||||
|
|
||||||
|
// Swap if some data was generated
|
||||||
|
base_type::_in->flush();
|
||||||
|
if (!base_type::out.swap(count)) { return -1; }
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void initBuffers() {
|
||||||
|
// Allocate FFT buffers
|
||||||
|
forwFFTIn = (float*)fftwf_malloc(_bins * sizeof(float));
|
||||||
|
forwFFTOut = (complex_t*)fftwf_malloc(_bins * sizeof(complex_t));
|
||||||
|
backFFTIn = (complex_t*)fftwf_malloc(_bins * sizeof(complex_t));
|
||||||
|
backFFTOut = (float*)fftwf_malloc(_bins * sizeof(float));
|
||||||
|
|
||||||
|
// Allocate and clear delay buffer
|
||||||
|
buffer = buffer::alloc<float>(STREAM_BUFFER_SIZE + 64000);
|
||||||
|
bufferStart = &buffer[_bins - 1];
|
||||||
|
buffer::clear(buffer, _bins - 1);
|
||||||
|
|
||||||
|
// Clear backward FFT input
|
||||||
|
buffer::clear(backFFTIn, _bins);
|
||||||
|
|
||||||
|
// Allocate amplitude buffer
|
||||||
|
ampBuf = buffer::alloc<float>(_bins);
|
||||||
|
scaledAmps = buffer::alloc<float>(_bins);
|
||||||
|
noisePrint = buffer::alloc<float>(_bins);
|
||||||
|
buffer::clear(noisePrint, _bins);
|
||||||
|
|
||||||
|
// Allocate and generate Window
|
||||||
|
fftWin = buffer::alloc<float>(_bins);
|
||||||
|
for (int i = 0; i < _bins; i++) { fftWin[i] = window::nuttall(i, _bins - 1); }
|
||||||
|
|
||||||
|
// Plan FFTs
|
||||||
|
forwardPlan = fftwf_plan_dft_r2c_1d(_bins, forwFFTIn, (fftwf_complex*)forwFFTOut, FFTW_ESTIMATE);
|
||||||
|
backwardPlan = fftwf_plan_dft_c2r_1d(_bins, (fftwf_complex*)backFFTIn, backFFTOut, FFTW_ESTIMATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroyBuffers() {
|
||||||
|
fftwf_destroy_plan(forwardPlan);
|
||||||
|
fftwf_destroy_plan(backwardPlan);
|
||||||
|
fftwf_free(forwFFTIn);
|
||||||
|
fftwf_free(forwFFTOut);
|
||||||
|
fftwf_free(backFFTIn);
|
||||||
|
fftwf_free(backFFTOut);
|
||||||
|
buffer::free(buffer);
|
||||||
|
buffer::free(ampBuf);
|
||||||
|
buffer::free(scaledAmps);
|
||||||
|
buffer::free(noisePrint);
|
||||||
|
buffer::free(fftWin);
|
||||||
|
}
|
||||||
|
|
||||||
|
float _level = 0.0f;
|
||||||
|
|
||||||
|
float* forwFFTIn;
|
||||||
|
complex_t* forwFFTOut;
|
||||||
|
complex_t* backFFTIn;
|
||||||
|
float* backFFTOut;
|
||||||
|
|
||||||
|
fftwf_plan forwardPlan;
|
||||||
|
fftwf_plan backwardPlan;
|
||||||
|
|
||||||
|
float* buffer;
|
||||||
|
float* bufferStart;
|
||||||
|
|
||||||
|
float* fftWin;
|
||||||
|
|
||||||
|
float* ampBuf;
|
||||||
|
float* scaledAmps;
|
||||||
|
float* noisePrint;
|
||||||
|
|
||||||
|
int _bins;
|
||||||
|
int complexBins;
|
||||||
|
float normFactor = 1.0f;
|
||||||
|
|
||||||
|
float alpha = 0.0001f;
|
||||||
|
float beta = 0.9999f;
|
||||||
|
};
|
||||||
|
}
|
@ -37,21 +37,17 @@ namespace dsp::noise_reduction {
|
|||||||
|
|
||||||
inline int process(int count, complex_t* in, complex_t* out) {
|
inline int process(int count, complex_t* in, complex_t* out) {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
// Get signal amplitude
|
// Get signal amplitude and pass value if null
|
||||||
float inAmp = in[i].amplitude();
|
float inAmp = in[i].amplitude();
|
||||||
|
if (!inAmp) {
|
||||||
// Update average amplitude
|
out[i] = in[i];
|
||||||
float gain = 1.0f;
|
|
||||||
if (inAmp != 0.0f) {
|
|
||||||
amp = (amp * _invRate) + (inAmp * _rate);
|
|
||||||
float excess = inAmp / amp;
|
|
||||||
if (excess > _level) {
|
|
||||||
gain = 1.0f / excess;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale output by gain
|
// Update running average of amplitude
|
||||||
out[i] = in[i] * gain;
|
amp = (_rate*inAmp) + (_invRate*amp);
|
||||||
|
|
||||||
|
// Null out if spike (Note: ideally, it should try to guess the real data)
|
||||||
|
out[i] = (inAmp > _level*amp) ? complex_t{0.0f,0.0f} : in[i];
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -16,7 +16,6 @@ namespace icons {
|
|||||||
ImTextureID UNMUTED;
|
ImTextureID UNMUTED;
|
||||||
ImTextureID NORMAL_TUNING;
|
ImTextureID NORMAL_TUNING;
|
||||||
ImTextureID CENTER_TUNING;
|
ImTextureID CENTER_TUNING;
|
||||||
ImTextureID ALIGN_CENTER;
|
|
||||||
|
|
||||||
GLuint loadTexture(std::string path) {
|
GLuint loadTexture(std::string path) {
|
||||||
int w, h, n;
|
int w, h, n;
|
||||||
@ -46,7 +45,6 @@ namespace icons {
|
|||||||
UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png");
|
UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png");
|
||||||
NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png");
|
NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png");
|
||||||
CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_tuning.png");
|
CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_tuning.png");
|
||||||
ALIGN_CENTER = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/align_center.png");
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,7 @@ namespace icons {
|
|||||||
extern ImTextureID UNMUTED;
|
extern ImTextureID UNMUTED;
|
||||||
extern ImTextureID NORMAL_TUNING;
|
extern ImTextureID NORMAL_TUNING;
|
||||||
extern ImTextureID CENTER_TUNING;
|
extern ImTextureID CENTER_TUNING;
|
||||||
extern ImTextureID ALIGN_CENTER;
|
|
||||||
|
|
||||||
GLuint loadTexture(std::string path);
|
GLuint loadTexture(std::string path);
|
||||||
bool load(std::string resDir);
|
bool load(std::string resDir);
|
||||||
}
|
}
|
@ -17,7 +17,6 @@
|
|||||||
#include <gui/menus/display.h>
|
#include <gui/menus/display.h>
|
||||||
#include <gui/menus/bandplan.h>
|
#include <gui/menus/bandplan.h>
|
||||||
#include <gui/menus/sink.h>
|
#include <gui/menus/sink.h>
|
||||||
#include <gui/menus/streams.h>
|
|
||||||
#include <gui/menus/vfo_color.h>
|
#include <gui/menus/vfo_color.h>
|
||||||
#include <gui/menus/module_manager.h>
|
#include <gui/menus/module_manager.h>
|
||||||
#include <gui/menus/theme.h>
|
#include <gui/menus/theme.h>
|
||||||
@ -73,7 +72,6 @@ void MainWindow::init() {
|
|||||||
|
|
||||||
gui::menu.registerEntry("Source", sourcemenu::draw, NULL);
|
gui::menu.registerEntry("Source", sourcemenu::draw, NULL);
|
||||||
gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL);
|
gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL);
|
||||||
gui::menu.registerEntry("Streams", streamsmenu::draw, NULL);
|
|
||||||
gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL);
|
gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL);
|
||||||
gui::menu.registerEntry("Display", displaymenu::draw, NULL);
|
gui::menu.registerEntry("Display", displaymenu::draw, NULL);
|
||||||
gui::menu.registerEntry("Theme", thememenu::draw, NULL);
|
gui::menu.registerEntry("Theme", thememenu::draw, NULL);
|
||||||
@ -167,7 +165,6 @@ void MainWindow::init() {
|
|||||||
|
|
||||||
sourcemenu::init();
|
sourcemenu::init();
|
||||||
sinkmenu::init();
|
sinkmenu::init();
|
||||||
streamsmenu::init();
|
|
||||||
bandplanmenu::init();
|
bandplanmenu::init();
|
||||||
displaymenu::init();
|
displaymenu::init();
|
||||||
vfo_color_menu::init();
|
vfo_color_menu::init();
|
||||||
@ -436,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();
|
||||||
@ -469,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);
|
||||||
@ -583,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);
|
||||||
|
@ -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;
|
||||||
@ -23,10 +22,6 @@ namespace displaymenu {
|
|||||||
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<float, float> uiScales;
|
OptionList<float, float> uiScales;
|
||||||
|
|
||||||
@ -62,10 +57,8 @@ namespace displaymenu {
|
|||||||
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() {
|
||||||
@ -111,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);
|
||||||
@ -127,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)) {
|
||||||
@ -166,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)) {
|
||||||
@ -221,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);
|
||||||
@ -263,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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,5 @@
|
|||||||
|
|
||||||
namespace displaymenu {
|
namespace displaymenu {
|
||||||
void init();
|
void init();
|
||||||
void checkKeybinds();
|
|
||||||
void draw(void* ctx);
|
void draw(void* ctx);
|
||||||
}
|
}
|
@ -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);
|
||||||
|
@ -1,177 +0,0 @@
|
|||||||
#include "streams.h"
|
|
||||||
#include <signal_path/signal_path.h>
|
|
||||||
#include <imgui.h>
|
|
||||||
#include <utils/flog.h>
|
|
||||||
#include <gui/style.h>
|
|
||||||
#include <gui/icons.h>
|
|
||||||
#include <utils/optionlist.h>
|
|
||||||
|
|
||||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
|
||||||
|
|
||||||
namespace streamsmenu {
|
|
||||||
std::vector<SinkID> sinksToBeRemoved;
|
|
||||||
|
|
||||||
std::recursive_mutex sinkTypesMtx;
|
|
||||||
OptionList<std::string, std::string> sinkTypes;
|
|
||||||
|
|
||||||
std::map<std::string, int> selectedSinkTypeId;
|
|
||||||
std::map<std::string, int> addSinkTypeId;
|
|
||||||
|
|
||||||
int addType = 0;
|
|
||||||
|
|
||||||
void updateSinkTypeList(const std::string& removed = "") {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck1(sinkTypesMtx);
|
|
||||||
auto lck2 = sigpath::streamManager.getSinkTypesLock();
|
|
||||||
const auto& types = sigpath::streamManager.getSinkTypes();
|
|
||||||
sinkTypes.clear();
|
|
||||||
for (const auto& type : types) {
|
|
||||||
if (type == removed) { continue; }
|
|
||||||
sinkTypes.define(type, type, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSinkProviderRegistered(const std::string& type) {
|
|
||||||
// Update the list
|
|
||||||
updateSinkTypeList();
|
|
||||||
|
|
||||||
// Update the ID of the Add dropdown
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
// Update the selected ID of each drop down
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSinkProviderUnregister(const std::string& type) {
|
|
||||||
// Update the list
|
|
||||||
updateSinkTypeList(type);
|
|
||||||
|
|
||||||
// Update the ID of the Add dropdown
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
// Update the selected ID of each drop down
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
void init() {
|
|
||||||
sigpath::streamManager.onSinkProviderRegistered.bind(onSinkProviderRegistered);
|
|
||||||
sigpath::streamManager.onSinkProviderUnregister.bind(onSinkProviderUnregister);
|
|
||||||
updateSinkTypeList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw(void* ctx) {
|
|
||||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
|
||||||
auto lck = sigpath::streamManager.getStreamsLock();
|
|
||||||
const auto& streams = sigpath::streamManager.getStreams();
|
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
int maxCount = streams.size();
|
|
||||||
for (auto& [name, stream] : streams) {
|
|
||||||
// Stream name
|
|
||||||
ImGui::SetCursorPosX((menuWidth / 2.0f) - (ImGui::CalcTextSize(name.c_str()).x / 2.0f));
|
|
||||||
ImGui::Text("%s", name.c_str());
|
|
||||||
|
|
||||||
// Display ever sink
|
|
||||||
if (ImGui::BeginTable(CONCAT("sdrpp_streams_tbl_", name), 1, ImGuiTableFlags_Borders)) {
|
|
||||||
auto lck2 = stream->getSinksLock();
|
|
||||||
auto sinks = stream->getSinks();
|
|
||||||
for (auto& [id, sink] : sinks) {
|
|
||||||
std::string sid = sink->getStringID();
|
|
||||||
ImGui::TableNextRow();
|
|
||||||
ImGui::TableSetColumnIndex(0);
|
|
||||||
float tableWidth = ImGui::GetContentRegionAvail().x;
|
|
||||||
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
// Sink type
|
|
||||||
int ttttt = 0;
|
|
||||||
ImGui::FillWidth();
|
|
||||||
if (ImGui::Combo(CONCAT("##sdrpp_streams_type_", sid), &ttttt, sinkTypes.txt)) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
sink->showMenu();
|
|
||||||
float vol = sink->getVolume();
|
|
||||||
bool muted = sink->getMuted();
|
|
||||||
float pan = sink->getPanning();
|
|
||||||
bool linked = true;
|
|
||||||
|
|
||||||
float height = ImGui::GetTextLineHeightWithSpacing() + 2;
|
|
||||||
ImGui::PushID(ImGui::GetID(("sdrpp_streams_center_btn_" + sid).c_str()));
|
|
||||||
if (ImGui::ImageButton(icons::ALIGN_CENTER, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
|
||||||
sink->setPanning(0.0f);
|
|
||||||
}
|
|
||||||
ImGui::PopID();
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::FillWidth();
|
|
||||||
if (ImGui::SliderFloat(CONCAT("##sdrpp_streams_pan_", sid), &pan, -1, 1, "")) {
|
|
||||||
sink->setPanning(pan);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (muted) {
|
|
||||||
ImGui::PushID(ImGui::GetID(("sdrpp_unmute_btn_" + sid).c_str()));
|
|
||||||
if (ImGui::ImageButton(icons::MUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
|
||||||
sink->setMuted(false);
|
|
||||||
}
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ImGui::PushID(ImGui::GetID(("sdrpp_mute_btn_" + sid).c_str()));
|
|
||||||
if (ImGui::ImageButton(icons::UNMUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
|
||||||
sink->setMuted(true);
|
|
||||||
}
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::FillWidth();
|
|
||||||
if (ImGui::SliderFloat(CONCAT("##sdrpp_streams_vol_", sid), &vol, 0, 1, "")) {
|
|
||||||
sink->setVolume(vol);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int startCur = ImGui::GetCursorPosX();
|
|
||||||
if (ImGui::Checkbox(CONCAT("Link volume##sdrpp_streams_vol_", sid), &linked)) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (ImGui::Button(CONCAT("Remove##sdrpp_streams_remove_type_", sid), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) {
|
|
||||||
sinksToBeRemoved.push_back(id);
|
|
||||||
}
|
|
||||||
ImGui::Spacing();
|
|
||||||
}
|
|
||||||
lck2.unlock();
|
|
||||||
|
|
||||||
ImGui::TableNextRow();
|
|
||||||
ImGui::TableSetColumnIndex(0);
|
|
||||||
float tableWidth = ImGui::GetContentRegionAvail().x;
|
|
||||||
|
|
||||||
ImGui::Spacing();
|
|
||||||
int startCur = ImGui::GetCursorPosX();
|
|
||||||
{
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(sinkTypesMtx);
|
|
||||||
ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &addType, sinkTypes.txt);
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) {
|
|
||||||
stream->addSink(sinkTypes.value(addType));
|
|
||||||
}
|
|
||||||
ImGui::Spacing();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
|
|
||||||
// Remove sinks that need to be removed
|
|
||||||
if (!sinksToBeRemoved.empty()) {
|
|
||||||
for (auto& id : sinksToBeRemoved) {
|
|
||||||
stream->removeSink(id);
|
|
||||||
}
|
|
||||||
sinksToBeRemoved.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
count++;
|
|
||||||
if (count < maxCount) {
|
|
||||||
ImGui::Spacing();
|
|
||||||
}
|
|
||||||
ImGui::Spacing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace streamsmenu {
|
|
||||||
void init();
|
|
||||||
void draw(void* ctx);
|
|
||||||
};
|
|
@ -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();
|
||||||
|
|
||||||
}
|
}
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
||||||
|
@ -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;
|
||||||
};
|
};
|
@ -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;
|
||||||
|
@ -5,5 +5,4 @@ namespace sigpath {
|
|||||||
VFOManager vfoManager;
|
VFOManager vfoManager;
|
||||||
SourceManager sourceManager;
|
SourceManager sourceManager;
|
||||||
SinkManager sinkManager;
|
SinkManager sinkManager;
|
||||||
StreamManager streamManager;
|
|
||||||
};
|
};
|
@ -3,7 +3,6 @@
|
|||||||
#include "vfo_manager.h"
|
#include "vfo_manager.h"
|
||||||
#include "source.h"
|
#include "source.h"
|
||||||
#include "sink.h"
|
#include "sink.h"
|
||||||
#include "stream.h"
|
|
||||||
#include <module.h>
|
#include <module.h>
|
||||||
|
|
||||||
namespace sigpath {
|
namespace sigpath {
|
||||||
@ -11,5 +10,4 @@ namespace sigpath {
|
|||||||
SDRPP_EXPORT VFOManager vfoManager;
|
SDRPP_EXPORT VFOManager vfoManager;
|
||||||
SDRPP_EXPORT SourceManager sourceManager;
|
SDRPP_EXPORT SourceManager sourceManager;
|
||||||
SDRPP_EXPORT SinkManager sinkManager;
|
SDRPP_EXPORT SinkManager sinkManager;
|
||||||
SDRPP_EXPORT StreamManager streamManager;
|
|
||||||
};
|
};
|
@ -84,7 +84,7 @@ void SourceManager::tune(double freq) {
|
|||||||
if (selectedHandler == NULL) {
|
if (selectedHandler == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: No need to always retune the hardware in Panadapter mode
|
// TODO: No need to always retune the hardware in panadpter mode
|
||||||
selectedHandler->tuneHandler(((tuneMode == TuningMode::NORMAL) ? freq : ifFreq) + tuneOffset, selectedHandler->ctx);
|
selectedHandler->tuneHandler(((tuneMode == TuningMode::NORMAL) ? freq : ifFreq) + tuneOffset, selectedHandler->ctx);
|
||||||
onRetune.emit(freq);
|
onRetune.emit(freq);
|
||||||
currentFreq = freq;
|
currentFreq = freq;
|
||||||
@ -100,7 +100,7 @@ void SourceManager::setTuningMode(TuningMode mode) {
|
|||||||
tune(currentFreq);
|
tune(currentFreq);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SourceManager::setPanadapterIF(double freq) {
|
void SourceManager::setPanadpterIF(double freq) {
|
||||||
ifFreq = freq;
|
ifFreq = freq;
|
||||||
tune(currentFreq);
|
tune(currentFreq);
|
||||||
}
|
}
|
@ -35,7 +35,7 @@ public:
|
|||||||
void tune(double freq);
|
void tune(double freq);
|
||||||
void setTuningOffset(double offset);
|
void setTuningOffset(double offset);
|
||||||
void setTuningMode(TuningMode mode);
|
void setTuningMode(TuningMode mode);
|
||||||
void setPanadapterIF(double freq);
|
void setPanadpterIF(double freq);
|
||||||
|
|
||||||
std::vector<std::string> getSourceNames();
|
std::vector<std::string> getSourceNames();
|
||||||
|
|
||||||
|
@ -1,519 +0,0 @@
|
|||||||
#include "stream.h"
|
|
||||||
#include <utils/flog.h>
|
|
||||||
|
|
||||||
Sink::Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) :
|
|
||||||
entry(entry),
|
|
||||||
stream(stream),
|
|
||||||
streamName(name),
|
|
||||||
id(id),
|
|
||||||
stringId(stringId)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void Sink::showMenu() {}
|
|
||||||
|
|
||||||
SinkEntry::SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate) :
|
|
||||||
manager(manager),
|
|
||||||
parentStream(parentStream),
|
|
||||||
id(id)
|
|
||||||
{
|
|
||||||
this->type = type;
|
|
||||||
this->inputSamplerate = inputSamplerate;
|
|
||||||
|
|
||||||
// Generate string ID
|
|
||||||
stringId = parentStream->getName();
|
|
||||||
char buf[16];
|
|
||||||
sprintf(buf, "%d", (int)id);
|
|
||||||
stringId += buf;
|
|
||||||
|
|
||||||
// Initialize DSP
|
|
||||||
resamp.init(&input, inputSamplerate, inputSamplerate);
|
|
||||||
volumeAdjust.init(&resamp.out, 1.0f, false);
|
|
||||||
|
|
||||||
// Initialize the sink
|
|
||||||
setType(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string SinkEntry::getType() const {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SinkEntry::setType(const std::string& type) {
|
|
||||||
// Get unique lock on the entry
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
|
|
||||||
// Delete existing sink
|
|
||||||
if (sink) {
|
|
||||||
provider->destroySink(std::move(sink));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get shared lock on sink types
|
|
||||||
auto lck2 = manager->getSinkTypesLock();
|
|
||||||
|
|
||||||
// Get the provider or throw error
|
|
||||||
const auto& types = manager->getSinkTypes();
|
|
||||||
if (std::find(types.begin(), types.end(), type) == types.end()) {
|
|
||||||
this->type.clear();
|
|
||||||
throw SinkEntryCreateException("Invalid sink type");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create sink
|
|
||||||
this->type = type;
|
|
||||||
provider = manager->providers[type];
|
|
||||||
sink = provider->createSink(this, &volumeAdjust.out, parentStream->getName(), id, stringId);
|
|
||||||
}
|
|
||||||
|
|
||||||
SinkID SinkEntry::getID() const {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
float SinkEntry::getVolume() const {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
return volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SinkEntry::setVolume(float volume) {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
this->volume = volume;
|
|
||||||
volumeAdjust.setVolume(volume);
|
|
||||||
onVolumeChanged(volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SinkEntry::getMuted() const {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
return muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SinkEntry::setMuted(bool muted) {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
this->muted = muted;
|
|
||||||
volumeAdjust.setMuted(muted);
|
|
||||||
onMutedChanged(muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
float SinkEntry::getPanning() const {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
return panning;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SinkEntry::setPanning(float panning) {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
this->panning = panning;
|
|
||||||
// TODO
|
|
||||||
onPanningChanged(panning);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SinkEntry::showMenu() {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
sink->showMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SinkEntry::startSink() {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
sink->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SinkEntry::stopSink() {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
sink->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard<std::recursive_mutex> SinkEntry::getLock() const {
|
|
||||||
return std::lock_guard<std::recursive_mutex>(mtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SinkEntry::setSamplerate(double samplerate) {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
resamp.setOutSamplerate(samplerate);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SinkEntry::startDSP() {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
resamp.start();
|
|
||||||
volumeAdjust.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SinkEntry::stopDSP() {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
resamp.stop();
|
|
||||||
volumeAdjust.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SinkEntry::destroy(bool forgetSettings) {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
if (sink) {
|
|
||||||
provider->destroySink(std::move(sink));
|
|
||||||
}
|
|
||||||
type.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SinkEntry::setInputSamplerate(double samplerate) {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
resamp.setInSamplerate(samplerate);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string SinkEntry::getStringID() const {
|
|
||||||
return stringId;
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream::Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
|
|
||||||
manager(manager),
|
|
||||||
name(name)
|
|
||||||
{
|
|
||||||
this->samplerate = samplerate;
|
|
||||||
|
|
||||||
// Initialize DSP
|
|
||||||
split.init(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream::~Stream() {
|
|
||||||
// Copy sink IDs
|
|
||||||
std::vector<SinkID> ids;
|
|
||||||
for (auto& [id, sink] : sinks) {
|
|
||||||
ids.push_back(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove them all
|
|
||||||
for (auto& id : ids) {
|
|
||||||
removeSink(id, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& Stream::getName() const {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
SinkID Stream::addSink(const std::string& type, SinkID id) {
|
|
||||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
|
||||||
|
|
||||||
// Find a free ID if not provided
|
|
||||||
if (id < 0) {
|
|
||||||
for (id = 0; sinks.find(id) != sinks.end(); id++);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Check that the provided ID is valid
|
|
||||||
if (sinks.find(id) != sinks.end()) {
|
|
||||||
flog::error("Tried to create sink for stream '{}' with existing ID: {}", name, id);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create sink entry
|
|
||||||
std::shared_ptr<SinkEntry> sink;
|
|
||||||
try {
|
|
||||||
sink = std::make_shared<SinkEntry>(manager, this, type, id, samplerate);
|
|
||||||
}
|
|
||||||
catch (SinkEntryCreateException e) {
|
|
||||||
flog::error("Tried to create sink for stream '{}' with ID '{}': {}", name, id, e.what());
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the sink and DSP
|
|
||||||
sink->startSink();
|
|
||||||
if (running) { sink->startDSP(); }
|
|
||||||
|
|
||||||
// Bind the sinks's input
|
|
||||||
split.bindStream(&sink->input);
|
|
||||||
|
|
||||||
// Add sink to list
|
|
||||||
sinks[id] = sink;
|
|
||||||
|
|
||||||
// Release lock and emit event
|
|
||||||
lck.unlock();
|
|
||||||
onSinkAdded(sink);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Stream::removeSink(SinkID id, bool forgetSettings) {
|
|
||||||
// Acquire shared lock
|
|
||||||
std::shared_ptr<SinkEntry> sink;
|
|
||||||
{
|
|
||||||
std::shared_lock<std::shared_mutex> lck(sinksMtx);
|
|
||||||
|
|
||||||
// Check that the ID exists
|
|
||||||
if (sinks.find(id) == sinks.end()) {
|
|
||||||
flog::error("Tried to remove sink with unknown ID: {}", id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get sink
|
|
||||||
sink = sinks[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit event
|
|
||||||
onSinkRemove(sink);
|
|
||||||
|
|
||||||
// Acquire unique lock
|
|
||||||
{
|
|
||||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
|
||||||
|
|
||||||
// Check that it's still in the list
|
|
||||||
if (sinks.find(id) == sinks.end()) {
|
|
||||||
flog::error("Tried to remove sink with unknown ID: {}", id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from list
|
|
||||||
sinks.erase(id);
|
|
||||||
|
|
||||||
// Unbind the sink's steam
|
|
||||||
split.unbindStream(&sink->input);
|
|
||||||
|
|
||||||
// Stop the sink and DSP
|
|
||||||
sink->stopDSP();
|
|
||||||
sink->stopSink();
|
|
||||||
|
|
||||||
// Delete instance
|
|
||||||
sink->destroy(forgetSettings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_lock<std::shared_mutex> Stream::getSinksLock() {
|
|
||||||
return std::shared_lock<std::shared_mutex>(sinksMtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::map<SinkID, std::shared_ptr<SinkEntry>>& Stream::getSinks() const {
|
|
||||||
return sinks;
|
|
||||||
}
|
|
||||||
|
|
||||||
MasterStream::MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
|
|
||||||
Stream(manager, name, stream, samplerate)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void MasterStream::setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate) {
|
|
||||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
|
||||||
|
|
||||||
// If all that's needed is to set the input, do it and return
|
|
||||||
if (samplerate == 0.0) {
|
|
||||||
split.setInput(stream);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update samplerate
|
|
||||||
this->samplerate = samplerate;
|
|
||||||
|
|
||||||
// Stop DSP
|
|
||||||
if (running) {
|
|
||||||
split.stop();
|
|
||||||
for (auto& [id, sink] : sinks) {
|
|
||||||
sink->stopDSP();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Set input and samplerate
|
|
||||||
split.setInput(stream);
|
|
||||||
for (auto& [id, sink] : sinks) {
|
|
||||||
sink->setInputSamplerate(samplerate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start DSP
|
|
||||||
if (running) {
|
|
||||||
for (auto& [id, sink] : sinks) {
|
|
||||||
sink->startDSP();
|
|
||||||
}
|
|
||||||
split.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MasterStream::setSamplerate(double samplerate) {
|
|
||||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
|
||||||
|
|
||||||
// Update samplerate
|
|
||||||
this->samplerate = samplerate;
|
|
||||||
|
|
||||||
// TODO: Maybe simply disallow while running?
|
|
||||||
|
|
||||||
// Stop DSP if it was running
|
|
||||||
if (running) {
|
|
||||||
split.stop();
|
|
||||||
for (auto& [id, sink] : sinks) {
|
|
||||||
sink->stopDSP();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set samplerate
|
|
||||||
for (auto& [id, sink] : sinks) {
|
|
||||||
sink->setInputSamplerate(samplerate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start DSP if it was running
|
|
||||||
if (running) {
|
|
||||||
for (auto& [id, sink] : sinks) {
|
|
||||||
sink->startDSP();
|
|
||||||
}
|
|
||||||
split.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MasterStream::startDSP() {
|
|
||||||
// TODO: Maybe add a different mutex for the stream?
|
|
||||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
|
||||||
|
|
||||||
// Check if already running
|
|
||||||
if (running) { return; }
|
|
||||||
|
|
||||||
// Start all DSP
|
|
||||||
split.start();
|
|
||||||
for (auto& [id, sink] : sinks) {
|
|
||||||
sink->startDSP();
|
|
||||||
}
|
|
||||||
running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MasterStream::stopDSP() {
|
|
||||||
// TODO: Maybe add a different mutex for the stream?
|
|
||||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
|
||||||
|
|
||||||
// Check if already running
|
|
||||||
if (!running) { return; }
|
|
||||||
|
|
||||||
// Start all DSP
|
|
||||||
split.stop();
|
|
||||||
for (auto& [id, sink] : sinks) {
|
|
||||||
sink->stopDSP();
|
|
||||||
}
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<MasterStream> StreamManager::createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) {
|
|
||||||
std::unique_lock<std::shared_mutex> lck(streamsMtx);
|
|
||||||
|
|
||||||
// Check that no stream with that name already exists
|
|
||||||
if (streams.find(name) != streams.end()) {
|
|
||||||
flog::error("Tried to created stream with an existing name: {}", name);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and save stream
|
|
||||||
std::shared_ptr<MasterStream> newStream(new MasterStream(this, name, stream, samplerate));
|
|
||||||
streams[name] = newStream;
|
|
||||||
|
|
||||||
// Release lock and emit event
|
|
||||||
lck.unlock();
|
|
||||||
onStreamCreated(newStream);
|
|
||||||
|
|
||||||
return newStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
void StreamManager::destroyStream(std::shared_ptr<MasterStream>& stream) {
|
|
||||||
// Emit event
|
|
||||||
onStreamDestroy(stream);
|
|
||||||
|
|
||||||
// Aquire complete lock on the stream list
|
|
||||||
{
|
|
||||||
std::unique_lock<std::shared_mutex> lck(streamsMtx);
|
|
||||||
|
|
||||||
// Get iterator of the stream
|
|
||||||
auto it = std::find_if(streams.begin(), streams.end(), [&stream](std::pair<const std::string, std::shared_ptr<Stream>> e) {
|
|
||||||
return e.second == stream;
|
|
||||||
});
|
|
||||||
if (it == streams.end()) {
|
|
||||||
flog::error("Tried to delete a stream using an invalid pointer. Stream not found in list");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete entry from list
|
|
||||||
flog::debug("Stream pointer uses, should be 2 and is {}", (int)stream.use_count());
|
|
||||||
streams.erase(it);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset passed pointer
|
|
||||||
stream.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_lock<std::shared_mutex> StreamManager::getStreamsLock() {
|
|
||||||
return std::shared_lock<std::shared_mutex>(streamsMtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::map<std::string, std::shared_ptr<Stream>>& StreamManager::getStreams() const {
|
|
||||||
return streams;
|
|
||||||
}
|
|
||||||
|
|
||||||
void StreamManager::registerSinkProvider(const std::string& name, SinkProvider* provider) {
|
|
||||||
std::unique_lock<std::shared_mutex> lck(providersMtx);
|
|
||||||
|
|
||||||
// Check that a provider with that name doesn't already exist
|
|
||||||
if (providers.find(name) != providers.end()) {
|
|
||||||
flog::error("Tried to register a sink provider with an existing name: {}", name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add provider to the list and sort name list
|
|
||||||
providers[name] = provider;
|
|
||||||
sinkTypes.push_back(name);
|
|
||||||
std::sort(sinkTypes.begin(), sinkTypes.end());
|
|
||||||
|
|
||||||
// Release lock and emit event
|
|
||||||
lck.unlock();
|
|
||||||
onSinkProviderRegistered(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StreamManager::unregisterSinkProvider(SinkProvider* provider) {
|
|
||||||
// Get provider name for event
|
|
||||||
std::string type;
|
|
||||||
{
|
|
||||||
std::shared_lock<std::shared_mutex> lck(providersMtx);
|
|
||||||
auto it = std::find_if(providers.begin(), providers.end(), [&provider](std::pair<const std::string, SinkProvider *> e) {
|
|
||||||
return e.second == provider;
|
|
||||||
});
|
|
||||||
if (it == providers.end()) {
|
|
||||||
flog::error("Tried to unregister sink provider using invalid pointer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
type = (*it).first;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit event
|
|
||||||
onSinkProviderUnregister(type);
|
|
||||||
|
|
||||||
// Acquire shared lock on streams
|
|
||||||
{
|
|
||||||
std::unique_lock<std::shared_mutex> lck1(providersMtx);
|
|
||||||
std::shared_lock<std::shared_mutex> lck2(streamsMtx);
|
|
||||||
for (auto& [name, stream] : streams) {
|
|
||||||
// Aquire lock on sink list
|
|
||||||
auto sLock = stream->getSinksLock();
|
|
||||||
const auto& sinks = stream->getSinks();
|
|
||||||
|
|
||||||
// Find all sinks with the type that is about to be removed
|
|
||||||
std::vector<SinkID> toRemove;
|
|
||||||
for (auto& [id, sink] : sinks) {
|
|
||||||
if (sink->getType() != type) { continue; }
|
|
||||||
toRemove.push_back(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove them all (TODO: THERE IS RACE CONDITION IF A SINK IS CHANGED AFTER LISTING)
|
|
||||||
sLock.unlock();
|
|
||||||
for (auto& id : toRemove) {
|
|
||||||
stream->removeSink(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from the lists
|
|
||||||
if (providers.find(type) != providers.end()) {
|
|
||||||
providers.erase(type);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
flog::error("Could not remove sink provider from list");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = std::find(sinkTypes.begin(), sinkTypes.end(), type);
|
|
||||||
if (it != sinkTypes.end()) {
|
|
||||||
sinkTypes.erase(it);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
flog::error("Could not remove sink provider from sink type list");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_lock<std::shared_mutex> StreamManager::getSinkTypesLock() {
|
|
||||||
return std::shared_lock<std::shared_mutex>(providersMtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<std::string>& StreamManager::getSinkTypes() const {
|
|
||||||
// TODO: This allows code to modify the names...
|
|
||||||
return sinkTypes;
|
|
||||||
}
|
|
@ -1,334 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
#include <map>
|
|
||||||
#include <dsp/stream.h>
|
|
||||||
#include <dsp/types.h>
|
|
||||||
#include <dsp/routing/splitter.h>
|
|
||||||
#include <dsp/multirate/rational_resampler.h>
|
|
||||||
#include <dsp/audio/volume.h>
|
|
||||||
#include <utils/new_event.h>
|
|
||||||
#include <shared_mutex>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
class SinkEntry;
|
|
||||||
class Stream;
|
|
||||||
class MasterStream;
|
|
||||||
class StreamManager;
|
|
||||||
|
|
||||||
using SinkID = int;
|
|
||||||
|
|
||||||
class Sink {
|
|
||||||
public:
|
|
||||||
Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId);
|
|
||||||
virtual ~Sink() {}
|
|
||||||
|
|
||||||
virtual void start() = 0;
|
|
||||||
virtual void stop() = 0;
|
|
||||||
virtual void showMenu();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
SinkEntry* const entry;
|
|
||||||
dsp::stream<dsp::stereo_t>* const stream;
|
|
||||||
const std::string streamName;
|
|
||||||
const SinkID id;
|
|
||||||
const std::string stringId;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SinkProvider {
|
|
||||||
friend Sink;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Create a sink instance.
|
|
||||||
* @param name Name of the audio stream.
|
|
||||||
* @param index Index of the sink in the menu. Should be use to keep settings.
|
|
||||||
*/
|
|
||||||
virtual std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy a sink instance. This function is so that the provide knows at all times how many instances there are.
|
|
||||||
* @param sink Instance of the sink.
|
|
||||||
*/
|
|
||||||
virtual void destroySink(std::unique_ptr<Sink> sink) {
|
|
||||||
sink.reset();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class SinkEntryCreateException : public std::runtime_error {
|
|
||||||
public:
|
|
||||||
SinkEntryCreateException(const char* what) : std::runtime_error(what) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Would be cool to have data and audio sinks instead of just audio.
|
|
||||||
class SinkEntry {
|
|
||||||
friend Sink;
|
|
||||||
friend Stream;
|
|
||||||
friend MasterStream;
|
|
||||||
public:
|
|
||||||
SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the type of the sink.
|
|
||||||
* @return Type of the sink.
|
|
||||||
*/
|
|
||||||
std::string getType() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the type of the sink.
|
|
||||||
* @param type New sink type.
|
|
||||||
*/
|
|
||||||
void setType(const std::string& type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the ID of the sink.
|
|
||||||
* @return ID of the sink.
|
|
||||||
*/
|
|
||||||
SinkID getID() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get sink volume.
|
|
||||||
* @return Volume as value between 0.0 and 1.0.
|
|
||||||
*/
|
|
||||||
float getVolume() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set sink volume.
|
|
||||||
* @param volume Volume as value between 0.0 and 1.0.
|
|
||||||
*/
|
|
||||||
void setVolume(float volume);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the sink is muted.
|
|
||||||
* @return True if muted, false if not.
|
|
||||||
*/
|
|
||||||
bool getMuted() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set wether or not the sink is muted
|
|
||||||
* @param muted True to mute, false to unmute.
|
|
||||||
*/
|
|
||||||
void setMuted(bool muted);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get sink panning.
|
|
||||||
* @return Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively.
|
|
||||||
*/
|
|
||||||
float getPanning() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set sink panning.
|
|
||||||
* @param panning Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively.
|
|
||||||
*/
|
|
||||||
void setPanning(float panning);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the sink type-specific menu.
|
|
||||||
*/
|
|
||||||
void showMenu();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the string form ID unique to both the sink and stream. Be used to reference settings.
|
|
||||||
* @return Unique string ID.
|
|
||||||
*/
|
|
||||||
std::string getStringID() const;
|
|
||||||
|
|
||||||
// Emitted when the type of the sink was changed
|
|
||||||
NewEvent<const std::string&> onTypeChanged;
|
|
||||||
// Emmited when volume of the sink was changed
|
|
||||||
NewEvent<float> onVolumeChanged;
|
|
||||||
// Emitted when the muted state of the sink was changed
|
|
||||||
NewEvent<bool> onMutedChanged;
|
|
||||||
// Emitted when the panning of the sink was changed
|
|
||||||
NewEvent<float> onPanningChanged;
|
|
||||||
|
|
||||||
// TODO: Need to allow the sink to change the entry samplerate and start/stop the DSP
|
|
||||||
// This will also require allowing it to get a lock on the sink so others don't attempt to mess with it.
|
|
||||||
std::lock_guard<std::recursive_mutex> getLock() const;
|
|
||||||
void startDSP();
|
|
||||||
void stopDSP();
|
|
||||||
void setSamplerate(double samplerate);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void startSink();
|
|
||||||
void stopSink();
|
|
||||||
|
|
||||||
void destroy(bool forgetSettings);
|
|
||||||
void setInputSamplerate(double samplerate);
|
|
||||||
|
|
||||||
mutable std::recursive_mutex mtx;
|
|
||||||
dsp::stream<dsp::stereo_t> input;
|
|
||||||
dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
|
|
||||||
dsp::audio::Volume volumeAdjust;
|
|
||||||
|
|
||||||
SinkProvider* provider = NULL;
|
|
||||||
std::unique_ptr<Sink> sink;
|
|
||||||
std::string type;
|
|
||||||
const SinkID id;
|
|
||||||
double inputSamplerate;
|
|
||||||
Stream* const parentStream;
|
|
||||||
StreamManager* const manager;
|
|
||||||
|
|
||||||
std::string stringId;
|
|
||||||
|
|
||||||
float volume = 1.0f;
|
|
||||||
bool muted = false;
|
|
||||||
float panning = 0.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Stream {
|
|
||||||
protected:
|
|
||||||
Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
|
|
||||||
public:
|
|
||||||
~Stream();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the name of the stream.
|
|
||||||
* @return Name of the stream.
|
|
||||||
*/
|
|
||||||
const std::string& getName() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a sink to the stream.
|
|
||||||
* @param type Type of the sink.
|
|
||||||
* @param id ID of the sink. Optional, -1 if automatic.
|
|
||||||
* @return ID of the new sink or -1 on error.
|
|
||||||
*/
|
|
||||||
SinkID addSink(const std::string& type, SinkID id = -1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a sink from a stream.
|
|
||||||
* @param id ID of the sink.
|
|
||||||
* @param forgetSettings Forget the settings for the sink.
|
|
||||||
*/
|
|
||||||
void removeSink(SinkID id, bool forgetSettings = true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aquire a lock for the sink list.
|
|
||||||
* @return Shared lock for the sink list.
|
|
||||||
*/
|
|
||||||
std::shared_lock<std::shared_mutex> getSinksLock();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the list of all sinks belonging to this stream.
|
|
||||||
* @return Sink list.
|
|
||||||
*/
|
|
||||||
const std::map<SinkID, std::shared_ptr<SinkEntry>>& getSinks() const;
|
|
||||||
|
|
||||||
// Emitted when the samplerate of the stream was changed
|
|
||||||
NewEvent<double> onSamplerateChanged;
|
|
||||||
// Emitted when a sink was added
|
|
||||||
NewEvent<std::shared_ptr<SinkEntry>> onSinkAdded;
|
|
||||||
// Emitted when a sink is being removed
|
|
||||||
NewEvent<std::shared_ptr<SinkEntry>> onSinkRemove;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
StreamManager* const manager;
|
|
||||||
const std::string name;
|
|
||||||
double samplerate;
|
|
||||||
dsp::routing::Splitter<dsp::stereo_t> split;
|
|
||||||
bool running = false;
|
|
||||||
|
|
||||||
std::map<SinkID, std::shared_ptr<SinkEntry>> sinks;
|
|
||||||
std::shared_mutex sinksMtx;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MasterStream : public Stream {
|
|
||||||
friend StreamManager;
|
|
||||||
MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Set DSP stream input.
|
|
||||||
* @param stream DSP stream.
|
|
||||||
* @param samplerate New samplerate (optional, 0.0 if not used).
|
|
||||||
*/
|
|
||||||
void setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate = 0.0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the samplerate of the input stream.
|
|
||||||
* @param samplerate Samplerate in Hz.
|
|
||||||
*/
|
|
||||||
void setSamplerate(double samplerate);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the DSP.
|
|
||||||
*/
|
|
||||||
void startDSP();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the DSP.
|
|
||||||
*/
|
|
||||||
void stopDSP();
|
|
||||||
};
|
|
||||||
|
|
||||||
class StreamManager {
|
|
||||||
friend SinkEntry;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Create an audio stream.
|
|
||||||
* @param name Name of the stream.
|
|
||||||
* @param stream DSP stream that outputs the audio.
|
|
||||||
* @param samplerate Samplerate of the audio data.
|
|
||||||
* @return Audio stream instance.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<MasterStream> createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy an audio stream.
|
|
||||||
* @param stream Stream to destroy. The passed shared pointer will be automatically reset.
|
|
||||||
*/
|
|
||||||
void destroyStream(std::shared_ptr<MasterStream>& stream);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aquire a lock for the stream list.
|
|
||||||
* @return Shared lock for the stream list.
|
|
||||||
*/
|
|
||||||
std::shared_lock<std::shared_mutex> getStreamsLock();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of streams and their associated names.
|
|
||||||
* @return Map of names to stream instance.
|
|
||||||
*/
|
|
||||||
const std::map<std::string, std::shared_ptr<Stream>>& getStreams() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a sink provider.
|
|
||||||
* @param name Name of the sink type.
|
|
||||||
* @param provider Sink provider instance.
|
|
||||||
*/
|
|
||||||
void registerSinkProvider(const std::string& name, SinkProvider* provider);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregister a sink provider.
|
|
||||||
* @param name Name of the sink type.
|
|
||||||
*/
|
|
||||||
void unregisterSinkProvider(SinkProvider* provider);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aquire a lock for the sink type list.
|
|
||||||
* @return Shared lock for the sink type list.
|
|
||||||
*/
|
|
||||||
std::shared_lock<std::shared_mutex> getSinkTypesLock();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of sink types.
|
|
||||||
* @return List of sink type names in alphabetical order.
|
|
||||||
*/
|
|
||||||
const std::vector<std::string>& getSinkTypes() const;
|
|
||||||
|
|
||||||
// Emitted when a stream was created
|
|
||||||
NewEvent<std::shared_ptr<Stream>> onStreamCreated;
|
|
||||||
// Emitted when a stream is about to be destroyed
|
|
||||||
NewEvent<std::shared_ptr<Stream>> onStreamDestroy;
|
|
||||||
// Emitted when a sink provider was registered
|
|
||||||
NewEvent<const std::string&> onSinkProviderRegistered;
|
|
||||||
// Emitted when a sink provider is about to be unregistered
|
|
||||||
NewEvent<const std::string&> onSinkProviderUnregister;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::map<std::string, std::shared_ptr<Stream>> streams;
|
|
||||||
std::shared_mutex streamsMtx;
|
|
||||||
|
|
||||||
std::map<std::string, SinkProvider*> providers;
|
|
||||||
std::vector<std::string> sinkTypes;
|
|
||||||
std::shared_mutex providersMtx;
|
|
||||||
};
|
|
@ -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) {
|
||||||
@ -384,25 +374,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 +392,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
@ -320,7 +320,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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
};
|
|
@ -63,7 +63,6 @@ namespace net::http {
|
|||||||
|
|
||||||
std::string MessageHeader::getField(const std::string name) {
|
std::string MessageHeader::getField(const std::string name) {
|
||||||
// TODO: Check if exists
|
// TODO: Check if exists
|
||||||
// TODO: Maybe declare the set/get field functions to do type conversions automatically?
|
|
||||||
return fields[name];
|
return fields[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,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) {
|
||||||
@ -276,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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
// };
|
|
||||||
}
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -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)
|
|
@ -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;
|
|
||||||
};
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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/")
|
|
@ -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;
|
|
||||||
};
|
|
@ -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;
|
|
||||||
};
|
|
@ -1,5 +0,0 @@
|
|||||||
#include "flex.h"
|
|
||||||
|
|
||||||
namespace flex {
|
|
||||||
// TODO
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace flex {
|
|
||||||
class Decoder {
|
|
||||||
public:
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
private:
|
|
||||||
// TODO
|
|
||||||
};
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
@ -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;
|
|
||||||
};
|
|
@ -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;
|
|
||||||
};
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
@ -24,12 +24,13 @@ namespace demod {
|
|||||||
class Demodulator {
|
class Demodulator {
|
||||||
public:
|
public:
|
||||||
virtual ~Demodulator() {}
|
virtual ~Demodulator() {}
|
||||||
virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) = 0;
|
virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) = 0;
|
||||||
virtual void start() = 0;
|
virtual void start() = 0;
|
||||||
virtual void stop() = 0;
|
virtual void stop() = 0;
|
||||||
virtual void showMenu() = 0;
|
virtual void showMenu() = 0;
|
||||||
virtual void setBandwidth(double bandwidth) = 0;
|
virtual void setBandwidth(double bandwidth) = 0;
|
||||||
virtual void setInput(dsp::stream<dsp::complex_t>* input) = 0;
|
virtual void setInput(dsp::stream<dsp::complex_t>* input) = 0;
|
||||||
|
virtual void AFSampRateChanged(double newSR) = 0;
|
||||||
virtual const char* getName() = 0;
|
virtual const char* getName() = 0;
|
||||||
virtual double getIFSampleRate() = 0;
|
virtual double getIFSampleRate() = 0;
|
||||||
virtual double getAFSampleRate() = 0;
|
virtual double getAFSampleRate() = 0;
|
||||||
@ -44,6 +45,7 @@ namespace demod {
|
|||||||
virtual int getDefaultDeemphasisMode() = 0;
|
virtual int getDefaultDeemphasisMode() = 0;
|
||||||
virtual bool getFMIFNRAllowed() = 0;
|
virtual bool getFMIFNRAllowed() = 0;
|
||||||
virtual bool getNBAllowed() = 0;
|
virtual bool getNBAllowed() = 0;
|
||||||
|
virtual bool getAFNRAllowed() = 0;
|
||||||
virtual dsp::stream<dsp::stereo_t>* getOutput() = 0;
|
virtual dsp::stream<dsp::stereo_t>* getOutput() = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,13 @@ namespace demod {
|
|||||||
public:
|
public:
|
||||||
AM() {}
|
AM() {}
|
||||||
|
|
||||||
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
init(name, config, input, bandwidth);
|
init(name, config, input, bandwidth, audioSR);
|
||||||
}
|
}
|
||||||
|
|
||||||
~AM() { stop(); }
|
~AM() { stop(); }
|
||||||
|
|
||||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
this->name = name;
|
this->name = name;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
|
||||||
@ -68,6 +68,8 @@ namespace demod {
|
|||||||
|
|
||||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||||
|
|
||||||
|
void AFSampRateChanged(double newSR) {}
|
||||||
|
|
||||||
// ============= INFO =============
|
// ============= INFO =============
|
||||||
|
|
||||||
const char* getName() { return "AM"; }
|
const char* getName() { return "AM"; }
|
||||||
@ -84,6 +86,7 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return false; }
|
bool getFMIFNRAllowed() { return false; }
|
||||||
bool getNBAllowed() { return false; }
|
bool getNBAllowed() { return false; }
|
||||||
|
bool getAFNRAllowed() { return false; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -7,15 +7,15 @@ namespace demod {
|
|||||||
public:
|
public:
|
||||||
CW() {}
|
CW() {}
|
||||||
|
|
||||||
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
init(name, config, input, bandwidth);
|
init(name, config, input, bandwidth, audioSR);
|
||||||
}
|
}
|
||||||
|
|
||||||
~CW() {
|
~CW() {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
this->name = name;
|
this->name = name;
|
||||||
this->_config = config;
|
this->_config = config;
|
||||||
this->afbwChangeHandler = afbwChangeHandler;
|
this->afbwChangeHandler = afbwChangeHandler;
|
||||||
@ -74,6 +74,8 @@ namespace demod {
|
|||||||
|
|
||||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||||
|
|
||||||
|
void AFSampRateChanged(double newSR) {}
|
||||||
|
|
||||||
// ============= INFO =============
|
// ============= INFO =============
|
||||||
|
|
||||||
const char* getName() { return "CW"; }
|
const char* getName() { return "CW"; }
|
||||||
@ -90,6 +92,7 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return false; }
|
bool getFMIFNRAllowed() { return false; }
|
||||||
bool getNBAllowed() { return false; }
|
bool getNBAllowed() { return false; }
|
||||||
|
bool getAFNRAllowed() { return false; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -7,15 +7,15 @@ namespace demod {
|
|||||||
public:
|
public:
|
||||||
DSB() {}
|
DSB() {}
|
||||||
|
|
||||||
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
init(name, config, input, bandwidth);
|
init(name, config, input, bandwidth, audioSR);
|
||||||
}
|
}
|
||||||
|
|
||||||
~DSB() {
|
~DSB() {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
this->name = name;
|
this->name = name;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
|
||||||
@ -61,6 +61,8 @@ namespace demod {
|
|||||||
|
|
||||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||||
|
|
||||||
|
void AFSampRateChanged(double newSR) {}
|
||||||
|
|
||||||
// ============= INFO =============
|
// ============= INFO =============
|
||||||
|
|
||||||
const char* getName() { return "DSB"; }
|
const char* getName() { return "DSB"; }
|
||||||
@ -77,6 +79,7 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return false; }
|
bool getFMIFNRAllowed() { return false; }
|
||||||
bool getNBAllowed() { return true; }
|
bool getNBAllowed() { return true; }
|
||||||
|
bool getAFNRAllowed() { return false; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -7,15 +7,15 @@ namespace demod {
|
|||||||
public:
|
public:
|
||||||
LSB() {}
|
LSB() {}
|
||||||
|
|
||||||
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
init(name, config, input, bandwidth);
|
init(name, config, input, bandwidth, audioSR);
|
||||||
}
|
}
|
||||||
|
|
||||||
~LSB() {
|
~LSB() {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
this->name = name;
|
this->name = name;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
|
||||||
@ -61,6 +61,8 @@ namespace demod {
|
|||||||
|
|
||||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||||
|
|
||||||
|
void AFSampRateChanged(double newSR) {}
|
||||||
|
|
||||||
// ============= INFO =============
|
// ============= INFO =============
|
||||||
|
|
||||||
const char* getName() { return "LSB"; }
|
const char* getName() { return "LSB"; }
|
||||||
@ -77,6 +79,7 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return false; }
|
bool getFMIFNRAllowed() { return false; }
|
||||||
bool getNBAllowed() { return true; }
|
bool getNBAllowed() { return true; }
|
||||||
|
bool getAFNRAllowed() { return false; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -7,13 +7,13 @@ namespace demod {
|
|||||||
public:
|
public:
|
||||||
NFM() {}
|
NFM() {}
|
||||||
|
|
||||||
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
init(name, config, input, bandwidth);
|
init(name, config, input, bandwidth, audioSR);
|
||||||
}
|
}
|
||||||
|
|
||||||
~NFM() { stop(); }
|
~NFM() { stop(); }
|
||||||
|
|
||||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
this->name = name;
|
this->name = name;
|
||||||
this->_config = config;
|
this->_config = config;
|
||||||
|
|
||||||
@ -57,6 +57,8 @@ namespace demod {
|
|||||||
|
|
||||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||||
|
|
||||||
|
void AFSampRateChanged(double newSR) {}
|
||||||
|
|
||||||
// ============= INFO =============
|
// ============= INFO =============
|
||||||
|
|
||||||
const char* getName() { return "FM"; }
|
const char* getName() { return "FM"; }
|
||||||
@ -73,6 +75,7 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return true; }
|
bool getFMIFNRAllowed() { return true; }
|
||||||
bool getNBAllowed() { return false; }
|
bool getNBAllowed() { return false; }
|
||||||
|
bool getAFNRAllowed() { return false; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -7,18 +7,17 @@ namespace demod {
|
|||||||
public:
|
public:
|
||||||
RAW() {}
|
RAW() {}
|
||||||
|
|
||||||
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
init(name, config, input, bandwidth);
|
init(name, config, input, bandwidth, audioSR);
|
||||||
}
|
}
|
||||||
|
|
||||||
~RAW() {
|
~RAW() {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
this->name = name;
|
this->name = name;
|
||||||
audioSampleRate = 48000;
|
audioSampleRate = audioSR;
|
||||||
// TODO: This needs to be selectable
|
|
||||||
|
|
||||||
// Define structure
|
// Define structure
|
||||||
c2s.init(input);
|
c2s.init(input);
|
||||||
@ -40,6 +39,10 @@ namespace demod {
|
|||||||
c2s.setInput(input);
|
c2s.setInput(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AFSampRateChanged(double newSR) {
|
||||||
|
audioSampleRate = newSR;
|
||||||
|
}
|
||||||
|
|
||||||
// ============= INFO =============
|
// ============= INFO =============
|
||||||
|
|
||||||
const char* getName() { return "RAW"; }
|
const char* getName() { return "RAW"; }
|
||||||
@ -56,6 +59,7 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return false; }
|
bool getFMIFNRAllowed() { return false; }
|
||||||
bool getNBAllowed() { return true; }
|
bool getNBAllowed() { return true; }
|
||||||
|
bool getAFNRAllowed() { return false; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &c2s.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &c2s.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -8,15 +8,15 @@ namespace demod {
|
|||||||
public:
|
public:
|
||||||
USB() {}
|
USB() {}
|
||||||
|
|
||||||
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
init(name, config, input, bandwidth);
|
init(name, config, input, bandwidth, audioSR);
|
||||||
}
|
}
|
||||||
|
|
||||||
~USB() {
|
~USB() {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
this->name = name;
|
this->name = name;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
|
||||||
@ -62,6 +62,8 @@ namespace demod {
|
|||||||
|
|
||||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||||
|
|
||||||
|
void AFSampRateChanged(double newSR) {}
|
||||||
|
|
||||||
// ============= INFO =============
|
// ============= INFO =============
|
||||||
|
|
||||||
const char* getName() { return "USB"; }
|
const char* getName() { return "USB"; }
|
||||||
@ -78,6 +80,7 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return false; }
|
bool getFMIFNRAllowed() { return false; }
|
||||||
bool getNBAllowed() { return true; }
|
bool getNBAllowed() { return true; }
|
||||||
|
bool getAFNRAllowed() { return true; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
#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) : 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);
|
init(name, config, input, bandwidth, audioSR);
|
||||||
}
|
}
|
||||||
|
|
||||||
~WFM() {
|
~WFM() {
|
||||||
@ -25,22 +25,14 @@ namespace demod {
|
|||||||
gui::waterfall.onFFTRedraw.unbindHandler(&fftRedrawHandler);
|
gui::waterfall.onFFTRedraw.unbindHandler(&fftRedrawHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||||
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) {
|
||||||
@ -252,6 +112,8 @@ namespace demod {
|
|||||||
demod.setInput(input);
|
demod.setInput(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AFSampRateChanged(double newSR) {}
|
||||||
|
|
||||||
// ============= INFO =============
|
// ============= INFO =============
|
||||||
|
|
||||||
const char* getName() { return "WFM"; }
|
const char* getName() { return "WFM"; }
|
||||||
@ -268,6 +130,7 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; }
|
||||||
bool getFMIFNRAllowed() { return true; }
|
bool getFMIFNRAllowed() { return true; }
|
||||||
bool getNBAllowed() { return false; }
|
bool getNBAllowed() { return false; }
|
||||||
|
bool getAFNRAllowed() { return false; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
// ============= DEDICATED FUNCTIONS =============
|
// ============= DEDICATED FUNCTIONS =============
|
||||||
@ -277,24 +140,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; }
|
||||||
@ -336,31 +187,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;
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -9,6 +9,7 @@
|
|||||||
#include <dsp/noise_reduction/noise_blanker.h>
|
#include <dsp/noise_reduction/noise_blanker.h>
|
||||||
#include <dsp/noise_reduction/fm_if.h>
|
#include <dsp/noise_reduction/fm_if.h>
|
||||||
#include <dsp/noise_reduction/squelch.h>
|
#include <dsp/noise_reduction/squelch.h>
|
||||||
|
#include <dsp/noise_reduction/audio.h>
|
||||||
#include <dsp/multirate/rational_resampler.h>
|
#include <dsp/multirate/rational_resampler.h>
|
||||||
#include <dsp/filter/deephasis.h>
|
#include <dsp/filter/deephasis.h>
|
||||||
#include <core.h>
|
#include <core.h>
|
||||||
@ -81,16 +82,32 @@ public:
|
|||||||
// Initialize audio DSP chain
|
// Initialize audio DSP chain
|
||||||
afChain.init(&dummyAudioStream);
|
afChain.init(&dummyAudioStream);
|
||||||
|
|
||||||
|
resamp.init(NULL, 250000.0, 48000.0);
|
||||||
deemp.init(NULL, 50e-6, 48000.0);
|
deemp.init(NULL, 50e-6, 48000.0);
|
||||||
|
afNR.init(NULL, 1024);
|
||||||
|
|
||||||
|
afChain.addBlock(&resamp, true);
|
||||||
afChain.addBlock(&deemp, false);
|
afChain.addBlock(&deemp, false);
|
||||||
|
afChain.addBlock(&afNR, false);
|
||||||
|
|
||||||
// Initialize the sink
|
// Initialize the sink
|
||||||
stream = sigpath::streamManager.createStream(name, afChain.out, 48000);
|
srChangeHandler.ctx = this;
|
||||||
|
srChangeHandler.handler = sampleRateChangeHandler;
|
||||||
|
stream.init(afChain.out, &srChangeHandler, audioSampleRate);
|
||||||
|
sigpath::sinkManager.registerStream(name, &stream);
|
||||||
|
|
||||||
// Select the demodulator
|
// Select the demodulator
|
||||||
selectDemodByID((DemodID)selectedDemodID);
|
selectDemodByID((DemodID)selectedDemodID);
|
||||||
|
|
||||||
|
// Start IF chain
|
||||||
|
ifChain.start();
|
||||||
|
|
||||||
|
// Start AF chain
|
||||||
|
afChain.start();
|
||||||
|
|
||||||
|
// Start stream, the rest was started when selecting the demodulator
|
||||||
|
stream.start();
|
||||||
|
|
||||||
// Register the menu
|
// Register the menu
|
||||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||||
|
|
||||||
@ -101,10 +118,11 @@ public:
|
|||||||
~RadioModule() {
|
~RadioModule() {
|
||||||
core::modComManager.unregisterInterface(name);
|
core::modComManager.unregisterInterface(name);
|
||||||
gui::menu.removeEntry(name);
|
gui::menu.removeEntry(name);
|
||||||
|
stream.stop();
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
disable();
|
disable();
|
||||||
}
|
}
|
||||||
sigpath::streamManager.destroyStream(stream);
|
sigpath::sinkManager.unregisterStream(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void postInit() {}
|
void postInit() {}
|
||||||
@ -116,7 +134,9 @@ public:
|
|||||||
vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
|
vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
|
||||||
}
|
}
|
||||||
ifChain.setInput(vfo->output, [=](dsp::stream<dsp::complex_t>* out){ ifChainOutputChangeHandler(out, this); });
|
ifChain.setInput(vfo->output, [=](dsp::stream<dsp::complex_t>* out){ ifChainOutputChangeHandler(out, this); });
|
||||||
|
ifChain.start();
|
||||||
selectDemodByID((DemodID)selectedDemodID);
|
selectDemodByID((DemodID)selectedDemodID);
|
||||||
|
afChain.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void disable() {
|
void disable() {
|
||||||
@ -124,7 +144,6 @@ public:
|
|||||||
ifChain.stop();
|
ifChain.stop();
|
||||||
if (selectedDemod) { selectedDemod->stop(); }
|
if (selectedDemod) { selectedDemod->stop(); }
|
||||||
afChain.stop();
|
afChain.stop();
|
||||||
stream->stopDSP();
|
|
||||||
if (vfo) { sigpath::vfoManager.deleteVFO(vfo); }
|
if (vfo) { sigpath::vfoManager.deleteVFO(vfo); }
|
||||||
vfo = NULL;
|
vfo = NULL;
|
||||||
}
|
}
|
||||||
@ -231,6 +250,12 @@ private:
|
|||||||
if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); }
|
if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Noise reduction
|
||||||
|
if (_this->afNRAllowed) {
|
||||||
|
if (ImGui::Checkbox(("Audio Noise Reduction##_radio_afnr_ena_" + _this->name).c_str(), &_this->afNREnabled)) {
|
||||||
|
_this->setAFNREnabled(_this->afNREnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Squelch
|
// Squelch
|
||||||
if (ImGui::Checkbox(("Squelch##_radio_sqelch_ena_" + _this->name).c_str(), &_this->squelchEnabled)) {
|
if (ImGui::Checkbox(("Squelch##_radio_sqelch_ena_" + _this->name).c_str(), &_this->squelchEnabled)) {
|
||||||
@ -297,7 +322,7 @@ private:
|
|||||||
bw = std::clamp<double>(bw, demod->getMinBandwidth(), demod->getMaxBandwidth());
|
bw = std::clamp<double>(bw, demod->getMinBandwidth(), demod->getMaxBandwidth());
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
demod->init(name, &config, ifChain.out, bw);
|
demod->init(name, &config, ifChain.out, bw, stream.getSampleRate());
|
||||||
|
|
||||||
return demod;
|
return demod;
|
||||||
}
|
}
|
||||||
@ -321,34 +346,22 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void selectDemod(demod::Demodulator* demod) {
|
void selectDemod(demod::Demodulator* demod) {
|
||||||
// Stop the IF chain
|
// Stopcurrently selected demodulator and select new
|
||||||
ifChain.stop();
|
afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||||
|
|
||||||
// Stop the current demodulator
|
|
||||||
if (selectedDemod) {
|
if (selectedDemod) {
|
||||||
selectedDemod->stop();
|
selectedDemod->stop();
|
||||||
}
|
|
||||||
|
|
||||||
// Stop AF chain
|
|
||||||
afChain.stop();
|
|
||||||
|
|
||||||
// Stop audio stream's DSP
|
|
||||||
stream->stopDSP();
|
|
||||||
|
|
||||||
// Destroy the old demodulator
|
|
||||||
afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
|
||||||
if (selectedDemod) {
|
|
||||||
delete selectedDemod;
|
delete selectedDemod;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select the new demodulator
|
|
||||||
selectedDemod = demod;
|
selectedDemod = demod;
|
||||||
|
|
||||||
|
// Give the demodulator the most recent audio SR
|
||||||
|
selectedDemod->AFSampRateChanged(audioSampleRate);
|
||||||
|
|
||||||
// Set the demodulator's input
|
// Set the demodulator's input
|
||||||
selectedDemod->setInput(ifChain.out);
|
selectedDemod->setInput(ifChain.out);
|
||||||
|
|
||||||
// Set AF chain's input
|
// Set AF chain's input
|
||||||
afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||||
|
|
||||||
// Load config
|
// Load config
|
||||||
bandwidth = selectedDemod->getDefaultBandwidth();
|
bandwidth = selectedDemod->getDefaultBandwidth();
|
||||||
@ -366,6 +379,8 @@ private:
|
|||||||
fmIFPresetId = ifnrPresets.valueId(IFNR_PRESET_VOICE);
|
fmIFPresetId = ifnrPresets.valueId(IFNR_PRESET_VOICE);
|
||||||
nbAllowed = selectedDemod->getNBAllowed();
|
nbAllowed = selectedDemod->getNBAllowed();
|
||||||
nbEnabled = false;
|
nbEnabled = false;
|
||||||
|
afNRAllowed = selectedDemod->getAFNRAllowed();
|
||||||
|
afNREnabled = false;
|
||||||
nbLevel = 0.0f;
|
nbLevel = 0.0f;
|
||||||
double ifSamplerate = selectedDemod->getIFSampleRate();
|
double ifSamplerate = selectedDemod->getIFSampleRate();
|
||||||
config.acquire();
|
config.acquire();
|
||||||
@ -407,6 +422,9 @@ private:
|
|||||||
if (config.conf[name][selectedDemod->getName()].contains("noiseBlankerLevel")) {
|
if (config.conf[name][selectedDemod->getName()].contains("noiseBlankerLevel")) {
|
||||||
nbLevel = config.conf[name][selectedDemod->getName()]["noiseBlankerLevel"];
|
nbLevel = config.conf[name][selectedDemod->getName()]["noiseBlankerLevel"];
|
||||||
}
|
}
|
||||||
|
if (config.conf[name][selectedDemod->getName()].contains("audioNoiseReductionEnabled")) {
|
||||||
|
nbEnabled = config.conf[name][selectedDemod->getName()]["audioNoiseReductionEnabled"];
|
||||||
|
}
|
||||||
config.release();
|
config.release();
|
||||||
|
|
||||||
// Configure VFO
|
// Configure VFO
|
||||||
@ -436,30 +454,24 @@ private:
|
|||||||
// Configure AF chain
|
// Configure AF chain
|
||||||
if (postProcEnabled) {
|
if (postProcEnabled) {
|
||||||
// Configure resampler
|
// Configure resampler
|
||||||
deemp.setSamplerate(selectedDemod->getAFSampleRate());
|
afChain.stop();
|
||||||
|
resamp.setInSamplerate(selectedDemod->getAFSampleRate());
|
||||||
|
setAudioSampleRate(audioSampleRate);
|
||||||
|
afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||||
|
|
||||||
// Configure deemphasis
|
// Configure deemphasis
|
||||||
setDeemphasisMode(deempModes[deempId]);
|
setDeemphasisMode(deempAllowed ? deempModes[deempId] : DEEMP_MODE_NONE);
|
||||||
|
|
||||||
|
// Configure AF NR
|
||||||
|
setAFNREnabled(afNRAllowed && afNREnabled);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Disable everything if post processing is disabled
|
// Disable everything if post processing is disabled
|
||||||
afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update audo samplerate
|
|
||||||
stream->setSamplerate(selectedDemod->getAFSampleRate());
|
|
||||||
|
|
||||||
// Start the IF chain
|
|
||||||
ifChain.start();
|
|
||||||
|
|
||||||
// Start new demodulator
|
// Start new demodulator
|
||||||
selectedDemod->start();
|
selectedDemod->start();
|
||||||
|
|
||||||
// Start the AF chain
|
|
||||||
afChain.start();
|
|
||||||
|
|
||||||
// Start the audio stream
|
|
||||||
stream->startDSP();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -475,12 +487,37 @@ private:
|
|||||||
config.release(true);
|
config.release(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAudioSampleRate(double sr) {
|
||||||
|
audioSampleRate = sr;
|
||||||
|
if (!selectedDemod) { return; }
|
||||||
|
selectedDemod->AFSampRateChanged(audioSampleRate);
|
||||||
|
if (!postProcEnabled && vfo) {
|
||||||
|
// If postproc is disabled, IF SR = AF SR
|
||||||
|
minBandwidth = selectedDemod->getMinBandwidth();
|
||||||
|
maxBandwidth = selectedDemod->getMaxBandwidth();
|
||||||
|
bandwidth = selectedDemod->getIFSampleRate();
|
||||||
|
vfo->setBandwidthLimits(minBandwidth, maxBandwidth, selectedDemod->getBandwidthLocked());
|
||||||
|
vfo->setSampleRate(selectedDemod->getIFSampleRate(), bandwidth);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
afChain.stop();
|
||||||
|
|
||||||
|
// Configure resampler
|
||||||
|
resamp.setOutSamplerate(audioSampleRate);
|
||||||
|
|
||||||
|
// Configure deemphasis sample rate
|
||||||
|
deemp.setSamplerate(audioSampleRate);
|
||||||
|
|
||||||
|
afChain.start();
|
||||||
|
}
|
||||||
|
|
||||||
void setDeemphasisMode(DeemphasisMode mode) {
|
void setDeemphasisMode(DeemphasisMode mode) {
|
||||||
deempId = deempModes.valueId(mode);
|
deempId = deempModes.valueId(mode);
|
||||||
if (!postProcEnabled || !selectedDemod) { return; }
|
if (!postProcEnabled || !selectedDemod) { return; }
|
||||||
bool deempEnabled = (mode != DEEMP_MODE_NONE);
|
bool deempEnabled = (mode != DEEMP_MODE_NONE);
|
||||||
if (deempEnabled) { deemp.setTau(deempTaus[mode]); }
|
if (deempEnabled) { deemp.setTau(deempTaus[mode]); }
|
||||||
afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||||
|
|
||||||
// Save config
|
// Save config
|
||||||
config.acquire();
|
config.acquire();
|
||||||
@ -488,6 +525,17 @@ private:
|
|||||||
config.release(true);
|
config.release(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAFNREnabled(bool enable) {
|
||||||
|
afNREnabled = enable;
|
||||||
|
if (!postProcEnabled || !selectedDemod) { return; }
|
||||||
|
afChain.setBlockEnabled(&afNR, afNREnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||||
|
|
||||||
|
// Save config
|
||||||
|
config.acquire();
|
||||||
|
config.conf[name][selectedDemod->getName()]["audioNoiseReductionEnabled"] = nbEnabled;
|
||||||
|
config.release(true);
|
||||||
|
}
|
||||||
|
|
||||||
void setNBEnabled(bool enable) {
|
void setNBEnabled(bool enable) {
|
||||||
nbEnabled = enable;
|
nbEnabled = enable;
|
||||||
if (!selectedDemod) { return; }
|
if (!selectedDemod) { return; }
|
||||||
@ -564,6 +612,11 @@ private:
|
|||||||
_this->setBandwidth(newBw);
|
_this->setBandwidth(newBw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sampleRateChangeHandler(float sampleRate, void* ctx) {
|
||||||
|
RadioModule* _this = (RadioModule*)ctx;
|
||||||
|
_this->setAudioSampleRate(sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
static void ifChainOutputChangeHandler(dsp::stream<dsp::complex_t>* output, void* ctx) {
|
static void ifChainOutputChangeHandler(dsp::stream<dsp::complex_t>* output, void* ctx) {
|
||||||
RadioModule* _this = (RadioModule*)ctx;
|
RadioModule* _this = (RadioModule*)ctx;
|
||||||
if (!_this->selectedDemod) { return; }
|
if (!_this->selectedDemod) { return; }
|
||||||
@ -618,6 +671,7 @@ private:
|
|||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
EventHandler<double> onUserChangedBandwidthHandler;
|
EventHandler<double> onUserChangedBandwidthHandler;
|
||||||
|
EventHandler<float> srChangeHandler;
|
||||||
EventHandler<dsp::stream<dsp::complex_t>*> ifChainOutputChanged;
|
EventHandler<dsp::stream<dsp::complex_t>*> ifChainOutputChanged;
|
||||||
EventHandler<dsp::stream<dsp::stereo_t>*> afChainOutputChanged;
|
EventHandler<dsp::stream<dsp::stereo_t>*> afChainOutputChanged;
|
||||||
|
|
||||||
@ -632,9 +686,11 @@ private:
|
|||||||
// Audio chain
|
// Audio chain
|
||||||
dsp::stream<dsp::stereo_t> dummyAudioStream;
|
dsp::stream<dsp::stereo_t> dummyAudioStream;
|
||||||
dsp::chain<dsp::stereo_t> afChain;
|
dsp::chain<dsp::stereo_t> afChain;
|
||||||
|
dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
|
||||||
dsp::filter::Deemphasis<dsp::stereo_t> deemp;
|
dsp::filter::Deemphasis<dsp::stereo_t> deemp;
|
||||||
|
dsp::noise_reduction::Audio afNR;
|
||||||
|
|
||||||
std::shared_ptr<MasterStream> stream;
|
SinkManager::Stream stream;
|
||||||
|
|
||||||
demod::Demodulator* selectedDemod = NULL;
|
demod::Demodulator* selectedDemod = NULL;
|
||||||
|
|
||||||
@ -656,6 +712,9 @@ private:
|
|||||||
int deempId = 0;
|
int deempId = 0;
|
||||||
bool deempAllowed;
|
bool deempAllowed;
|
||||||
|
|
||||||
|
bool afNREnabled = false;
|
||||||
|
bool afNRAllowed;
|
||||||
|
|
||||||
bool FMIFNRAllowed;
|
bool FMIFNRAllowed;
|
||||||
bool FMIFNREnabled = false;
|
bool FMIFNREnabled = false;
|
||||||
int fmIFPresetId;
|
int fmIFPresetId;
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 = " ";
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -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;
|
|
||||||
};
|
|
@ -1,4 +0,0 @@
|
|||||||
FROM debian:bookworm
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
COPY do_build.sh /root
|
|
||||||
RUN chmod +x /root/do_build.sh
|
|
@ -1,35 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
cd /root
|
|
||||||
|
|
||||||
# Install dependencies and tools
|
|
||||||
apt update
|
|
||||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
|
||||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
|
||||||
libcodec2-dev autoconf libtool xxd
|
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
|
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
|
||||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
|
||||||
cp inc/* /usr/include/
|
|
||||||
|
|
||||||
# Install libperseus
|
|
||||||
git clone https://github.com/Microtelecom/libperseus-sdr
|
|
||||||
cd libperseus-sdr
|
|
||||||
autoreconf -i
|
|
||||||
./configure
|
|
||||||
make
|
|
||||||
make install
|
|
||||||
ldconfig
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd SDRPlusPlus
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
|
||||||
make VERBOSE=1 -j2
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev, libzstd-dev'
|
|
@ -4,31 +4,21 @@ cd /root
|
|||||||
|
|
||||||
# Install dependencies and tools
|
# Install dependencies and tools
|
||||||
apt update
|
apt update
|
||||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \
|
||||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||||
libcodec2-dev autoconf libtool xxd
|
libcodec2-dev
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
||||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install libperseus
|
|
||||||
git clone https://github.com/Microtelecom/libperseus-sdr
|
|
||||||
cd libperseus-sdr
|
|
||||||
autoreconf -i
|
|
||||||
./configure
|
|
||||||
make
|
|
||||||
make install
|
|
||||||
ldconfig
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd SDRPlusPlus
|
cd SDRPlusPlus
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
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
|
||||||
make VERBOSE=1 -j2
|
make VERBOSE=1 -j2
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
|
@ -4,31 +4,21 @@ cd /root
|
|||||||
|
|
||||||
# Install dependencies and tools
|
# Install dependencies and tools
|
||||||
apt update
|
apt update
|
||||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \
|
||||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||||
libcodec2-dev autoconf libtool xxd
|
libcodec2-dev
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
||||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install libperseus
|
|
||||||
git clone https://github.com/Microtelecom/libperseus-sdr
|
|
||||||
cd libperseus-sdr
|
|
||||||
autoreconf -i
|
|
||||||
./configure
|
|
||||||
make
|
|
||||||
make install
|
|
||||||
ldconfig
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd SDRPlusPlus
|
cd SDRPlusPlus
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
|
||||||
make VERBOSE=1 -j2
|
make VERBOSE=1 -j2
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
|
@ -4,32 +4,22 @@ cd /root
|
|||||||
|
|
||||||
# Install dependencies and tools
|
# Install dependencies and tools
|
||||||
apt update
|
apt update
|
||||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \
|
||||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||||
libcodec2-dev autoconf libtool xxd
|
libcodec2-dev
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
||||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install libperseus
|
|
||||||
git clone https://github.com/Microtelecom/libperseus-sdr
|
|
||||||
cd libperseus-sdr
|
|
||||||
autoreconf -i
|
|
||||||
./configure
|
|
||||||
make
|
|
||||||
make install
|
|
||||||
ldconfig
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd SDRPlusPlus
|
cd SDRPlusPlus
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
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
|
||||||
make VERBOSE=1 -j2
|
make VERBOSE=1 -j2
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev'
|
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev, libzstd-dev'
|
@ -10,15 +10,15 @@ echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://ap
|
|||||||
apt update
|
apt update
|
||||||
|
|
||||||
# Install dependencies and tools
|
# Install dependencies and tools
|
||||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libairspy-dev \
|
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libsoapysdr-dev libairspy-dev \
|
||||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||||
libcodec2-dev libudev-dev autoconf libtool xxd
|
libcodec2-dev libudev-dev
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
||||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install a more recent libusb version
|
# Install a more recent libusb version
|
||||||
@ -41,16 +41,6 @@ make install
|
|||||||
ldconfig
|
ldconfig
|
||||||
cd ../../
|
cd ../../
|
||||||
|
|
||||||
# Install libperseus
|
|
||||||
git clone https://github.com/Microtelecom/libperseus-sdr
|
|
||||||
cd libperseus-sdr
|
|
||||||
autoreconf -i
|
|
||||||
./configure
|
|
||||||
make
|
|
||||||
make install
|
|
||||||
ldconfig
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
# Fix missing .pc file for codec2
|
# Fix missing .pc file for codec2
|
||||||
echo 'prefix=/usr/' >> /usr/share/pkgconfig/codec2.pc
|
echo 'prefix=/usr/' >> /usr/share/pkgconfig/codec2.pc
|
||||||
echo 'libdir=/usr/include/x86_64-linux-gnu/' >> /usr/share/pkgconfig/codec2.pc
|
echo 'libdir=/usr/include/x86_64-linux-gnu/' >> /usr/share/pkgconfig/codec2.pc
|
||||||
@ -66,7 +56,7 @@ echo 'Cflags: -I/usr/include/codec2' >> /usr/share/pkgconfig/codec2.pc
|
|||||||
cd SDRPlusPlus
|
cd SDRPlusPlus
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON
|
||||||
make VERBOSE=1 -j2
|
make VERBOSE=1 -j2
|
||||||
|
|
||||||
# Generate package
|
# Generate package
|
||||||
|
@ -4,31 +4,21 @@ cd /root
|
|||||||
|
|
||||||
# Install dependencies and tools
|
# Install dependencies and tools
|
||||||
apt update
|
apt update
|
||||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \
|
||||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||||
libcodec2-dev autoconf libtool xxd
|
libcodec2-dev
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
||||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install libperseus
|
|
||||||
git clone https://github.com/Microtelecom/libperseus-sdr
|
|
||||||
cd libperseus-sdr
|
|
||||||
autoreconf -i
|
|
||||||
./configure
|
|
||||||
make
|
|
||||||
make install
|
|
||||||
ldconfig
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd SDRPlusPlus
|
cd SDRPlusPlus
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
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
|
||||||
make VERBOSE=1 -j2
|
make VERBOSE=1 -j2
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
|
@ -4,31 +4,21 @@ cd /root
|
|||||||
|
|
||||||
# Install dependencies and tools
|
# Install dependencies and tools
|
||||||
apt update
|
apt update
|
||||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \
|
||||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||||
libcodec2-dev autoconf libtool xxd
|
libcodec2-dev
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
||||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install libperseus
|
|
||||||
git clone https://github.com/Microtelecom/libperseus-sdr
|
|
||||||
cd libperseus-sdr
|
|
||||||
autoreconf -i
|
|
||||||
./configure
|
|
||||||
make
|
|
||||||
make install
|
|
||||||
ldconfig
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd SDRPlusPlus
|
cd SDRPlusPlus
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
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
|
||||||
make VERBOSE=1 -j2
|
make VERBOSE=1 -j2
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
FROM ubuntu:mantic
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
COPY do_build.sh /root
|
|
||||||
RUN chmod +x /root/do_build.sh
|
|
@ -1,35 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
cd /root
|
|
||||||
|
|
||||||
# Install dependencies and tools
|
|
||||||
apt update
|
|
||||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
|
||||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
|
||||||
libcodec2-dev autoconf libtool xxd
|
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
|
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
|
||||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
|
||||||
cp inc/* /usr/include/
|
|
||||||
|
|
||||||
# Install libperseus
|
|
||||||
git clone https://github.com/Microtelecom/libperseus-sdr
|
|
||||||
cd libperseus-sdr
|
|
||||||
autoreconf -i
|
|
||||||
./configure
|
|
||||||
make
|
|
||||||
make install
|
|
||||||
ldconfig
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd SDRPlusPlus
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
|
||||||
make VERBOSE=1 -j2
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev'
|
|
@ -26,8 +26,7 @@ bundle_is_not_to_be_installed() {
|
|||||||
if [ "$1" = "CFNetwork" ]; then echo 1; fi
|
if [ "$1" = "CFNetwork" ]; then echo 1; fi
|
||||||
if [ "$1" = "SystemConfiguration" ]; then echo 1; fi
|
if [ "$1" = "SystemConfiguration" ]; then echo 1; fi
|
||||||
if [ "$1" = "Security" ]; then echo 1; fi
|
if [ "$1" = "Security" ]; then echo 1; fi
|
||||||
if [ "$1" = "AppleFSCompression" ]; then echo 1; fi
|
if [ "$1" = "AppleFSCompression" ]; then echo 1; fi
|
||||||
if [ "$1" = "libsdrplay_api.so.3.14" ]; then echo 1; fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ========================= FOR INTERNAL USE ONLY =========================
|
# ========================= FOR INTERNAL USE ONLY =========================
|
||||||
@ -82,7 +81,7 @@ bundle_find_full_path() {
|
|||||||
|
|
||||||
# Correct dep path
|
# Correct dep path
|
||||||
echo $RPATH/$RPATH_NEXT
|
echo $RPATH/$RPATH_NEXT
|
||||||
return -1
|
return
|
||||||
done
|
done
|
||||||
|
|
||||||
# Search other common paths
|
# Search other common paths
|
||||||
|
@ -38,13 +38,13 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules
|
|||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hackrf_source/hackrf_source.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hackrf_source/hackrf_source.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hermes_source/hermes_source.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hermes_source/hermes_source.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/limesdr_source/limesdr_source.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/limesdr_source/limesdr_source.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/perseus_source/perseus_source.dylib
|
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/plutosdr_source/plutosdr_source.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/plutosdr_source/plutosdr_source.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rfspace_source/rfspace_source.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rfspace_source/rfspace_source.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_sdr_source/rtl_sdr_source.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_sdr_source/rtl_sdr_source.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_tcp_source/rtl_tcp_source.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_tcp_source/rtl_tcp_source.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/sdrplay_source/sdrplay_source.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/sdrplay_source/sdrplay_source.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/sdrpp_server_source/sdrpp_server_source.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/sdrpp_server_source/sdrpp_server_source.dylib
|
||||||
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/soapy_source/soapy_source.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/spyserver_source/spyserver_source.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/spyserver_source/spyserver_source.dylib
|
||||||
# bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/usrp_source/usrp_source.dylib
|
# bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/usrp_source/usrp_source.dylib
|
||||||
|
|
||||||
@ -62,7 +62,6 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/decoder_module
|
|||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/discord_integration/discord_integration.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/discord_integration/discord_integration.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/frequency_manager/frequency_manager.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/frequency_manager/frequency_manager.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/recorder/recorder.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/recorder/recorder.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/rigctl_client/rigctl_client.dylib
|
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/rigctl_server/rigctl_server.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/rigctl_server/rigctl_server.dylib
|
||||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/scanner/scanner.dylib
|
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/scanner/scanner.dylib
|
||||||
|
|
||||||
|
@ -32,9 +32,6 @@ cp $build_dir/source_modules/hermes_source/Release/hermes_source.dll sdrpp_windo
|
|||||||
cp $build_dir/source_modules/limesdr_source/Release/limesdr_source.dll sdrpp_windows_x64/modules/
|
cp $build_dir/source_modules/limesdr_source/Release/limesdr_source.dll sdrpp_windows_x64/modules/
|
||||||
cp 'C:/Program Files/PothosSDR/bin/LimeSuite.dll' sdrpp_windows_x64/
|
cp 'C:/Program Files/PothosSDR/bin/LimeSuite.dll' sdrpp_windows_x64/
|
||||||
|
|
||||||
cp $build_dir/source_modules/perseus_source/Release/perseus_source.dll sdrpp_windows_x64/modules/
|
|
||||||
cp 'C:/Program Files/PothosSDR/bin/perseus-sdr.dll' sdrpp_windows_x64/
|
|
||||||
|
|
||||||
cp $build_dir/source_modules/plutosdr_source/Release/plutosdr_source.dll sdrpp_windows_x64/modules/
|
cp $build_dir/source_modules/plutosdr_source/Release/plutosdr_source.dll sdrpp_windows_x64/modules/
|
||||||
cp 'C:/Program Files/PothosSDR/bin/libiio.dll' sdrpp_windows_x64/
|
cp 'C:/Program Files/PothosSDR/bin/libiio.dll' sdrpp_windows_x64/
|
||||||
cp 'C:/Program Files/PothosSDR/bin/libad9361.dll' sdrpp_windows_x64/
|
cp 'C:/Program Files/PothosSDR/bin/libad9361.dll' sdrpp_windows_x64/
|
||||||
@ -51,6 +48,8 @@ cp 'C:/Program Files/SDRplay/API/x64/sdrplay_api.dll' sdrpp_windows_x64/ -ErrorA
|
|||||||
|
|
||||||
cp $build_dir/source_modules/sdrpp_server_source/Release/sdrpp_server_source.dll sdrpp_windows_x64/modules/
|
cp $build_dir/source_modules/sdrpp_server_source/Release/sdrpp_server_source.dll sdrpp_windows_x64/modules/
|
||||||
|
|
||||||
|
cp $build_dir/source_modules/soapy_source/Release/soapy_source.dll sdrpp_windows_x64/modules/
|
||||||
|
|
||||||
cp $build_dir/source_modules/spyserver_source/Release/spyserver_source.dll sdrpp_windows_x64/modules/
|
cp $build_dir/source_modules/spyserver_source/Release/spyserver_source.dll sdrpp_windows_x64/modules/
|
||||||
|
|
||||||
# cp $build_dir/source_modules/usrp_source/Release/usrp_source.dll sdrpp_windows_x64/modules/
|
# cp $build_dir/source_modules/usrp_source/Release/usrp_source.dll sdrpp_windows_x64/modules/
|
||||||
@ -77,8 +76,6 @@ cp $build_dir/misc_modules/discord_integration/Release/discord_integration.dll s
|
|||||||
|
|
||||||
cp $build_dir/misc_modules/frequency_manager/Release/frequency_manager.dll sdrpp_windows_x64/modules/
|
cp $build_dir/misc_modules/frequency_manager/Release/frequency_manager.dll sdrpp_windows_x64/modules/
|
||||||
|
|
||||||
cp $build_dir/misc_modules/iq_exporter/Release/iq_exporter.dll sdrpp_windows_x64/modules/
|
|
||||||
|
|
||||||
cp $build_dir/misc_modules/recorder/Release/recorder.dll sdrpp_windows_x64/modules/
|
cp $build_dir/misc_modules/recorder/Release/recorder.dll sdrpp_windows_x64/modules/
|
||||||
|
|
||||||
cp $build_dir/misc_modules/rigctl_client/Release/rigctl_client.dll sdrpp_windows_x64/modules/
|
cp $build_dir/misc_modules/rigctl_client/Release/rigctl_client.dll sdrpp_windows_x64/modules/
|
||||||
|
@ -485,7 +485,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bookmark list
|
// Bookmark list
|
||||||
if (ImGui::BeginTable(("freq_manager_bkm_table" + _this->name).c_str(), 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200.0f * style::uiScale))) {
|
if (ImGui::BeginTable(("freq_manager_bkm_table" + _this->name).c_str(), 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200))) {
|
||||||
ImGui::TableSetupColumn("Name");
|
ImGui::TableSetupColumn("Name");
|
||||||
ImGui::TableSetupColumn("Bookmark");
|
ImGui::TableSetupColumn("Bookmark");
|
||||||
ImGui::TableSetupScrollFreeze(2, 1);
|
ImGui::TableSetupScrollFreeze(2, 1);
|
||||||
@ -531,7 +531,7 @@ private:
|
|||||||
ImGui::TableSetColumnIndex(0);
|
ImGui::TableSetColumnIndex(0);
|
||||||
if (ImGui::Button(("Import##_freq_mgr_imp_" + _this->name).c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0)) && !_this->importOpen) {
|
if (ImGui::Button(("Import##_freq_mgr_imp_" + _this->name).c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0)) && !_this->importOpen) {
|
||||||
_this->importOpen = true;
|
_this->importOpen = true;
|
||||||
_this->importDialog = new pfd::open_file("Import bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" }, pfd::opt::multiselect);
|
_this->importDialog = new pfd::open_file("Import bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" }, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::TableSetColumnIndex(1);
|
ImGui::TableSetColumnIndex(1);
|
||||||
@ -544,7 +544,7 @@ private:
|
|||||||
}
|
}
|
||||||
config.release();
|
config.release();
|
||||||
_this->exportOpen = true;
|
_this->exportOpen = true;
|
||||||
_this->exportDialog = new pfd::save_file("Export bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" });
|
_this->exportDialog = new pfd::save_file("Export bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" }, true);
|
||||||
}
|
}
|
||||||
if (selectedNames.size() == 0 && _this->selectedListName != "") { style::endDisabled(); }
|
if (selectedNames.size() == 0 && _this->selectedListName != "") { style::endDisabled(); }
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
@ -787,7 +787,7 @@ private:
|
|||||||
|
|
||||||
void exportBookmarks(std::string path) {
|
void exportBookmarks(std::string path) {
|
||||||
std::ofstream fs(path);
|
std::ofstream fs(path);
|
||||||
fs << exportedBookmarks;
|
exportedBookmarks >> fs;
|
||||||
fs.close();
|
fs.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.13)
|
|
||||||
project(iq_exporter)
|
|
||||||
|
|
||||||
file(GLOB SRC "src/*.cpp")
|
|
||||||
|
|
||||||
include(${SDRPP_MODULE_CMAKE})
|
|
@ -1,590 +0,0 @@
|
|||||||
#include <utils/net.h>
|
|
||||||
#include <imgui.h>
|
|
||||||
#include <module.h>
|
|
||||||
#include <gui/gui.h>
|
|
||||||
#include <gui/style.h>
|
|
||||||
#include <utils/optionlist.h>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <dsp/sink/handler_sink.h>
|
|
||||||
#include <volk/volk.h>
|
|
||||||
#include <signal_path/signal_path.h>
|
|
||||||
#include <dsp/buffer/reshaper.h>
|
|
||||||
#include <gui/dialogs/dialog_box.h>
|
|
||||||
#include <core.h>
|
|
||||||
|
|
||||||
SDRPP_MOD_INFO{
|
|
||||||
/* Name: */ "iq_exporter",
|
|
||||||
/* Description: */ "Export raw IQ through TCP or UDP",
|
|
||||||
/* Author: */ "Ryzerth",
|
|
||||||
/* Version: */ 0, 1, 0,
|
|
||||||
/* Max instances */ -1
|
|
||||||
};
|
|
||||||
|
|
||||||
ConfigManager config;
|
|
||||||
|
|
||||||
enum Mode {
|
|
||||||
MODE_NONE = -1,
|
|
||||||
MODE_BASEBAND,
|
|
||||||
MODE_VFO
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Protocol {
|
|
||||||
PROTOCOL_TCP_SERVER,
|
|
||||||
PROTOCOL_TCP_CLIENT,
|
|
||||||
PROTOCOL_UDP
|
|
||||||
};
|
|
||||||
|
|
||||||
enum SampleType {
|
|
||||||
SAMPLE_TYPE_INT8,
|
|
||||||
SAMPLE_TYPE_INT16,
|
|
||||||
SAMPLE_TYPE_INT32,
|
|
||||||
SAMPLE_TYPE_FLOAT32
|
|
||||||
};
|
|
||||||
|
|
||||||
class IQExporterModule : public ModuleManager::Instance {
|
|
||||||
public:
|
|
||||||
IQExporterModule(std::string name) {
|
|
||||||
this->name = name;
|
|
||||||
|
|
||||||
// Define operating modes
|
|
||||||
modes.define("Baseband", MODE_BASEBAND);
|
|
||||||
modes.define("VFO", MODE_VFO);
|
|
||||||
|
|
||||||
// Define VFO samplerates
|
|
||||||
for (int i = 3000; i <= 192000; i <<= 1) {
|
|
||||||
samplerates.define(i, getSrScaled(i), i);
|
|
||||||
}
|
|
||||||
for (int i = 250000; i < 1000000; i += 250000) {
|
|
||||||
samplerates.define(i, getSrScaled(i), i);
|
|
||||||
}
|
|
||||||
for (int i = 1000000; i < 10000000; i += 500000) {
|
|
||||||
samplerates.define(i, getSrScaled(i), i);
|
|
||||||
}
|
|
||||||
for (int i = 10000000; i <= 100000000; i += 5000000) {
|
|
||||||
samplerates.define(i, getSrScaled(i), i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define protocols
|
|
||||||
protocols.define("TCP (Server)", PROTOCOL_TCP_SERVER);
|
|
||||||
protocols.define("TCP (Client)", PROTOCOL_TCP_CLIENT);
|
|
||||||
protocols.define("UDP", PROTOCOL_UDP);
|
|
||||||
|
|
||||||
// Define sample types
|
|
||||||
sampleTypes.define("Int8", SAMPLE_TYPE_INT8);
|
|
||||||
sampleTypes.define("Int16", SAMPLE_TYPE_INT16);
|
|
||||||
sampleTypes.define("Int32", SAMPLE_TYPE_INT32);
|
|
||||||
sampleTypes.define("Float32", SAMPLE_TYPE_FLOAT32);
|
|
||||||
|
|
||||||
// Define packet sizes
|
|
||||||
for (int i = 8; i <= 32768; i <<= 1) {
|
|
||||||
char buf[16];
|
|
||||||
sprintf(buf, "%d Bytes", i);
|
|
||||||
packetSizes.define(i, buf, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load config
|
|
||||||
bool autoStart = false;
|
|
||||||
Mode nMode = MODE_BASEBAND;
|
|
||||||
config.acquire();
|
|
||||||
if (config.conf[name].contains("mode")) {
|
|
||||||
std::string modeStr = config.conf[name]["mode"];
|
|
||||||
if (modes.keyExists(modeStr)) { nMode = modes.value(modes.keyId(modeStr)); }
|
|
||||||
}
|
|
||||||
if (config.conf[name].contains("samplerate")) {
|
|
||||||
int sr = config.conf[name]["samplerate"];
|
|
||||||
if (samplerates.keyExists(sr)) { samplerate = samplerates.value(samplerates.keyId(sr)); }
|
|
||||||
}
|
|
||||||
if (config.conf[name].contains("protocol")) {
|
|
||||||
std::string protoStr = config.conf[name]["protocol"];
|
|
||||||
if (protocols.keyExists(protoStr)) { proto = protocols.value(protocols.keyId(protoStr)); }
|
|
||||||
}
|
|
||||||
if (config.conf[name].contains("sampleType")) {
|
|
||||||
std::string sampTypeStr = config.conf[name]["sampleType"];
|
|
||||||
if (sampleTypes.keyExists(sampTypeStr)) { sampType = sampleTypes.value(sampleTypes.keyId(sampTypeStr)); }
|
|
||||||
}
|
|
||||||
if (config.conf[name].contains("packetSize")) {
|
|
||||||
int size = config.conf[name]["packetSize"];
|
|
||||||
if (packetSizes.keyExists(size)) { packetSize = packetSizes.value(packetSizes.keyId(size)); }
|
|
||||||
}
|
|
||||||
if (config.conf[name].contains("host")) {
|
|
||||||
std::string hostStr = config.conf[name]["host"];
|
|
||||||
strcpy(hostname, hostStr.c_str());
|
|
||||||
}
|
|
||||||
if (config.conf[name].contains("port")) {
|
|
||||||
port = config.conf[name]["port"];
|
|
||||||
port = std::clamp<int>(port, 1, 65535);
|
|
||||||
}
|
|
||||||
if (config.conf[name].contains("running")) {
|
|
||||||
autoStart = config.conf[name]["running"];
|
|
||||||
}
|
|
||||||
config.release();
|
|
||||||
|
|
||||||
// Set menu IDs
|
|
||||||
modeId = modes.valueId(nMode);
|
|
||||||
srId = samplerates.valueId(samplerate);
|
|
||||||
protoId = protocols.valueId(proto);
|
|
||||||
sampTypeId = sampleTypes.valueId(sampType);
|
|
||||||
packetSizeId = packetSizes.valueId(packetSize);
|
|
||||||
|
|
||||||
// Allocate buffer
|
|
||||||
buffer = dsp::buffer::alloc<uint8_t>(STREAM_BUFFER_SIZE * sizeof(dsp::complex_t));
|
|
||||||
|
|
||||||
// Init DSP
|
|
||||||
reshape.init(&iqStream, packetSize/sampleSize(), 0);
|
|
||||||
handler.init(&reshape.out, dataHandler, this);
|
|
||||||
|
|
||||||
// Set operating mode
|
|
||||||
setMode(nMode);
|
|
||||||
|
|
||||||
// Start if needed
|
|
||||||
if (autoStart) { start(); }
|
|
||||||
|
|
||||||
// Register menu entry
|
|
||||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
~IQExporterModule() {
|
|
||||||
// Un-register menu entry
|
|
||||||
gui::menu.removeEntry(name);
|
|
||||||
|
|
||||||
// Stop networking
|
|
||||||
stop();
|
|
||||||
|
|
||||||
// Stop DSP
|
|
||||||
setMode(MODE_NONE);
|
|
||||||
|
|
||||||
// Free buffer
|
|
||||||
dsp::buffer::free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void postInit() {}
|
|
||||||
|
|
||||||
void enable() {
|
|
||||||
// Rebind streams and start DSP
|
|
||||||
setMode(mode, true);
|
|
||||||
|
|
||||||
// Restart networking if it was running
|
|
||||||
if (wasRunning) { start(); }
|
|
||||||
|
|
||||||
// Mark as running
|
|
||||||
enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void disable() {
|
|
||||||
// Save running state
|
|
||||||
wasRunning = running;
|
|
||||||
|
|
||||||
// Stop networking
|
|
||||||
stop();
|
|
||||||
|
|
||||||
// Stop the DSP and unbind streams
|
|
||||||
setMode(MODE_NONE);
|
|
||||||
|
|
||||||
// Mark as disabled
|
|
||||||
enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isEnabled() {
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
void start() {
|
|
||||||
if (running) { return; }
|
|
||||||
|
|
||||||
// Acquire lock on the socket
|
|
||||||
std::lock_guard lck1(sockMtx);
|
|
||||||
|
|
||||||
// Start listening or open UDP socket
|
|
||||||
try {
|
|
||||||
if (proto == PROTOCOL_TCP_SERVER) {
|
|
||||||
// Create listener
|
|
||||||
listener = net::listen(hostname, port);
|
|
||||||
|
|
||||||
// Start listen worker
|
|
||||||
listenWorkerThread = std::thread(&IQExporterModule::listenWorker, this);
|
|
||||||
}
|
|
||||||
else if (proto == PROTOCOL_TCP_CLIENT) {
|
|
||||||
// Connect to TCP server
|
|
||||||
sock = net::connect(hostname, port);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Open UDP socket
|
|
||||||
sock = net::openudp(hostname, port, "0.0.0.0", 0, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (const std::exception& e) {
|
|
||||||
flog::error("[IQExporter] Could not start socket: {}", e.what());
|
|
||||||
errorStr = e.what();
|
|
||||||
showError = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
if (!running) { return; }
|
|
||||||
|
|
||||||
// Acquire lock on the socket
|
|
||||||
std::lock_guard lck1(sockMtx);
|
|
||||||
|
|
||||||
// Stop listening or close UDP socket
|
|
||||||
if (proto == PROTOCOL_TCP_SERVER) {
|
|
||||||
// Stop listener
|
|
||||||
if (listener) {
|
|
||||||
listener->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for worker to stop
|
|
||||||
if (listenWorkerThread.joinable()) { listenWorkerThread.join(); }
|
|
||||||
|
|
||||||
// Free listener
|
|
||||||
listener.reset();
|
|
||||||
|
|
||||||
// Close socket and free it
|
|
||||||
if (sock) {
|
|
||||||
sock->close();
|
|
||||||
sock.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Close socket and free it
|
|
||||||
if (sock) {
|
|
||||||
sock->close();
|
|
||||||
sock.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string getSrScaled(double sr) {
|
|
||||||
char buf[1024];
|
|
||||||
if (sr >= 1000000.0) {
|
|
||||||
sprintf(buf, "%.1lf MS/s", sr / 1000000.0);
|
|
||||||
}
|
|
||||||
else if (sr >= 1000.0) {
|
|
||||||
sprintf(buf, "%.1lf KS/s", sr / 1000.0);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sprintf(buf, "%.1lf S/s", sr);
|
|
||||||
}
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void menuHandler(void* ctx) {
|
|
||||||
IQExporterModule* _this = (IQExporterModule*)ctx;
|
|
||||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
|
||||||
|
|
||||||
// Error message box
|
|
||||||
ImGui::GenericDialog("##iq_exporter_err_", _this->showError, GENERIC_DIALOG_BUTTONS_OK, [=](){
|
|
||||||
ImGui::Text("Error: %s", _this->errorStr.c_str());
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!_this->enabled) { ImGui::BeginDisabled(); }
|
|
||||||
|
|
||||||
if (_this->running) { ImGui::BeginDisabled(); }
|
|
||||||
|
|
||||||
// Mode selector
|
|
||||||
ImGui::LeftLabel("Mode");
|
|
||||||
ImGui::FillWidth();
|
|
||||||
if (ImGui::Combo(("##iq_exporter_mode_" + _this->name).c_str(), &_this->modeId, _this->modes.txt)) {
|
|
||||||
_this->setMode(_this->modes.value(_this->modeId));
|
|
||||||
config.acquire();
|
|
||||||
config.conf[_this->name]["mode"] = _this->modes.key(_this->modeId);
|
|
||||||
config.release(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// In VFO mode, show samplerate selector
|
|
||||||
if (_this->mode == MODE_VFO) {
|
|
||||||
ImGui::LeftLabel("Samplerate");
|
|
||||||
ImGui::FillWidth();
|
|
||||||
if (ImGui::Combo(("##iq_exporter_sr_" + _this->name).c_str(), &_this->srId, _this->samplerates.txt)) {
|
|
||||||
_this->samplerate = _this->samplerates.value(_this->srId);
|
|
||||||
if (_this->vfo) {
|
|
||||||
_this->vfo->setBandwidthLimits(_this->samplerate, _this->samplerate, true);
|
|
||||||
_this->vfo->setSampleRate(_this->samplerate, _this->samplerate);
|
|
||||||
}
|
|
||||||
config.acquire();
|
|
||||||
config.conf[_this->name]["samplerate"] = _this->samplerates.key(_this->srId);
|
|
||||||
config.release(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode protocol selector
|
|
||||||
ImGui::LeftLabel("Protocol");
|
|
||||||
ImGui::FillWidth();
|
|
||||||
if (ImGui::Combo(("##iq_exporter_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
|
|
||||||
_this->proto = _this->protocols.value(_this->protoId);
|
|
||||||
config.acquire();
|
|
||||||
config.conf[_this->name]["protocol"] = _this->protocols.key(_this->protoId);
|
|
||||||
config.release(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sample type selector
|
|
||||||
ImGui::LeftLabel("Sample type");
|
|
||||||
ImGui::FillWidth();
|
|
||||||
if (ImGui::Combo(("##iq_exporter_samp_" + _this->name).c_str(), &_this->sampTypeId, _this->sampleTypes.txt)) {
|
|
||||||
_this->sampType = _this->sampleTypes.value(_this->sampTypeId);
|
|
||||||
_this->reshape.setKeep(_this->packetSize/_this->sampleSize());
|
|
||||||
config.acquire();
|
|
||||||
config.conf[_this->name]["sampleType"] = _this->sampleTypes.key(_this->sampTypeId);
|
|
||||||
config.release(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packet size selector
|
|
||||||
ImGui::LeftLabel("Packet size");
|
|
||||||
ImGui::FillWidth();
|
|
||||||
if (ImGui::Combo(("##iq_exporter_pkt_sz_" + _this->name).c_str(), &_this->packetSizeId, _this->packetSizes.txt)) {
|
|
||||||
_this->packetSize = _this->packetSizes.value(_this->packetSizeId);
|
|
||||||
_this->reshape.setKeep(_this->packetSize/_this->sampleSize());
|
|
||||||
config.acquire();
|
|
||||||
config.conf[_this->name]["packetSize"] = _this->packetSizes.key(_this->packetSizeId);
|
|
||||||
config.release(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hostname and port field
|
|
||||||
if (ImGui::InputText(("##iq_exporter_host_" + _this->name).c_str(), _this->hostname, sizeof(_this->hostname))) {
|
|
||||||
config.acquire();
|
|
||||||
config.conf[_this->name]["host"] = _this->hostname;
|
|
||||||
config.release(true);
|
|
||||||
}
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::FillWidth();
|
|
||||||
if (ImGui::InputInt(("##iq_exporter_port_" + _this->name).c_str(), &_this->port, 0, 0)) {
|
|
||||||
_this->port = std::clamp<int>(_this->port, 1, 65535);
|
|
||||||
config.acquire();
|
|
||||||
config.conf[_this->name]["port"] = _this->port;
|
|
||||||
config.release(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_this->running) { ImGui::EndDisabled(); }
|
|
||||||
|
|
||||||
// Start/Stop buttons
|
|
||||||
if (_this->running || (!_this->enabled && _this->wasRunning)) {
|
|
||||||
if (ImGui::Button(("Stop##iq_exporter_stop_" + _this->name).c_str(), ImVec2(menuWidth, 0))) {
|
|
||||||
_this->stop();
|
|
||||||
config.acquire();
|
|
||||||
config.conf[_this->name]["running"] = false;
|
|
||||||
config.release(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (ImGui::Button(("Start##iq_exporter_start_" + _this->name).c_str(), ImVec2(menuWidth, 0))) {
|
|
||||||
_this->start();
|
|
||||||
config.acquire();
|
|
||||||
config.conf[_this->name]["running"] = true;
|
|
||||||
config.release(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the socket is open by attempting a read
|
|
||||||
bool sockOpen;
|
|
||||||
{
|
|
||||||
uint8_t dummy;
|
|
||||||
sockOpen = !(!_this->sock || !_this->sock->isOpen() || (_this->proto != PROTOCOL_UDP && _this->sock->recv(&dummy, 1, false, net::NONBLOCKING) == 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status text
|
|
||||||
ImGui::TextUnformatted("Status:");
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (sockOpen) {
|
|
||||||
ImGui::TextColored(ImVec4(0.0, 1.0, 0.0, 1.0), (_this->proto == PROTOCOL_TCP_SERVER || _this->proto == PROTOCOL_TCP_CLIENT) ? "Connected" : "Sending");
|
|
||||||
}
|
|
||||||
else if (_this->listener && _this->listener->listening()) {
|
|
||||||
ImGui::TextColored(ImVec4(1.0, 1.0, 0.0, 1.0), "Listening");
|
|
||||||
}
|
|
||||||
else if (!_this->enabled) {
|
|
||||||
ImGui::TextUnformatted("Disabled");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// If we're idle and still supposed to be running, the server has closed the connection (TODO: kinda jank...)
|
|
||||||
if (_this->running) { _this->stop(); }
|
|
||||||
|
|
||||||
ImGui::TextUnformatted("Idle");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_this->enabled) { ImGui::EndDisabled(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
void setMode(Mode newMode, bool forceSet = false) {
|
|
||||||
// If there is no mode to change, do nothing
|
|
||||||
if (!forceSet && mode == newMode) { return; }
|
|
||||||
|
|
||||||
// Stop the DSP
|
|
||||||
reshape.stop();
|
|
||||||
handler.stop();
|
|
||||||
|
|
||||||
// Delete VFO or unbind IQ stream
|
|
||||||
if (vfo) {
|
|
||||||
sigpath::vfoManager.deleteVFO(vfo);
|
|
||||||
vfo = NULL;
|
|
||||||
}
|
|
||||||
if (streamBound) {
|
|
||||||
sigpath::iqFrontEnd.unbindIQStream(&iqStream);
|
|
||||||
streamBound = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the mode was none, we're done
|
|
||||||
if (newMode == MODE_NONE) { return; }
|
|
||||||
|
|
||||||
// Create VFO or bind IQ stream
|
|
||||||
if (newMode == MODE_VFO) {
|
|
||||||
// Create VFO
|
|
||||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, samplerate, samplerate, samplerate, samplerate, true);
|
|
||||||
|
|
||||||
// Set its output as the input to the DSP
|
|
||||||
reshape.setInput(vfo->output);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Bind IQ stream
|
|
||||||
sigpath::iqFrontEnd.bindIQStream(&iqStream);
|
|
||||||
streamBound = true;
|
|
||||||
|
|
||||||
// Set its output as the input to the DSP
|
|
||||||
reshape.setInput(&iqStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start DSP
|
|
||||||
reshape.start();
|
|
||||||
handler.start();
|
|
||||||
|
|
||||||
// Update mode
|
|
||||||
mode = newMode;
|
|
||||||
modeId = modes.valueId(newMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void listenWorker() {
|
|
||||||
while (true) {
|
|
||||||
// Accept a client
|
|
||||||
auto newSock = listener->accept();
|
|
||||||
if (!newSock) { break; }
|
|
||||||
|
|
||||||
// Update socket
|
|
||||||
{
|
|
||||||
std::lock_guard lck(sockMtx);
|
|
||||||
sock = newSock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int sampleSize() {
|
|
||||||
switch (sampType) {
|
|
||||||
case SAMPLE_TYPE_INT8:
|
|
||||||
return sizeof(int8_t)*2;
|
|
||||||
case SAMPLE_TYPE_INT16:
|
|
||||||
return sizeof(int16_t)*2;
|
|
||||||
case SAMPLE_TYPE_INT32:
|
|
||||||
return sizeof(int32_t)*2;
|
|
||||||
case SAMPLE_TYPE_FLOAT32:
|
|
||||||
return sizeof(dsp::complex_t);
|
|
||||||
default:
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dataHandler(dsp::complex_t* data, int count, void* ctx) {
|
|
||||||
IQExporterModule* _this = (IQExporterModule*)ctx;
|
|
||||||
|
|
||||||
// Try to cquire lock on socket
|
|
||||||
if (!_this->sockMtx.try_lock()) { return; }
|
|
||||||
|
|
||||||
// If not valid or open, give uo
|
|
||||||
if (!_this->sock || !_this->sock->isOpen()) {
|
|
||||||
// Unlock socket mutex
|
|
||||||
_this->sockMtx.unlock();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the samples or send directory for float32
|
|
||||||
int size;
|
|
||||||
switch (_this->sampType) {
|
|
||||||
case SAMPLE_TYPE_INT8:
|
|
||||||
volk_32f_s32f_convert_8i((int8_t*)_this->buffer, (float*)data, 128.0f, count*2);
|
|
||||||
size = sizeof(int8_t)*2;
|
|
||||||
break;
|
|
||||||
case SAMPLE_TYPE_INT16:
|
|
||||||
volk_32f_s32f_convert_16i((int16_t*)_this->buffer, (float*)data, 32768.0f, count*2);
|
|
||||||
size = sizeof(int16_t)*2;
|
|
||||||
break;
|
|
||||||
case SAMPLE_TYPE_INT32:
|
|
||||||
volk_32f_s32f_convert_32i((int32_t*)_this->buffer, (float*)data, 2147483647.0f, count*2);
|
|
||||||
size = sizeof(int32_t)*2;
|
|
||||||
break;
|
|
||||||
case SAMPLE_TYPE_FLOAT32:
|
|
||||||
_this->sock->send((uint8_t*)data, count*sizeof(dsp::complex_t));
|
|
||||||
default:
|
|
||||||
// Unlock socket mutex
|
|
||||||
_this->sockMtx.unlock();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send converted samples
|
|
||||||
_this->sock->send(_this->buffer, count*size);
|
|
||||||
|
|
||||||
// Unlock socket mutex
|
|
||||||
_this->sockMtx.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string name;
|
|
||||||
bool enabled = true;
|
|
||||||
|
|
||||||
Mode mode = MODE_NONE;
|
|
||||||
int modeId;
|
|
||||||
int samplerate = 1000000.0;
|
|
||||||
int srId;
|
|
||||||
Protocol proto = PROTOCOL_TCP_SERVER;
|
|
||||||
int protoId;
|
|
||||||
SampleType sampType = SAMPLE_TYPE_INT16;
|
|
||||||
int sampTypeId;
|
|
||||||
int packetSize = 1024;
|
|
||||||
int packetSizeId;
|
|
||||||
char hostname[1024] = "localhost";
|
|
||||||
int port = 1234;
|
|
||||||
bool running = false;
|
|
||||||
bool wasRunning = false;
|
|
||||||
|
|
||||||
bool showError = false;
|
|
||||||
std::string errorStr = "";
|
|
||||||
|
|
||||||
OptionList<std::string, Mode> modes;
|
|
||||||
OptionList<int, int> samplerates;
|
|
||||||
OptionList<std::string, Protocol> protocols;
|
|
||||||
OptionList<std::string, SampleType> sampleTypes;
|
|
||||||
OptionList<int, int> packetSizes;
|
|
||||||
|
|
||||||
VFOManager::VFO* vfo = NULL;
|
|
||||||
bool streamBound = false;
|
|
||||||
dsp::stream<dsp::complex_t> iqStream;
|
|
||||||
dsp::buffer::Reshaper<dsp::complex_t> reshape;
|
|
||||||
dsp::sink::Handler<dsp::complex_t> handler;
|
|
||||||
uint8_t* buffer = NULL;
|
|
||||||
|
|
||||||
std::thread listenWorkerThread;
|
|
||||||
|
|
||||||
std::mutex sockMtx;
|
|
||||||
std::shared_ptr<net::Socket> sock;
|
|
||||||
std::shared_ptr<net::Listener> listener;
|
|
||||||
};
|
|
||||||
|
|
||||||
MOD_EXPORT void _INIT_() {
|
|
||||||
json def = json({});
|
|
||||||
std::string root = (std::string)core::args["root"];
|
|
||||||
config.setPath(root + "/iq_exporter_config.json");
|
|
||||||
config.load(def);
|
|
||||||
config.enableAutoSave();
|
|
||||||
}
|
|
||||||
|
|
||||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
|
||||||
return new IQExporterModule(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
|
||||||
delete (IQExporterModule*)instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
MOD_EXPORT void _END_() {
|
|
||||||
config.disableAutoSave();
|
|
||||||
config.save();
|
|
||||||
}
|
|
@ -5,5 +5,4 @@ file(GLOB SRC "src/*.cpp")
|
|||||||
|
|
||||||
include(${SDRPP_MODULE_CMAKE})
|
include(${SDRPP_MODULE_CMAKE})
|
||||||
|
|
||||||
target_include_directories(recorder PRIVATE "src/")
|
target_include_directories(recorder PRIVATE "src/")
|
||||||
target_include_directories(recorder PRIVATE "../../decoder_modules/radio/src")
|
|
@ -21,7 +21,6 @@
|
|||||||
#include <core.h>
|
#include <core.h>
|
||||||
#include <utils/optionlist.h>
|
#include <utils/optionlist.h>
|
||||||
#include <utils/wav.h>
|
#include <utils/wav.h>
|
||||||
#include <radio_interface.h>
|
|
||||||
|
|
||||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||||
|
|
||||||
@ -169,7 +168,7 @@ public:
|
|||||||
|
|
||||||
// Open file
|
// Open file
|
||||||
std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband";
|
std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband";
|
||||||
std::string vfoName = (recMode == RECORDER_MODE_AUDIO) ? selectedStreamName : "";
|
std::string vfoName = (recMode == RECORDER_MODE_AUDIO) ? gui::waterfall.selectedVFO : "";
|
||||||
std::string extension = ".wav";
|
std::string extension = ".wav";
|
||||||
std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, type, vfoName) + extension);
|
std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, type, vfoName) + extension);
|
||||||
if (!writer.open(expandedPath)) {
|
if (!writer.open(expandedPath)) {
|
||||||
@ -438,17 +437,6 @@ private:
|
|||||||
if (dbLvl.r > lvl.r) { lvl.r = dbLvl.r; }
|
if (dbLvl.r > lvl.r) { lvl.r = dbLvl.r; }
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<int, const char*> radioModeToString = {
|
|
||||||
{ RADIO_IFACE_MODE_NFM, "NFM" },
|
|
||||||
{ RADIO_IFACE_MODE_WFM, "WFM" },
|
|
||||||
{ RADIO_IFACE_MODE_AM, "AM" },
|
|
||||||
{ RADIO_IFACE_MODE_DSB, "DSB" },
|
|
||||||
{ RADIO_IFACE_MODE_USB, "USB" },
|
|
||||||
{ RADIO_IFACE_MODE_CW, "CW" },
|
|
||||||
{ RADIO_IFACE_MODE_LSB, "LSB" },
|
|
||||||
{ RADIO_IFACE_MODE_RAW, "RAW" }
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string genFileName(std::string templ, std::string type, std::string name) {
|
std::string genFileName(std::string templ, std::string type, std::string name) {
|
||||||
// Get data
|
// Get data
|
||||||
time_t now = time(0);
|
time_t now = time(0);
|
||||||
@ -467,7 +455,6 @@ private:
|
|||||||
char dayStr[128];
|
char dayStr[128];
|
||||||
char monStr[128];
|
char monStr[128];
|
||||||
char yearStr[128];
|
char yearStr[128];
|
||||||
const char* modeStr = "Unknown";
|
|
||||||
sprintf(freqStr, "%.0lfHz", freq);
|
sprintf(freqStr, "%.0lfHz", freq);
|
||||||
sprintf(hourStr, "%02d", ltm->tm_hour);
|
sprintf(hourStr, "%02d", ltm->tm_hour);
|
||||||
sprintf(minStr, "%02d", ltm->tm_min);
|
sprintf(minStr, "%02d", ltm->tm_min);
|
||||||
@ -475,11 +462,6 @@ private:
|
|||||||
sprintf(dayStr, "%02d", ltm->tm_mday);
|
sprintf(dayStr, "%02d", ltm->tm_mday);
|
||||||
sprintf(monStr, "%02d", ltm->tm_mon + 1);
|
sprintf(monStr, "%02d", ltm->tm_mon + 1);
|
||||||
sprintf(yearStr, "%02d", ltm->tm_year + 1900);
|
sprintf(yearStr, "%02d", ltm->tm_year + 1900);
|
||||||
if (core::modComManager.getModuleName(name) == "radio") {
|
|
||||||
int mode;
|
|
||||||
core::modComManager.callInterface(name, RADIO_IFACE_CMD_GET_MODE, NULL, &mode);
|
|
||||||
modeStr = radioModeToString[mode];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace in template
|
// Replace in template
|
||||||
templ = std::regex_replace(templ, std::regex("\\$t"), type);
|
templ = std::regex_replace(templ, std::regex("\\$t"), type);
|
||||||
@ -490,7 +472,6 @@ private:
|
|||||||
templ = std::regex_replace(templ, std::regex("\\$d"), dayStr);
|
templ = std::regex_replace(templ, std::regex("\\$d"), dayStr);
|
||||||
templ = std::regex_replace(templ, std::regex("\\$M"), monStr);
|
templ = std::regex_replace(templ, std::regex("\\$M"), monStr);
|
||||||
templ = std::regex_replace(templ, std::regex("\\$y"), yearStr);
|
templ = std::regex_replace(templ, std::regex("\\$y"), yearStr);
|
||||||
templ = std::regex_replace(templ, std::regex("\\$r"), modeStr);
|
|
||||||
return templ;
|
return templ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,13 +80,13 @@ public:
|
|||||||
try {
|
try {
|
||||||
client = net::rigctl::connect(host, port);
|
client = net::rigctl::connect(host, port);
|
||||||
}
|
}
|
||||||
catch (const std::exception& e) {
|
catch (std::exception e) {
|
||||||
flog::error("Could not connect: {}", e.what());
|
flog::error("Could not connect: {0}", e.what());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch source to panadapter mode
|
// Switch source to panadapter mode
|
||||||
sigpath::sourceManager.setPanadapterIF(ifFreq);
|
sigpath::sourceManager.setPanadpterIF(ifFreq);
|
||||||
sigpath::sourceManager.setTuningMode(SourceManager::TuningMode::PANADAPTER);
|
sigpath::sourceManager.setTuningMode(SourceManager::TuningMode::PANADAPTER);
|
||||||
sigpath::sourceManager.onRetune.bindHandler(&_retuneHandler);
|
sigpath::sourceManager.onRetune.bindHandler(&_retuneHandler);
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ private:
|
|||||||
ImGui::FillWidth();
|
ImGui::FillWidth();
|
||||||
if (ImGui::InputDouble(CONCAT("##_rigctl_if_freq_", _this->name), &_this->ifFreq, 100.0, 100000.0, "%.0f")) {
|
if (ImGui::InputDouble(CONCAT("##_rigctl_if_freq_", _this->name), &_this->ifFreq, 100.0, 100000.0, "%.0f")) {
|
||||||
if (_this->running) {
|
if (_this->running) {
|
||||||
sigpath::sourceManager.setPanadapterIF(_this->ifFreq);
|
sigpath::sourceManager.setPanadpterIF(_this->ifFreq);
|
||||||
}
|
}
|
||||||
config.acquire();
|
config.acquire();
|
||||||
config.conf[_this->name]["ifFreq"] = _this->ifFreq;
|
config.conf[_this->name]["ifFreq"] = _this->ifFreq;
|
||||||
|
@ -200,8 +200,8 @@ private:
|
|||||||
listener = net::listen(hostname, port);
|
listener = net::listen(hostname, port);
|
||||||
listener->acceptAsync(clientHandler, this);
|
listener->acceptAsync(clientHandler, this);
|
||||||
}
|
}
|
||||||
catch (const std::exception& e) {
|
catch (std::exception e) {
|
||||||
flog::error("Could not start rigctl server: {}", e.what());
|
flog::error("Could not start rigctl server: {0}", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,17 +333,6 @@ private:
|
|||||||
_this->client->readAsync(1024, _this->dataBuf, dataHandler, _this, false);
|
_this->client->readAsync(1024, _this->dataBuf, dataHandler, _this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<int, const char*> radioModeToString = {
|
|
||||||
{ RADIO_IFACE_MODE_NFM, "FM" },
|
|
||||||
{ RADIO_IFACE_MODE_WFM, "WFM" },
|
|
||||||
{ RADIO_IFACE_MODE_AM, "AM" },
|
|
||||||
{ RADIO_IFACE_MODE_DSB, "DSB" },
|
|
||||||
{ RADIO_IFACE_MODE_USB, "USB" },
|
|
||||||
{ RADIO_IFACE_MODE_CW, "CW" },
|
|
||||||
{ RADIO_IFACE_MODE_LSB, "LSB" },
|
|
||||||
{ RADIO_IFACE_MODE_RAW, "RAW" }
|
|
||||||
};
|
|
||||||
|
|
||||||
void commandHandler(std::string cmd) {
|
void commandHandler(std::string cmd) {
|
||||||
std::string corr = "";
|
std::string corr = "";
|
||||||
std::vector<std::string> parts;
|
std::vector<std::string> parts;
|
||||||
@ -453,18 +442,38 @@ private:
|
|||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& newModeStr = parts[1];
|
|
||||||
float newBandwidth = std::atoi(parts[2].c_str());
|
float newBandwidth = std::atoi(parts[2].c_str());
|
||||||
|
|
||||||
auto it = std::find_if(radioModeToString.begin(), radioModeToString.end(), [&newModeStr](const auto& e) {
|
int newMode;
|
||||||
return e.second == newModeStr;
|
if (parts[1] == "FM") {
|
||||||
});
|
newMode = RADIO_IFACE_MODE_NFM;
|
||||||
if (it == radioModeToString.end()) {
|
}
|
||||||
|
else if (parts[1] == "WFM") {
|
||||||
|
newMode = RADIO_IFACE_MODE_WFM;
|
||||||
|
}
|
||||||
|
else if (parts[1] == "AM") {
|
||||||
|
newMode = RADIO_IFACE_MODE_AM;
|
||||||
|
}
|
||||||
|
else if (parts[1] == "DSB") {
|
||||||
|
newMode = RADIO_IFACE_MODE_DSB;
|
||||||
|
}
|
||||||
|
else if (parts[1] == "USB") {
|
||||||
|
newMode = RADIO_IFACE_MODE_USB;
|
||||||
|
}
|
||||||
|
else if (parts[1] == "CW") {
|
||||||
|
newMode = RADIO_IFACE_MODE_CW;
|
||||||
|
}
|
||||||
|
else if (parts[1] == "LSB") {
|
||||||
|
newMode = RADIO_IFACE_MODE_LSB;
|
||||||
|
}
|
||||||
|
else if (parts[1] == "RAW") {
|
||||||
|
newMode = RADIO_IFACE_MODE_RAW;
|
||||||
|
}
|
||||||
|
else {
|
||||||
resp = "RPRT 1\n";
|
resp = "RPRT 1\n";
|
||||||
client->write(resp.size(), (uint8_t*)resp.c_str());
|
client->write(resp.size(), (uint8_t*)resp.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int newMode = it->first;
|
|
||||||
|
|
||||||
// If tuning is enabled, set the mode and optionally the bandwidth
|
// If tuning is enabled, set the mode and optionally the bandwidth
|
||||||
if (!selectedVfo.empty() && core::modComManager.getModuleName(selectedVfo) == "radio" && tuningEnabled) {
|
if (!selectedVfo.empty() && core::modComManager.getModuleName(selectedVfo) == "radio" && tuningEnabled) {
|
||||||
@ -483,9 +492,31 @@ private:
|
|||||||
if (!selectedVfo.empty() && core::modComManager.getModuleName(selectedVfo) == "radio") {
|
if (!selectedVfo.empty() && core::modComManager.getModuleName(selectedVfo) == "radio") {
|
||||||
int mode;
|
int mode;
|
||||||
core::modComManager.callInterface(selectedVfo, RADIO_IFACE_CMD_GET_MODE, NULL, &mode);
|
core::modComManager.callInterface(selectedVfo, RADIO_IFACE_CMD_GET_MODE, NULL, &mode);
|
||||||
resp = std::string(radioModeToString[mode]) + "\n";
|
|
||||||
|
if (mode == RADIO_IFACE_MODE_NFM) {
|
||||||
|
resp = "FM\n";
|
||||||
|
}
|
||||||
|
else if (mode == RADIO_IFACE_MODE_WFM) {
|
||||||
|
resp = "WFM\n";
|
||||||
|
}
|
||||||
|
else if (mode == RADIO_IFACE_MODE_AM) {
|
||||||
|
resp = "AM\n";
|
||||||
|
}
|
||||||
|
else if (mode == RADIO_IFACE_MODE_DSB) {
|
||||||
|
resp = "DSB\n";
|
||||||
|
}
|
||||||
|
else if (mode == RADIO_IFACE_MODE_USB) {
|
||||||
|
resp = "USB\n";
|
||||||
|
}
|
||||||
|
else if (mode == RADIO_IFACE_MODE_CW) {
|
||||||
|
resp = "CW\n";
|
||||||
|
}
|
||||||
|
else if (mode == RADIO_IFACE_MODE_LSB) {
|
||||||
|
resp = "LSB\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!selectedVfo.empty()) {
|
|
||||||
|
if (!selectedVfo.empty()) {
|
||||||
resp += std::to_string((int)sigpath::vfoManager.getBandwidth(selectedVfo)) + "\n";
|
resp += std::to_string((int)sigpath::vfoManager.getBandwidth(selectedVfo)) + "\n";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -659,11 +690,6 @@ private:
|
|||||||
"0\n" /* RIG_PARM_NONE */;
|
"0\n" /* RIG_PARM_NONE */;
|
||||||
client->write(resp.size(), (uint8_t*)resp.c_str());
|
client->write(resp.size(), (uint8_t*)resp.c_str());
|
||||||
}
|
}
|
||||||
// This get_powerstat stuff is a wordaround for WSJT-X 2.7.0
|
|
||||||
else if (parts[0] == "\\get_powerstat") {
|
|
||||||
resp = "1\n";
|
|
||||||
client->write(resp.size(), (uint8_t*)resp.c_str());
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
// If command is not recognized, return error
|
// If command is not recognized, return error
|
||||||
flog::error("Rigctl client sent invalid command: '{0}'", cmd);
|
flog::error("Rigctl client sent invalid command: '{0}'", cmd);
|
||||||
|
@ -89,7 +89,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::BeginTable(("freq_manager_bkm_table" + _this->name).c_str(), 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200.0f * style::uiScale))) {
|
if (ImGui::BeginTable(("freq_manager_bkm_table" + _this->name).c_str(), 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200))) {
|
||||||
ImGui::TableSetupColumn("Name");
|
ImGui::TableSetupColumn("Name");
|
||||||
ImGui::TableSetupColumn("Countdown");
|
ImGui::TableSetupColumn("Countdown");
|
||||||
ImGui::TableSetupScrollFreeze(2, 1);
|
ImGui::TableSetupScrollFreeze(2, 1);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user