mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-07-08 18:15:21 +02:00
Compare commits
71 Commits
new_sinks
...
dab_experi
Author | SHA1 | Date | |
---|---|---|---|
f8078ac3f0 | |||
064f25ee73 | |||
d87ae23560 | |||
17eccf5156 | |||
acb1be121c | |||
0fa89614bb | |||
256affd918 | |||
e80cdbf248 | |||
75e66226c3 | |||
c2f0e756a5 | |||
79dd5bdcbb | |||
6dce28345c | |||
fe9ac6c9a1 | |||
bfdfa2b30b | |||
46e98b9b03 | |||
e674a73771 | |||
118e1fbff0 | |||
bcadb36232 | |||
554ba2f596 | |||
949fde022d | |||
123e34d250 | |||
f1c7010437 | |||
33a7795de1 | |||
fe7299c18a | |||
8a9e0abcc2 | |||
13abe4860b | |||
981592fa19 | |||
582750f79b | |||
36f2a083ce | |||
d753135a61 | |||
07744e5bae | |||
9ec78da7ac | |||
0066994899 | |||
93ab51bf2f | |||
f9d7d20073 | |||
e81db5d85c | |||
5c3a66642b | |||
36492e799a | |||
0110dfbef6 | |||
0b5a2ff786 | |||
ce0f1f05ae | |||
46a5ff8ac5 | |||
03559b928b | |||
206ce6e8c3 | |||
d7a1f46af0 | |||
89e6e4f7ad | |||
6ced9b15c3 | |||
0de189a7b7 | |||
bb9024fadd | |||
d1dc20f4e2 | |||
309717b5f8 | |||
762444d340 | |||
18300e8916 | |||
a93bb9d468 | |||
ea0362b927 | |||
ffc642f270 | |||
1b5975f563 | |||
733dc55723 | |||
b841180f84 | |||
e99e84e809 | |||
7a4281dd76 | |||
c89763a989 | |||
27edc260c9 | |||
2ea7ac496f | |||
314d78d9d2 | |||
4e455e6661 | |||
58b86fcee5 | |||
27072e9fe7 | |||
da1417b5ab | |||
e60eca5d6d | |||
ccb10bfb9a |
70
.github/workflows/build_all.yml
vendored
70
.github/workflows/build_all.yml
vendored
@ -36,6 +36,13 @@ jobs:
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/" ; rm "C:/Program Files/PothosSDR/lib/libusb-1.0.lib" ; cp "libusb_old/MS64/dll/libusb-1.0.lib" "C:/Program Files/PothosSDR/lib/"
|
||||
|
||||
- name: Download librtlsdr
|
||||
run: Invoke-WebRequest -Uri "https://ftp.osmocom.org/binaries/windows/rtl-sdr/rtl-sdr-64bit-20240623.zip" -OutFile ${{runner.workspace}}/rtl-sdr.zip
|
||||
|
||||
- name: Patch Pothos with newer librtlsdr version
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: 7z x rtl-sdr.zip ; rm "C:/Program Files/PothosSDR/bin/rtlsdr.dll" ; cp "rtl-sdr-64bit-20240623/librtlsdr.dll" "C:/Program Files/PothosSDR/bin/rtlsdr.dll"
|
||||
|
||||
- name: Download SDRPlay API
|
||||
run: Invoke-WebRequest -Uri "https://www.sdrpp.org/SDRplay.zip" -OutFile ${{runner.workspace}}/SDRplay.zip
|
||||
|
||||
@ -58,17 +65,23 @@ jobs:
|
||||
run: mkdir "C:/Program Files/codec2" ; mkdir "C:/Program Files/codec2/include" ; mkdir "C:/Program Files/codec2/include/codec2" ; mkdir "C:/Program Files/codec2/lib" ; cd "codec2" ; xcopy "src" "C:/Program Files/codec2/include" ; cd "build" ; xcopy "src" "C:/Program Files/codec2/lib" ; xcopy "codec2" "C:/Program Files/codec2/include/codec2"
|
||||
|
||||
- name: Install vcpkg dependencies
|
||||
run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows libusb:x64-windows
|
||||
run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows libusb:x64-windows spdlog:x64-windows
|
||||
|
||||
- name: Install rtaudio
|
||||
run: git clone https://github.com/thestk/rtaudio ; cd rtaudio ; git checkout 2f2fca4502d506abc50f6d4473b2836d24cfb1e3 ; mkdir build ; cd build ; cmake .. ; cmake --build . --config Release ; cmake --install .
|
||||
|
||||
- name: Install libperseus-sdr
|
||||
run: git clone https://github.com/AlexandreRouma/libperseus-sdr ; cd libperseus-sdr ; mkdir build ; cd build ; cmake "-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"
|
||||
run: git clone https://github.com/AlexandreRouma/libperseus-sdr ; cd libperseus-sdr ; mkdir build ; cd build ; cmake -DCMAKE_BUILD_TYPE=Release "-DLIBUSB_LIBRARIES=C:/Program Files/PothosSDR/lib/libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIRS=C:/Program Files/PothosSDR/include/libusb-1.0" .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr"
|
||||
|
||||
- name: Install librfnm
|
||||
run: git clone https://github.com/AlexandreRouma/librfnm ; cd librfnm ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install .
|
||||
|
||||
- name: Install libfobos
|
||||
run: git clone https://github.com/AlexandreRouma/libfobos ; cd libfobos ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install .
|
||||
|
||||
- name: Prepare CMake
|
||||
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 -DCOPY_MSVC_REDISTRIBUTABLES=ON "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@ -94,13 +107,13 @@ jobs:
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool && pip3 install mako
|
||||
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && pip3 install mako
|
||||
|
||||
- name: Install volk
|
||||
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install SDRplay API
|
||||
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target /
|
||||
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.0.pkg -target /
|
||||
|
||||
- name: Install libiio
|
||||
run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
@ -112,14 +125,20 @@ jobs:
|
||||
run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install libperseus
|
||||
run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && make install && cd ..
|
||||
run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && sudo make install && cd ..
|
||||
|
||||
- name: Install librfnm
|
||||
run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||
|
||||
- name: Install libfobos
|
||||
run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||
|
||||
- name: Install more recent librtlsdr
|
||||
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
|
||||
|
||||
- name: Prepare CMake
|
||||
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 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@ -145,13 +164,13 @@ jobs:
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool && pip3 install mako --break-system-packages
|
||||
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && pip3 install mako --break-system-packages
|
||||
|
||||
- name: Install volk
|
||||
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install SDRplay API
|
||||
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target /
|
||||
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.0.pkg -target /
|
||||
|
||||
- name: Install libiio
|
||||
run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
@ -165,12 +184,18 @@ jobs:
|
||||
# - name: Install libperseus
|
||||
# run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && make install && cd ..
|
||||
|
||||
- name: Install librfnm
|
||||
run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||
|
||||
- name: Install libfobos
|
||||
run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||
|
||||
- name: Install more recent librtlsdr
|
||||
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
|
||||
|
||||
- name: Prepare CMake
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@ -340,6 +365,28 @@ jobs:
|
||||
name: sdrpp_ubuntu_mantic_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_noble:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_ubuntu_noble_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_raspios_bullseye_armhf:
|
||||
runs-on: ARM
|
||||
|
||||
@ -395,7 +442,7 @@ jobs:
|
||||
path: ${{runner.workspace}}/sdrpp.apk
|
||||
|
||||
create_full_archive:
|
||||
needs: ['build_windows', 'build_macos_intel', 'build_macos_arm', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_raspios_bullseye_armhf', 'build_android']
|
||||
needs: ['build_windows', 'build_macos_intel', 'build_macos_arm', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_ubuntu_noble', 'build_raspios_bullseye_armhf', 'build_android']
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -415,6 +462,7 @@ jobs:
|
||||
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
|
||||
mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb &&
|
||||
mv sdrpp_ubuntu_mantic_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_mantic_amd64.deb &&
|
||||
mv sdrpp_ubuntu_noble_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_amd64.deb &&
|
||||
mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb &&
|
||||
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
|
||||
|
||||
|
@ -12,14 +12,18 @@ option(OPT_OVERRIDE_STD_FILESYSTEM "Use a local version of std::filesystem on sy
|
||||
option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Dependencies: libairspy)" ON)
|
||||
option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Dependencies: libairspyhf)" ON)
|
||||
option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio)" ON)
|
||||
option(OPT_BUILD_BADGESDR_SOURCE "Build BadgeSDR Source Module (Dependencies: libusb)" OFF)
|
||||
option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF)
|
||||
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
|
||||
option(OPT_BUILD_FOBOSSDR_SOURCE "Build FobosSDR Source Module (Dependencies: libfobos)" OFF)
|
||||
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON)
|
||||
option(OPT_BUILD_HAROGIC_SOURCE "Build Harogic Source Module (Dependencies: htra_api)" OFF)
|
||||
option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF)
|
||||
option(OPT_BUILD_NETWORK_SOURCE "Build Network Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_PERSEUS_SOURCE "Build Perseus Source Module (Dependencies: libperseus-sdr)" OFF)
|
||||
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON)
|
||||
option(OPT_BUILD_RFNM_SOURCE "Build RFNM Source Module (Dependencies: librfnm)" OFF)
|
||||
option(OPT_BUILD_RFSPACE_SOURCE "Build RFspace Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_RTL_SDR_SOURCE "Build RTL-SDR Source Module (Dependencies: librtlsdr)" ON)
|
||||
option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON)
|
||||
@ -40,12 +44,14 @@ option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: port
|
||||
|
||||
# Decoders
|
||||
option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF)
|
||||
option(OPT_BUILD_DAB_DECODER "Build the DAB/DAB+ decoder (no dependencies required)" OFF)
|
||||
option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF)
|
||||
option(OPT_BUILD_KG_SSTV_DECODER "Build the KG SSTV (KG-STV) decoder module (no dependencies required)" OFF)
|
||||
option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (Dependencies: codec2)" OFF)
|
||||
option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_PAGER_DECODER "Build the pager decoder module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
|
||||
option(OPT_BUILD_RYFI_DECODER "RyFi data link decoder" OFF)
|
||||
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
|
||||
|
||||
# Misc
|
||||
@ -61,6 +67,7 @@ option(OPT_BUILD_SCHEDULER "Build the scheduler" OFF)
|
||||
# Other options
|
||||
option(USE_INTERNAL_LIBCORRECT "Use an internal version of libcorrect" ON)
|
||||
option(USE_BUNDLE_DEFAULTS "Set the default resource and module directories to the right ones for a MacOS .app" OFF)
|
||||
option(COPY_MSVC_REDISTRIBUTABLES "Copy over the Visual C++ Redistributable" OFF)
|
||||
|
||||
# Module cmake path
|
||||
set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake")
|
||||
@ -125,6 +132,10 @@ if (OPT_BUILD_AUDIO_SOURCE)
|
||||
add_subdirectory("source_modules/audio_source")
|
||||
endif (OPT_BUILD_AUDIO_SOURCE)
|
||||
|
||||
if (OPT_BUILD_BADGESDR_SOURCE)
|
||||
add_subdirectory("source_modules/badgesdr_source")
|
||||
endif (OPT_BUILD_BADGESDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_BLADERF_SOURCE)
|
||||
add_subdirectory("source_modules/bladerf_source")
|
||||
endif (OPT_BUILD_BLADERF_SOURCE)
|
||||
@ -133,10 +144,18 @@ if (OPT_BUILD_FILE_SOURCE)
|
||||
add_subdirectory("source_modules/file_source")
|
||||
endif (OPT_BUILD_FILE_SOURCE)
|
||||
|
||||
if (OPT_BUILD_FOBOSSDR_SOURCE)
|
||||
add_subdirectory("source_modules/fobossdr_source")
|
||||
endif (OPT_BUILD_FOBOSSDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HACKRF_SOURCE)
|
||||
add_subdirectory("source_modules/hackrf_source")
|
||||
endif (OPT_BUILD_HACKRF_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HAROGIC_SOURCE)
|
||||
add_subdirectory("source_modules/harogic_source")
|
||||
endif (OPT_BUILD_HAROGIC_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HERMES_SOURCE)
|
||||
add_subdirectory("source_modules/hermes_source")
|
||||
endif (OPT_BUILD_HERMES_SOURCE)
|
||||
@ -157,6 +176,10 @@ if (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
add_subdirectory("source_modules/plutosdr_source")
|
||||
endif (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_RFNM_SOURCE)
|
||||
add_subdirectory("source_modules/rfnm_source")
|
||||
endif (OPT_BUILD_RFNM_SOURCE)
|
||||
|
||||
if (OPT_BUILD_RFSPACE_SOURCE)
|
||||
add_subdirectory("source_modules/rfspace_source")
|
||||
endif (OPT_BUILD_RFSPACE_SOURCE)
|
||||
@ -225,6 +248,10 @@ if (OPT_BUILD_ATV_DECODER)
|
||||
add_subdirectory("decoder_modules/atv_decoder")
|
||||
endif (OPT_BUILD_ATV_DECODER)
|
||||
|
||||
if (OPT_BUILD_DAB_DECODER)
|
||||
add_subdirectory("decoder_modules/dab_decoder")
|
||||
endif (OPT_BUILD_DAB_DECODER)
|
||||
|
||||
if (OPT_BUILD_FALCON9_DECODER)
|
||||
add_subdirectory("decoder_modules/falcon9_decoder")
|
||||
endif (OPT_BUILD_FALCON9_DECODER)
|
||||
@ -249,6 +276,10 @@ if (OPT_BUILD_RADIO)
|
||||
add_subdirectory("decoder_modules/radio")
|
||||
endif (OPT_BUILD_RADIO)
|
||||
|
||||
if (OPT_BUILD_RYFI_DECODER)
|
||||
add_subdirectory("decoder_modules/ryfi_decoder")
|
||||
endif (OPT_BUILD_RYFI_DECODER)
|
||||
|
||||
if (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
add_subdirectory("decoder_modules/weather_sat_decoder")
|
||||
endif (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
@ -302,6 +333,21 @@ target_compile_options(sdrpp PRIVATE ${SDRPP_COMPILER_FLAGS})
|
||||
if (MSVC)
|
||||
add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
|
||||
add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
|
||||
|
||||
if (COPY_MSVC_REDISTRIBUTABLES)
|
||||
# Get the list of Visual C++ runtime DLLs
|
||||
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP True)
|
||||
include(InstallRequiredSystemLibraries)
|
||||
|
||||
# Create a space sperated list
|
||||
set(REDIST_DLLS_STR "")
|
||||
foreach(DLL IN LISTS CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS)
|
||||
set(REDIST_DLLS_STR COMMAND xcopy /F \"${DLL}\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y ${REDIST_DLLS_STR})
|
||||
endforeach()
|
||||
|
||||
# Create target
|
||||
add_custom_target(do_always_msvc ALL ${REDIST_DLLS_STR})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
|
||||
@ -342,4 +388,6 @@ endif ()
|
||||
|
||||
# Create uninstall target
|
||||
configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY)
|
||||
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
|
||||
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
|
||||
|
||||
# Create headers target
|
@ -10,7 +10,7 @@ android {
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.1.0"
|
||||
versionName "1.2.0"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
|
@ -173,16 +173,22 @@ int sdrpp_main(int argc, char* argv[]) {
|
||||
defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["File Source"]["module"] = "file_source";
|
||||
defConfig["moduleInstances"]["File Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["FobosSDR Source"]["module"] = "fobossdr_source";
|
||||
defConfig["moduleInstances"]["FobosSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["HackRF Source"]["module"] = "hackrf_source";
|
||||
defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["Harogic Source"]["module"] = "harogic_source";
|
||||
defConfig["moduleInstances"]["Harogic Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["Hermes Source"]["module"] = "hermes_source";
|
||||
defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
|
||||
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source";
|
||||
defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["RFNM Source"]["module"] = "rfnm_source";
|
||||
defConfig["moduleInstances"]["RFNM Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source";
|
||||
defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source";
|
||||
@ -193,8 +199,12 @@ int sdrpp_main(int argc, char* argv[]) {
|
||||
defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source";
|
||||
defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["Spectran HTTP Source"]["module"] = "spectran_http_source";
|
||||
defConfig["moduleInstances"]["Spectran HTTP Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source";
|
||||
defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["USRP Source"]["module"] = "usrp_source";
|
||||
defConfig["moduleInstances"]["USRP Source"]["enabled"] = true;
|
||||
|
||||
defConfig["moduleInstances"]["Audio Sink"] = "audio_sink";
|
||||
defConfig["moduleInstances"]["Network Sink"] = "network_sink";
|
||||
|
@ -37,14 +37,20 @@ namespace sdrpp_credits {
|
||||
const char* hardwareDonators[] = {
|
||||
"Aaronia AG",
|
||||
"Airspy",
|
||||
"Alex 4Z5LV",
|
||||
"Analog Devices",
|
||||
"CaribouLabs",
|
||||
"Deepace",
|
||||
"Ettus Research",
|
||||
"Harogic",
|
||||
"Howard Su",
|
||||
"MicroPhase",
|
||||
"Microtelecom",
|
||||
"MyriadRF",
|
||||
"Nuand",
|
||||
"RFNM",
|
||||
"RFspace",
|
||||
"RigExpert",
|
||||
"RTL-SDRblog",
|
||||
"SDRplay"
|
||||
};
|
||||
|
@ -9,6 +9,7 @@
|
||||
namespace dsp {
|
||||
class generic_block {
|
||||
public:
|
||||
virtual ~generic_block() {}
|
||||
virtual void start() {}
|
||||
virtual void stop() {}
|
||||
virtual int run() { return -1; }
|
||||
@ -16,8 +17,6 @@ namespace dsp {
|
||||
|
||||
class block : public generic_block {
|
||||
public:
|
||||
virtual void init() {}
|
||||
|
||||
virtual ~block() {
|
||||
if (!_block_init) { return; }
|
||||
stop();
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <volk/volk.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace dsp::buffer {
|
||||
template<class T>
|
||||
|
@ -10,6 +10,8 @@ namespace dsp {
|
||||
|
||||
Operator(stream<A>* a, stream<B>* b) { init(a, b); }
|
||||
|
||||
virtual ~Operator() {}
|
||||
|
||||
virtual void init(stream<A>* a, stream<B>* b) {
|
||||
_a = a;
|
||||
_b = b;
|
||||
|
@ -11,6 +11,7 @@
|
||||
namespace dsp {
|
||||
class untyped_stream {
|
||||
public:
|
||||
virtual ~untyped_stream() {}
|
||||
virtual bool swap(int size) { return false; }
|
||||
virtual int read() { return -1; }
|
||||
virtual void flush() {}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <volk/volk.h>
|
||||
#include "../buffer/buffer.h"
|
||||
|
||||
namespace dsp {
|
||||
template<class T>
|
||||
|
@ -16,7 +16,6 @@ namespace icons {
|
||||
ImTextureID UNMUTED;
|
||||
ImTextureID NORMAL_TUNING;
|
||||
ImTextureID CENTER_TUNING;
|
||||
ImTextureID ALIGN_CENTER;
|
||||
|
||||
GLuint loadTexture(std::string path) {
|
||||
int w, h, n;
|
||||
@ -46,7 +45,6 @@ namespace icons {
|
||||
UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png");
|
||||
NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png");
|
||||
CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_tuning.png");
|
||||
ALIGN_CENTER = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/align_center.png");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -13,8 +13,7 @@ namespace icons {
|
||||
extern ImTextureID UNMUTED;
|
||||
extern ImTextureID NORMAL_TUNING;
|
||||
extern ImTextureID CENTER_TUNING;
|
||||
extern ImTextureID ALIGN_CENTER;
|
||||
|
||||
|
||||
GLuint loadTexture(std::string path);
|
||||
bool load(std::string resDir);
|
||||
}
|
@ -17,7 +17,6 @@
|
||||
#include <gui/menus/display.h>
|
||||
#include <gui/menus/bandplan.h>
|
||||
#include <gui/menus/sink.h>
|
||||
#include <gui/menus/streams.h>
|
||||
#include <gui/menus/vfo_color.h>
|
||||
#include <gui/menus/module_manager.h>
|
||||
#include <gui/menus/theme.h>
|
||||
@ -73,7 +72,6 @@ void MainWindow::init() {
|
||||
|
||||
gui::menu.registerEntry("Source", sourcemenu::draw, NULL);
|
||||
gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Streams", streamsmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Display", displaymenu::draw, NULL);
|
||||
gui::menu.registerEntry("Theme", thememenu::draw, NULL);
|
||||
@ -167,7 +165,6 @@ void MainWindow::init() {
|
||||
|
||||
sourcemenu::init();
|
||||
sinkmenu::init();
|
||||
streamsmenu::init();
|
||||
bandplanmenu::init();
|
||||
displaymenu::init();
|
||||
vfo_color_menu::init();
|
||||
|
@ -19,6 +19,7 @@ namespace displaymenu {
|
||||
std::string colorMapAuthor = "";
|
||||
int selectedWindow = 0;
|
||||
int fftRate = 20;
|
||||
int fftSizeId = 0;
|
||||
int uiScaleId = 0;
|
||||
bool restartRequired = false;
|
||||
bool fftHold = false;
|
||||
@ -28,34 +29,9 @@ namespace displaymenu {
|
||||
bool snrSmoothing = false;
|
||||
int snrSmoothingSpeed = 20;
|
||||
|
||||
OptionList<int, int> fftSizes;
|
||||
OptionList<float, float> uiScales;
|
||||
|
||||
const int FFTSizes[] = {
|
||||
524288,
|
||||
262144,
|
||||
131072,
|
||||
65536,
|
||||
32768,
|
||||
16384,
|
||||
8192,
|
||||
4096,
|
||||
2048,
|
||||
1024
|
||||
};
|
||||
|
||||
const char* FFTSizesStr = "524288\0"
|
||||
"262144\0"
|
||||
"131072\0"
|
||||
"65536\0"
|
||||
"32768\0"
|
||||
"16384\0"
|
||||
"8192\0"
|
||||
"4096\0"
|
||||
"2048\0"
|
||||
"1024\0";
|
||||
|
||||
int fftSizeId = 0;
|
||||
|
||||
const IQFrontEnd::FFTWindow fftWindowList[] = {
|
||||
IQFrontEnd::FFTWindow::RECTANGULAR,
|
||||
IQFrontEnd::FFTWindow::BLACKMAN,
|
||||
@ -69,6 +45,18 @@ namespace displaymenu {
|
||||
}
|
||||
|
||||
void init() {
|
||||
// Define FFT sizes
|
||||
fftSizes.define(524288, "524288", 524288);
|
||||
fftSizes.define(262144, "262144", 262144);
|
||||
fftSizes.define(131072, "131072", 131072);
|
||||
fftSizes.define(65536, "65536", 65536);
|
||||
fftSizes.define(32768, "32768", 32768);
|
||||
fftSizes.define(16384, "16384", 16384);
|
||||
fftSizes.define(8192, "8192", 8192);
|
||||
fftSizes.define(4096, "4096", 4096);
|
||||
fftSizes.define(2048, "2048", 2048);
|
||||
fftSizes.define(1024, "1024", 1024);
|
||||
|
||||
showWaterfall = core::configManager.conf["showWaterfall"];
|
||||
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
|
||||
std::string colormapName = core::configManager.conf["colorMap"];
|
||||
@ -90,15 +78,12 @@ namespace displaymenu {
|
||||
fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"];
|
||||
gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate);
|
||||
|
||||
fftSizeId = 3;
|
||||
int fftSize = core::configManager.conf["fftSize"];
|
||||
for (int i = 0; i < 7; i++) {
|
||||
if (fftSize == FFTSizes[i]) {
|
||||
fftSizeId = i;
|
||||
break;
|
||||
}
|
||||
fftSizeId = fftSizes.valueId(65536);
|
||||
int size = core::configManager.conf["fftSize"];
|
||||
if (fftSizes.keyExists(size)) {
|
||||
fftSizeId = fftSizes.keyId(size);
|
||||
}
|
||||
sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]);
|
||||
sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId));
|
||||
|
||||
fftRate = core::configManager.conf["fftRate"];
|
||||
sigpath::iqFrontEnd.setFFTRate(fftRate);
|
||||
@ -229,10 +214,10 @@ namespace displaymenu {
|
||||
|
||||
ImGui::LeftLabel("FFT Size");
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, FFTSizesStr)) {
|
||||
sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]);
|
||||
if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, fftSizes.txt)) {
|
||||
sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId));
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fftSize"] = FFTSizes[fftSizeId];
|
||||
core::configManager.conf["fftSize"] = fftSizes.key(fftSizeId);
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
};
|
@ -42,6 +42,7 @@ public:
|
||||
|
||||
class Instance {
|
||||
public:
|
||||
virtual ~Instance() {}
|
||||
virtual void postInit() = 0;
|
||||
virtual void enable() = 0;
|
||||
virtual void disable() = 0;
|
||||
|
@ -5,5 +5,4 @@ namespace sigpath {
|
||||
VFOManager vfoManager;
|
||||
SourceManager sourceManager;
|
||||
SinkManager sinkManager;
|
||||
StreamManager streamManager;
|
||||
};
|
@ -3,7 +3,6 @@
|
||||
#include "vfo_manager.h"
|
||||
#include "source.h"
|
||||
#include "sink.h"
|
||||
#include "stream.h"
|
||||
#include <module.h>
|
||||
|
||||
namespace sigpath {
|
||||
@ -11,5 +10,4 @@ namespace sigpath {
|
||||
SDRPP_EXPORT VFOManager vfoManager;
|
||||
SDRPP_EXPORT SourceManager sourceManager;
|
||||
SDRPP_EXPORT SinkManager sinkManager;
|
||||
SDRPP_EXPORT StreamManager streamManager;
|
||||
};
|
@ -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;
|
||||
};
|
@ -63,7 +63,6 @@ namespace net::http {
|
||||
|
||||
std::string MessageHeader::getField(const std::string name) {
|
||||
// TODO: Check if exists
|
||||
// TODO: Maybe declare the set/get field functions to do type conversions automatically?
|
||||
return fields[name];
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define VERSION_STR "1.1.0"
|
||||
#define VERSION_STR "1.2.0"
|
37
decoder_modules/dab_decoder/CMakeLists.txt
Normal file
37
decoder_modules/dab_decoder/CMakeLists.txt
Normal file
@ -0,0 +1,37 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(dab_decoder)
|
||||
|
||||
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
target_include_directories(dab_decoder PRIVATE "src/")
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_include_directories(dab_decoder PRIVATE "C:/Program Files/codec2/include/")
|
||||
target_link_directories(dab_decoder PRIVATE "C:/Program Files/codec2/lib")
|
||||
|
||||
target_link_libraries(dab_decoder PRIVATE libcodec2)
|
||||
elseif (ANDROID)
|
||||
target_include_directories(dab_decoder PUBLIC
|
||||
/sdr-kit/${ANDROID_ABI}/include/codec2
|
||||
)
|
||||
|
||||
target_link_libraries(dab_decoder PUBLIC
|
||||
/sdr-kit/${ANDROID_ABI}/lib/libcodec2.so
|
||||
)
|
||||
else ()
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(LIBCODEC2 REQUIRED codec2)
|
||||
|
||||
target_include_directories(dab_decoder PRIVATE ${LIBCODEC2_INCLUDE_DIRS})
|
||||
target_link_directories(dab_decoder PRIVATE ${LIBCODEC2_LIBRARY_DIRS})
|
||||
target_link_libraries(dab_decoder PRIVATE ${LIBCODEC2_LIBRARIES})
|
||||
|
||||
# Include it because for some reason pkgconfig doesn't look here?
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
target_include_directories(dab_decoder PRIVATE "/usr/local/include")
|
||||
endif()
|
||||
endif ()
|
280
decoder_modules/dab_decoder/src/dab_dsp.h
Normal file
280
decoder_modules/dab_decoder/src/dab_dsp.h
Normal file
@ -0,0 +1,280 @@
|
||||
#pragma once
|
||||
#include <dsp/processor.h>
|
||||
#include <utils/flog.h>
|
||||
#include <fftw3.h>
|
||||
#include "dab_phase_sym.h"
|
||||
|
||||
namespace dab {
|
||||
// class CyclicSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
|
||||
// using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
|
||||
// public:
|
||||
// CyclicSync() {}
|
||||
|
||||
// // TODO: The default AGC rate is probably way too fast, plot out the avgCorr to see how much it moves
|
||||
// CyclicSync(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) { init(in, symbolLength, cyclicPrefixLength, samplerate, agcRate); }
|
||||
|
||||
// void init(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) {
|
||||
// // Computer the number of samples for the symbol and its cyclic prefix
|
||||
// symbolSamps = round(samplerate * symbolLength);
|
||||
// prefixSamps = round(samplerate * cyclicPrefixLength);
|
||||
|
||||
// // Allocate and clear the delay buffer
|
||||
// delayBuf = dsp::buffer::alloc<dsp::complex_t>(STREAM_BUFFER_SIZE + 64000);
|
||||
// dsp::buffer::clear(delayBuf, symbolSamps);
|
||||
|
||||
// // Allocate and clear the history buffer
|
||||
// histBuf = dsp::buffer::alloc<dsp::complex_t>(prefixSamps);
|
||||
// dsp::buffer::clear(histBuf, prefixSamps);
|
||||
|
||||
// // Compute the delay input addresses
|
||||
// delayBufInput = &delayBuf[symbolSamps];
|
||||
|
||||
// // Compute the correlation AGC configuration
|
||||
// this->agcRate = agcRate;
|
||||
// agcRateInv = 1.0f - agcRate;
|
||||
|
||||
// base_type::init(in);
|
||||
// }
|
||||
|
||||
// void reset() {
|
||||
// assert(base_type::_block_init);
|
||||
// std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
// base_type::tempStop();
|
||||
|
||||
// base_type::tempStart();
|
||||
// }
|
||||
|
||||
// int run() {
|
||||
// int count = base_type::_in->read();
|
||||
// if (count < 0) { return -1; }
|
||||
|
||||
// // Copy the data into the normal delay buffer
|
||||
// memcpy(delayBufInput, base_type::_in->readBuf, count * sizeof(dsp::complex_t));
|
||||
|
||||
// // Flush the input stream
|
||||
// base_type::_in->flush();
|
||||
|
||||
// // Do cross-correlation
|
||||
// for (int i = 0; i < count; i++) {
|
||||
// // Get the current history slot
|
||||
// dsp::complex_t* slot = &histBuf[histId++];
|
||||
|
||||
// // Wrap around the history slot index (TODO: Check that the history buffer's length is correct)
|
||||
// histId %= prefixSamps;
|
||||
|
||||
// // Kick out last value from the correlation
|
||||
// corr -= *slot;
|
||||
|
||||
// // Save input value and compute the new prodct
|
||||
// dsp::complex_t val = delayBuf[i];
|
||||
// dsp::complex_t prod = val.conj()*delayBuf[i+symbolSamps];
|
||||
|
||||
// // Add the new value to the correlation
|
||||
// *slot = prod;
|
||||
|
||||
// // Add the new value to the history buffer
|
||||
// corr += prod;
|
||||
|
||||
// // Compute sample amplitude
|
||||
// float rcorr = corr.amplitude();
|
||||
|
||||
// // If a high enough peak is reached, reset the symbol counter
|
||||
// if (rcorr > avgCorr && rcorr > peakCorr) { // Note keeping an average level might not be needed
|
||||
// peakCorr = rcorr;
|
||||
// peakLCorr = lastCorr;
|
||||
// samplesSincePeak = 0;
|
||||
// }
|
||||
|
||||
// // If this is the sample right after the peak, save it
|
||||
// if (samplesSincePeak == 1) {
|
||||
// peakRCorr = rcorr;
|
||||
// }
|
||||
|
||||
// // Write the sample to the output
|
||||
// out.writeBuf[samplesSincePeak++] = val;
|
||||
|
||||
// // If the end of the symbol is reached, send it off
|
||||
// if (samplesSincePeak >= symbolSamps) {
|
||||
// if (!out.swap(symbolSamps)) {
|
||||
// return -1;
|
||||
// }
|
||||
// samplesSincePeak = 0;
|
||||
// peakCorr = 0;
|
||||
// }
|
||||
|
||||
// // Update the average correlation
|
||||
// lastCorr = rcorr;
|
||||
|
||||
// // Update the average correlation value
|
||||
// avgCorr = agcRate*rcorr + agcRateInv*avgCorr;
|
||||
// }
|
||||
|
||||
// // Move unused data
|
||||
// memmove(delayBuf, &delayBuf[count], symbolSamps * sizeof(dsp::complex_t));
|
||||
|
||||
// return count;
|
||||
// }
|
||||
|
||||
// protected:
|
||||
// int symbolSamps;
|
||||
// int prefixSamps;
|
||||
|
||||
// int histId = 0;
|
||||
// dsp::complex_t* histBuf;
|
||||
|
||||
// dsp::complex_t* delayBuf;
|
||||
// dsp::complex_t* delayBufInput;
|
||||
|
||||
// dsp::complex_t corr = { 0.0f, 0.0f };
|
||||
|
||||
// int samplesSincePeak = 0;
|
||||
// float lastCorr = 0.0f;
|
||||
// float peakCorr = 0.0f;
|
||||
// float peakLCorr = 0.0f;
|
||||
// float peakRCorr = 0.0f;
|
||||
|
||||
// // Note only required for DAB
|
||||
// float avgCorr = 0.0f;
|
||||
// float agcRate;
|
||||
// float agcRateInv;
|
||||
// };
|
||||
|
||||
class FrameFreqSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
|
||||
using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
|
||||
public:
|
||||
FrameFreqSync() {}
|
||||
|
||||
FrameFreqSync(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) { init(in, agcRate); }
|
||||
|
||||
void init(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) {
|
||||
// Allocate buffers
|
||||
amps = dsp::buffer::alloc<float>(2048);
|
||||
conjRef = dsp::buffer::alloc<dsp::complex_t>(2048);
|
||||
corrIn = (dsp::complex_t*)fftwf_alloc_complex(2048);
|
||||
corrOut = (dsp::complex_t*)fftwf_alloc_complex(2048);
|
||||
|
||||
// Copy the phase reference
|
||||
memcpy(conjRef, DAB_PHASE_SYM_CONJ, 2048 * sizeof(dsp::complex_t));
|
||||
|
||||
// Plan the FFT computation
|
||||
plan = fftwf_plan_dft_1d(2048, (fftwf_complex*)corrIn, (fftwf_complex*)corrOut, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
|
||||
// Compute the correlation AGC configuration
|
||||
this->agcRate = agcRate;
|
||||
agcRateInv = 1.0f - agcRate;
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// Apply frequency shift
|
||||
lv_32fc_t phase = lv_cmake(1.0f, 0.0f);
|
||||
lv_32fc_t phaseDelta = lv_cmake(cos(offset), sin(offset));
|
||||
#if VOLK_VERSION >= 030100
|
||||
volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
|
||||
#else
|
||||
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
|
||||
#endif
|
||||
|
||||
// Compute the amplitude amplitude of all samples
|
||||
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)_in->readBuf, 2048);
|
||||
|
||||
// Compute the average signal level by adding up all values
|
||||
float level = 0.0f;
|
||||
volk_32f_accumulator_s32f(&level, amps, 2048);
|
||||
|
||||
// Detect a frame sync condition
|
||||
if (level < avgLvl * 0.5f) {
|
||||
// Reset symbol counter
|
||||
sym = 1;
|
||||
|
||||
// Update the average level
|
||||
avgLvl = agcRate*level + agcRateInv*avgLvl;
|
||||
|
||||
// Flush the input stream and return
|
||||
base_type::_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
// Update the average level
|
||||
avgLvl = agcRate*level + agcRateInv*avgLvl;
|
||||
|
||||
// Handle phase reference
|
||||
if (sym == 1) {
|
||||
// Output the symbols (DEBUG ONLY)
|
||||
memcpy(corrIn, _in->readBuf, 2048 * sizeof(dsp::complex_t));
|
||||
fftwf_execute(plan);
|
||||
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048);
|
||||
int outCount = 0;
|
||||
dsp::complex_t pi4 = { cos(3.1415926535*0.25), sin(3.1415926535*0.25) };
|
||||
for (int i = -767; i < 768; i++) {
|
||||
if (!i) { continue; }
|
||||
int cid0 = ((i-1) >= 0) ? (i-1) : 2048+(i-1);
|
||||
int cid1 = (i >= 0) ? i : 2048+i;;
|
||||
out.writeBuf[outCount++] = pi4 * (corrOut[cid1] * corrOut[cid0].conj()) * (1.0f/(amps[cid0]*amps[cid0]));
|
||||
}
|
||||
out.swap(outCount);
|
||||
|
||||
// Multiply the samples with the conjugated phase reference signal
|
||||
volk_32fc_x2_multiply_32fc((lv_32fc_t*)corrIn, (lv_32fc_t*)_in->readBuf, (lv_32fc_t*)conjRef, 2048);
|
||||
|
||||
// Compute the FFT of the product
|
||||
fftwf_execute(plan);
|
||||
|
||||
// Compute the amplitude of the bins
|
||||
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048);
|
||||
|
||||
// Locate highest power bin
|
||||
uint32_t peakId;
|
||||
volk_32f_index_max_32u(&peakId, amps, 2048);
|
||||
|
||||
// Obtain the value of the bins next to the peak
|
||||
float peakL = amps[(peakId + 2047) % 2048];
|
||||
float peakR = amps[(peakId + 1) % 2048];
|
||||
|
||||
// Compute the integer frequency offset
|
||||
float offInt = (peakId < 1024) ? (float)peakId : ((float)peakId - 2048.0f);
|
||||
|
||||
// Compute the frequency offset in rad/samp
|
||||
float off = 3.1415926535f * (offInt + ((peakR - peakL) / (peakR + peakL))) * (1.0f / 1024.0f);
|
||||
|
||||
// Run control loop
|
||||
offset -= 0.1f*off;
|
||||
flog::debug("Offset: {} Hz, Error: {} Hz, Avg Level: {}", offset * (0.5f/3.1415926535f)*2.048e6, off * (0.5f/3.1415926535f)*2.048e6, avgLvl);
|
||||
}
|
||||
|
||||
// Increment the symbol counter
|
||||
sym++;
|
||||
|
||||
// Flush the input stream and return
|
||||
base_type::_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
protected:
|
||||
fftwf_plan plan;
|
||||
|
||||
float* amps;
|
||||
dsp::complex_t* conjRef;
|
||||
dsp::complex_t* corrIn;
|
||||
dsp::complex_t* corrOut;
|
||||
|
||||
int sym;
|
||||
float offset = 0.0f;
|
||||
|
||||
float avgLvl = 0.0f;
|
||||
float agcRate;
|
||||
float agcRateInv;
|
||||
};
|
||||
}
|
2053
decoder_modules/dab_decoder/src/dab_phase_sym.h
Normal file
2053
decoder_modules/dab_decoder/src/dab_phase_sym.h
Normal file
File diff suppressed because it is too large
Load Diff
165
decoder_modules/dab_decoder/src/main.cpp
Normal file
165
decoder_modules/dab_decoder/src/main.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
#include <imgui.h>
|
||||
#include <config.h>
|
||||
#include <core.h>
|
||||
#include <gui/style.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <module.h>
|
||||
#include <filesystem>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/buffer/reshaper.h>
|
||||
#include <dsp/multirate/rational_resampler.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include "dab_dsp.h"
|
||||
#include <gui/widgets/constellation_diagram.h>
|
||||
#include "ofdm.h"
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "dab_decoder",
|
||||
/* Description: */ "DAB/DAB+ Decoder for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
#define INPUT_SAMPLE_RATE 2.048e6
|
||||
#define VFO_BANDWIDTH 1.6e6
|
||||
|
||||
class M17DecoderModule : public ModuleManager::Instance {
|
||||
public:
|
||||
M17DecoderModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
file = std::ofstream("sync5.f32", std::ios::out | std::ios::binary);
|
||||
|
||||
// Load config
|
||||
config.acquire();
|
||||
|
||||
config.release(true);
|
||||
|
||||
// Initialize VFO
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, VFO_BANDWIDTH, INPUT_SAMPLE_RATE, VFO_BANDWIDTH, VFO_BANDWIDTH, true);
|
||||
vfo->setSnapInterval(250);
|
||||
|
||||
// Initialize DSP here
|
||||
csync.init(vfo->output, 2048, 504, 1e-3, INPUT_SAMPLE_RATE, 1e-6, 0.01, 0.005);
|
||||
ffsync.init(&csync.out);
|
||||
ns.init(&ffsync.out, handler, this);
|
||||
|
||||
// Start DSO Here
|
||||
csync.start();
|
||||
ffsync.start();
|
||||
ns.start();
|
||||
|
||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||
}
|
||||
|
||||
~M17DecoderModule() {
|
||||
gui::menu.removeEntry(name);
|
||||
// Stop DSP Here
|
||||
if (enabled) {
|
||||
csync.stop();
|
||||
ffsync.stop();
|
||||
ns.stop();
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
}
|
||||
|
||||
sigpath::sinkManager.unregisterStream(name);
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
double bw = gui::waterfall.getBandwidth();
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), VFO_BANDWIDTH, INPUT_SAMPLE_RATE, VFO_BANDWIDTH, VFO_BANDWIDTH, true);
|
||||
vfo->setSnapInterval(250);
|
||||
|
||||
// Set Input of demod here
|
||||
csync.setInput(vfo->output);
|
||||
|
||||
// Start DSP here
|
||||
csync.start();
|
||||
ffsync.start();
|
||||
ns.start();
|
||||
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
// Stop DSP here
|
||||
csync.stop();
|
||||
ffsync.stop();
|
||||
ns.stop();
|
||||
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static void menuHandler(void* ctx) {
|
||||
M17DecoderModule* _this = (M17DecoderModule*)ctx;
|
||||
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
if (!_this->enabled) { style::beginDisabled(); }
|
||||
|
||||
_this->constDiagram.draw();
|
||||
|
||||
if (!_this->enabled) { style::endDisabled(); }
|
||||
}
|
||||
|
||||
std::ofstream file;
|
||||
|
||||
static void handler(dsp::complex_t* data, int count, void* ctx) {
|
||||
M17DecoderModule* _this = (M17DecoderModule*)ctx;
|
||||
//_this->file.write((char*)data, count * sizeof(dsp::complex_t));
|
||||
|
||||
dsp::complex_t* buf = _this->constDiagram.acquireBuffer();
|
||||
memcpy(buf, data, 1024 * sizeof(dsp::complex_t));
|
||||
_this->constDiagram.releaseBuffer();
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
|
||||
//dab::CyclicSync csync;
|
||||
dab::FrameFreqSync ffsync;
|
||||
dsp::ofdm::CyclicTimeSync csync;
|
||||
dsp::sink::Handler<dsp::complex_t> ns;
|
||||
|
||||
ImGui::ConstellationDiagram constDiagram;
|
||||
|
||||
// DSP Chain
|
||||
VFOManager::VFO* vfo;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
// Create default recording directory
|
||||
json def = json({});
|
||||
config.setPath(core::args["root"].s() + "/dab_decoder_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new M17DecoderModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (M17DecoderModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
324
decoder_modules/dab_decoder/src/ofdm.h
Normal file
324
decoder_modules/dab_decoder/src/ofdm.h
Normal file
@ -0,0 +1,324 @@
|
||||
#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>
|
||||
|
||||
namespace dsp::ofdm {
|
||||
class CyclicTimeSync : public Processor<complex_t, complex_t> {
|
||||
using base_type = Processor<complex_t, complex_t> ;
|
||||
public:
|
||||
CyclicTimeSync() {}
|
||||
|
||||
CyclicTimeSync(stream<complex_t>* in, int fftSize, int cpSize, double usefulSymbolTime, double samplerate,
|
||||
double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
|
||||
init(in, fftSize, cpSize, usefulSymbolTime, samplerate, omegaGain, muGain, omegaRelLimit, interpPhaseCount, interpTapCount);
|
||||
}
|
||||
|
||||
~CyclicTimeSync() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
dsp::multirate::freePolyphaseBank(interpBank);
|
||||
buffer::free(corrSampCache);
|
||||
buffer::free(corrProdCache);
|
||||
buffer::free(buffer);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, int fftSize, int cpSize, double usefulSymbolTime, double samplerate,
|
||||
double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
|
||||
// Save parameters
|
||||
this->fftSize = fftSize;
|
||||
this->cpSize = cpSize;
|
||||
period = fftSize + cpSize;
|
||||
|
||||
// Compute the interpolator settings
|
||||
omega = (usefulSymbolTime * samplerate) / (double)fftSize;
|
||||
this->omegaGain = omegaGain;
|
||||
this->muGain = muGain;
|
||||
this->omegaRelLimit = omegaRelLimit;
|
||||
this->interpPhaseCount = interpPhaseCount;
|
||||
this->interpTapCount = interpTapCount;
|
||||
|
||||
// Compute the correlator AGC settings
|
||||
// TODO: Compute it using he FFT and CP sizes
|
||||
this->corrAgcRate = 1e-4;
|
||||
corrAgcInvRate = 1.0f - corrAgcRate;
|
||||
|
||||
// Initialize the control loop
|
||||
pcl.init(muGain, omegaGain, 0.0, 0.0, 1.0, omega, omega * (1.0 - omegaRelLimit), omega * (1.0 + omegaRelLimit));
|
||||
|
||||
// Generate the interpolation taps
|
||||
generateInterpTaps();
|
||||
|
||||
// Allocate the buffers
|
||||
corrSampCache = buffer::alloc<complex_t>(fftSize);
|
||||
corrProdCache = buffer::alloc<complex_t>(cpSize);
|
||||
buffer = buffer::alloc<complex_t>(STREAM_BUFFER_SIZE + interpTapCount);
|
||||
bufStart = &buffer[interpTapCount - 1];
|
||||
|
||||
// Clear the buffers
|
||||
buffer::clear(corrSampCache, fftSize);
|
||||
buffer::clear(corrProdCache, cpSize);
|
||||
buffer::clear(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);
|
||||
this->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);
|
||||
this->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);
|
||||
this->omegaRelLimit = omegaRelLimit;
|
||||
pcl.setFreqLimits(omega * (1.0 - omegaRelLimit), omega * (1.0 + omegaRelLimit));
|
||||
}
|
||||
|
||||
void setInterpParams(int interpPhaseCount, int interpTapCount) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
this->interpPhaseCount = interpPhaseCount;
|
||||
this->interpTapCount = interpTapCount;
|
||||
dsp::multirate::freePolyphaseBank(interpBank);
|
||||
buffer::free(buffer);
|
||||
generateInterpTaps();
|
||||
buffer = buffer::alloc<complex_t>(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;
|
||||
// TODO: The rest
|
||||
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(complex_t));
|
||||
|
||||
// Process all samples
|
||||
while (offset < count) {
|
||||
// Get the cache slots
|
||||
complex_t* sampSlot = &corrSampCache[corrSampCacheId++];
|
||||
complex_t* prodSlot = &corrProdCache[corrProdCacheId++];
|
||||
corrSampCacheId %= fftSize;
|
||||
corrProdCacheId %= cpSize;
|
||||
|
||||
// Compute the interpolated sample
|
||||
complex_t sample;
|
||||
int phase = std::clamp<int>(floorf(pcl.phase * (float)interpPhaseCount), 0, interpPhaseCount - 1);
|
||||
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&sample, (lv_32fc_t*)&buffer[offset], interpBank.phases[phase], interpTapCount);
|
||||
|
||||
// Write the sample to the output
|
||||
if (outCount >= cpSize) {
|
||||
out.writeBuf[outCount - cpSize] = sample;
|
||||
}
|
||||
|
||||
// Send out a symbol when it's fully received
|
||||
if ((++outCount) >= fftSize+cpSize) {
|
||||
if (!out.swap(outCount)) { break; }
|
||||
outCount = 0;
|
||||
}
|
||||
|
||||
// Run autocorrelation
|
||||
complex_t prod = sample.conj()*(*sampSlot);
|
||||
corr += prod;
|
||||
corr -= *prodSlot;
|
||||
|
||||
// Write back the new sample and product value to the cache
|
||||
*sampSlot = sample;
|
||||
*prodSlot = prod;
|
||||
|
||||
// Compute the correlation level
|
||||
float corrLvl = corr.amplitude();
|
||||
|
||||
// Detect peak in autocorrelation (TODO: level check maybe not needed now that corrPeak is reset to corrLvl)
|
||||
if (corrLvl > corrAvg && corrLvl > corrPeak) {
|
||||
// Save the current correlation as the peak
|
||||
corrPeak = corrLvl;
|
||||
|
||||
// Save the value of the previous correlation as the left side of the peak
|
||||
corrPeakL = corrLast;
|
||||
|
||||
// Reset the peak distance counter
|
||||
sincePeak = 0;
|
||||
}
|
||||
|
||||
// The first sample after a peak is the right-side sample
|
||||
if (sincePeak == 1) {
|
||||
corrPeakR = corrLvl;
|
||||
}
|
||||
else if (sincePeak == cpSize) {
|
||||
// Start the useful symbol counter
|
||||
sinceCp = 0;
|
||||
|
||||
// Compute the fractional error (TODO: Probably very inaccurate with noise, use real slopes instead)
|
||||
if (corrPeakL > corrPeakR) {
|
||||
float maxSlope = corrPeakR - corrPeak;
|
||||
float slope = corrPeak - corrPeakL;
|
||||
fracErr = std::clamp<float>(0.5f * (1.0f + slope / maxSlope), -0.5f, 0.5f);
|
||||
}
|
||||
else {
|
||||
float maxSlope = corrPeak - corrPeakL;
|
||||
float slope = corrPeakR - corrPeak;
|
||||
fracErr = std::clamp<float>(-0.5f * (1.0f + slope / maxSlope), -0.5f, 0.5f);
|
||||
}
|
||||
}
|
||||
else if (sincePeak == fftSize) {
|
||||
// Reset the peak detector
|
||||
corrPeak = corrAvg;
|
||||
}
|
||||
// NOTE: THIS IS ONLY NEEDED FOR DAB
|
||||
// Detect a wider-than-normal distance to adapt the output counter
|
||||
else if (sincePeak == 2656) {
|
||||
// Reset the output counter
|
||||
outCount = 50;
|
||||
}
|
||||
|
||||
// Last sample of useful symbol
|
||||
if (sinceCp == fftSize) {
|
||||
// If the fractional error is valid, run closed-loop
|
||||
float err = 0.0f;
|
||||
if (!std::isnan(fracErr)) {
|
||||
// Compute the measured period using the distance to the last symbol
|
||||
float measuredPeriod = (float)sinceLastSym - fracErr;
|
||||
// NOTE: THIS IS ONLY NEEDED FOR DAB
|
||||
if (measuredPeriod > 3828.0f) {
|
||||
// Null symbol
|
||||
err = measuredPeriod - (2552.0f+2656.0f);
|
||||
}
|
||||
else {
|
||||
// Regular symbol
|
||||
err = measuredPeriod - period;
|
||||
}
|
||||
|
||||
err = std::clamp<float>(err, -10.0f, 10.0f);
|
||||
|
||||
// Run the control loop in closed-loop mode
|
||||
pcl.advance(err);
|
||||
}
|
||||
else {
|
||||
// Otherwise, run open-loop
|
||||
pcl.advancePhase();
|
||||
}
|
||||
|
||||
// printf("%d\n", outCount);
|
||||
|
||||
// Nudge the symbol window if it's too out of sync
|
||||
if (outCount > 100) {
|
||||
// TODO: MOVE THE LAST SAMPLES OR THE SYMBOL WILL BE CORRUPTED!
|
||||
outCount = 50;
|
||||
flog::debug("NUDGE!");
|
||||
}
|
||||
|
||||
// Reset the period counter
|
||||
sinceLastSym = 0;
|
||||
}
|
||||
else {
|
||||
// Run the control loop in open-loop mode
|
||||
pcl.advancePhase();
|
||||
}
|
||||
|
||||
// Update the offset and phase
|
||||
float delta = floorf(pcl.phase);
|
||||
offset += delta;
|
||||
pcl.phase -= delta;
|
||||
|
||||
// Update the last correlation level
|
||||
corrLast = corrLvl;
|
||||
|
||||
// Update correlation AGC
|
||||
corrAvg = corrAvg*corrAgcInvRate + corrLvl*corrAgcRate;
|
||||
|
||||
// Increment the distance counters (TODO: Check if they happen at the right point, eg. after being reset to zero)
|
||||
sincePeak++;
|
||||
sinceLastSym++;
|
||||
sinceCp++;
|
||||
}
|
||||
|
||||
// Prepare offset for next buffer of samples
|
||||
offset -= count;
|
||||
|
||||
// Update delay buffer
|
||||
memmove(buffer, &buffer[count], (interpTapCount - 1) * sizeof(complex_t));
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
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);
|
||||
taps::free(lp);
|
||||
}
|
||||
|
||||
// OFDM Configuration
|
||||
int fftSize;
|
||||
int cpSize;
|
||||
float period;
|
||||
|
||||
// Interpolator
|
||||
dsp::multirate::PolyphaseBank<float> interpBank;
|
||||
int interpPhaseCount;
|
||||
int interpTapCount;
|
||||
int offset = 0;
|
||||
complex_t* buffer = NULL;
|
||||
complex_t* bufStart;
|
||||
|
||||
// Control loop
|
||||
loop::PhaseControlLoop<float, false> pcl;
|
||||
double omega;
|
||||
double omegaGain;
|
||||
double muGain;
|
||||
double omegaRelLimit;
|
||||
float fracErr = 0.0f;
|
||||
|
||||
// Autocorrelator
|
||||
complex_t corr = {0.0f, 0.0f};
|
||||
complex_t* corrSampCache = NULL;
|
||||
complex_t* corrProdCache = NULL;
|
||||
int corrSampCacheId = 0;
|
||||
int corrProdCacheId = 0;
|
||||
float corrAgcRate;
|
||||
float corrAgcInvRate;
|
||||
float corrAvg = 0;
|
||||
float corrLast = 0;
|
||||
float corrPeakR = 0;
|
||||
float corrPeak = 0;
|
||||
float corrPeakL = 0;
|
||||
|
||||
// Peak detection
|
||||
int sincePeak = 0;
|
||||
int sinceLastSym = 0;
|
||||
int sinceCp = 0;
|
||||
|
||||
// Other shit to categorize
|
||||
int outCount = 0;
|
||||
};
|
||||
};
|
31
decoder_modules/dab_decoder/src/optmized.txt
Normal file
31
decoder_modules/dab_decoder/src/optmized.txt
Normal file
@ -0,0 +1,31 @@
|
||||
cyclicLen = 4
|
||||
usefulLen = 12
|
||||
|
||||
A = 0*12 + 1*13 + 2*14 + 3*15
|
||||
B = 1*13 + 2*14 + 3*15 + 4*16 = A - 0*12 + 4*16
|
||||
C = 2*14 + 3*15 + 4*16 + 5*17 = B - 1*13 + 5*17
|
||||
D = 3*15 + 4*16 + 5*17 + 6*18 = C - 2*14 + 6*18
|
||||
E = 4*16 + 5*17 + 6*18 + 7*19 = D - 3*15 + 7*19
|
||||
F = 5*17 + 6*18 + 7*19 + 8*20 = E - 4*16 + 8*20
|
||||
G = 6*18 + 7*19 + 8*20 + 9*21 = F - 5*17 + 9*21
|
||||
H = 7*19 + 8*20 + 9*21 + 10*22 = G - 6*18 + 10*22
|
||||
I = 8*20 + 9*21 + 10*22 + 11*23 = H - 7*19 + 11*23
|
||||
J = 9*21 + 10*22 + 11*23 + 12*24 = I - 8*20 + 12*24
|
||||
K = 10*22 + 11*23 + 12*24 + 13*25 = J - 9*21 + 13*25
|
||||
L = 11*23 + 12*24 + 13*25 + 14*26 = K - 10*22 + 14*26
|
||||
M = 12*24 + 13*25 + 14*26 + 15*27 = L - 11*23 + 15*27
|
||||
N = 13*25 + 14*26 + 15*27 + 16*28 = M - 12*24 + 16*28
|
||||
O = 14*26 + 15*27 + 16*28 + 17*29 = N - 13*25 + 17*29
|
||||
P = 15*27 + 16*28 + 17*29 + 18*30 = O - 14*26 + 18*30
|
||||
Q = 16*28 + 17*29 + 18*30 + 19*31 = P - 15*27 + 19*31
|
||||
R = 17*29 + 18*30 + 19*31 + 20*32 = Q - 16*28 + 20*32
|
||||
S = 18*30 + 19*31 + 20*32 + 21*33 = R - 17*29 + 21*33
|
||||
T = 19*31 + 20*32 + 21*33 + 22*34 = S - 18*30 + 22*34
|
||||
U = 20*32 + 21*33 + 22*34 + 23*35 = T - 19*31 + 23*35
|
||||
|
||||
Conclusion:
|
||||
sampCacheLen = usefulLen
|
||||
prodCacheLen = cyclicLen
|
||||
|
||||
Peak correlation occurs when the current interpolated value is the last FFT sample
|
||||
|
397
decoder_modules/dab_decoder/src/test.txt
Normal file
397
decoder_modules/dab_decoder/src/test.txt
Normal file
@ -0,0 +1,397 @@
|
||||
5209
|
||||
2553
|
||||
2551
|
||||
2551
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2555
|
||||
2549
|
||||
2552
|
||||
2552
|
||||
2554
|
||||
2550
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
2553
|
||||
2552
|
||||
2550
|
||||
2552
|
||||
2548
|
||||
2558
|
||||
2551
|
||||
2546
|
||||
2559
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2551
|
||||
2554
|
||||
2555
|
||||
2549
|
||||
2550
|
||||
2549
|
||||
2557
|
||||
2551
|
||||
2554
|
||||
2550
|
||||
2552
|
||||
2546
|
||||
2559
|
||||
2551
|
||||
2554
|
||||
2550
|
||||
2550
|
||||
2557
|
||||
2550
|
||||
2556
|
||||
2543
|
||||
2556
|
||||
2551
|
||||
2554
|
||||
2551
|
||||
2553
|
||||
2554
|
||||
2550
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2553
|
||||
2550
|
||||
2553
|
||||
2546
|
||||
2558
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2550
|
||||
2555
|
||||
2551
|
||||
2551
|
||||
2554
|
||||
2553
|
||||
2553
|
||||
2549
|
||||
2552
|
||||
5208
|
||||
2554
|
||||
2550
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2551
|
||||
2553
|
||||
2551
|
||||
2553
|
||||
2550
|
||||
2554
|
||||
2553
|
||||
2551
|
||||
2553
|
||||
2552
|
||||
2546
|
||||
2557
|
||||
2551
|
||||
2553
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2551
|
||||
2548
|
||||
2557
|
||||
2553
|
||||
2551
|
||||
2562
|
||||
2539
|
||||
2559
|
||||
2548
|
||||
2551
|
||||
2551
|
||||
2550
|
||||
2556
|
||||
2551
|
||||
2554
|
||||
2550
|
||||
2552
|
||||
2552
|
||||
2548
|
||||
2556
|
||||
2550
|
||||
2554
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
2552
|
||||
2552
|
||||
2550
|
||||
2555
|
||||
2558
|
||||
2545
|
||||
2552
|
||||
2553
|
||||
2548
|
||||
2555
|
||||
2552
|
||||
2552
|
||||
2557
|
||||
2550
|
||||
2548
|
||||
2553
|
||||
2554
|
||||
2550
|
||||
2552
|
||||
2547
|
||||
2558
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2555
|
||||
2549
|
||||
2552
|
||||
5208
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2550
|
||||
2555
|
||||
2550
|
||||
2553
|
||||
2553
|
||||
2551
|
||||
2552
|
||||
2547
|
||||
2557
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2546
|
||||
2558
|
||||
2551
|
||||
2552
|
||||
2553
|
||||
2551
|
||||
2553
|
||||
2553
|
||||
2552
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2556
|
||||
2548
|
||||
2551
|
||||
2555
|
||||
2550
|
||||
2552
|
||||
2555
|
||||
2549
|
||||
2552
|
||||
2552
|
||||
2550
|
||||
2553
|
||||
2551
|
||||
2554
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2553
|
||||
2551
|
||||
2548
|
||||
2550
|
||||
2555
|
||||
2554
|
||||
2553
|
||||
2556
|
||||
2547
|
||||
2552
|
||||
2554
|
||||
2552
|
||||
2550
|
||||
2552
|
||||
2552
|
||||
2553
|
||||
2552
|
||||
2551
|
||||
2552
|
||||
5209
|
||||
2553
|
||||
2551
|
||||
2551
|
||||
2547
|
||||
2558
|
||||
2550
|
||||
2553
|
||||
2552
|
||||
2553
|
||||
2552
|
||||
2549
|
||||
2551
|
||||
2557
|
||||
2549
|
||||
2554
|
||||
2551
|
||||
2554
|
||||
2550
|
||||
2553
|
||||
2553
|
||||
2551
|
||||
2551
|
||||
2548
|
||||
2559
|
||||
2549
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2551
|
||||
2551
|
||||
2550
|
||||
2557
|
||||
2551
|
||||
2552
|
||||
2555
|
||||
2549
|
||||
2557
|
||||
2549
|
||||
2550
|
||||
2553
|
||||
2549
|
||||
2554
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
2552
|
||||
2553
|
||||
2550
|
||||
2553
|
||||
2553
|
||||
2551
|
||||
2547
|
||||
2557
|
||||
2552
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2549
|
||||
2555
|
||||
2556
|
||||
2550
|
||||
2550
|
||||
2553
|
||||
2551
|
||||
2552
|
||||
2553
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2553
|
||||
2551
|
||||
2541
|
||||
2563
|
||||
2552
|
||||
5210
|
||||
2550
|
||||
2551
|
||||
2552
|
||||
2553
|
||||
2553
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2558
|
||||
2547
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2550
|
||||
2554
|
||||
2551
|
||||
2553
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2551
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
2551
|
||||
2555
|
||||
2550
|
||||
2552
|
||||
2552
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
2552
|
||||
2553
|
||||
2550
|
||||
2553
|
||||
2558
|
||||
2546
|
||||
2553
|
||||
2554
|
||||
2550
|
||||
2552
|
||||
2553
|
||||
2548
|
||||
2554
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2551
|
||||
2554
|
||||
2550
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
2551
|
||||
2552
|
||||
2554
|
||||
2553
|
||||
2551
|
||||
2551
|
||||
2555
|
||||
2550
|
||||
2553
|
||||
2552
|
||||
2550
|
||||
2554
|
||||
2550
|
||||
2553
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
5210
|
||||
2553
|
||||
2549
|
||||
2553
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2556
|
||||
2548
|
||||
2549
|
||||
2555
|
||||
2551
|
||||
2557
|
||||
2548
|
||||
2552
|
||||
2552
|
||||
2552
|
@ -24,12 +24,13 @@ namespace demod {
|
||||
class Demodulator {
|
||||
public:
|
||||
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 stop() = 0;
|
||||
virtual void showMenu() = 0;
|
||||
virtual void setBandwidth(double bandwidth) = 0;
|
||||
virtual void setInput(dsp::stream<dsp::complex_t>* input) = 0;
|
||||
virtual void AFSampRateChanged(double newSR) = 0;
|
||||
virtual const char* getName() = 0;
|
||||
virtual double getIFSampleRate() = 0;
|
||||
virtual double getAFSampleRate() = 0;
|
||||
|
@ -7,13 +7,13 @@ namespace demod {
|
||||
public:
|
||||
AM() {}
|
||||
|
||||
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~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;
|
||||
_config = config;
|
||||
|
||||
@ -68,6 +68,8 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "AM"; }
|
||||
|
@ -7,15 +7,15 @@ namespace demod {
|
||||
public:
|
||||
CW() {}
|
||||
|
||||
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~CW() {
|
||||
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->_config = config;
|
||||
this->afbwChangeHandler = afbwChangeHandler;
|
||||
@ -74,6 +74,8 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "CW"; }
|
||||
|
@ -7,15 +7,15 @@ namespace demod {
|
||||
public:
|
||||
DSB() {}
|
||||
|
||||
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~DSB() {
|
||||
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;
|
||||
_config = config;
|
||||
|
||||
@ -61,6 +61,8 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "DSB"; }
|
||||
|
@ -7,15 +7,15 @@ namespace demod {
|
||||
public:
|
||||
LSB() {}
|
||||
|
||||
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~LSB() {
|
||||
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;
|
||||
_config = config;
|
||||
|
||||
@ -61,6 +61,8 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "LSB"; }
|
||||
|
@ -7,13 +7,13 @@ namespace demod {
|
||||
public:
|
||||
NFM() {}
|
||||
|
||||
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~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->_config = config;
|
||||
|
||||
@ -57,6 +57,8 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "FM"; }
|
||||
|
@ -7,18 +7,17 @@ namespace demod {
|
||||
public:
|
||||
RAW() {}
|
||||
|
||||
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~RAW() {
|
||||
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;
|
||||
audioSampleRate = 48000;
|
||||
// TODO: This needs to be selectable
|
||||
audioSampleRate = audioSR;
|
||||
|
||||
// Define structure
|
||||
c2s.init(input);
|
||||
@ -40,6 +39,10 @@ namespace demod {
|
||||
c2s.setInput(input);
|
||||
}
|
||||
|
||||
void AFSampRateChanged(double newSR) {
|
||||
audioSampleRate = newSR;
|
||||
}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "RAW"; }
|
||||
|
@ -8,15 +8,15 @@ namespace demod {
|
||||
public:
|
||||
USB() {}
|
||||
|
||||
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~USB() {
|
||||
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;
|
||||
_config = config;
|
||||
|
||||
@ -62,6 +62,8 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "USB"; }
|
||||
|
@ -16,8 +16,8 @@ namespace demod {
|
||||
public:
|
||||
WFM() : diag(0.5, 4096) {}
|
||||
|
||||
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) : diag(0.5, 4096) {
|
||||
init(name, config, input, bandwidth);
|
||||
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) : diag(0.5, 4096) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~WFM() {
|
||||
@ -25,7 +25,7 @@ namespace demod {
|
||||
gui::waterfall.onFFTRedraw.unbindHandler(&fftRedrawHandler);
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
this->name = name;
|
||||
_config = config;
|
||||
|
||||
@ -252,6 +252,8 @@ namespace demod {
|
||||
demod.setInput(input);
|
||||
}
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "WFM"; }
|
||||
|
@ -81,16 +81,30 @@ public:
|
||||
// Initialize audio DSP chain
|
||||
afChain.init(&dummyAudioStream);
|
||||
|
||||
resamp.init(NULL, 250000.0, 48000.0);
|
||||
deemp.init(NULL, 50e-6, 48000.0);
|
||||
|
||||
afChain.addBlock(&resamp, true);
|
||||
afChain.addBlock(&deemp, false);
|
||||
|
||||
// Initialize the sink
|
||||
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
|
||||
selectDemodByID((DemodID)selectedDemodID);
|
||||
|
||||
// Start IF chain
|
||||
ifChain.start();
|
||||
|
||||
// Start AF chain
|
||||
afChain.start();
|
||||
|
||||
// Start stream, the rest was started when selecting the demodulator
|
||||
stream.start();
|
||||
|
||||
// Register the menu
|
||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||
|
||||
@ -101,10 +115,11 @@ public:
|
||||
~RadioModule() {
|
||||
core::modComManager.unregisterInterface(name);
|
||||
gui::menu.removeEntry(name);
|
||||
stream.stop();
|
||||
if (enabled) {
|
||||
disable();
|
||||
}
|
||||
sigpath::streamManager.destroyStream(stream);
|
||||
sigpath::sinkManager.unregisterStream(name);
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
@ -116,7 +131,9 @@ public:
|
||||
vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
|
||||
}
|
||||
ifChain.setInput(vfo->output, [=](dsp::stream<dsp::complex_t>* out){ ifChainOutputChangeHandler(out, this); });
|
||||
ifChain.start();
|
||||
selectDemodByID((DemodID)selectedDemodID);
|
||||
afChain.start();
|
||||
}
|
||||
|
||||
void disable() {
|
||||
@ -124,7 +141,6 @@ public:
|
||||
ifChain.stop();
|
||||
if (selectedDemod) { selectedDemod->stop(); }
|
||||
afChain.stop();
|
||||
stream->stopDSP();
|
||||
if (vfo) { sigpath::vfoManager.deleteVFO(vfo); }
|
||||
vfo = NULL;
|
||||
}
|
||||
@ -297,7 +313,7 @@ private:
|
||||
bw = std::clamp<double>(bw, demod->getMinBandwidth(), demod->getMaxBandwidth());
|
||||
|
||||
// Initialize
|
||||
demod->init(name, &config, ifChain.out, bw);
|
||||
demod->init(name, &config, ifChain.out, bw, stream.getSampleRate());
|
||||
|
||||
return demod;
|
||||
}
|
||||
@ -321,34 +337,22 @@ private:
|
||||
}
|
||||
|
||||
void selectDemod(demod::Demodulator* demod) {
|
||||
// Stop the IF chain
|
||||
ifChain.stop();
|
||||
|
||||
// Stop the current demodulator
|
||||
// Stopcurrently selected demodulator and select new
|
||||
afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
if (selectedDemod) {
|
||||
selectedDemod->stop();
|
||||
}
|
||||
|
||||
// Stop AF chain
|
||||
afChain.stop();
|
||||
|
||||
// Stop audio stream's DSP
|
||||
stream->stopDSP();
|
||||
|
||||
// Destroy the old demodulator
|
||||
afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
||||
if (selectedDemod) {
|
||||
delete selectedDemod;
|
||||
}
|
||||
|
||||
// Select the new demodulator
|
||||
selectedDemod = demod;
|
||||
|
||||
// Give the demodulator the most recent audio SR
|
||||
selectedDemod->AFSampRateChanged(audioSampleRate);
|
||||
|
||||
// Set the demodulator's input
|
||||
selectedDemod->setInput(ifChain.out);
|
||||
|
||||
// Set AF chain's input
|
||||
afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
||||
afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
|
||||
// Load config
|
||||
bandwidth = selectedDemod->getDefaultBandwidth();
|
||||
@ -436,30 +440,21 @@ private:
|
||||
// Configure AF chain
|
||||
if (postProcEnabled) {
|
||||
// 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
|
||||
setDeemphasisMode(deempModes[deempId]);
|
||||
}
|
||||
else {
|
||||
// Disable everything if post processing is disabled
|
||||
afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
||||
afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
}
|
||||
|
||||
// Update audo samplerate
|
||||
stream->setSamplerate(selectedDemod->getAFSampleRate());
|
||||
|
||||
// Start the IF chain
|
||||
ifChain.start();
|
||||
|
||||
// Start new demodulator
|
||||
selectedDemod->start();
|
||||
|
||||
// Start the AF chain
|
||||
afChain.start();
|
||||
|
||||
// Start the audio stream
|
||||
stream->startDSP();
|
||||
}
|
||||
|
||||
|
||||
@ -475,12 +470,37 @@ private:
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
void setAudioSampleRate(double sr) {
|
||||
audioSampleRate = sr;
|
||||
if (!selectedDemod) { return; }
|
||||
selectedDemod->AFSampRateChanged(audioSampleRate);
|
||||
if (!postProcEnabled && vfo) {
|
||||
// If postproc is disabled, IF SR = AF SR
|
||||
minBandwidth = selectedDemod->getMinBandwidth();
|
||||
maxBandwidth = selectedDemod->getMaxBandwidth();
|
||||
bandwidth = selectedDemod->getIFSampleRate();
|
||||
vfo->setBandwidthLimits(minBandwidth, maxBandwidth, selectedDemod->getBandwidthLocked());
|
||||
vfo->setSampleRate(selectedDemod->getIFSampleRate(), bandwidth);
|
||||
return;
|
||||
}
|
||||
|
||||
afChain.stop();
|
||||
|
||||
// Configure resampler
|
||||
resamp.setOutSamplerate(audioSampleRate);
|
||||
|
||||
// Configure deemphasis sample rate
|
||||
deemp.setSamplerate(audioSampleRate);
|
||||
|
||||
afChain.start();
|
||||
}
|
||||
|
||||
void setDeemphasisMode(DeemphasisMode mode) {
|
||||
deempId = deempModes.valueId(mode);
|
||||
if (!postProcEnabled || !selectedDemod) { return; }
|
||||
bool deempEnabled = (mode != DEEMP_MODE_NONE);
|
||||
if (deempEnabled) { deemp.setTau(deempTaus[mode]); }
|
||||
afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
||||
afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
|
||||
// Save config
|
||||
config.acquire();
|
||||
@ -564,6 +584,11 @@ private:
|
||||
_this->setBandwidth(newBw);
|
||||
}
|
||||
|
||||
static void sampleRateChangeHandler(float sampleRate, void* ctx) {
|
||||
RadioModule* _this = (RadioModule*)ctx;
|
||||
_this->setAudioSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
static void ifChainOutputChangeHandler(dsp::stream<dsp::complex_t>* output, void* ctx) {
|
||||
RadioModule* _this = (RadioModule*)ctx;
|
||||
if (!_this->selectedDemod) { return; }
|
||||
@ -572,14 +597,16 @@ private:
|
||||
|
||||
static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) {
|
||||
RadioModule* _this = (RadioModule*)ctx;
|
||||
if (!_this->enabled || !_this->selectedDemod) { return; }
|
||||
|
||||
// If no demod is selected, reject the command
|
||||
if (!_this->selectedDemod) { return; }
|
||||
|
||||
// Execute commands
|
||||
if (code == RADIO_IFACE_CMD_GET_MODE && out) {
|
||||
int* _out = (int*)out;
|
||||
*_out = _this->selectedDemodID;
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_MODE && in) {
|
||||
else if (code == RADIO_IFACE_CMD_SET_MODE && in && _this->enabled) {
|
||||
int* _in = (int*)in;
|
||||
_this->selectDemodByID((DemodID)*_in);
|
||||
}
|
||||
@ -587,7 +614,7 @@ private:
|
||||
float* _out = (float*)out;
|
||||
*_out = _this->bandwidth;
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in) {
|
||||
else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in && _this->enabled) {
|
||||
float* _in = (float*)in;
|
||||
if (_this->bandwidthLocked) { return; }
|
||||
_this->setBandwidth(*_in);
|
||||
@ -596,7 +623,7 @@ private:
|
||||
bool* _out = (bool*)out;
|
||||
*_out = _this->squelchEnabled;
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in) {
|
||||
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in && _this->enabled) {
|
||||
bool* _in = (bool*)in;
|
||||
_this->setSquelchEnabled(*_in);
|
||||
}
|
||||
@ -604,7 +631,7 @@ private:
|
||||
float* _out = (float*)out;
|
||||
*_out = _this->squelchLevel;
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in) {
|
||||
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in && _this->enabled) {
|
||||
float* _in = (float*)in;
|
||||
_this->setSquelchLevel(*_in);
|
||||
}
|
||||
@ -618,6 +645,7 @@ private:
|
||||
|
||||
// Handlers
|
||||
EventHandler<double> onUserChangedBandwidthHandler;
|
||||
EventHandler<float> srChangeHandler;
|
||||
EventHandler<dsp::stream<dsp::complex_t>*> ifChainOutputChanged;
|
||||
EventHandler<dsp::stream<dsp::stereo_t>*> afChainOutputChanged;
|
||||
|
||||
@ -632,9 +660,10 @@ private:
|
||||
// Audio chain
|
||||
dsp::stream<dsp::stereo_t> dummyAudioStream;
|
||||
dsp::chain<dsp::stereo_t> afChain;
|
||||
dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
|
||||
dsp::filter::Deemphasis<dsp::stereo_t> deemp;
|
||||
|
||||
std::shared_ptr<MasterStream> stream;
|
||||
SinkManager::Stream stream;
|
||||
|
||||
demod::Demodulator* selectedDemod = NULL;
|
||||
|
||||
|
8
decoder_modules/ryfi_decoder/CMakeLists.txt
Normal file
8
decoder_modules/ryfi_decoder/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(ryfi_decoder)
|
||||
|
||||
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
target_include_directories(ryfi_decoder PRIVATE "src/")
|
139
decoder_modules/ryfi_decoder/src/main.cpp
Normal file
139
decoder_modules/ryfi_decoder/src/main.cpp
Normal file
@ -0,0 +1,139 @@
|
||||
#include <imgui.h>
|
||||
#include <config.h>
|
||||
#include <core.h>
|
||||
#include <gui/style.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <module.h>
|
||||
#include <filesystem>
|
||||
#include <dsp/routing/splitter.h>
|
||||
#include <dsp/buffer/reshaper.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include <gui/widgets/folder_select.h>
|
||||
#include <gui/widgets/constellation_diagram.h>
|
||||
#include "ryfi/receiver.h"
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "ryfi_decoder",
|
||||
/* Description: */ "RyFi decoder for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
#define INPUT_BANDWIDTH 600e3
|
||||
#define INPUT_SAMPLE_RATE 1000e3
|
||||
#define INPUT_BAUDRATE 500e3
|
||||
|
||||
#define SYMBOL_DIAG_RATE 30
|
||||
#define SYMBOL_DIAG_COUNT 1024
|
||||
|
||||
class RyFiDecoderModule : public ModuleManager::Instance {
|
||||
public:
|
||||
RyFiDecoderModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_BANDWIDTH, INPUT_SAMPLE_RATE, INPUT_BANDWIDTH, INPUT_BANDWIDTH, true);
|
||||
rx.init(vfo->output, INPUT_BAUDRATE, INPUT_SAMPLE_RATE);
|
||||
reshape.init(rx.softOut, SYMBOL_DIAG_COUNT, (INPUT_BAUDRATE / SYMBOL_DIAG_RATE) - SYMBOL_DIAG_COUNT);
|
||||
symSink.init(&reshape.out, symSinkHandler, this);
|
||||
rx.onPacket.bind(&RyFiDecoderModule::packetHandler, this);
|
||||
|
||||
rx.start();
|
||||
reshape.start();
|
||||
symSink.start();
|
||||
|
||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||
}
|
||||
|
||||
~RyFiDecoderModule() {
|
||||
rx.stop();
|
||||
reshape.stop();
|
||||
symSink.stop();
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
gui::menu.removeEntry(name);
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
double bw = gui::waterfall.getBandwidth();
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), INPUT_BANDWIDTH, INPUT_SAMPLE_RATE, INPUT_BANDWIDTH, INPUT_BANDWIDTH, true);
|
||||
|
||||
rx.setInput(vfo->output);
|
||||
|
||||
rx.start();
|
||||
reshape.start();
|
||||
symSink.start();
|
||||
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
rx.stop();
|
||||
reshape.stop();
|
||||
symSink.stop();
|
||||
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
void packetHandler(ryfi::Packet pkt) {
|
||||
flog::debug("Got a {} byte packet!", pkt.size());
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
RyFiDecoderModule* _this = (RyFiDecoderModule*)ctx;
|
||||
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
if (!_this->enabled) { style::beginDisabled(); }
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
_this->constDiagram.draw();
|
||||
|
||||
if (!_this->enabled) { style::endDisabled(); }
|
||||
}
|
||||
|
||||
static void symSinkHandler(dsp::complex_t* data, int count, void* ctx) {
|
||||
RyFiDecoderModule* _this = (RyFiDecoderModule*)ctx;
|
||||
|
||||
dsp::complex_t* buf = _this->constDiagram.acquireBuffer();
|
||||
memcpy(buf, data, 1024 * sizeof(dsp::complex_t));
|
||||
_this->constDiagram.releaseBuffer();
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
|
||||
// DSP Chain
|
||||
VFOManager::VFO* vfo;
|
||||
ryfi::Receiver rx;
|
||||
dsp::buffer::Reshaper<dsp::complex_t> reshape;
|
||||
dsp::sink::Handler<dsp::complex_t> symSink;
|
||||
|
||||
ImGui::ConstellationDiagram constDiagram;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new RyFiDecoderModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (RyFiDecoderModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
|
||||
}
|
74
decoder_modules/ryfi_decoder/src/ryfi/conv_codec.cpp
Normal file
74
decoder_modules/ryfi_decoder/src/ryfi/conv_codec.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
#include "conv_codec.h"
|
||||
|
||||
namespace ryfi {
|
||||
ConvEncoder::ConvEncoder(dsp::stream<uint8_t>* in) {
|
||||
// Create the convolutional encoder instance
|
||||
conv = correct_convolutional_create(2, 7, correct_conv_r12_7_polynomial);
|
||||
|
||||
// Init the base class
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
ConvEncoder::~ConvEncoder() {
|
||||
// Destroy the convolutional encoder instance
|
||||
correct_convolutional_destroy(conv);
|
||||
}
|
||||
|
||||
int ConvEncoder::encode(const uint8_t* in, uint8_t* out, int count) {
|
||||
// Run convolutional encoder on the data
|
||||
return correct_convolutional_encode(conv, in, count, out);
|
||||
}
|
||||
|
||||
int ConvEncoder::run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count);
|
||||
|
||||
base_type::_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
ConvDecoder::ConvDecoder(dsp::stream<dsp::complex_t>* in) {
|
||||
// Create the convolutional encoder instance
|
||||
conv = correct_convolutional_create(2, 7, correct_conv_r12_7_polynomial);
|
||||
|
||||
// Allocate the soft symbol buffer
|
||||
soft = dsp::buffer::alloc<uint8_t>(STREAM_BUFFER_SIZE);
|
||||
|
||||
// Init the base class
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
ConvDecoder::~ConvDecoder() {
|
||||
// Destroy the convolutional encoder instance
|
||||
correct_convolutional_destroy(conv);
|
||||
|
||||
// Free the soft symbol buffer
|
||||
dsp::buffer::free(soft);
|
||||
}
|
||||
|
||||
int ConvDecoder::decode(const dsp::complex_t* in, uint8_t* out, int count) {
|
||||
// Convert to uint8
|
||||
const float* _in = (const float*)in;
|
||||
count *= 2;
|
||||
for (int i = 0; i < count; i++) {
|
||||
soft[i] = std::clamp<int>((_in[i] * 127.0f) + 128.0f, 0, 255);
|
||||
}
|
||||
|
||||
// Run convolutional decoder on the data
|
||||
return correct_convolutional_decode_soft(conv, soft, count, out);
|
||||
}
|
||||
|
||||
int ConvDecoder::run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
count = decode(base_type::_in->readBuf, base_type::out.writeBuf, count);
|
||||
|
||||
base_type::_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
}
|
71
decoder_modules/ryfi_decoder/src/ryfi/conv_codec.h
Normal file
71
decoder_modules/ryfi_decoder/src/ryfi/conv_codec.h
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "dsp/processor.h"
|
||||
|
||||
extern "C" {
|
||||
#include "correct.h"
|
||||
}
|
||||
|
||||
namespace ryfi {
|
||||
/**
|
||||
* RyFi Convolutional Encoder.
|
||||
*/
|
||||
class ConvEncoder : public dsp::Processor<uint8_t, uint8_t> {
|
||||
using base_type = dsp::Processor<uint8_t, uint8_t>;
|
||||
public:
|
||||
/**
|
||||
* Create a convolutional encoder specifying an input stream.
|
||||
* @param in Input stream.
|
||||
*/
|
||||
ConvEncoder(dsp::stream<uint8_t>* in = NULL);
|
||||
|
||||
// Destructor
|
||||
~ConvEncoder();
|
||||
|
||||
/**
|
||||
* Encode data.
|
||||
* @param in Input bytes.
|
||||
* @param out Output bits.
|
||||
* @param count Number of input bytes.
|
||||
* @return Number of output bits.
|
||||
*/
|
||||
int encode(const uint8_t* in, uint8_t* out, int count);
|
||||
|
||||
private:
|
||||
int run();
|
||||
|
||||
correct_convolutional* conv;
|
||||
};
|
||||
|
||||
/**
|
||||
* RyFi Convolutional Decoder.
|
||||
*/
|
||||
class ConvDecoder : public dsp::Processor<dsp::complex_t, uint8_t> {
|
||||
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
|
||||
public:
|
||||
/**
|
||||
* Create a convolutional encoder specifying an input stream.
|
||||
* @param in Input stream.
|
||||
*/
|
||||
ConvDecoder(dsp::stream<dsp::complex_t>* in = NULL);
|
||||
|
||||
// Destructor
|
||||
~ConvDecoder();
|
||||
|
||||
/**
|
||||
* Decode soft symbols.
|
||||
* @param in Input soft symbols.
|
||||
* @param out Output bytes.
|
||||
* @param count Number of input bytes.
|
||||
* @return Number of output bits.
|
||||
*/
|
||||
int decode(const dsp::complex_t* in, uint8_t* out, int count);
|
||||
|
||||
private:
|
||||
int run();
|
||||
|
||||
correct_convolutional* conv;
|
||||
uint8_t* soft = NULL;
|
||||
};
|
||||
}
|
37
decoder_modules/ryfi_decoder/src/ryfi/frame.cpp
Normal file
37
decoder_modules/ryfi_decoder/src/ryfi/frame.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include "frame.h"
|
||||
|
||||
namespace ryfi {
|
||||
int Frame::serialize(uint8_t* bytes) const {
|
||||
// Write the counter
|
||||
bytes[0] = (counter >> 8) & 0xFF;
|
||||
bytes[1] = counter & 0xFF;
|
||||
|
||||
// Write the first packet pointer
|
||||
bytes[2] = (firstPacket >> 8) & 0xFF;
|
||||
bytes[3] = firstPacket & 0xFF;
|
||||
|
||||
// Write the last packet pointer
|
||||
bytes[4] = (lastPacket >> 8) & 0xFF;
|
||||
bytes[5] = lastPacket & 0xFF;
|
||||
|
||||
// Write the data
|
||||
memcpy(&bytes[6], content, FRAME_DATA_SIZE);
|
||||
|
||||
// Return the length of a serialized frame
|
||||
return FRAME_SIZE;
|
||||
}
|
||||
|
||||
void Frame::deserialize(const uint8_t* bytes, Frame& frame) {
|
||||
// Read the counter
|
||||
frame.counter = (((uint16_t)bytes[0]) << 8) | ((uint16_t)bytes[1]);
|
||||
|
||||
// Read the first packet pointer
|
||||
frame.firstPacket = (((uint16_t)bytes[2]) << 8) | ((uint16_t)bytes[3]);
|
||||
|
||||
// Read the last packet pointer
|
||||
frame.lastPacket = (((uint16_t)bytes[4]) << 8) | ((uint16_t)bytes[5]);
|
||||
|
||||
// Read the data
|
||||
memcpy(frame.content, &bytes[6], FRAME_DATA_SIZE);
|
||||
}
|
||||
}
|
42
decoder_modules/ryfi_decoder/src/ryfi/frame.h
Normal file
42
decoder_modules/ryfi_decoder/src/ryfi/frame.h
Normal file
@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "rs_codec.h"
|
||||
|
||||
namespace ryfi {
|
||||
enum PacketOffset {
|
||||
PKT_OFFS_NONE = 0xFFFF
|
||||
};
|
||||
|
||||
struct Frame {
|
||||
/**
|
||||
* Serialize the frame to bytes.
|
||||
* @param bytes Buffer to write the serialized frame to.
|
||||
*/
|
||||
int serialize(uint8_t* bytes) const;
|
||||
|
||||
/**
|
||||
* Deserialize a frame from bytes.
|
||||
* @param bytes Buffer to deserialize the frame from.
|
||||
* @param frame Object that will contain the deserialize frame.
|
||||
*/
|
||||
static void deserialize(const uint8_t* bytes, Frame& frame);
|
||||
|
||||
// Size of a serialized frame
|
||||
static inline const int FRAME_SIZE = RS_BLOCK_DEC_SIZE*RS_BLOCK_COUNT;
|
||||
|
||||
// Size of the data area of the frame
|
||||
static inline const int FRAME_DATA_SIZE = FRAME_SIZE - 6;
|
||||
|
||||
// Steadily increasing counter.
|
||||
uint16_t counter = 0;
|
||||
|
||||
// Byte offset of the first packet in the frame.
|
||||
uint16_t firstPacket = 0;
|
||||
|
||||
// Byte offset of the last packet in the frame.
|
||||
uint16_t lastPacket = 0;
|
||||
|
||||
// Data area of the frame.
|
||||
uint8_t content[FRAME_DATA_SIZE];
|
||||
};
|
||||
}
|
137
decoder_modules/ryfi_decoder/src/ryfi/framing.cpp
Normal file
137
decoder_modules/ryfi_decoder/src/ryfi/framing.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
#include "framing.h"
|
||||
|
||||
namespace ryfi {
|
||||
dsp::complex_t QPSK_SYMBOLS[4] = {
|
||||
{ -0.070710678118f, -0.070710678118f },
|
||||
{ -0.070710678118f, 0.070710678118f },
|
||||
{ 0.070710678118f, -0.070710678118f },
|
||||
{ 0.070710678118f, 0.070710678118f },
|
||||
};
|
||||
|
||||
Framer::Framer(dsp::stream<uint8_t>* in) {
|
||||
// Generate the sync symbols
|
||||
int k = 0;
|
||||
for (int i = 62; i >= 0; i -= 2) {
|
||||
syncSyms[k++] = QPSK_SYMBOLS[(SYNC_WORD >> i) & 0b11];
|
||||
}
|
||||
|
||||
|
||||
// Initialize base class
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
int Framer::encode(const uint8_t* in, dsp::complex_t* out, int count) {
|
||||
// Copy sync symbols
|
||||
memcpy(out, syncSyms, SYNC_SYMS*sizeof(dsp::complex_t));
|
||||
|
||||
// Modulate the rest of the bits
|
||||
dsp::complex_t* dataOut = &out[SYNC_SYMS];
|
||||
int dataSyms = count / 2;
|
||||
for (int i = 0; i < dataSyms; i++) {
|
||||
uint8_t bits = (in[i >> 2] >> (6 - 2*(i & 0b11))) & 0b11;
|
||||
dataOut[i] = QPSK_SYMBOLS[bits];
|
||||
}
|
||||
|
||||
// Compute and return the total number of symbols
|
||||
return SYNC_SYMS + dataSyms;
|
||||
}
|
||||
|
||||
int Framer::run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count);
|
||||
|
||||
base_type::_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
Deframer::Deframer(dsp::stream<dsp::complex_t> *in) {
|
||||
// Compute sync word rotations
|
||||
// 0: 00 01 11 10
|
||||
// 90: 10 00 01 11
|
||||
// 180: 11 10 00 01
|
||||
// 270: 01 11 10 00
|
||||
|
||||
// For 0 and 180 it's the sync and its complement
|
||||
syncRots[ROT_0_DEG] = SYNC_WORD;
|
||||
syncRots[ROT_180_DEG] = ~SYNC_WORD;
|
||||
|
||||
// For 90 and 270 its the quadrature and its complement
|
||||
uint64_t quad;
|
||||
for (int i = 62; i >= 0; i -= 2) {
|
||||
// Get the symbol
|
||||
uint8_t sym = (SYNC_WORD >> i) & 0b11;
|
||||
|
||||
// Rotate it 90 degrees
|
||||
uint8_t rsym;
|
||||
switch (sym) {
|
||||
case 0b00: rsym = 0b10; break;
|
||||
case 0b01: rsym = 0b00; break;
|
||||
case 0b11: rsym = 0b01; break;
|
||||
case 0b10: rsym = 0b11; break;
|
||||
}
|
||||
|
||||
// Push it into the quadrature
|
||||
quad = (quad << 2) | rsym;
|
||||
}
|
||||
syncRots[ROT_90_DEG] = quad;
|
||||
syncRots[ROT_270_DEG] = ~quad;
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
int Deframer::run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
dsp::complex_t* in = base_type::_in->readBuf;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (recv) {
|
||||
// Copy the symbol to the output and rotate it approprieate
|
||||
base_type::out.writeBuf[outCount++] = in[i] * symRot;
|
||||
|
||||
// Check if we're done receiving the frame, send it out
|
||||
if (!(--recv)) {
|
||||
if (!base_type::out.swap(outCount)) {
|
||||
base_type::_in->flush();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Get the raw symbol
|
||||
dsp::complex_t fsym = in[i];
|
||||
|
||||
// Decode the symbol
|
||||
uint8_t sym = ((fsym.re > 0) ? 0b10 : 0b00) | ((fsym.im > 0) ? 0b01 : 0b00);
|
||||
|
||||
// Push it to the shift register
|
||||
shift = (shift << 2) | sym;
|
||||
|
||||
// Find the rotation starting with the last known one
|
||||
for (int i = 0; i < 4; i++) {
|
||||
// Get the test rotation
|
||||
int testRot = (knownRot+i) & 0b11;
|
||||
|
||||
// Check if the hamming distance is close enough
|
||||
int dist;
|
||||
if (distance(shift, syncRots[testRot]) < 6) {
|
||||
// Save the new rotation
|
||||
knownRot = testRot;
|
||||
|
||||
// Start reading in symbols for the frame
|
||||
symRot = symRots[knownRot];
|
||||
recv = 8168; // TODO: Don't hardcode!
|
||||
outCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base_type::_in->flush();
|
||||
return count;
|
||||
}
|
||||
}
|
87
decoder_modules/ryfi_decoder/src/ryfi/framing.h
Normal file
87
decoder_modules/ryfi_decoder/src/ryfi/framing.h
Normal file
@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include "dsp/processor.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace ryfi {
|
||||
// Synchronization word.
|
||||
inline const uint64_t SYNC_WORD = 0x341CC540819D8963;
|
||||
|
||||
// Number of synchronization bits.
|
||||
inline const int SYNC_BITS = 64;
|
||||
|
||||
// Number of synchronization symbols.
|
||||
inline const int SYNC_SYMS = SYNC_BITS / 2;
|
||||
|
||||
// Possible constellation rotations
|
||||
enum {
|
||||
ROT_0_DEG = 0,
|
||||
ROT_90_DEG = 1,
|
||||
ROT_180_DEG = 2,
|
||||
ROT_270_DEG = 3
|
||||
};
|
||||
|
||||
/**
|
||||
* RyFi Framer.
|
||||
*/
|
||||
class Framer : public dsp::Processor<uint8_t, dsp::complex_t> {
|
||||
using base_type = dsp::Processor<uint8_t, dsp::complex_t>;
|
||||
public:
|
||||
/**
|
||||
* Create a framer specifying an input stream.
|
||||
* @param in Input stream.
|
||||
*/
|
||||
Framer(dsp::stream<uint8_t>* in = NULL);
|
||||
|
||||
/**
|
||||
* Encode a frame to symbols adding a sync word.
|
||||
*/
|
||||
int encode(const uint8_t* in, dsp::complex_t* out, int count);
|
||||
|
||||
private:
|
||||
int run();
|
||||
|
||||
dsp::complex_t syncSyms[SYNC_SYMS];
|
||||
};
|
||||
|
||||
class Deframer : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
|
||||
using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
|
||||
public:
|
||||
/**
|
||||
* Create a deframer specifying an input stream.
|
||||
* @param in Input stream.
|
||||
*/
|
||||
Deframer(dsp::stream<dsp::complex_t> *in = NULL);
|
||||
|
||||
private:
|
||||
int run();
|
||||
|
||||
inline static constexpr int distance(uint64_t a, uint64_t b) {
|
||||
int dist = 0;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
dist += ((a & 1) != (b & 1));
|
||||
a >>= 1;
|
||||
b >>= 1;
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
|
||||
// Frame reading counters
|
||||
int recv = 0;
|
||||
int outCount = 0;
|
||||
|
||||
// Rotation handling
|
||||
int knownRot = 0;
|
||||
uint64_t syncRots[4];
|
||||
dsp::complex_t symRot;
|
||||
const dsp::complex_t symRots[4] = {
|
||||
{ 1.0f, 0.0f }, // 0 deg
|
||||
{ 0.0f, -1.0f }, // 90 deg
|
||||
{ -1.0f, 0.0f }, // 180 deg
|
||||
{ 0.0f, 1.0f }, // 270 deg
|
||||
};
|
||||
|
||||
// Shift register
|
||||
uint64_t shift;
|
||||
};
|
||||
}
|
126
decoder_modules/ryfi_decoder/src/ryfi/packet.cpp
Normal file
126
decoder_modules/ryfi_decoder/src/ryfi/packet.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
#include "packet.h"
|
||||
#include "string.h"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ryfi {
|
||||
Packet::Packet() {}
|
||||
|
||||
Packet::Packet(uint8_t* content, int size) {
|
||||
// Check that the size isn't too large
|
||||
if (size > MAX_CONTENT_SIZE) {
|
||||
throw std::runtime_error("Content size is too large to fit in a packet");
|
||||
}
|
||||
|
||||
|
||||
// Allocate the buffer
|
||||
allocate(size);
|
||||
|
||||
// Copy over the content
|
||||
memcpy(_content, content, size);
|
||||
}
|
||||
|
||||
Packet::Packet(const Packet& b) {
|
||||
// Reallocate the buffer
|
||||
allocate(b._size);
|
||||
|
||||
// Copy over the content
|
||||
memcpy(_content, b._content, b._size);
|
||||
}
|
||||
|
||||
Packet::Packet(Packet&& b) {
|
||||
// Move members
|
||||
_content = b._content;
|
||||
_size = b._size;
|
||||
|
||||
// Destroy old object
|
||||
b._content = NULL;
|
||||
b._size = 0;
|
||||
}
|
||||
|
||||
Packet::~Packet() {
|
||||
// Delete the content
|
||||
if (_content) { delete[] _content; }
|
||||
}
|
||||
|
||||
Packet& Packet::operator=(const Packet& b) {
|
||||
// Reallocate the buffer
|
||||
allocate(b._size);
|
||||
|
||||
// Copy over the content
|
||||
memcpy(_content, b._content, b._size);
|
||||
|
||||
// Return self
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::operator=(Packet&& b) {
|
||||
// Move members
|
||||
_content = b._content;
|
||||
_size = b._size;
|
||||
|
||||
// Destroy old object
|
||||
b._content = NULL;
|
||||
b._size = 0;
|
||||
|
||||
// Return self
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet::operator bool() const {
|
||||
return _size > 0;
|
||||
}
|
||||
|
||||
int Packet::size() const {
|
||||
// Return the size
|
||||
return _size;
|
||||
}
|
||||
|
||||
const uint8_t* Packet::data() const {
|
||||
// Return the size
|
||||
return _content;
|
||||
}
|
||||
|
||||
void Packet::setContent(uint8_t* content, int size) {
|
||||
// Check that the size isn't too large
|
||||
if (size > MAX_CONTENT_SIZE) {
|
||||
throw std::runtime_error("Content size is too large to fit in a packet");
|
||||
}
|
||||
|
||||
// Reallocate the buffer
|
||||
allocate(size);
|
||||
|
||||
// Copy over the content
|
||||
memcpy(_content, content, size);
|
||||
}
|
||||
|
||||
int Packet::serializedSize() const {
|
||||
// Two size bytes + Size of the content
|
||||
return _size + 2;
|
||||
}
|
||||
|
||||
int Packet::serialize(uint8_t* bytes) const {
|
||||
// Write the size in big-endian
|
||||
bytes[0] = (_size >> 8) & 0xFF;
|
||||
bytes[1] = _size & 0xFF;
|
||||
|
||||
// Copy the content of the packet
|
||||
memcpy(&bytes[2], _content, _size);
|
||||
|
||||
// Return the serialized size
|
||||
return serializedSize();
|
||||
}
|
||||
|
||||
void Packet::allocate(int newSize) {
|
||||
// If the size hasn't changed, do nothing
|
||||
if (newSize == _size) { return; }
|
||||
|
||||
// Free the old buffer
|
||||
if (_content) { delete[] _content; };
|
||||
|
||||
// Update the size
|
||||
_size = newSize;
|
||||
|
||||
// Allocate the buffer
|
||||
_content = new uint8_t[newSize];
|
||||
}
|
||||
}
|
89
decoder_modules/ryfi_decoder/src/ryfi/packet.h
Normal file
89
decoder_modules/ryfi_decoder/src/ryfi/packet.h
Normal file
@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace ryfi {
|
||||
/**
|
||||
* RyFi Protocol Packet.
|
||||
*/
|
||||
class Packet {
|
||||
public:
|
||||
// Default constructor
|
||||
Packet();
|
||||
|
||||
/**
|
||||
* Create a packet from its content.
|
||||
* @param content Content of the packet.
|
||||
* @param size Number of bytes of content.
|
||||
*/
|
||||
Packet(uint8_t* content, int size);
|
||||
|
||||
// Copy constructor
|
||||
Packet(const Packet& b);
|
||||
|
||||
// Move constructor
|
||||
Packet(Packet&& b);
|
||||
|
||||
// Destructor
|
||||
~Packet();
|
||||
|
||||
// Copy assignment operator
|
||||
Packet& operator=(const Packet& b);
|
||||
|
||||
// Move assignment operator
|
||||
Packet& operator=(Packet&& b);
|
||||
|
||||
// Cast to bool operator
|
||||
operator bool() const;
|
||||
|
||||
/**
|
||||
* Get the size of the content of the packet.
|
||||
* @return Size of the content of the packet.
|
||||
*/
|
||||
int size() const;
|
||||
|
||||
/**
|
||||
* Get the content of the packet. The pointer is only valid until reallocation or deletion.
|
||||
* @return Content of the packet.
|
||||
*/
|
||||
const uint8_t* data() const;
|
||||
|
||||
/**
|
||||
* Set the content of the packet.
|
||||
* @param content Content of the packet.
|
||||
* @param size Number of bytes of content.
|
||||
*/
|
||||
void setContent(uint8_t* content, int size);
|
||||
|
||||
/**
|
||||
* Get the size of the serialized packet.
|
||||
* @return Size of the serialized packet.
|
||||
*/
|
||||
int serializedSize() const;
|
||||
|
||||
/**
|
||||
* Serialize the packet to bytes.
|
||||
* @param bytes Buffer to which to write the serialized packet.
|
||||
* @return Size of the serialized packet.
|
||||
*/
|
||||
int serialize(uint8_t* bytes) const;
|
||||
|
||||
/**
|
||||
* Deserialize a packet from bytes.
|
||||
* TODO
|
||||
*/
|
||||
static bool deserialize(uint8_t* bytes, int size, Packet& pkt);
|
||||
|
||||
// Maximum size of the content of the packet.
|
||||
static inline const int MAX_CONTENT_SIZE = 0xFFFF;
|
||||
|
||||
// Maximum size of the serialized packet.
|
||||
static inline const int MAX_SERIALIZED_SIZE = MAX_CONTENT_SIZE + 2;
|
||||
|
||||
private:
|
||||
void allocate(int newSize);
|
||||
|
||||
uint8_t* _content = NULL;
|
||||
int _size = 0;
|
||||
};
|
||||
}
|
194
decoder_modules/ryfi_decoder/src/ryfi/receiver.cpp
Normal file
194
decoder_modules/ryfi_decoder/src/ryfi/receiver.cpp
Normal file
@ -0,0 +1,194 @@
|
||||
#include "receiver.h"
|
||||
|
||||
#include "utils/flog.h"
|
||||
|
||||
namespace ryfi {
|
||||
Receiver::Receiver() {}
|
||||
|
||||
Receiver::Receiver(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate) {
|
||||
init(in, baudrate, samplerate);
|
||||
}
|
||||
|
||||
Receiver::~Receiver() {
|
||||
// Stop everything
|
||||
stop();
|
||||
}
|
||||
|
||||
void Receiver::init(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate) {
|
||||
// Initialize the DSP
|
||||
demod.init(in, baudrate, samplerate, 31, 0.6, 0.1f, 0.005f, 1e-6, 0.01);
|
||||
doubler.init(&demod.out);
|
||||
softOut = &doubler.outA;
|
||||
deframer.setInput(&doubler.outB);
|
||||
conv.setInput(&deframer.out);
|
||||
rs.setInput(&conv.out);
|
||||
}
|
||||
|
||||
void Receiver::setInput(dsp::stream<dsp::complex_t>* in) {
|
||||
demod.setInput(in);
|
||||
}
|
||||
|
||||
void Receiver::start() {
|
||||
// Do nothing if already running
|
||||
if (running) { return; }
|
||||
|
||||
// Start the worker thread
|
||||
workerThread = std::thread(&Receiver::worker, this);
|
||||
|
||||
// Start the DSP
|
||||
demod.start();
|
||||
doubler.start();
|
||||
deframer.start();
|
||||
conv.start();
|
||||
rs.start();
|
||||
|
||||
// Update the running state
|
||||
running = true;
|
||||
}
|
||||
|
||||
void Receiver::stop() {
|
||||
// Do nothing if not running
|
||||
if (!running) { return; }
|
||||
|
||||
// Stop the worker thread
|
||||
rs.out.stopReader();
|
||||
if (workerThread.joinable()) { workerThread.join(); }
|
||||
rs.out.clearReadStop();
|
||||
|
||||
// Stop the DSP
|
||||
demod.stop();
|
||||
doubler.stop();
|
||||
deframer.stop();
|
||||
conv.stop();
|
||||
rs.stop();
|
||||
|
||||
// Update the running state
|
||||
running = false;
|
||||
}
|
||||
|
||||
void Receiver::worker() {
|
||||
Frame frame;
|
||||
uint16_t lastCounter = 0;
|
||||
uint8_t* pktBuffer = new uint8_t[Packet::MAX_CONTENT_SIZE];
|
||||
int pktExpected = 0;
|
||||
int pktRead = 0;
|
||||
int valid = 0;
|
||||
|
||||
while (true) {
|
||||
// Read a frame
|
||||
int count = rs.out.read();
|
||||
if (count <= 0) { break; }
|
||||
|
||||
// Deserialize the frame
|
||||
Frame::deserialize(rs.out.readBuf, frame);
|
||||
valid++;
|
||||
|
||||
// Flush the stream
|
||||
rs.out.flush();
|
||||
|
||||
//flog::info("Frame[{}]: FirstPacket={}, LastPacket={}", frame.counter, frame.firstPacket, frame.lastPacket);
|
||||
|
||||
// Compute the expected frame counter
|
||||
uint16_t expectedCounter = lastCounter + 1;
|
||||
lastCounter = frame.counter;
|
||||
|
||||
// If the frames aren't consecutive
|
||||
int frameRead = 0;
|
||||
if (frame.counter != expectedCounter) {
|
||||
flog::warn("Lost at least {} frames after {} valid frames", ((int)frame.counter - (int)expectedCounter + 0x10000) % 0x10000, valid);
|
||||
|
||||
// Cancel the partial packet if there was one
|
||||
pktExpected = 0;
|
||||
pktRead = 0;
|
||||
valid = 1;
|
||||
|
||||
// If this frame is not an idle frame or continuation frame
|
||||
if (frame.firstPacket != PKT_OFFS_NONE) {
|
||||
// If the offset of the first packet is not plausible
|
||||
if (frame.firstPacket > Frame::FRAME_DATA_SIZE-2) {
|
||||
flog::warn("Packet had non-plausible offset: {}", frameRead);
|
||||
|
||||
// Skip the frame
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip to the end of the packet
|
||||
frameRead = frame.firstPacket;
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no partial packet and the frame doesn't contain a packet start, skip it
|
||||
if (!pktExpected && frame.firstPacket == PKT_OFFS_NONE) { continue; }
|
||||
|
||||
// Extract packets from the frame
|
||||
bool firstPacket = true;
|
||||
bool lastPacket = false;
|
||||
while (frameRead < Frame::FRAME_DATA_SIZE) {
|
||||
// If there is a partial packet read as much as possible from it
|
||||
if (pktExpected) {
|
||||
// Compute how many bytes of the packet are available in the frame
|
||||
int readable = std::min<int>(pktExpected - pktRead, Frame::FRAME_DATA_SIZE - frameRead);
|
||||
//flog::debug("Reading {} bytes", readable);
|
||||
|
||||
// Write them to the packet
|
||||
memcpy(&pktBuffer[pktRead], &frame.content[frameRead], readable);
|
||||
pktRead += readable;
|
||||
frameRead += readable;
|
||||
|
||||
// If the packet is read entirely
|
||||
if (pktRead >= pktExpected) {
|
||||
// Create the packet object
|
||||
Packet pkt(pktBuffer, pktExpected);
|
||||
|
||||
// Send off the packet
|
||||
onPacket(pkt);
|
||||
|
||||
// Prepare for the next packet
|
||||
pktRead = 0;
|
||||
pktExpected = 0;
|
||||
|
||||
// If this was the last packet of the frame
|
||||
if (lastPacket || frame.firstPacket == PKT_OFFS_NONE) {
|
||||
// Skip the rest of the frame
|
||||
frameRead = Frame::FRAME_DATA_SIZE;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Go to next packet
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the packet offset is not plausible
|
||||
if (Frame::FRAME_DATA_SIZE - frameRead < 2) {
|
||||
flog::warn("Packet had non-plausible offset: {}", frameRead);
|
||||
|
||||
// Skip the rest of the frame and the packet
|
||||
frameRead = Frame::FRAME_DATA_SIZE;
|
||||
pktExpected = 0;
|
||||
pktRead = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is the first packet, use the frame info to skip possible left over data
|
||||
if (firstPacket) {
|
||||
frameRead = frame.firstPacket;
|
||||
firstPacket = false;
|
||||
}
|
||||
|
||||
// Check if this is the last packet
|
||||
lastPacket = (frameRead == frame.lastPacket);
|
||||
|
||||
// Parse the packet size
|
||||
pktExpected = ((uint16_t)frame.content[frameRead]) << 8;
|
||||
pktExpected |= (uint16_t)frame.content[frameRead+1];
|
||||
//flog::debug("Starting to read a {} byte packet at offset {}", pktExpected, frameRead);
|
||||
|
||||
// Skip to the packet content
|
||||
frameRead += 2;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] pktBuffer;
|
||||
}
|
||||
}
|
69
decoder_modules/ryfi_decoder/src/ryfi/receiver.h
Normal file
69
decoder_modules/ryfi_decoder/src/ryfi/receiver.h
Normal file
@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#include "utils/new_event.h"
|
||||
#include "dsp/demod/psk.h"
|
||||
#include "dsp/routing/doubler.h"
|
||||
#include "packet.h"
|
||||
#include "frame.h"
|
||||
#include "rs_codec.h"
|
||||
#include "conv_codec.h"
|
||||
#include "framing.h"
|
||||
#include <mutex>
|
||||
|
||||
namespace ryfi {
|
||||
class Receiver {
|
||||
public:
|
||||
Receiver();
|
||||
|
||||
/**
|
||||
* Create a transmitter.
|
||||
* @param in Baseband input.
|
||||
* @param baudrate Baudrate to use over the air.
|
||||
* @param samplerate Samplerate of the baseband.
|
||||
*/
|
||||
Receiver(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate);
|
||||
|
||||
/**
|
||||
* Create a transmitter.
|
||||
* @param in Baseband input.
|
||||
* @param baudrate Baudrate to use over the air.
|
||||
* @param samplerate Samplerate of the baseband.
|
||||
*/
|
||||
void init(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate);
|
||||
|
||||
/**
|
||||
* Set the input stream.
|
||||
* @param in Baseband input.
|
||||
*/
|
||||
void setInput(dsp::stream<dsp::complex_t>* in);
|
||||
|
||||
// Destructor
|
||||
~Receiver();
|
||||
|
||||
/**
|
||||
* Start the transmitter's DSP.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Stop the transmitter's DSP.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
dsp::stream<dsp::complex_t>* softOut;
|
||||
|
||||
NewEvent<Packet> onPacket;
|
||||
|
||||
private:
|
||||
void worker();
|
||||
|
||||
// DSP
|
||||
dsp::demod::PSK<4> demod;
|
||||
dsp::routing::Doubler<dsp::complex_t> doubler;
|
||||
Deframer deframer;
|
||||
ConvDecoder conv;
|
||||
RSDecoder rs;
|
||||
|
||||
bool running = false;
|
||||
std::thread workerThread;
|
||||
};
|
||||
}
|
169
decoder_modules/ryfi_decoder/src/ryfi/rs_codec.cpp
Normal file
169
decoder_modules/ryfi_decoder/src/ryfi/rs_codec.cpp
Normal file
@ -0,0 +1,169 @@
|
||||
#include "rs_codec.h"
|
||||
|
||||
namespace ryfi {
|
||||
RSEncoder::RSEncoder(dsp::stream<uint8_t>* in) {
|
||||
// Create the convolutional encoder instance
|
||||
rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 1, 1, 32);
|
||||
|
||||
// Init the base class
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
RSEncoder::~RSEncoder() {
|
||||
// Destroy the convolutional encoder instance
|
||||
correct_reed_solomon_destroy(rs);
|
||||
}
|
||||
|
||||
int RSEncoder::encode(const uint8_t* in, uint8_t* out, int count) {
|
||||
// Check the size
|
||||
assert(count == RS_BLOCK_COUNT*RS_BLOCK_DEC_SIZE);
|
||||
|
||||
// Go through each block
|
||||
uint8_t block[RS_BLOCK_ENC_SIZE];
|
||||
for (int i = 0; i < RS_BLOCK_COUNT; i++) {
|
||||
// Encode block
|
||||
correct_reed_solomon_encode(rs, &in[i*RS_BLOCK_DEC_SIZE], RS_BLOCK_DEC_SIZE, block);
|
||||
|
||||
// Interleave into the frame
|
||||
int k = 0;
|
||||
for (int j = i; j < RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT; j += RS_BLOCK_COUNT) {
|
||||
out[j] = block[k++];
|
||||
}
|
||||
}
|
||||
|
||||
// Scramble
|
||||
for (int i = 0; i < RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE; i++) {
|
||||
out[i] ^= RS_SCRAMBLER_SEQ[i];
|
||||
}
|
||||
|
||||
return RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE;
|
||||
}
|
||||
|
||||
int RSEncoder::run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count);
|
||||
|
||||
base_type::_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
RSDecoder::RSDecoder(dsp::stream<uint8_t>* in) {
|
||||
// Create the convolutional encoder instance
|
||||
rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 1, 1, 32);
|
||||
|
||||
// Init the base class
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
RSDecoder::~RSDecoder() {
|
||||
// Destroy the convolutional encoder instance
|
||||
correct_reed_solomon_destroy(rs);
|
||||
}
|
||||
|
||||
int RSDecoder::decode(uint8_t* in, uint8_t* out, int count) {
|
||||
// Check the size
|
||||
assert(count == RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE);
|
||||
|
||||
// Descramble (TODO: Don't do it in-place)
|
||||
for (int i = 0; i < RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE; i++) {
|
||||
in[i] ^= RS_SCRAMBLER_SEQ[i];
|
||||
}
|
||||
|
||||
// Go through each block
|
||||
uint8_t block[RS_BLOCK_ENC_SIZE];
|
||||
for (int i = 0; i < RS_BLOCK_COUNT; i++) {
|
||||
// Deinterleave out of the frame
|
||||
int k = 0;
|
||||
for (int j = i; j < count; j += RS_BLOCK_COUNT) {
|
||||
block[k++] = in[j];
|
||||
}
|
||||
|
||||
// Decode block and return if decoding fails
|
||||
int res = correct_reed_solomon_decode(rs, block, RS_BLOCK_ENC_SIZE, &out[i*RS_BLOCK_DEC_SIZE]);
|
||||
if (res < 0) { return 0; }
|
||||
}
|
||||
|
||||
return RS_BLOCK_COUNT*RS_BLOCK_DEC_SIZE;
|
||||
}
|
||||
|
||||
int RSDecoder::run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
count = decode(base_type::_in->readBuf, base_type::out.writeBuf, count);
|
||||
|
||||
base_type::_in->flush();
|
||||
if (count && !out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
const uint8_t RS_SCRAMBLER_SEQ[RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT] = {
|
||||
0x75, 0x05, 0x7C, 0xCE, 0xF1, 0xD0, 0x6C, 0xF6, 0xFA, 0x65, 0xF6, 0xFC, 0xE0, 0x0A, 0x82, 0x17,
|
||||
0x6C, 0xBE, 0x76, 0xA0, 0xD6, 0x46, 0x12, 0x2E, 0xDE, 0xB5, 0xF7, 0xAD, 0xCB, 0x51, 0x63, 0x47,
|
||||
0x27, 0x30, 0x7E, 0x43, 0xD1, 0xA1, 0xCB, 0x10, 0x08, 0x49, 0xDF, 0x86, 0xD4, 0xC4, 0xD7, 0x3C,
|
||||
0x6D, 0x03, 0x07, 0x37, 0x5B, 0xB3, 0xCD, 0x79, 0x6F, 0x1E, 0xBA, 0xC5, 0x6E, 0xC3, 0x8C, 0x7A,
|
||||
0x25, 0x99, 0x61, 0x54, 0x5A, 0x96, 0x57, 0x9B, 0xE0, 0x60, 0x5B, 0x09, 0x6D, 0x8B, 0x2D, 0x9D,
|
||||
0x15, 0x9D, 0x0E, 0xBF, 0x57, 0xFB, 0x9C, 0x49, 0x82, 0x2C, 0x48, 0x59, 0x92, 0x47, 0x79, 0x17,
|
||||
0x16, 0x74, 0xEA, 0xEA, 0xBB, 0xC5, 0x72, 0x32, 0x17, 0xD1, 0xB3, 0xDE, 0xEB, 0x15, 0xC7, 0x55,
|
||||
0x8A, 0xF2, 0x88, 0xC2, 0x33, 0xA6, 0x17, 0x8B, 0xD4, 0x77, 0x22, 0x00, 0x63, 0x47, 0x45, 0x5F,
|
||||
0x36, 0x35, 0x58, 0x8B, 0x88, 0xEC, 0xCA, 0xC4, 0x60, 0x53, 0x9E, 0xBD, 0xB2, 0xF5, 0x51, 0x46,
|
||||
0x34, 0x9A, 0x07, 0x25, 0x3F, 0xF5, 0x65, 0x63, 0x77, 0x3C, 0x5A, 0xFA, 0x4E, 0x0C, 0xF7, 0x1B,
|
||||
0x82, 0xAB, 0x73, 0x06, 0x7F, 0xB7, 0xC6, 0x6B, 0xBF, 0xB1, 0x46, 0xF3, 0x01, 0x91, 0xB1, 0xFF,
|
||||
0x5C, 0x6F, 0xF9, 0x43, 0x0E, 0x6A, 0x70, 0x89, 0x0B, 0xEA, 0x8C, 0xD4, 0x1B, 0x51, 0x01, 0x31,
|
||||
0x71, 0x2E, 0xDF, 0x24, 0xC1, 0xD5, 0xDB, 0x0E, 0xF5, 0xEB, 0x78, 0x79, 0x39, 0x5B, 0xAD, 0xC3,
|
||||
0xA9, 0xA6, 0x60, 0x30, 0xA2, 0x9A, 0x7B, 0xA0, 0xF4, 0xAA, 0xC5, 0x57, 0xB3, 0x16, 0xF9, 0xB5,
|
||||
0x79, 0x20, 0xC1, 0x88, 0x9A, 0x00, 0x43, 0xB2, 0xC6, 0x84, 0x8D, 0x03, 0xF2, 0xD8, 0x90, 0x7A,
|
||||
0x21, 0x37, 0x7E, 0xF7, 0x75, 0xE5, 0xFB, 0xC9, 0xDC, 0xAB, 0x4B, 0xBC, 0x35, 0x38, 0xB9, 0x3A,
|
||||
0x53, 0x89, 0x7E, 0xD5, 0x94, 0x12, 0x2D, 0x9B, 0x91, 0x90, 0x1D, 0x4D, 0x0E, 0xE0, 0x93, 0xF3,
|
||||
0xC1, 0xA1, 0x9B, 0x73, 0x27, 0x22, 0x41, 0x27, 0xEE, 0x2A, 0xD7, 0x45, 0xBC, 0x8F, 0x9B, 0xA2,
|
||||
0x36, 0x11, 0x16, 0x37, 0x1A, 0xF1, 0x2E, 0x71, 0xCF, 0x86, 0x89, 0x83, 0x5A, 0xF1, 0x24, 0x6C,
|
||||
0x56, 0x71, 0x53, 0xE4, 0xD2, 0xCB, 0xCA, 0x86, 0x1E, 0xA0, 0xD5, 0x83, 0x3B, 0xEF, 0x09, 0x09,
|
||||
0xC2, 0x07, 0x53, 0x86, 0xE6, 0x8A, 0xC6, 0x70, 0xFB, 0x91, 0x43, 0xCB, 0x91, 0x6E, 0xA9, 0xBC,
|
||||
0x31, 0x42, 0x61, 0x0C, 0x88, 0xB8, 0x2C, 0xED, 0xD8, 0xE6, 0xA3, 0xEC, 0xAC, 0xB9, 0x45, 0x5E,
|
||||
0x2C, 0x73, 0x3F, 0x2E, 0x06, 0xE0, 0xBF, 0x73, 0xDD, 0x2E, 0x45, 0x50, 0x6C, 0x53, 0x55, 0xF0,
|
||||
0x7F, 0x6E, 0x61, 0xFA, 0xA0, 0x7A, 0x1C, 0xF0, 0xBD, 0xAC, 0x48, 0x61, 0x03, 0x6B, 0xED, 0x54,
|
||||
0x2A, 0x27, 0x94, 0xF6, 0xF9, 0x6A, 0x04, 0x08, 0x0B, 0x3C, 0xC3, 0x30, 0x66, 0x01, 0xFB, 0xDC,
|
||||
0xC9, 0x65, 0x03, 0x83, 0x7D, 0x0A, 0xDF, 0xA5, 0x04, 0x14, 0xE4, 0xF2, 0x4C, 0x01, 0xDF, 0x04,
|
||||
0xD2, 0x80, 0xB9, 0x9B, 0xD9, 0x5E, 0xF8, 0x2A, 0x93, 0x8D, 0x8C, 0x09, 0x9B, 0x38, 0xEC, 0x3B,
|
||||
0xC4, 0x29, 0x90, 0x7C, 0x65, 0x3A, 0xF2, 0x4B, 0x69, 0xD3, 0x63, 0x9B, 0x40, 0x95, 0xC3, 0xFB,
|
||||
0x67, 0x54, 0x40, 0x9B, 0x26, 0x9F, 0x52, 0xFE, 0xD8, 0xD0, 0x24, 0x9C, 0x5C, 0xD4, 0xEF, 0xDE,
|
||||
0x28, 0x66, 0x75, 0x04, 0xCB, 0xA4, 0xC0, 0xB9, 0x4B, 0xC9, 0x20, 0x4B, 0x56, 0xC7, 0x86, 0xC5,
|
||||
0x39, 0x45, 0x18, 0xA7, 0x48, 0x14, 0x1A, 0x51, 0xCA, 0xD0, 0xC0, 0x15, 0xDD, 0xC1, 0x28, 0x4A,
|
||||
0x7A, 0xD2, 0x10, 0xEA, 0x83, 0xD3, 0x3A, 0xEF, 0x48, 0x29, 0x41, 0xA4, 0xD4, 0x57, 0xA6, 0x1D,
|
||||
0x76, 0x24, 0x93, 0x58, 0x7E, 0xB7, 0xDD, 0x0B, 0xF2, 0xCE, 0x71, 0x55, 0xF5, 0xAB, 0x8C, 0xC8,
|
||||
0x70, 0x59, 0x73, 0x69, 0x9D, 0x29, 0x5E, 0x59, 0xF4, 0xB2, 0xC4, 0x97, 0x75, 0xF0, 0x65, 0x1B,
|
||||
0x66, 0x5F, 0xA4, 0x33, 0x5C, 0xC7, 0xBF, 0x45, 0xE6, 0x20, 0xC0, 0xBD, 0xAD, 0xAE, 0x9F, 0x97,
|
||||
0x05, 0xD8, 0x04, 0x2B, 0x0A, 0x46, 0xE8, 0xB8, 0xCB, 0x00, 0xE2, 0x7C, 0x70, 0x1B, 0x49, 0xDE,
|
||||
0x81, 0xEB, 0x24, 0xAC, 0x1B, 0x3E, 0x09, 0xFB, 0xAC, 0xB7, 0xF2, 0xD1, 0xB2, 0x78, 0xF3, 0xAC,
|
||||
0xC7, 0x6A, 0xA2, 0x07, 0x4C, 0xED, 0x61, 0xAD, 0x04, 0x7F, 0x45, 0x83, 0x59, 0x31, 0x27, 0xF0,
|
||||
0x16, 0x6B, 0x0C, 0xAA, 0xD4, 0xD1, 0xCB, 0x1C, 0x51, 0x41, 0x0D, 0x2F, 0x8F, 0xF9, 0xF9, 0x7F,
|
||||
0x22, 0x89, 0x46, 0xF4, 0xB8, 0x93, 0x98, 0x9E, 0x3E, 0x23, 0xF1, 0x6E, 0x64, 0x08, 0xB6, 0xC9,
|
||||
0x6E, 0x53, 0x53, 0xED, 0xAD, 0x21, 0xCD, 0x1A, 0xF0, 0x45, 0xFC, 0x14, 0x00, 0xEA, 0xF7, 0x42,
|
||||
0xEE, 0xDA, 0x58, 0x0D, 0x85, 0xBC, 0x74, 0xFB, 0x73, 0x78, 0xB5, 0x5E, 0x5E, 0x6F, 0x6F, 0x7E,
|
||||
0x39, 0xC2, 0x05, 0x50, 0xDB, 0x3D, 0xB8, 0xF3, 0x8F, 0x80, 0xEC, 0x46, 0x29, 0x39, 0x89, 0xF3,
|
||||
0x55, 0x9C, 0x6A, 0x5F, 0x7C, 0xD9, 0x7C, 0x13, 0xE4, 0x56, 0x5E, 0xE9, 0x60, 0x19, 0xE2, 0x7D,
|
||||
0xC4, 0x41, 0x92, 0x8D, 0xDA, 0x21, 0x58, 0x20, 0xE9, 0xA8, 0x4C, 0x16, 0x34, 0x99, 0xAC, 0xB7,
|
||||
0x30, 0xBD, 0x39, 0x19, 0xAC, 0x9B, 0x4B, 0x27, 0xFA, 0x32, 0xC1, 0x48, 0xA1, 0x80, 0x34, 0x36,
|
||||
0x1E, 0xFB, 0x92, 0x43, 0x35, 0x72, 0x2D, 0xEF, 0xD2, 0xF2, 0xFC, 0xC2, 0x85, 0xAB, 0x59, 0x40,
|
||||
0x8D, 0x9D, 0x1A, 0x1F, 0xE2, 0x92, 0x87, 0xA2, 0xF9, 0x2C, 0x78, 0xE4, 0xC3, 0x26, 0x56, 0x07,
|
||||
0xB3, 0x78, 0xAF, 0x79, 0x3D, 0x88, 0xF4, 0xAD, 0x66, 0x7C, 0x07, 0x58, 0x98, 0x82, 0x1A, 0x26,
|
||||
0xF7, 0xFD, 0xCE, 0xFF, 0x75, 0xED, 0xAB, 0xBD, 0xAE, 0x6D, 0x5C, 0x28, 0x91, 0xF3, 0xB7, 0x5C,
|
||||
0x27, 0x05, 0xEC, 0x3B, 0xE3, 0xDD, 0x93, 0x24, 0x7F, 0xAD, 0x14, 0xAA, 0x49, 0x61, 0x8F, 0x96,
|
||||
0x1F, 0xAA, 0xB2, 0xEE, 0xA8, 0x24, 0x41, 0x7C, 0xDC, 0xF1, 0x28, 0x26, 0xE6, 0x7F, 0x98, 0x20,
|
||||
0x50, 0x5F, 0x90, 0x21, 0x8A, 0x09, 0x26, 0x59, 0xD0, 0x07, 0x2F, 0xE1, 0x35, 0x4D, 0x0B, 0x20,
|
||||
0xB2, 0xD5, 0xDD, 0xB5, 0xAC, 0x1B, 0xFE, 0xD9, 0xE3, 0x35, 0xF1, 0xB8, 0x3F, 0x3D, 0xFC, 0x0B,
|
||||
0x5A, 0x57, 0xA9, 0x92, 0x2B, 0xC8, 0x3E, 0xC2, 0xAA, 0xEF, 0xB9, 0x98, 0x2C, 0xA8, 0xAB, 0xF6,
|
||||
0xA1, 0xBF, 0xBC, 0x8D, 0x97, 0xA2, 0x74, 0xD9, 0xE5, 0x99, 0x85, 0x81, 0x15, 0xB0, 0xE7, 0x8B,
|
||||
0x48, 0x86, 0xF4, 0x94, 0x9C, 0x62, 0x82, 0xD1, 0x2C, 0x24, 0x4B, 0xAC, 0x7A, 0xB8, 0x4E, 0x4A,
|
||||
0xD2, 0xF6, 0xAA, 0xED, 0xE0, 0x9C, 0x98, 0xD2, 0xDF, 0xC1, 0xBC, 0xBF, 0x55, 0x7D, 0x40, 0xB5,
|
||||
0xDE, 0xD4, 0x25, 0xBB, 0x81, 0xF4, 0x07, 0x1D, 0xE7, 0x3C, 0xB4, 0x62, 0xC9, 0x55, 0x0A, 0x3A,
|
||||
0xD5, 0xCE, 0x97, 0xED, 0x30, 0x76, 0x76, 0x51, 0xBC, 0x8C, 0xE4, 0x54, 0xBE, 0xB7, 0xB5, 0xCD,
|
||||
0xF8, 0x76, 0x37, 0x53, 0x2C, 0x9F, 0xE4, 0xC7, 0xEB, 0xF5, 0x8D, 0x23, 0x8A, 0xDA, 0xD1, 0xA9,
|
||||
0xD8, 0x4C, 0x53, 0xF3, 0x49, 0xA7, 0x1A, 0x5D, 0xE5, 0x03, 0x49, 0x52, 0xD3, 0xE2, 0x1F, 0xA5,
|
||||
0x35, 0x9C, 0xBB, 0x0B, 0xC7, 0x0D, 0xA4, 0x65, 0x54, 0x8B, 0x39, 0xF1, 0x3B, 0x67, 0x21, 0x71,
|
||||
0x10, 0xE7, 0x76, 0xC4, 0xA8, 0xC2, 0x9D, 0x93, 0xC6, 0x51, 0xBA, 0x23
|
||||
};
|
||||
}
|
82
decoder_modules/ryfi_decoder/src/ryfi/rs_codec.h
Normal file
82
decoder_modules/ryfi_decoder/src/ryfi/rs_codec.h
Normal file
@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "dsp/processor.h"
|
||||
|
||||
extern "C" {
|
||||
#include "correct.h"
|
||||
}
|
||||
|
||||
namespace ryfi {
|
||||
// Size of an encoded reed-solomon block.
|
||||
inline const int RS_BLOCK_ENC_SIZE = 255;
|
||||
|
||||
// Size of a decoded reed-solomon block.
|
||||
inline const int RS_BLOCK_DEC_SIZE = 223;
|
||||
|
||||
// Number of reed-solomon blocks.
|
||||
inline const int RS_BLOCK_COUNT = 4;
|
||||
|
||||
// Scrambler sequence
|
||||
extern const uint8_t RS_SCRAMBLER_SEQ[RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT];
|
||||
|
||||
/**
|
||||
* RyFi Reed-Solomon Encoder.
|
||||
*/
|
||||
class RSEncoder : public dsp::Processor<uint8_t, uint8_t> {
|
||||
using base_type = dsp::Processor<uint8_t, uint8_t>;
|
||||
public:
|
||||
/**
|
||||
* Create a reed-solomon encoder specifying an input stream.
|
||||
* @param in Input stream
|
||||
*/
|
||||
RSEncoder(dsp::stream<uint8_t>* in = NULL);
|
||||
|
||||
// Destructor
|
||||
~RSEncoder();
|
||||
|
||||
/**
|
||||
* Encode data.
|
||||
* @param in Input bytes.
|
||||
* @param out Output bytes.
|
||||
* @param count Number of input bytes.
|
||||
* @return Number of output bytes.
|
||||
*/
|
||||
int encode(const uint8_t* in, uint8_t* out, int count);
|
||||
|
||||
private:
|
||||
int run();
|
||||
|
||||
correct_reed_solomon* rs;
|
||||
};
|
||||
|
||||
/**
|
||||
* RyFi Reed-Solomon Decoder.
|
||||
*/
|
||||
class RSDecoder : public dsp::Processor<uint8_t, uint8_t> {
|
||||
using base_type = dsp::Processor<uint8_t, uint8_t>;
|
||||
public:
|
||||
/**
|
||||
* Create a reed-solomon decoder specifying an input stream.
|
||||
* @param in Input stream
|
||||
*/
|
||||
RSDecoder(dsp::stream<uint8_t>* in = NULL);
|
||||
|
||||
// Destructor
|
||||
~RSDecoder();
|
||||
|
||||
/**
|
||||
* Decode data.
|
||||
* @param in Input bytes.
|
||||
* @param out Output bytes.
|
||||
* @param count Number of input bytes.
|
||||
* @return Number of output bytes.
|
||||
*/
|
||||
int decode(uint8_t* in, uint8_t* out, int count);
|
||||
|
||||
private:
|
||||
int run();
|
||||
|
||||
correct_reed_solomon* rs;
|
||||
};
|
||||
}
|
177
decoder_modules/ryfi_decoder/src/ryfi/transmitter.cpp
Normal file
177
decoder_modules/ryfi_decoder/src/ryfi/transmitter.cpp
Normal file
@ -0,0 +1,177 @@
|
||||
#include "transmitter.h"
|
||||
|
||||
namespace ryfi {
|
||||
Transmitter::Transmitter(double baudrate, double samplerate) {
|
||||
// Initialize the DSP
|
||||
rs.setInput(&in);
|
||||
conv.setInput(&rs.out);
|
||||
framer.setInput(&conv.out);
|
||||
resamp.init(&framer.out, baudrate, samplerate);
|
||||
|
||||
rrcTaps = dsp::taps::rootRaisedCosine<float>(511, 0.6, baudrate, samplerate);
|
||||
// Normalize the taps
|
||||
float tot = 0.0f;
|
||||
for (int i = 0; i < rrcTaps.size; i++) {
|
||||
tot += rrcTaps.taps[i];
|
||||
}
|
||||
for (int i = 0; i < rrcTaps.size; i++) {
|
||||
rrcTaps.taps[i] /= tot;
|
||||
}
|
||||
|
||||
rrc.init(&resamp.out, rrcTaps);
|
||||
out = &rrc.out;
|
||||
}
|
||||
|
||||
Transmitter::~Transmitter() {
|
||||
// Stop everything
|
||||
stop();
|
||||
}
|
||||
|
||||
void Transmitter::start() {
|
||||
// Do nothing if already running
|
||||
if (running) { return; }
|
||||
|
||||
// Start the worker thread
|
||||
workerThread = std::thread(&Transmitter::worker, this);
|
||||
|
||||
// Start the DSP
|
||||
rs.start();
|
||||
conv.start();
|
||||
framer.start();
|
||||
resamp.start();
|
||||
rrc.start();
|
||||
|
||||
// Update the running state
|
||||
running = true;
|
||||
}
|
||||
|
||||
void Transmitter::stop() {
|
||||
// Do nothing if not running
|
||||
if (!running) { return; }
|
||||
|
||||
// Stop the worker thread
|
||||
in.stopWriter();
|
||||
if (workerThread.joinable()) { workerThread.join(); }
|
||||
in.clearWriteStop();
|
||||
|
||||
// Stop the DSP
|
||||
rs.stop();
|
||||
conv.stop();
|
||||
framer.stop();
|
||||
resamp.stop();
|
||||
rrc.stop();
|
||||
|
||||
// Update the running state
|
||||
running = false;
|
||||
}
|
||||
|
||||
bool Transmitter::send(const Packet& pkt) {
|
||||
// Acquire the packet queue
|
||||
std::lock_guard<std::mutex> lck(packetsMtx);
|
||||
|
||||
// If there are too many packets queued up, drop the packet
|
||||
if (packets.size() >= MAX_QUEUE_SIZE) { return false; }
|
||||
|
||||
// Push the packet onto the queue
|
||||
packets.push(pkt);
|
||||
}
|
||||
|
||||
bool Transmitter::txFrame(const Frame& frame) {
|
||||
// Serialize the frame
|
||||
int count = frame.serialize(in.writeBuf);
|
||||
|
||||
// Send it off
|
||||
return in.swap(count);
|
||||
}
|
||||
|
||||
Packet Transmitter::popPacket() {
|
||||
// Acquire the packet queue
|
||||
std::unique_lock<std::mutex> lck(packetsMtx);
|
||||
|
||||
// If no packets are available, return empty packet
|
||||
if (!packets.size()) { return Packet(); }
|
||||
|
||||
// Pop the front packet and return it
|
||||
Packet pkt = packets.front();
|
||||
packets.pop();
|
||||
return pkt;
|
||||
}
|
||||
|
||||
void Transmitter::worker() {
|
||||
Frame frame;
|
||||
Packet pkt;
|
||||
uint16_t counter = 0;
|
||||
int pktToWrite = 0;
|
||||
int pktWritten = 0;
|
||||
uint8_t* pktBuffer = new uint8_t[Packet::MAX_SERIALIZED_SIZE];
|
||||
|
||||
while (true) {
|
||||
// Initialize the frame
|
||||
frame.counter = counter++;
|
||||
frame.firstPacket = PKT_OFFS_NONE;
|
||||
frame.lastPacket = PKT_OFFS_NONE;
|
||||
int frameOffset = 0;
|
||||
|
||||
// Fill the frame with as much packet data as possible
|
||||
while (frameOffset < sizeof(Frame::content)) {
|
||||
// If there is no packet in the process of being sent
|
||||
if (!pktWritten) {
|
||||
// If there is not enough space for the size of the packet
|
||||
if ((sizeof(Frame::content) - frameOffset) < 2) {
|
||||
// Fill the rest of the frame with noise and send it
|
||||
for (int i = frameOffset; i < sizeof(Frame::content); i++) { frame.content[i] = rand(); }
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the next packet
|
||||
pkt = popPacket();
|
||||
|
||||
// If there was an available packet
|
||||
if (pkt) {
|
||||
// Serialize the packet
|
||||
pktToWrite = pkt.serializedSize();
|
||||
pkt.serialize(pktBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
// If none was available
|
||||
if (!pkt) {
|
||||
// Fill the rest of the frame with noise and send it
|
||||
for (int i = frameOffset; i < sizeof(Frame::content); i++) { frame.content[i] = rand(); }
|
||||
break;
|
||||
}
|
||||
|
||||
// If this is the beginning of the packet
|
||||
if (!pktWritten) {
|
||||
//flog::debug("Starting to write a {} byte packet at offset {}", pktToWrite-2, frameOffset);
|
||||
|
||||
// If this is the first packet of the frame, update its offset
|
||||
if (frame.firstPacket == PKT_OFFS_NONE) { frame.firstPacket = frameOffset; }
|
||||
|
||||
// Update the last packet pointer
|
||||
frame.lastPacket = frameOffset;
|
||||
}
|
||||
|
||||
// Compute the amount of data writeable to the frame
|
||||
int writeable = std::min<int>(pktToWrite - pktWritten, sizeof(Frame::content) - frameOffset);
|
||||
|
||||
// Copy the data to the frame
|
||||
memcpy(&frame.content[frameOffset], &pktBuffer[pktWritten], writeable);
|
||||
pktWritten += writeable;
|
||||
frameOffset += writeable;
|
||||
|
||||
// If the packet is done being sent
|
||||
if (pktWritten >= pktToWrite) {
|
||||
// Prepare for a new packet
|
||||
pktToWrite = 0;
|
||||
pktWritten = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Send the frame
|
||||
if (!txFrame(frame)) { break; }
|
||||
}
|
||||
|
||||
delete[] pktBuffer;
|
||||
}
|
||||
}
|
69
decoder_modules/ryfi_decoder/src/ryfi/transmitter.h
Normal file
69
decoder_modules/ryfi_decoder/src/ryfi/transmitter.h
Normal file
@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#include "dsp/multirate/rational_resampler.h"
|
||||
#include "dsp/taps/root_raised_cosine.h"
|
||||
#include "dsp/filter/fir.h"
|
||||
#include "packet.h"
|
||||
#include "frame.h"
|
||||
#include "rs_codec.h"
|
||||
#include "conv_codec.h"
|
||||
#include "framing.h"
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
|
||||
namespace ryfi {
|
||||
class Transmitter {
|
||||
public:
|
||||
/**
|
||||
* Create a transmitter.
|
||||
* @param baudrate Baudrate to use over the air.
|
||||
* @param samplerate Samplerate of the baseband.
|
||||
*/
|
||||
Transmitter(double baudrate, double samplerate);
|
||||
|
||||
// Destructor
|
||||
~Transmitter();
|
||||
|
||||
/**
|
||||
* Start the transmitter's DSP.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Stop the transmitter's DSP.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Send a packet.
|
||||
* @param pkg Packet to send.
|
||||
* @return True if the packet was send, false if it was dropped.
|
||||
*/
|
||||
bool send(const Packet& pkt);
|
||||
|
||||
// Baseband output
|
||||
dsp::stream<dsp::complex_t>* out;
|
||||
|
||||
static inline const int MAX_QUEUE_SIZE = 32;
|
||||
|
||||
private:
|
||||
bool txFrame(const Frame& frame);
|
||||
Packet popPacket();
|
||||
void worker();
|
||||
|
||||
// Packet queue
|
||||
std::mutex packetsMtx;
|
||||
std::queue<Packet> packets;
|
||||
|
||||
// DSP
|
||||
dsp::stream<uint8_t> in;
|
||||
RSEncoder rs;
|
||||
ConvEncoder conv;
|
||||
Framer framer;
|
||||
dsp::multirate::RationalResampler<dsp::complex_t> resamp;
|
||||
dsp::tap<float> rrcTaps;
|
||||
dsp::filter::FIR<dsp::complex_t, float> rrc;
|
||||
|
||||
bool running = false;
|
||||
std::thread workerThread;
|
||||
};
|
||||
}
|
@ -6,13 +6,13 @@ cd /root
|
||||
apt update
|
||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||
libcodec2-dev autoconf libtool xxd
|
||||
libcodec2-dev autoconf libtool xxd libspdlog-dev
|
||||
|
||||
# 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
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1
|
||||
cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@ -25,10 +25,30 @@ make install
|
||||
ldconfig
|
||||
cd ..
|
||||
|
||||
# Install librfnm
|
||||
git clone https://github.com/AlexandreRouma/librfnm
|
||||
cd librfnm
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
# Install libfobos
|
||||
git clone https://github.com/AlexandreRouma/libfobos
|
||||
cd libfobos
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
@ -6,13 +6,13 @@ cd /root
|
||||
apt update
|
||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||
libcodec2-dev autoconf libtool xxd
|
||||
libcodec2-dev autoconf libtool xxd libspdlog-dev
|
||||
|
||||
# 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
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1
|
||||
cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@ -25,10 +25,30 @@ make install
|
||||
ldconfig
|
||||
cd ..
|
||||
|
||||
# Install librfnm
|
||||
git clone https://github.com/AlexandreRouma/librfnm
|
||||
cd librfnm
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
# Install libfobos
|
||||
git clone https://github.com/AlexandreRouma/libfobos
|
||||
cd libfobos
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
@ -6,13 +6,13 @@ cd /root
|
||||
apt update
|
||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||
libcodec2-dev autoconf libtool xxd
|
||||
libcodec2-dev autoconf libtool xxd libspdlog-dev
|
||||
|
||||
# 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
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1
|
||||
cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@ -25,10 +25,30 @@ make install
|
||||
ldconfig
|
||||
cd ..
|
||||
|
||||
# Install librfnm
|
||||
git clone https://github.com/AlexandreRouma/librfnm
|
||||
cd librfnm
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
# Install libfobos
|
||||
git clone https://github.com/AlexandreRouma/libfobos
|
||||
cd libfobos
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
@ -6,13 +6,13 @@ cd /root
|
||||
apt update
|
||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||
libcodec2-dev autoconf libtool xxd
|
||||
libcodec2-dev autoconf libtool xxd libspdlog-dev
|
||||
|
||||
# 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
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1
|
||||
cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@ -25,10 +25,30 @@ make install
|
||||
ldconfig
|
||||
cd ..
|
||||
|
||||
# Install librfnm
|
||||
git clone https://github.com/AlexandreRouma/librfnm
|
||||
cd librfnm
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
# Install libfobos
|
||||
git clone https://github.com/AlexandreRouma/libfobos
|
||||
cd libfobos
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
@ -12,13 +12,13 @@ apt update
|
||||
# Install dependencies and tools
|
||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libairspy-dev \
|
||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||
libcodec2-dev libudev-dev autoconf libtool xxd
|
||||
libcodec2-dev libudev-dev autoconf libtool xxd libspdlog-dev
|
||||
|
||||
# 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
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1
|
||||
cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install a more recent libusb version
|
||||
@ -51,6 +51,26 @@ make install
|
||||
ldconfig
|
||||
cd ..
|
||||
|
||||
# Install librfnm
|
||||
git clone https://github.com/AlexandreRouma/librfnm
|
||||
cd librfnm
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
# Install libfobos
|
||||
git clone https://github.com/AlexandreRouma/libfobos
|
||||
cd libfobos
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
# Fix missing .pc file for codec2
|
||||
echo 'prefix=/usr/' >> /usr/share/pkgconfig/codec2.pc
|
||||
echo 'libdir=/usr/include/x86_64-linux-gnu/' >> /usr/share/pkgconfig/codec2.pc
|
||||
@ -66,7 +86,7 @@ echo 'Cflags: -I/usr/include/codec2' >> /usr/share/pkgconfig/codec2.pc
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
# Generate package
|
||||
|
@ -6,13 +6,13 @@ cd /root
|
||||
apt update
|
||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||
libcodec2-dev autoconf libtool xxd
|
||||
libcodec2-dev autoconf libtool xxd libspdlog-dev
|
||||
|
||||
# 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
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1
|
||||
cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@ -25,10 +25,30 @@ make install
|
||||
ldconfig
|
||||
cd ..
|
||||
|
||||
# Install librfnm
|
||||
git clone https://github.com/AlexandreRouma/librfnm
|
||||
cd librfnm
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
# Install libfobos
|
||||
git clone https://github.com/AlexandreRouma/libfobos
|
||||
cd libfobos
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
@ -6,13 +6,13 @@ cd /root
|
||||
apt update
|
||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||
libcodec2-dev autoconf libtool xxd
|
||||
libcodec2-dev autoconf libtool xxd libspdlog-dev
|
||||
|
||||
# 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
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1
|
||||
cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@ -25,10 +25,30 @@ make install
|
||||
ldconfig
|
||||
cd ..
|
||||
|
||||
# Install librfnm
|
||||
git clone https://github.com/AlexandreRouma/librfnm
|
||||
cd librfnm
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
# Install libfobos
|
||||
git clone https://github.com/AlexandreRouma/libfobos
|
||||
cd libfobos
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
@ -6,13 +6,13 @@ cd /root
|
||||
apt update
|
||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||
libcodec2-dev autoconf libtool xxd
|
||||
libcodec2-dev autoconf libtool xxd libspdlog-dev
|
||||
|
||||
# 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
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1
|
||||
cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@ -25,10 +25,30 @@ make install
|
||||
ldconfig
|
||||
cd ..
|
||||
|
||||
# Install librfnm
|
||||
git clone https://github.com/AlexandreRouma/librfnm
|
||||
cd librfnm
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
# Install libfobos
|
||||
git clone https://github.com/AlexandreRouma/libfobos
|
||||
cd libfobos
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
4
docker_builds/ubuntu_noble/Dockerfile
Normal file
4
docker_builds/ubuntu_noble/Dockerfile
Normal file
@ -0,0 +1,4 @@
|
||||
FROM ubuntu:noble
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
COPY do_build.sh /root
|
||||
RUN chmod +x /root/do_build.sh
|
55
docker_builds/ubuntu_noble/do_build.sh
Normal file
55
docker_builds/ubuntu_noble/do_build.sh
Normal file
@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd /root
|
||||
|
||||
# Install dependencies and tools
|
||||
apt update
|
||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||
libcodec2-dev autoconf libtool xxd libspdlog-dev
|
||||
|
||||
# Install SDRPlay libraries
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.1
|
||||
cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
git clone https://github.com/Microtelecom/libperseus-sdr
|
||||
cd libperseus-sdr
|
||||
autoreconf -i
|
||||
./configure
|
||||
make
|
||||
make install
|
||||
ldconfig
|
||||
cd ..
|
||||
|
||||
# Install librfnm
|
||||
git clone https://github.com/AlexandreRouma/librfnm
|
||||
cd librfnm
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
# Install libfobos
|
||||
git clone https://github.com/AlexandreRouma/libfobos
|
||||
cd libfobos
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev'
|
@ -8,7 +8,7 @@ mkdir sdrpp_debian_amd64/DEBIAN
|
||||
# Create package info
|
||||
echo Create package info
|
||||
echo Package: sdrpp >> sdrpp_debian_amd64/DEBIAN/control
|
||||
echo Version: 1.1.0$BUILD_NO >> sdrpp_debian_amd64/DEBIAN/control
|
||||
echo Version: 1.2.0$BUILD_NO >> sdrpp_debian_amd64/DEBIAN/control
|
||||
echo Maintainer: Ryzerth >> sdrpp_debian_amd64/DEBIAN/control
|
||||
echo Architecture: all >> sdrpp_debian_amd64/DEBIAN/control
|
||||
echo Description: Bloat-free SDR receiver software >> sdrpp_debian_amd64/DEBIAN/control
|
||||
|
@ -22,7 +22,7 @@ cp -R root/res/* $BUNDLE/Contents/Resources/
|
||||
bundle_create_icns root/res/icons/sdrpp.macos.png $BUNDLE/Contents/Resources/sdrpp
|
||||
|
||||
# Create the property list
|
||||
bundle_create_plist sdrpp SDR++ org.sdrpp.sdrpp 1.1.0 sdrp sdrpp sdrpp $BUNDLE/Contents/Info.plist
|
||||
bundle_create_plist sdrpp SDR++ org.sdrpp.sdrpp 1.2.0 sdrp sdrpp sdrpp $BUNDLE/Contents/Info.plist
|
||||
|
||||
# ========================= Install binaries =========================
|
||||
|
||||
@ -35,11 +35,13 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/airspyhf_source/airspyhf_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/bladerf_source/bladerf_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/file_source/file_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/file_source/fobossdr_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hackrf_source/hackrf_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hermes_source/hermes_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/limesdr_source/limesdr_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/perseus_source/perseus_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/plutosdr_source/plutosdr_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rfnm_source/rfnm_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rfspace_source/rfspace_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_sdr_source/rtl_sdr_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_tcp_source/rtl_tcp_source.dylib
|
||||
|
@ -24,6 +24,9 @@ cp 'C:/Program Files/PothosSDR/bin/bladeRF.dll' sdrpp_windows_x64/
|
||||
|
||||
cp $build_dir/source_modules/file_source/Release/file_source.dll sdrpp_windows_x64/modules/
|
||||
|
||||
cp $build_dir/source_modules/fobossdr_source/Release/fobossdr_source.dll sdrpp_windows_x64/modules/
|
||||
cp 'C:/Program Files/RigExpert/Fobos/bin/fobos.dll' sdrpp_windows_x64/
|
||||
|
||||
cp $build_dir/source_modules/hackrf_source/Release/hackrf_source.dll sdrpp_windows_x64/modules/
|
||||
cp 'C:/Program Files/PothosSDR/bin/hackrf.dll' sdrpp_windows_x64/
|
||||
|
||||
@ -39,6 +42,11 @@ cp $build_dir/source_modules/plutosdr_source/Release/plutosdr_source.dll sdrpp_w
|
||||
cp 'C:/Program Files/PothosSDR/bin/libiio.dll' sdrpp_windows_x64/
|
||||
cp 'C:/Program Files/PothosSDR/bin/libad9361.dll' sdrpp_windows_x64/
|
||||
|
||||
cp $build_dir/source_modules/rfnm_source/Release/rfnm_source.dll sdrpp_windows_x64/modules/
|
||||
cp 'C:/Program Files/RFNM/bin/rfnm.dll' sdrpp_windows_x64/
|
||||
cp 'C:/Program Files/RFNM/bin/spdlog.dll' sdrpp_windows_x64/
|
||||
cp 'C:/Program Files/RFNM/bin/fmt.dll' sdrpp_windows_x64/
|
||||
|
||||
cp $build_dir/source_modules/rfspace_source/Release/rfspace_source.dll sdrpp_windows_x64/modules/
|
||||
|
||||
cp $build_dir/source_modules/rtl_sdr_source/Release/rtl_sdr_source.dll sdrpp_windows_x64/modules/
|
||||
|
@ -249,7 +249,6 @@ private:
|
||||
}
|
||||
ImGui::Columns(1, CONCAT("EndRecorderModeColumns##_", _this->name), false);
|
||||
ImGui::EndGroup();
|
||||
if (_this->recording) { style::endDisabled(); }
|
||||
|
||||
// Recording path
|
||||
if (_this->folderSelect.render("##_recorder_fold_" + _this->name)) {
|
||||
@ -284,8 +283,11 @@ private:
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
if (_this->recording) { style::endDisabled(); }
|
||||
|
||||
// Show additional audio options
|
||||
if (_this->recMode == RECORDER_MODE_AUDIO) {
|
||||
if (_this->recording) { style::beginDisabled(); }
|
||||
ImGui::LeftLabel("Stream");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(CONCAT("##_recorder_stream_", _this->name), &_this->streamId, _this->audioStreams.txt)) {
|
||||
@ -294,6 +296,7 @@ private:
|
||||
config.conf[_this->name]["audioStream"] = _this->audioStreams.key(_this->streamId);
|
||||
config.release(true);
|
||||
}
|
||||
if (_this->recording) { style::endDisabled(); }
|
||||
|
||||
_this->updateAudioMeter(_this->audioLvl);
|
||||
ImGui::FillWidth();
|
||||
@ -476,9 +479,9 @@ private:
|
||||
sprintf(monStr, "%02d", ltm->tm_mon + 1);
|
||||
sprintf(yearStr, "%02d", ltm->tm_year + 1900);
|
||||
if (core::modComManager.getModuleName(name) == "radio") {
|
||||
int mode;
|
||||
int mode = -1;
|
||||
core::modComManager.callInterface(name, RADIO_IFACE_CMD_GET_MODE, NULL, &mode);
|
||||
modeStr = radioModeToString[mode];
|
||||
if (mode >= 0) { modeStr = radioModeToString[mode]; };
|
||||
}
|
||||
|
||||
// Replace in template
|
||||
|
15
readme.md
15
readme.md
@ -41,14 +41,13 @@ To create a desktop shortcut, rightclick the exe and select `Send to -> Desktop
|
||||
|
||||
Download the latest release from [the Releases page](https://github.com/AlexandreRouma/SDRPlusPlus/releases) and extract to the directory of your choice.
|
||||
|
||||
Then, run:
|
||||
Then, use apt to install it:
|
||||
|
||||
```sh
|
||||
sudo apt install libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev
|
||||
sudo dpkg -i sdrpp_debian_amd64.deb
|
||||
sudo apt install path/to/the/sdrpp_debian_amd64.deb
|
||||
```
|
||||
|
||||
If `libvolk2-dev` is not available, use `libvolk1-dev`.
|
||||
**IMPORTANT: You must install the drivers for your SDR. Follow instructions from your manufacturer as to how to do this on your particular distro.**
|
||||
|
||||
### Arch-based
|
||||
|
||||
@ -325,12 +324,15 @@ Modules in beta are still included in releases for the most part but not enabled
|
||||
| audio_source | Working | rtaudio | OPT_BUILD_AUDIO_SOURCE | ✅ | ✅ | ✅ |
|
||||
| bladerf_source | Working | libbladeRF | OPT_BUILD_BLADERF_SOURCE | ⛔ | ✅ (not Debian Buster) | ✅ |
|
||||
| file_source | Working | - | OPT_BUILD_FILE_SOURCE | ✅ | ✅ | ✅ |
|
||||
| fobossdr_source | Beta | libfobos | OPT_BUILD_FOBOSSDR_SOURCE | ✅ | ✅ | ✅ |
|
||||
| hackrf_source | Working | libhackrf | OPT_BUILD_HACKRF_SOURCE | ✅ | ✅ | ✅ |
|
||||
| harogic_source | Beta | htra_api | OPT_BUILD_HAROGIC_SOURCE | ⛔ | ⛔ | ✅ |
|
||||
| hermes_source | Beta | - | OPT_BUILD_HERMES_SOURCE | ✅ | ✅ | ✅ |
|
||||
| limesdr_source | Working | liblimesuite | OPT_BUILD_LIMESDR_SOURCE | ⛔ | ✅ | ✅ |
|
||||
| network_source | Unfinished | - | OPT_BUILD_NETWORK_SOURCE | ✅ | ✅ | ⛔ |
|
||||
| perseus_source | Beta | libperseus-sdr | OPT_BUILD_PERSEUS_SOURCE | ⛔ | ✅ | ✅ |
|
||||
| plutosdr_source | Working | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE | ✅ | ✅ | ✅ |
|
||||
| rfnm_source | Beta | librfnm | OPT_BUILD_RFNM_SOURCE | ⛔ | ✅ | ✅ |
|
||||
| rfspace_source | Working | - | OPT_BUILD_RFSPACE_SOURCE | ✅ | ✅ | ✅ |
|
||||
| rtl_sdr_source | Working | librtlsdr | OPT_BUILD_RTL_SDR_SOURCE | ✅ | ✅ | ✅ |
|
||||
| rtl_tcp_source | Working | - | OPT_BUILD_RTL_TCP_SOURCE | ✅ | ✅ | ✅ |
|
||||
@ -338,9 +340,9 @@ Modules in beta are still included in releases for the most part but not enabled
|
||||
| sdrpp_server_source | Working | - | OPT_BUILD_SDRPP_SERVER_SOURCE | ✅ | ✅ | ✅ |
|
||||
| soapy_source | Deprecated | soapysdr | OPT_BUILD_SOAPY_SOURCE | ⛔ | ⛔ | ⛔ |
|
||||
| spectran_source | Unfinished | RTSA Suite | OPT_BUILD_SPECTRAN_SOURCE | ⛔ | ⛔ | ⛔ |
|
||||
| spectran_http_source | Beta | - | OPT_BUILD_SPECTRAN_HTTP_SOURCE | ✅ | ✅ | ⛔ |
|
||||
| spectran_http_source | Beta | - | OPT_BUILD_SPECTRAN_HTTP_SOURCE | ✅ | ✅ | ✅ |
|
||||
| spyserver_source | Working | - | OPT_BUILD_SPYSERVER_SOURCE | ✅ | ✅ | ✅ |
|
||||
| usrp_source | Beta | libuhd | OPT_BUILD_USRP_SOURCE | ⛔ | ⛔ | ⛔ |
|
||||
| usrp_source | Beta | libuhd | OPT_BUILD_USRP_SOURCE | ⛔ | ⛔ | ✅ |
|
||||
|
||||
## Sinks
|
||||
|
||||
@ -357,6 +359,7 @@ Modules in beta are still included in releases for the most part but not enabled
|
||||
| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default |
|
||||
|---------------------|------------|--------------|-------------------------------|:---------------:|:----------------:|:---------------------------:|
|
||||
| atv_decoder | Unfinished | - | OPT_BUILD_ATV_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
| dab_decoder | Unfinished | - | OPT_BUILD_DAB_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
| falcon9_decoder | Unfinished | ffplay | OPT_BUILD_FALCON9_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
| kgsstv_decoder | Unfinished | - | OPT_BUILD_KGSSTV_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
| m17_decoder | Working | - | OPT_BUILD_M17_DECODER | ⛔ | ✅ | ⛔ |
|
||||
|
645
root/res/bandplans/brazil.json
Normal file
645
root/res/bandplans/brazil.json
Normal file
@ -0,0 +1,645 @@
|
||||
{
|
||||
"name": "Brazilian Ham Bands",
|
||||
"country_name": "Brazil",
|
||||
"country_code": "BR",
|
||||
"author_name": "Rafael Beraldo",
|
||||
"author_url": "https://github.com/rberaldo/",
|
||||
"bands": [
|
||||
{
|
||||
"start": 135700,
|
||||
"end": 137800,
|
||||
"type": "amateur",
|
||||
"name": "2200m Ham Band CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 472000,
|
||||
"end": 479000,
|
||||
"type": "amateur",
|
||||
"name": "635m Ham Band CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 1800000,
|
||||
"end": 1810000,
|
||||
"type": "amateur",
|
||||
"name": "|160m Ham Band CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 1810000,
|
||||
"end": 1839000,
|
||||
"type": "amateur1",
|
||||
"name": "CW"
|
||||
},
|
||||
{
|
||||
"start": 1839000,
|
||||
"end": 1840000,
|
||||
"type": "amateur",
|
||||
"name": "CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 1840000,
|
||||
"end": 1843000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSB, Digital"
|
||||
},
|
||||
{
|
||||
"start": 1843000,
|
||||
"end": 1850000,
|
||||
"type": "amateur",
|
||||
"name": "CW, SSB"
|
||||
},
|
||||
{
|
||||
"start": 1850000,
|
||||
"end": 2000000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSB, AM, DV, Digital 160 Ham Band|"
|
||||
},
|
||||
{
|
||||
"start": 3500000,
|
||||
"end": 3570000,
|
||||
"type": "amateur",
|
||||
"name": "|80m Ham Band CW"
|
||||
},
|
||||
{
|
||||
"start": 3570000,
|
||||
"end": 3590000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 3590000,
|
||||
"end": 3600000,
|
||||
"type": "amateur",
|
||||
"name": "CW, SSD, AM, Digital"
|
||||
},
|
||||
{
|
||||
"start": 3600000,
|
||||
"end": 3775000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSD, AM, DV, Digital"
|
||||
},
|
||||
{
|
||||
"start": 3775000,
|
||||
"end": 3875000,
|
||||
"type": "amateur",
|
||||
"name": "CW, SSD, DV, Digital"
|
||||
},
|
||||
{
|
||||
"start": 3775000,
|
||||
"end": 3875000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSD, DV, Digital"
|
||||
},
|
||||
{
|
||||
"start": 3875000,
|
||||
"end": 4000000,
|
||||
"type": "amateur",
|
||||
"name": "CW, SSD, AM, DV, Digital, 80m Ham Band|"
|
||||
},
|
||||
{
|
||||
"start": 5351500,
|
||||
"end": 5354000,
|
||||
"type": "amateur",
|
||||
"name": "|60m Ham Band CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 5354000,
|
||||
"end": 5366000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSB, DV, Digital"
|
||||
},
|
||||
{
|
||||
"start": 5366000,
|
||||
"end": 5366500,
|
||||
"type": "amateur",
|
||||
"name": "CW, Digital 60m Ham Band|"
|
||||
},
|
||||
{
|
||||
"start": 7000000,
|
||||
"end": 7040000,
|
||||
"type": "amateur",
|
||||
"name": "|40m Ham Band CW"
|
||||
},
|
||||
{
|
||||
"start": 7040000,
|
||||
"end": 7047000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 7047000,
|
||||
"end": 7050000,
|
||||
"type": "amateur",
|
||||
"name": "CW, SSB, Digital"
|
||||
},
|
||||
{
|
||||
"start": 7050000,
|
||||
"end": 7100000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSB, DV, Digital"
|
||||
},
|
||||
{
|
||||
"start": 7100000,
|
||||
"end": 7300000,
|
||||
"type": "amateur",
|
||||
"name": "CW, SSB, AM, DV, Digital 40m Ham Band|"
|
||||
},
|
||||
{
|
||||
"start": 10100000,
|
||||
"end": 10130000,
|
||||
"type": "amateur",
|
||||
"name": "|30m Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 10130000,
|
||||
"end": 10150000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, Digital 30m Ham Band|"
|
||||
},
|
||||
{
|
||||
"start": 14000000,
|
||||
"end": 14070000,
|
||||
"type": "amateur",
|
||||
"name": "|20m Ham Band CW"
|
||||
},
|
||||
{
|
||||
"start": 14070000,
|
||||
"end": 14099000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 14099000,
|
||||
"end": 14101000,
|
||||
"type": "amateur",
|
||||
"name": "CW IBP"
|
||||
},
|
||||
{
|
||||
"start": 14101000,
|
||||
"end": 14282000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSB, DV, Digital"
|
||||
},
|
||||
{
|
||||
"start": 14285000,
|
||||
"end": 14350000,
|
||||
"type": "amateur",
|
||||
"name": "CW, SSB, AM, DV, Digital 20m Ham Band|"
|
||||
},
|
||||
{
|
||||
"start": 18068000,
|
||||
"end": 18095000,
|
||||
"type": "amateur",
|
||||
"name": "|17m Ham Band CW"
|
||||
},
|
||||
{
|
||||
"start": 18095000,
|
||||
"end": 18109000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 18109000,
|
||||
"end": 18111000,
|
||||
"type": "amateur",
|
||||
"name": "CW IBP"
|
||||
},
|
||||
{
|
||||
"start": 18111000,
|
||||
"end": 18168000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSB, DV, Digital 17m Ham Band|"
|
||||
},
|
||||
{
|
||||
"start": 21000000,
|
||||
"end": 21070000,
|
||||
"type": "amateur",
|
||||
"name": "|15m Ham Band CW"
|
||||
},
|
||||
{
|
||||
"start": 21070000,
|
||||
"end": 21149000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 21149000,
|
||||
"end": 21151000,
|
||||
"type": "amateur",
|
||||
"name": "CW, IBP"
|
||||
},
|
||||
{
|
||||
"start": 21151000,
|
||||
"end": 21380000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSB, DV, Digital"
|
||||
},
|
||||
{
|
||||
"start": 21380000,
|
||||
"end": 21450000,
|
||||
"type": "amateur",
|
||||
"name": "CW, SSB, AM, DV, Digital 15m Ham Band|"
|
||||
},
|
||||
{
|
||||
"start": 24890000,
|
||||
"end": 24915000,
|
||||
"type": "amateur",
|
||||
"name": "|12m Ham Band CW"
|
||||
},
|
||||
{
|
||||
"start": 24915000,
|
||||
"end": 24929000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 24929000,
|
||||
"end": 24931000,
|
||||
"type": "amateur",
|
||||
"name": "CW IBP"
|
||||
},
|
||||
{
|
||||
"start": 24931000,
|
||||
"end": 24990000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSB, DV, Digital 12m Ham Band|"
|
||||
},
|
||||
{
|
||||
"start": 28000000,
|
||||
"end": 28070000,
|
||||
"type": "amateur",
|
||||
"name": "|10m Ham Band CW"
|
||||
},
|
||||
{
|
||||
"start": 28070000,
|
||||
"end": 28190000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 28190000,
|
||||
"end": 28199000,
|
||||
"type": "amateur",
|
||||
"name": "CW - Pilot Emissions"
|
||||
},
|
||||
{
|
||||
"start": 28199000,
|
||||
"end": 28201000,
|
||||
"type": "amateur1",
|
||||
"name": "CW IBP"
|
||||
},
|
||||
{
|
||||
"start": 28201000,
|
||||
"end": 28225000,
|
||||
"type": "amateur",
|
||||
"name": "CW - Pilot Emissions"
|
||||
},
|
||||
{
|
||||
"start": 28225000,
|
||||
"end": 28300000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, Digital - Pilot Emissions"
|
||||
},
|
||||
{
|
||||
"start": 28300000,
|
||||
"end": 29000000,
|
||||
"type": "amateur",
|
||||
"name": "CW, SSB, DV, Digital"
|
||||
},
|
||||
{
|
||||
"start": 29000000,
|
||||
"end": 29300000,
|
||||
"type": "amateur1",
|
||||
"name": "All Modes"
|
||||
},
|
||||
{
|
||||
"start": 29300000,
|
||||
"end": 29510000,
|
||||
"type": "amateur",
|
||||
"name": "All Modes - Satellites"
|
||||
},
|
||||
{
|
||||
"start": 29510000,
|
||||
"end": 29520000,
|
||||
"type": "amateur1",
|
||||
"name": "All Modes"
|
||||
},
|
||||
{
|
||||
"start": 29520000,
|
||||
"end": 29590000,
|
||||
"type": "amateur",
|
||||
"name": "FM, DV - Repeater input"
|
||||
},
|
||||
{
|
||||
"start": 29590000,
|
||||
"end": 29620000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, FM, DV - FM calling freq: 29.600 kHz"
|
||||
},
|
||||
{
|
||||
"start": 29620000,
|
||||
"end": 29700000,
|
||||
"type": "amateur",
|
||||
"name": "FM, DV - Repeater output 10m Ham Band|"
|
||||
},
|
||||
{
|
||||
"start": 50000000,
|
||||
"end": 54000000,
|
||||
"type": "amateur",
|
||||
"name": "6m Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 144000000,
|
||||
"end": 144025000,
|
||||
"type": "amateur",
|
||||
"name": "|2m Ham Band All Modes - Satellites"
|
||||
},
|
||||
{
|
||||
"start": 144025000,
|
||||
"end": 144110000,
|
||||
"type": "amateur1",
|
||||
"name": "CW - EME"
|
||||
},
|
||||
{
|
||||
"start": 144110000,
|
||||
"end": 144150000,
|
||||
"type": "amateur",
|
||||
"name": "CW, Digital - EME"
|
||||
},
|
||||
{
|
||||
"start": 144150000,
|
||||
"end": 144180000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSB, Digital"
|
||||
},
|
||||
{
|
||||
"start": 144180000,
|
||||
"end": 144275000,
|
||||
"type": "amateur",
|
||||
"name": "CW, SSB - Calling freq: 144.2 MHz"
|
||||
},
|
||||
{
|
||||
"start": 144275000,
|
||||
"end": 144300000,
|
||||
"type": "amateur1",
|
||||
"name": "CW - Pilot Emissions"
|
||||
},
|
||||
{
|
||||
"start": 144300000,
|
||||
"end": 144360000,
|
||||
"type": "amateur",
|
||||
"name": "CW, SSB - Calling freq: 144.2 MHz"
|
||||
},
|
||||
{
|
||||
"start": 144360000,
|
||||
"end": 144400000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSB, Digital"
|
||||
},
|
||||
{
|
||||
"start": 144400000,
|
||||
"end": 144600000,
|
||||
"type": "amateur",
|
||||
"name": "All Modes"
|
||||
},
|
||||
{
|
||||
"start": 144600000,
|
||||
"end": 144900000,
|
||||
"type": "amateur1",
|
||||
"name": "FM, DV - Repeater input"
|
||||
},
|
||||
{
|
||||
"start": 144900000,
|
||||
"end": 145000000,
|
||||
"type": "amateur",
|
||||
"name": "CW, FM, DV, Digital"
|
||||
},
|
||||
{
|
||||
"start": 145000000,
|
||||
"end": 145200000,
|
||||
"type": "amateur1",
|
||||
"name": "All Modes, IVG"
|
||||
},
|
||||
{
|
||||
"start": 145200000,
|
||||
"end": 145500000,
|
||||
"type": "amateur",
|
||||
"name": "FM, DV - Repeater output"
|
||||
},
|
||||
{
|
||||
"start": 145500000,
|
||||
"end": 145565000,
|
||||
"type": "amateur1",
|
||||
"name": "All Modes"
|
||||
},
|
||||
{
|
||||
"start": 145565000,
|
||||
"end": 145575000,
|
||||
"type": "amateur",
|
||||
"name": "APRS"
|
||||
},
|
||||
{
|
||||
"start": 145575000,
|
||||
"end": 145790000,
|
||||
"type": "amateur1",
|
||||
"name": "All Modes"
|
||||
},
|
||||
{
|
||||
"start": 145790000,
|
||||
"end": 145800000,
|
||||
"type": "amateur",
|
||||
"name": "Guard Band"
|
||||
},
|
||||
{
|
||||
"start": 145800000,
|
||||
"end": 146000000,
|
||||
"type": "amateur1",
|
||||
"name": "All Modes - Satellites"
|
||||
},
|
||||
{
|
||||
"start": 146000000,
|
||||
"end": 146390000,
|
||||
"type": "amateur",
|
||||
"name": "FM, DV - Repeater input"
|
||||
},
|
||||
{
|
||||
"start": 146390000,
|
||||
"end": 146600000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, FM, DV - Calling freq: 146.52 MHz"
|
||||
},
|
||||
{
|
||||
"start": 146600000,
|
||||
"end": 146990000,
|
||||
"type": "amateur",
|
||||
"name": "FM, DV - Repeater output"
|
||||
},
|
||||
{
|
||||
"start": 146990000,
|
||||
"end": 147400000,
|
||||
"type": "amateur1",
|
||||
"name": "FM, DV - Repeater input"
|
||||
},
|
||||
{
|
||||
"start": 147400000,
|
||||
"end": 147590000,
|
||||
"type": "amateur",
|
||||
"name": "CW, FM, DV"
|
||||
},
|
||||
{
|
||||
"start": 147590000,
|
||||
"end": 148000000,
|
||||
"type": "amateur1",
|
||||
"name": "FM, DV - Repeater output 2m Ham Band|"
|
||||
},
|
||||
{
|
||||
"start": 220000000,
|
||||
"end": 225000000,
|
||||
"type": "amateur",
|
||||
"name": "1.3m Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 430000000,
|
||||
"end": 432000000,
|
||||
"type": "amateur",
|
||||
"name": "|70cm Ham Band All Modes"
|
||||
},
|
||||
{
|
||||
"start": 432000000,
|
||||
"end": 432025000,
|
||||
"type": "amateur1",
|
||||
"name": "CW - EME"
|
||||
},
|
||||
{
|
||||
"start": 432025000,
|
||||
"end": 432100000,
|
||||
"type": "amateur",
|
||||
"name": "CW, Digital - EME"
|
||||
},
|
||||
{
|
||||
"start": 432100000,
|
||||
"end": 432300000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, SSB - Calling freq: 432.1 MHz"
|
||||
},
|
||||
{
|
||||
"start": 432300000,
|
||||
"end": 432400000,
|
||||
"type": "amateur",
|
||||
"name": "CW - Pilot Emissions"
|
||||
},
|
||||
{
|
||||
"start": 432400000,
|
||||
"end": 432420000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, Digital - Pilot Emissions"
|
||||
},
|
||||
{
|
||||
"start": 432420000,
|
||||
"end": 433000000,
|
||||
"type": "amateur",
|
||||
"name": "CW, SSB, Digital"
|
||||
},
|
||||
{
|
||||
"start": 433000000,
|
||||
"end": 433050000,
|
||||
"type": "amateur1",
|
||||
"name": "CW, Digital"
|
||||
},
|
||||
{
|
||||
"start": 433050000,
|
||||
"end": 434000000,
|
||||
"type": "amateur",
|
||||
"name": "All Modes"
|
||||
},
|
||||
{
|
||||
"start": 434000000,
|
||||
"end": 435000000,
|
||||
"type": "amateur1",
|
||||
"name": "Fm, DV - Repeater input"
|
||||
},
|
||||
{
|
||||
"start": 435000000,
|
||||
"end": 438000000,
|
||||
"type": "amateur",
|
||||
"name": "All Modes - Satellites"
|
||||
},
|
||||
{
|
||||
"start": 438000000,
|
||||
"end": 439000000,
|
||||
"type": "amateur1",
|
||||
"name": "All Modes"
|
||||
},
|
||||
{
|
||||
"start": 439000000,
|
||||
"end": 440000000,
|
||||
"type": "amateur",
|
||||
"name": "FM, DV - Repeater output 70cm Ham Band|"
|
||||
},
|
||||
{
|
||||
"start": 902000000,
|
||||
"end": 928000000,
|
||||
"type": "amateur",
|
||||
"name": "33cm Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 1240000000,
|
||||
"end": 1300000000,
|
||||
"type": "amateur",
|
||||
"name": "23cm Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 2330000000,
|
||||
"end": 2450000000,
|
||||
"type": "amateur",
|
||||
"name": "13cm Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 3400000000,
|
||||
"end": 3500000000,
|
||||
"type": "amateur",
|
||||
"name": "9cm Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 5650000000,
|
||||
"end": 5925000000,
|
||||
"type": "amateur",
|
||||
"name": "5cm Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 10000000000,
|
||||
"end": 10500000000,
|
||||
"type": "amateur",
|
||||
"name": "3cm Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 24000000000,
|
||||
"end": 24250000000,
|
||||
"type": "amateur",
|
||||
"name": "1.2cm Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 47000000000,
|
||||
"end": 47200000000,
|
||||
"type": "amateur",
|
||||
"name": "6mm Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 122250000000,
|
||||
"end": 123000000000,
|
||||
"type": "amateur",
|
||||
"name": "2.5mm Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 134000000000,
|
||||
"end": 141000000000,
|
||||
"type": "amateur",
|
||||
"name": "2mm Ham Band"
|
||||
},
|
||||
{
|
||||
"start": 241000000000,
|
||||
"end": 250000000000,
|
||||
"type": "amateur",
|
||||
"name": "1mm Ham Band"
|
||||
}
|
||||
]
|
||||
}
|
@ -2,8 +2,8 @@
|
||||
"name": "France",
|
||||
"country_name": "France",
|
||||
"country_code": "FR",
|
||||
"author_name": "Fred F4EED",
|
||||
"author_url": "http://f4eed.wordpress.com",
|
||||
"author_name": "Fred F4EED, Armand31",
|
||||
"author_url": "http://f4eed.wordpress.com, https://github.com/Armand31",
|
||||
"bands": [
|
||||
{
|
||||
"name": "137KHz - Radioamateur",
|
||||
@ -355,7 +355,7 @@
|
||||
"end": 54000000
|
||||
},
|
||||
{
|
||||
"name": "Bande FM - Radiodif.",
|
||||
"name": "Radiodiffusion - Bande FM",
|
||||
"type": "broadcast",
|
||||
"start": 80000000,
|
||||
"end": 108000000
|
||||
@ -396,6 +396,12 @@
|
||||
"start": 162362500,
|
||||
"end": 162587500
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusion - Bande DAB",
|
||||
"type": "broadcast",
|
||||
"start": 174000000,
|
||||
"end": 223000000
|
||||
},
|
||||
{
|
||||
"name": "Military Aviation",
|
||||
"type": "military",
|
||||
@ -408,6 +414,12 @@
|
||||
"start": 240000000,
|
||||
"end": 270000000
|
||||
},
|
||||
{
|
||||
"name": "Police (TETRAPOL)",
|
||||
"type": "military",
|
||||
"start": 380000000,
|
||||
"end": 400000000
|
||||
},
|
||||
{
|
||||
"name": "70cm - Radioamateur",
|
||||
"type": "amateur",
|
||||
@ -420,12 +432,24 @@
|
||||
"start": 446000000,
|
||||
"end": 446200000
|
||||
},
|
||||
{
|
||||
"name": "TNT (DVB-T)",
|
||||
"type": "broadcast",
|
||||
"start": 470000000,
|
||||
"end": 694000000
|
||||
},
|
||||
{
|
||||
"name": "23cm - Radioamateur",
|
||||
"type": "amateur",
|
||||
"start": 1240000000,
|
||||
"end": 1300000000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusion - Bande DAB",
|
||||
"type": "broadcast",
|
||||
"start": 1452000000,
|
||||
"end": 1492000000
|
||||
},
|
||||
{
|
||||
"name": "13cm - Radioamateur",
|
||||
"type": "amateur",
|
||||
|
285
root/res/bandplans/turkey.json
Normal file
285
root/res/bandplans/turkey.json
Normal file
@ -0,0 +1,285 @@
|
||||
{
|
||||
"name": "Turkey",
|
||||
"country_name": "Turkey",
|
||||
"country_code": "TR",
|
||||
"author_name": "Yunus TA2PEA",
|
||||
"author_url": "https://github.com/ycanerol",
|
||||
"bands": [
|
||||
{
|
||||
"name": "LW",
|
||||
"type": "amateur",
|
||||
"start": 135700,
|
||||
"end": 137800
|
||||
},
|
||||
{
|
||||
"name": "630m",
|
||||
"type": "amateur",
|
||||
"start": 472000,
|
||||
"end": 479000
|
||||
},
|
||||
{
|
||||
"name": "160m",
|
||||
"type": "amateur",
|
||||
"start": 1810000,
|
||||
"end": 1850000
|
||||
},
|
||||
{
|
||||
"name": "80m",
|
||||
"type": "amateur",
|
||||
"start": 3500000,
|
||||
"end": 3800000
|
||||
},
|
||||
{
|
||||
"name": "60m",
|
||||
"type": "amateur",
|
||||
"start": 5351500,
|
||||
"end": 5366500
|
||||
},
|
||||
{
|
||||
"name": "40m",
|
||||
"type": "amateur",
|
||||
"start": 7000000,
|
||||
"end": 7200000
|
||||
},
|
||||
{
|
||||
"name": "30m",
|
||||
"type": "amateur",
|
||||
"start": 10100000,
|
||||
"end": 10150000
|
||||
},
|
||||
{
|
||||
"name": "20m",
|
||||
"type": "amateur",
|
||||
"start": 14000000,
|
||||
"end": 14350000
|
||||
},
|
||||
{
|
||||
"name": "17m",
|
||||
"type": "amateur",
|
||||
"start": 18068000,
|
||||
"end": 18168000
|
||||
},
|
||||
{
|
||||
"name": "15m",
|
||||
"type": "amateur",
|
||||
"start": 21000000,
|
||||
"end": 21450000
|
||||
},
|
||||
{
|
||||
"name": "12m",
|
||||
"type": "amateur",
|
||||
"start": 24890000,
|
||||
"end": 24990000
|
||||
},
|
||||
{
|
||||
"name": "CB",
|
||||
"type": "other",
|
||||
"start": 26565000,
|
||||
"end": 27405000
|
||||
},
|
||||
{
|
||||
"name": "Pagers",
|
||||
"type": "amateur",
|
||||
"start": 27750000,
|
||||
"end": 28000000
|
||||
},
|
||||
{
|
||||
"name": "10m",
|
||||
"type": "amateur",
|
||||
"start": 28000000,
|
||||
"end": 29700000
|
||||
},
|
||||
{
|
||||
"name": "6m",
|
||||
"type": "amateur",
|
||||
"start": 50030000,
|
||||
"end": 51000000
|
||||
},
|
||||
{
|
||||
"name": "FM",
|
||||
"type": "broadcast",
|
||||
"start": 87500000,
|
||||
"end": 108000000
|
||||
},
|
||||
{
|
||||
"name": "Airband VOR/ILS",
|
||||
"type": "aviation",
|
||||
"start": 108000000,
|
||||
"end": 117975000
|
||||
},
|
||||
{
|
||||
"name": "Airband Voice",
|
||||
"type": "aviation",
|
||||
"start": 117975000,
|
||||
"end": 137000000
|
||||
},
|
||||
{
|
||||
"name": "2m",
|
||||
"type": "amateur",
|
||||
"start": 144000000,
|
||||
"end": 146000000
|
||||
},
|
||||
{
|
||||
"name": "Sayac Okuma",
|
||||
"type": "other",
|
||||
"start": 169400000,
|
||||
"end": 169475000
|
||||
},
|
||||
{
|
||||
"name": "Pagers",
|
||||
"type": "other",
|
||||
"start": 167000000,
|
||||
"end": 167100000
|
||||
},
|
||||
{
|
||||
"name": "Public announcement systems",
|
||||
"type": "other",
|
||||
"start": 173882500,
|
||||
"end": 174000000
|
||||
},
|
||||
{
|
||||
"name": "DVB-T",
|
||||
"type": "broadcast",
|
||||
"start": 174000000,
|
||||
"end": 216000000
|
||||
},
|
||||
{
|
||||
"name": "T-DAB",
|
||||
"type": "broadcast",
|
||||
"start": 216000000,
|
||||
"end": 233000000
|
||||
},
|
||||
{
|
||||
"name": "ILS-Glide Path",
|
||||
"type": "aviation",
|
||||
"start": 328600000,
|
||||
"end": 335400000
|
||||
},
|
||||
{
|
||||
"name": "Public Safety/Emergency",
|
||||
"type": "other",
|
||||
"start": 380000000,
|
||||
"end": 385000000
|
||||
},
|
||||
{
|
||||
"name": "Public Safety/Emergency",
|
||||
"type": "other",
|
||||
"start": 390000000,
|
||||
"end": 395000000
|
||||
},
|
||||
{
|
||||
"name": "70cm",
|
||||
"type": "amateur",
|
||||
"start": 430200000,
|
||||
"end": 430700000
|
||||
},
|
||||
{
|
||||
"name": "70cm-RepeaterRX",
|
||||
"type": "amateur",
|
||||
"start": 431550000,
|
||||
"end": 431825000
|
||||
},
|
||||
{
|
||||
"name": "70cm",
|
||||
"type": "amateur",
|
||||
"start": 432000000,
|
||||
"end": 432975000
|
||||
},
|
||||
{
|
||||
"name": "70cm",
|
||||
"type": "amateur",
|
||||
"start": 433400000,
|
||||
"end": 434000000
|
||||
},
|
||||
{
|
||||
"name": "70cm",
|
||||
"type": "amateur",
|
||||
"start": 435000000,
|
||||
"end": 438000000
|
||||
},
|
||||
{
|
||||
"name": "70cm-RepeaterTX",
|
||||
"type": "amateur",
|
||||
"start": 439150000,
|
||||
"end": 439425000
|
||||
},
|
||||
{
|
||||
"name": "Public announcement systems",
|
||||
"type": "other",
|
||||
"start": 445250000,
|
||||
"end": 445462500
|
||||
},
|
||||
{
|
||||
"name": "PMR446",
|
||||
"type": "other",
|
||||
"start": 446006250,
|
||||
"end": 446196875
|
||||
},
|
||||
{
|
||||
"name": "RFID",
|
||||
"type": "other",
|
||||
"start": 865000000,
|
||||
"end": 868000000
|
||||
},
|
||||
{
|
||||
"name": "RFID",
|
||||
"type": "other",
|
||||
"start": 916100000,
|
||||
"end": 918900000
|
||||
},
|
||||
{
|
||||
"name": "23cm",
|
||||
"type": "amateur",
|
||||
"start": 1240000000,
|
||||
"end": 1300000000
|
||||
},
|
||||
{
|
||||
"name": "DECT",
|
||||
"type": "other",
|
||||
"start": 1880000000,
|
||||
"end": 1900000000
|
||||
},
|
||||
{
|
||||
"name": "5GHz",
|
||||
"type": "amateur",
|
||||
"start": 5650000000,
|
||||
"end": 5670000000
|
||||
},
|
||||
{
|
||||
"name": "5GHz",
|
||||
"type": "amateur",
|
||||
"start": 5820000000,
|
||||
"end": 5850000000
|
||||
},
|
||||
{
|
||||
"name": "3cm",
|
||||
"type": "amateur",
|
||||
"start": 104500000000,
|
||||
"end": 104520000000
|
||||
},
|
||||
{
|
||||
"name": "24GHz",
|
||||
"type": "amateur",
|
||||
"start": 24000000000,
|
||||
"end": 24050000000
|
||||
},
|
||||
{
|
||||
"name": "47GHz",
|
||||
"type": "amateur",
|
||||
"start": 47000000000,
|
||||
"end": 47200000000
|
||||
},
|
||||
{
|
||||
"name": "75GHz",
|
||||
"type": "amateur",
|
||||
"start": 75500000000,
|
||||
"end": 7600000000
|
||||
},
|
||||
{
|
||||
"name": "134GHz",
|
||||
"type": "amateur",
|
||||
"start": 134000000000,
|
||||
"end": 142000000000
|
||||
}
|
||||
]
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 8.8 KiB |
@ -2,10 +2,10 @@
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <signal_path/sink.h>
|
||||
#include <dsp/buffer/packer.h>
|
||||
#include <dsp/convert/stereo_to_mono.h>
|
||||
#include <utils/flog.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <RtAudio.h>
|
||||
#include <config.h>
|
||||
#include <core.h>
|
||||
@ -16,51 +16,36 @@ SDRPP_MOD_INFO{
|
||||
/* Name: */ "audio_sink",
|
||||
/* Description: */ "Audio sink module for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 2, 0,
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ 1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
bool operator==(const RtAudio::DeviceInfo& a, const RtAudio::DeviceInfo& b) {
|
||||
return a.name == b.name;
|
||||
}
|
||||
|
||||
class AudioSink : public Sink {
|
||||
class AudioSink : SinkManager::Sink {
|
||||
public:
|
||||
AudioSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) :
|
||||
Sink(entry, stream, name, id, stringId)
|
||||
{
|
||||
s2m.init(stream);
|
||||
AudioSink(SinkManager::Stream* stream, std::string streamName) {
|
||||
_stream = stream;
|
||||
_streamName = streamName;
|
||||
s2m.init(_stream->sinkOut);
|
||||
monoPacker.init(&s2m.out, 512);
|
||||
stereoPacker.init(stream, 512);
|
||||
stereoPacker.init(_stream->sinkOut, 512);
|
||||
|
||||
#if RTAUDIO_VERSION_MAJOR >= 6
|
||||
audio.setErrorCallback(&errorCallback);
|
||||
#endif
|
||||
|
||||
#if RTAUDIO_VERSION_MAJOR >= 6
|
||||
audio.setErrorCallback(&errorCallback);
|
||||
#endif
|
||||
|
||||
// Load config (TODO)
|
||||
bool created = false;
|
||||
std::string device = "";
|
||||
// config.acquire();
|
||||
// if (config.conf.contains(streamName)) {
|
||||
// if (!config.conf[streamName].is_array()) {
|
||||
// json tmp = config.conf[streamName];
|
||||
// config.conf[streamName] = json::array();
|
||||
// config.conf[streamName][0] = tmp;
|
||||
// modified = true;
|
||||
// }
|
||||
// if (config.conf[streamName].contains((int)id)) {
|
||||
// device = config.conf[streamName][(int)id]["device"];
|
||||
// }
|
||||
// }
|
||||
// config.release(modified);
|
||||
config.acquire();
|
||||
if (!config.conf.contains(_streamName)) {
|
||||
created = true;
|
||||
config.conf[_streamName]["device"] = "";
|
||||
config.conf[_streamName]["devices"] = json({});
|
||||
}
|
||||
device = config.conf[_streamName]["device"];
|
||||
config.release(created);
|
||||
|
||||
// List devices
|
||||
RtAudio::DeviceInfo info;
|
||||
#if RTAUDIO_VERSION_MAJOR >= 6
|
||||
for (int i : audio.getDeviceIds()) {
|
||||
@ -75,13 +60,15 @@ public:
|
||||
#endif
|
||||
if (info.outputChannels == 0) { continue; }
|
||||
if (info.isDefaultOutput) { defaultDevId = devList.size(); }
|
||||
devList.define(i, info.name, info);
|
||||
devList.push_back(info);
|
||||
deviceIds.push_back(i);
|
||||
txtDevList += info.name;
|
||||
txtDevList += '\0';
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
flog::error("AudioSinkModule Error getting audio device ({}) info: {}", i, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
selectByName(device);
|
||||
}
|
||||
|
||||
@ -115,92 +102,67 @@ public:
|
||||
}
|
||||
|
||||
void selectById(int id) {
|
||||
// Update ID
|
||||
devId = id;
|
||||
selectedDevName = devList[id].name;
|
||||
|
||||
// List samplerates and select default SR
|
||||
char buf[256];
|
||||
sampleRates.clear();
|
||||
const auto& srList = devList[id].sampleRates;
|
||||
unsigned int defaultSr = devList[id].preferredSampleRate;
|
||||
for (auto& sr : srList) {
|
||||
if (sr == defaultSr) {
|
||||
srId = sampleRates.size();
|
||||
sampleRate = sr;
|
||||
}
|
||||
sprintf(buf, "%d", sr);
|
||||
sampleRates.define(sr, buf, sr);
|
||||
bool created = false;
|
||||
config.acquire();
|
||||
if (!config.conf[_streamName]["devices"].contains(devList[id].name)) {
|
||||
created = true;
|
||||
config.conf[_streamName]["devices"][devList[id].name] = devList[id].preferredSampleRate;
|
||||
}
|
||||
|
||||
// // Load config
|
||||
// config.acquire();
|
||||
// if (config.conf[streamName][(int)id].contains(selectedDevName)) {
|
||||
// unsigned int wantedSr = config.conf[streamName][id][selectedDevName];
|
||||
// if (sampleRates.keyExists(wantedSr)) {
|
||||
// srId = sampleRates.keyId(wantedSr);
|
||||
// sampleRate = sampleRates[srId];
|
||||
// }
|
||||
// }
|
||||
// config.release();
|
||||
sampleRate = config.conf[_streamName]["devices"][devList[id].name];
|
||||
config.release(created);
|
||||
|
||||
// Lock the sink
|
||||
auto lck = entry->getLock();
|
||||
sampleRates = devList[id].sampleRates;
|
||||
sampleRatesTxt = "";
|
||||
char buf[256];
|
||||
bool found = false;
|
||||
unsigned int defaultId = 0;
|
||||
unsigned int defaultSr = devList[id].preferredSampleRate;
|
||||
for (int i = 0; i < sampleRates.size(); i++) {
|
||||
if (sampleRates[i] == sampleRate) {
|
||||
found = true;
|
||||
srId = i;
|
||||
}
|
||||
if (sampleRates[i] == defaultSr) {
|
||||
defaultId = i;
|
||||
}
|
||||
sprintf(buf, "%d", sampleRates[i]);
|
||||
sampleRatesTxt += buf;
|
||||
sampleRatesTxt += '\0';
|
||||
}
|
||||
if (!found) {
|
||||
sampleRate = defaultSr;
|
||||
srId = defaultId;
|
||||
}
|
||||
|
||||
// Stop the sink DSP
|
||||
// TODO: Only if the sink DSP is running, otherwise you risk starting it when it shouldn't
|
||||
entry->stopDSP();
|
||||
_stream->setSampleRate(sampleRate);
|
||||
|
||||
// Stop the sink
|
||||
if (running) { doStop(); }
|
||||
|
||||
// Update stream samplerate
|
||||
entry->setSamplerate(sampleRate);
|
||||
|
||||
// Start the DSP
|
||||
entry->startDSP();
|
||||
|
||||
// Start the sink
|
||||
if (running) { doStart(); }
|
||||
}
|
||||
|
||||
void showMenu() {
|
||||
void menuHandler() {
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(("##_audio_sink_dev_" + stringId).c_str(), &devId, devList.txt)) {
|
||||
if (ImGui::Combo(("##_audio_sink_dev_" + _streamName).c_str(), &devId, txtDevList.c_str())) {
|
||||
selectById(devId);
|
||||
// config.acquire();
|
||||
// config.conf[streamName]["device"] = devList[devId].name;
|
||||
// config.release(true);
|
||||
config.acquire();
|
||||
config.conf[_streamName]["device"] = devList[devId].name;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(("##_audio_sink_sr_" + stringId).c_str(), &srId, sampleRates.txt)) {
|
||||
if (ImGui::Combo(("##_audio_sink_sr_" + _streamName).c_str(), &srId, sampleRatesTxt.c_str())) {
|
||||
sampleRate = sampleRates[srId];
|
||||
|
||||
// Lock the sink
|
||||
auto lck = entry->getLock();
|
||||
|
||||
// Stop the sink DSP
|
||||
// TODO: Only if the sink DSP is running, otherwise you risk starting it when it shouldn't
|
||||
entry->stopDSP();
|
||||
|
||||
// Stop the sink
|
||||
if (running) { doStop(); }
|
||||
|
||||
// Update stream samplerate
|
||||
entry->setSamplerate(sampleRate);
|
||||
|
||||
// Start the DSP
|
||||
entry->startDSP();
|
||||
|
||||
// Start the sink
|
||||
if (running) { doStart(); }
|
||||
|
||||
// config.acquire();
|
||||
// config.conf[streamName]["devices"][devList[devId].name] = sampleRate;
|
||||
// config.release(true);
|
||||
_stream->setSampleRate(sampleRate);
|
||||
if (running) {
|
||||
doStop();
|
||||
doStart();
|
||||
}
|
||||
config.acquire();
|
||||
config.conf[_streamName]["devices"][devList[devId].name] = sampleRate;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,12 +185,12 @@ public:
|
||||
private:
|
||||
bool doStart() {
|
||||
RtAudio::StreamParameters parameters;
|
||||
parameters.deviceId = devList.key(devId);
|
||||
parameters.deviceId = deviceIds[devId];
|
||||
parameters.nChannels = 2;
|
||||
unsigned int bufferFrames = sampleRate / 60;
|
||||
RtAudio::StreamOptions opts;
|
||||
opts.flags = RTAUDIO_MINIMIZE_LATENCY;
|
||||
opts.streamName = streamName;
|
||||
opts.streamName = _streamName;
|
||||
|
||||
try {
|
||||
audio.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
|
||||
@ -267,34 +229,44 @@ private:
|
||||
return 0;
|
||||
}
|
||||
|
||||
SinkManager::Stream* _stream;
|
||||
dsp::convert::StereoToMono s2m;
|
||||
dsp::buffer::Packer<float> monoPacker;
|
||||
dsp::buffer::Packer<dsp::stereo_t> stereoPacker;
|
||||
|
||||
std::string _streamName;
|
||||
|
||||
int srId = 0;
|
||||
int devCount;
|
||||
int devId = 0;
|
||||
bool running = false;
|
||||
std::string selectedDevName;
|
||||
|
||||
unsigned int defaultDevId = 0;
|
||||
|
||||
OptionList<unsigned int, RtAudio::DeviceInfo> devList;
|
||||
OptionList<unsigned int, unsigned int> sampleRates;
|
||||
std::vector<RtAudio::DeviceInfo> devList;
|
||||
std::vector<unsigned int> deviceIds;
|
||||
std::string txtDevList;
|
||||
|
||||
std::vector<unsigned int> sampleRates;
|
||||
std::string sampleRatesTxt;
|
||||
unsigned int sampleRate = 48000;
|
||||
|
||||
RtAudio audio;
|
||||
};
|
||||
|
||||
class AudioSinkModule : public ModuleManager::Instance, public SinkProvider {
|
||||
class AudioSinkModule : public ModuleManager::Instance {
|
||||
public:
|
||||
AudioSinkModule(std::string name) {
|
||||
this->name = name;
|
||||
sigpath::streamManager.registerSinkProvider("Audio", this);
|
||||
provider.create = create_sink;
|
||||
provider.ctx = this;
|
||||
|
||||
sigpath::sinkManager.registerSinkProvider("Audio", provider);
|
||||
}
|
||||
|
||||
~AudioSinkModule() {
|
||||
sigpath::streamManager.unregisterSinkProvider(this);
|
||||
// Unregister sink, this will automatically stop and delete all instances of the audio sink
|
||||
sigpath::sinkManager.unregisterSinkProvider("Audio");
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
@ -311,13 +283,14 @@ public:
|
||||
return enabled;
|
||||
}
|
||||
|
||||
std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) {
|
||||
return std::make_unique<AudioSink>(entry, stream, name, id, stringId);
|
||||
private:
|
||||
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
|
||||
return (SinkManager::Sink*)(new AudioSink(stream, streamName));
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
SinkManager::SinkProvider provider;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
@ -339,4 +312,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,13 @@
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <signal_path/stream.h>
|
||||
#include <signal_path/sink.h>
|
||||
#include <dsp/buffer/packer.h>
|
||||
#include <dsp/convert/stereo_to_mono.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include <utils/flog.h>
|
||||
#include <config.h>
|
||||
#include <gui/style.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <core.h>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
@ -19,97 +18,84 @@ SDRPP_MOD_INFO{
|
||||
/* Name: */ "network_sink",
|
||||
/* Description: */ "Network sink module for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 2, 0,
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ 1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
enum SinkMode {
|
||||
enum {
|
||||
SINK_MODE_TCP,
|
||||
SINK_MODE_UDP
|
||||
};
|
||||
|
||||
const char* sinkModesTxt = "TCP\0UDP\0";
|
||||
|
||||
class NetworkSink : public Sink {
|
||||
class NetworkSink : SinkManager::Sink {
|
||||
public:
|
||||
NetworkSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) :
|
||||
Sink(entry, stream, name, id, stringId)
|
||||
{
|
||||
// Define modes
|
||||
modes.define("TCP", SINK_MODE_TCP);
|
||||
modes.define("UDP", SINK_MODE_UDP);
|
||||
NetworkSink(SinkManager::Stream* stream, std::string streamName) {
|
||||
_stream = stream;
|
||||
_streamName = streamName;
|
||||
|
||||
// Create a list of sample rates
|
||||
std::vector<int> srList;
|
||||
for (int sr = 12000; sr <= 200000; sr += 12000) {
|
||||
srList.push_back(sr);
|
||||
}
|
||||
for (int sr = 11025; sr <= 192000; sr += 11025) {
|
||||
srList.push_back(sr);
|
||||
// Load config
|
||||
config.acquire();
|
||||
if (!config.conf.contains(_streamName)) {
|
||||
config.conf[_streamName]["hostname"] = "localhost";
|
||||
config.conf[_streamName]["port"] = 7355;
|
||||
config.conf[_streamName]["protocol"] = SINK_MODE_UDP; // UDP
|
||||
config.conf[_streamName]["sampleRate"] = 48000.0;
|
||||
config.conf[_streamName]["stereo"] = false;
|
||||
config.conf[_streamName]["listening"] = false;
|
||||
}
|
||||
std::string host = config.conf[_streamName]["hostname"];
|
||||
strcpy(hostname, host.c_str());
|
||||
port = config.conf[_streamName]["port"];
|
||||
modeId = config.conf[_streamName]["protocol"];
|
||||
sampleRate = config.conf[_streamName]["sampleRate"];
|
||||
stereo = config.conf[_streamName]["stereo"];
|
||||
bool startNow = config.conf[_streamName]["listening"];
|
||||
config.release(true);
|
||||
|
||||
// Sort sample rate list
|
||||
std::sort(srList.begin(), srList.end(), [](double a, double b) { return (a < b); });
|
||||
|
||||
// Define samplerate options
|
||||
for (int sr : srList) {
|
||||
char buf[16];
|
||||
sprintf(buf, "%d", sr);
|
||||
samplerates.define(sr, buf, sr);
|
||||
}
|
||||
|
||||
// Allocate buffer
|
||||
netBuf = new int16_t[STREAM_BUFFER_SIZE];
|
||||
|
||||
// Init DSP
|
||||
packer.init(stream, 512);
|
||||
packer.init(_stream->sinkOut, 512);
|
||||
s2m.init(&packer.out);
|
||||
monoSink.init(&s2m.out, monoHandler, this);
|
||||
stereoSink.init(&packer.out, stereoHandler, this);
|
||||
|
||||
// Load config
|
||||
config.acquire();
|
||||
bool startNow = false;
|
||||
if (config.conf[stringId].contains("hostname")) {
|
||||
std::string host = config.conf[stringId]["hostname"];
|
||||
strcpy(hostname, host.c_str());
|
||||
}
|
||||
if (config.conf[stringId].contains("port")) {
|
||||
port = config.conf[stringId]["port"];
|
||||
}
|
||||
if (config.conf[stringId].contains("mode")) {
|
||||
std::string modeStr = config.conf[stringId]["mode"];
|
||||
if (modes.keyExists(modeStr)) {
|
||||
mode = modes.value(modes.keyId(modeStr));
|
||||
}
|
||||
else {
|
||||
mode = SINK_MODE_TCP;
|
||||
}
|
||||
}
|
||||
if (config.conf[stringId].contains("samplerate")) {
|
||||
int nSr = config.conf[stringId]["samplerate"];
|
||||
if (samplerates.keyExists(nSr)) {
|
||||
sampleRate = samplerates.value(samplerates.keyId(nSr));
|
||||
}
|
||||
else {
|
||||
sampleRate = 48000;
|
||||
}
|
||||
}
|
||||
if (config.conf[stringId].contains("stereo")) {
|
||||
stereo = config.conf[stringId]["stereo"];
|
||||
}
|
||||
if (config.conf[stringId].contains("running")) {
|
||||
startNow = config.conf[stringId]["running"];
|
||||
}
|
||||
config.release();
|
||||
|
||||
// Set mode ID
|
||||
modeId = modes.valueId(mode);
|
||||
// Create a list of sample rates
|
||||
for (int sr = 12000; sr < 200000; sr += 12000) {
|
||||
sampleRates.push_back(sr);
|
||||
}
|
||||
for (int sr = 11025; sr < 192000; sr += 11025) {
|
||||
sampleRates.push_back(sr);
|
||||
}
|
||||
|
||||
// Set samplerate ID
|
||||
srId = samplerates.valueId(sampleRate);
|
||||
// Sort sample rate list
|
||||
std::sort(sampleRates.begin(), sampleRates.end(), [](double a, double b) { return (a < b); });
|
||||
|
||||
// Generate text list for UI
|
||||
char buffer[128];
|
||||
int id = 0;
|
||||
int _48kId;
|
||||
bool found = false;
|
||||
for (auto sr : sampleRates) {
|
||||
sprintf(buffer, "%d", (int)sr);
|
||||
sampleRatesTxt += buffer;
|
||||
sampleRatesTxt += '\0';
|
||||
if (sr == sampleRate) {
|
||||
srId = id;
|
||||
found = true;
|
||||
}
|
||||
if (sr == 48000.0) { _48kId = id; }
|
||||
id++;
|
||||
}
|
||||
if (!found) {
|
||||
srId = _48kId;
|
||||
sampleRate = 48000.0;
|
||||
}
|
||||
_stream->setSampleRate(sampleRate);
|
||||
|
||||
// Start if needed
|
||||
if (startNow) { startServer(); }
|
||||
@ -136,30 +122,30 @@ public:
|
||||
running = false;
|
||||
}
|
||||
|
||||
void showMenu() {
|
||||
void menuHandler() {
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
bool listening = (listener && listener->isListening()) || (conn && conn->isOpen());
|
||||
|
||||
if (listening) { style::beginDisabled(); }
|
||||
if (ImGui::InputText(CONCAT("##_network_sink_host_", stringId), hostname, 1023)) {
|
||||
if (ImGui::InputText(CONCAT("##_network_sink_host_", _streamName), hostname, 1023)) {
|
||||
config.acquire();
|
||||
config.conf[stringId]["hostname"] = hostname;
|
||||
config.conf[_streamName]["hostname"] = hostname;
|
||||
config.release(true);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::InputInt(CONCAT("##_network_sink_port_", stringId), &port, 0, 0)) {
|
||||
if (ImGui::InputInt(CONCAT("##_network_sink_port_", _streamName), &port, 0, 0)) {
|
||||
config.acquire();
|
||||
config.conf[stringId]["port"] = port;
|
||||
config.conf[_streamName]["port"] = port;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
ImGui::LeftLabel("Protocol");
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo(CONCAT("##_network_sink_mode_", stringId), &modeId, sinkModesTxt)) {
|
||||
if (ImGui::Combo(CONCAT("##_network_sink_mode_", _streamName), &modeId, sinkModesTxt)) {
|
||||
config.acquire();
|
||||
config.conf[stringId]["mode"] = modeId;
|
||||
config.conf[_streamName]["protocol"] = modeId;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
@ -167,33 +153,33 @@ public:
|
||||
|
||||
ImGui::LeftLabel("Samplerate");
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo(CONCAT("##_network_sink_sr_", stringId), &srId, samplerates.txt)) {
|
||||
sampleRate = samplerates.value(srId);
|
||||
entry->setSamplerate(sampleRate);
|
||||
if (ImGui::Combo(CONCAT("##_network_sink_sr_", _streamName), &srId, sampleRatesTxt.c_str())) {
|
||||
sampleRate = sampleRates[srId];
|
||||
_stream->setSampleRate(sampleRate);
|
||||
packer.setSampleCount(sampleRate / 60);
|
||||
config.acquire();
|
||||
config.conf[stringId]["samplerate"] = sampleRate;
|
||||
config.conf[_streamName]["sampleRate"] = sampleRate;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", stringId), &stereo)) {
|
||||
if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", _streamName), &stereo)) {
|
||||
stop();
|
||||
start();
|
||||
config.acquire();
|
||||
config.conf[stringId]["stereo"] = stereo;
|
||||
config.conf[_streamName]["stereo"] = stereo;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
if (listening && ImGui::Button(CONCAT("Stop##_network_sink_stop_", stringId), ImVec2(menuWidth, 0))) {
|
||||
if (listening && ImGui::Button(CONCAT("Stop##_network_sink_stop_", _streamName), ImVec2(menuWidth, 0))) {
|
||||
stopServer();
|
||||
config.acquire();
|
||||
config.conf[stringId]["running"] = false;
|
||||
config.conf[_streamName]["listening"] = false;
|
||||
config.release(true);
|
||||
}
|
||||
else if (!listening && ImGui::Button(CONCAT("Start##_network_sink_stop_", stringId), ImVec2(menuWidth, 0))) {
|
||||
else if (!listening && ImGui::Button(CONCAT("Start##_network_sink_stop_", _streamName), ImVec2(menuWidth, 0))) {
|
||||
startServer();
|
||||
config.acquire();
|
||||
config.conf[stringId]["running"] = true;
|
||||
config.conf[_streamName]["listening"] = true;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
@ -231,14 +217,19 @@ private:
|
||||
}
|
||||
|
||||
void startServer() {
|
||||
if (modeId == SINK_MODE_TCP) {
|
||||
listener = net::listen(hostname, port);
|
||||
if (listener) {
|
||||
listener->acceptAsync(clientHandler, this);
|
||||
try {
|
||||
if (modeId == SINK_MODE_TCP) {
|
||||
listener = net::listen(hostname, port);
|
||||
if (listener) {
|
||||
listener->acceptAsync(clientHandler, this);
|
||||
}
|
||||
}
|
||||
else {
|
||||
conn = net::openUDP("0.0.0.0", port, hostname, port, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
conn = net::openUDP("0.0.0.0", port, hostname, port, false);
|
||||
catch (const std::exception& e) {
|
||||
flog::error("Failed to open socket: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,40 +276,47 @@ private:
|
||||
_this->listener->acceptAsync(clientHandler, _this);
|
||||
}
|
||||
|
||||
// DSP
|
||||
SinkManager::Stream* _stream;
|
||||
dsp::buffer::Packer<dsp::stereo_t> packer;
|
||||
dsp::convert::StereoToMono s2m;
|
||||
dsp::sink::Handler<float> monoSink;
|
||||
dsp::sink::Handler<dsp::stereo_t> stereoSink;
|
||||
|
||||
OptionList<std::string, SinkMode> modes;
|
||||
OptionList<int, double> samplerates;
|
||||
std::string _streamName;
|
||||
|
||||
char hostname[1024];
|
||||
int port = 7355;
|
||||
SinkMode mode = SINK_MODE_TCP;
|
||||
int modeId;
|
||||
int sampleRate = 48000;
|
||||
int srId;
|
||||
bool stereo = false;
|
||||
int srId = 0;
|
||||
bool running = false;
|
||||
|
||||
char hostname[1024];
|
||||
int port = 4242;
|
||||
|
||||
int modeId = 1;
|
||||
|
||||
std::vector<unsigned int> sampleRates;
|
||||
std::string sampleRatesTxt;
|
||||
unsigned int sampleRate = 48000;
|
||||
bool stereo = false;
|
||||
|
||||
int16_t* netBuf;
|
||||
|
||||
net::Listener listener;
|
||||
net::Conn conn;
|
||||
std::mutex connMtx;
|
||||
};
|
||||
|
||||
class NetworkSinkModule : public ModuleManager::Instance, SinkProvider {
|
||||
class NetworkSinkModule : public ModuleManager::Instance {
|
||||
public:
|
||||
NetworkSinkModule(std::string name) {
|
||||
// Register self as provider
|
||||
sigpath::streamManager.registerSinkProvider("Network", this);
|
||||
this->name = name;
|
||||
provider.create = create_sink;
|
||||
provider.ctx = this;
|
||||
|
||||
sigpath::sinkManager.registerSinkProvider("Network", provider);
|
||||
}
|
||||
|
||||
~NetworkSinkModule() {
|
||||
// Unregister self
|
||||
sigpath::streamManager.unregisterSinkProvider(this);
|
||||
// Unregister sink, this will automatically stop and delete all instances of the audio sink
|
||||
sigpath::sinkManager.unregisterSinkProvider("Network");
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
@ -335,13 +333,14 @@ public:
|
||||
return enabled;
|
||||
}
|
||||
|
||||
std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) {
|
||||
return std::make_unique<NetworkSink>(entry, stream, name, id, stringId);
|
||||
private:
|
||||
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
|
||||
return (SinkManager::Sink*)(new NetworkSink(stream, streamName));
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
SinkManager::SinkProvider provider;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
|
@ -152,7 +152,6 @@ public:
|
||||
|
||||
// Update samplerate from ID
|
||||
sampleRate = sampleRates[srId];
|
||||
core::setInputSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -232,6 +231,7 @@ private:
|
||||
if (SmGui::Combo(CONCAT("##_audio_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
|
||||
std::string dev = _this->devices.key(_this->devId);
|
||||
_this->select(dev);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
config.acquire();
|
||||
config.conf["device"] = dev;
|
||||
config.release(true);
|
||||
@ -253,6 +253,7 @@ private:
|
||||
if (SmGui::Button(CONCAT("Refresh##_audio_refr_", _this->name))) {
|
||||
_this->refresh();
|
||||
_this->select(_this->selectedDevice);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
if (_this->running) { SmGui::EndDisabled(); }
|
||||
|
25
source_modules/badgesdr_source/CMakeLists.txt
Normal file
25
source_modules/badgesdr_source/CMakeLists.txt
Normal file
@ -0,0 +1,25 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(badgesdr_source)
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
if (MSVC)
|
||||
find_package(libusb CONFIG REQUIRED)
|
||||
target_include_directories(badgesdr_source PRIVATE ${LIBUSB_INCLUDE_DIRS})
|
||||
target_link_libraries(badgesdr_source PRIVATE ${LIBUSB_LIBRARIES})
|
||||
elseif (ANDROID)
|
||||
target_link_libraries(badgesdr_source PUBLIC
|
||||
/sdr-kit/${ANDROID_ABI}/lib/libusb1.0.so
|
||||
/sdr-kit/${ANDROID_ABI}/lib/librtlsdr.so
|
||||
)
|
||||
else (MSVC)
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(LIBUSB REQUIRED libusb-1.0)
|
||||
|
||||
target_include_directories(badgesdr_source PRIVATE ${LIBUSB_INCLUDE_DIRS})
|
||||
target_link_directories(badgesdr_source PRIVATE ${LIBUSB_LIBRARY_DIRS})
|
||||
target_link_libraries(badgesdr_source PRIVATE ${LIBUSB_LIBRARIES})
|
||||
endif ()
|
309
source_modules/badgesdr_source/src/badgesdr.cpp
Normal file
309
source_modules/badgesdr_source/src/badgesdr.cpp
Normal file
@ -0,0 +1,309 @@
|
||||
#include "badgesdr.h"
|
||||
#include <stdexcept>
|
||||
#include <utils/flog.h>
|
||||
|
||||
#define R820T_I2C_ADDR 0x1A
|
||||
|
||||
namespace BadgeSDR {
|
||||
enum Commands {
|
||||
CMD_I2C_RW,
|
||||
CMD_I2C_STATUS,
|
||||
CMD_ADC_START,
|
||||
CMD_ADC_STOP,
|
||||
CMD_ADC_GET_SAMP_COUNT
|
||||
};
|
||||
|
||||
libusb_context* ctx = NULL;
|
||||
|
||||
bool DeviceInfo::operator==(const DeviceInfo& b) const {
|
||||
return serialNumber == b.serialNumber;
|
||||
}
|
||||
|
||||
void Device::write_reg(uint8_t reg, uint8_t value, void* ctx) {
|
||||
Device* dev = (Device*)ctx;
|
||||
dev->writeR820TReg(reg, value);
|
||||
dev->writeR820TReg(0x1F, 0); // TODO: Figure out why this is needed
|
||||
}
|
||||
|
||||
void Device::read_reg(uint8_t* data, int len, void* ctx) {
|
||||
Device* dev = (Device*)ctx;
|
||||
dev->readI2C(R820T_I2C_ADDR, data, len);
|
||||
}
|
||||
|
||||
Device::Device(libusb_device_handle* dev) {
|
||||
// Save device handle
|
||||
this->dev = dev;
|
||||
|
||||
// Init tuner
|
||||
r820t = {
|
||||
16000000, // xtal_freq => 16MHz
|
||||
3000000, // Set at boot to airspy_m0_m4_conf_t conf0 -> r820t_if_freq
|
||||
100000000, /* Default Freq 100Mhz */
|
||||
{
|
||||
/* 05 */ 0x9F, // LNA manual gain mode, init to 0
|
||||
/* 06 */ 0x80,
|
||||
/* 07 */ 0x60,
|
||||
/* 08 */ 0x80, // Image Gain Adjustment
|
||||
/* 09 */ 0x40, // Image Phase Adjustment
|
||||
/* 0A */ 0xA8, // Channel filter [0..3]: 0 = widest, f = narrowest - Optimal. Don't touch!
|
||||
/* 0B */ 0x0F, // High pass filter - Optimal. Don't touch!
|
||||
/* 0C */ 0x4F, // VGA control by code, init at 0
|
||||
/* 0D */ 0x63, // LNA AGC settings: [0..3]: Lower threshold; [4..7]: High threshold
|
||||
/* 0E */ 0x75,
|
||||
/* 0F */ 0xE8, // Filter Widest, LDO_5V OFF, clk out ON,
|
||||
/* 10 */ 0x7C,
|
||||
/* 11 */ 0x42,
|
||||
/* 12 */ 0x06,
|
||||
/* 13 */ 0x00,
|
||||
/* 14 */ 0x0F,
|
||||
/* 15 */ 0x00,
|
||||
/* 16 */ 0xC0,
|
||||
/* 17 */ 0xA0,
|
||||
/* 18 */ 0x48,
|
||||
/* 19 */ 0xCC,
|
||||
/* 1A */ 0x60,
|
||||
/* 1B */ 0x00,
|
||||
/* 1C */ 0x54,
|
||||
/* 1D */ 0xAE,
|
||||
/* 1E */ 0x0A,
|
||||
/* 1F */ 0xC0
|
||||
},
|
||||
0 /* uint16_t padding */
|
||||
};
|
||||
r820t_init(&r820t, 3000000, write_reg, read_reg, this);
|
||||
r820t_set_mixer_gain(&r820t, 15);
|
||||
r820t_set_vga_gain(&r820t, 15);
|
||||
}
|
||||
|
||||
Device::~Device() {
|
||||
// Release the bulk interface
|
||||
libusb_release_interface(dev, 0);
|
||||
|
||||
// Close device
|
||||
libusb_close(dev);
|
||||
}
|
||||
|
||||
void Device::setFrequency(double freq) {
|
||||
r820t_set_freq(&r820t, freq - 2125000);
|
||||
}
|
||||
|
||||
void Device::setLNAGain(int gain) {
|
||||
r820t_set_lna_gain(&r820t, gain);
|
||||
}
|
||||
|
||||
void Device::setMixerGain(int gain) {
|
||||
r820t_set_mixer_gain(&r820t, gain);
|
||||
}
|
||||
|
||||
void Device::setVGAGain(int gain) {
|
||||
r820t_set_vga_gain(&r820t, gain);
|
||||
}
|
||||
|
||||
|
||||
void Device::start(void (*callback)(const uint8_t* samples, int count, void* ctx), void* ctx, int minBufferSize) {
|
||||
// Do nothing if already running
|
||||
if (run) { return; }
|
||||
|
||||
// Save handler
|
||||
this->callback = callback;
|
||||
this->ctx = ctx;
|
||||
|
||||
// Compute buffer size
|
||||
int bufCount = minBufferSize / 64;
|
||||
if (minBufferSize % 64) { bufCount++; }
|
||||
bufferSize = bufCount * 64;
|
||||
|
||||
// Mark as running
|
||||
run = true;
|
||||
|
||||
// Start the ADC
|
||||
startADC();
|
||||
|
||||
// Start worker thread
|
||||
workerThread = std::thread(&Device::worker, this);
|
||||
}
|
||||
|
||||
void Device::stop() {
|
||||
// Do nothing if already stopped
|
||||
if (!run) { return; }
|
||||
|
||||
// Mark as stopped
|
||||
run = false;
|
||||
|
||||
// Wait for the worker to exit
|
||||
if (workerThread.joinable()) { workerThread.join(); }
|
||||
|
||||
// Stop the ADC
|
||||
stopADC();
|
||||
}
|
||||
|
||||
int Device::getI2CStatus() {
|
||||
int status;
|
||||
int ret = libusb_control_transfer(dev, (1 << 7) | (2 << 5), CMD_I2C_STATUS, 0, 0, (uint8_t*)&status, sizeof(status), 1000);
|
||||
if (ret <= 0 || status < 0) {
|
||||
return -1;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
int Device::readI2C(uint8_t addr, uint8_t* data, int len) {
|
||||
// Do read
|
||||
int bytes = libusb_control_transfer(dev, (1 << 7) | (2 << 5), CMD_I2C_RW, 0, addr, data, len, 1000);
|
||||
if (bytes < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get status (TODO: Use function)
|
||||
int status;
|
||||
int ret = libusb_control_transfer(dev, (1 << 7) | (2 << 5), CMD_I2C_STATUS, 0, 0, (uint8_t*)&status, sizeof(status), 1000);
|
||||
if (ret <= 0 || status < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
int Device::writeI2C(uint8_t addr, const uint8_t* data, int len) {
|
||||
// Do write
|
||||
int bytes = libusb_control_transfer(dev, (2 << 5), CMD_I2C_RW, 0, addr, (uint8_t*)data, len, 1000);
|
||||
if (bytes < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get status (TODO: Use function)
|
||||
int status;
|
||||
int ret = libusb_control_transfer(dev, (1 << 7) | (2 << 5), CMD_I2C_STATUS, 0, 0, (uint8_t*)&status, sizeof(status), 1000);
|
||||
if (ret <= 0 || status < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
uint8_t bitrev(uint8_t val) {
|
||||
return ((val & 0x01) << 7) | ((val & 0x02) << 5) | ((val & 0x04) << 3) | ((val & 0x08) << 1) | ((val & 0x10) >> 1) | ((val & 0x20) >> 3) | ((val & 0x40) >> 5) | ((val & 0x80) >> 7);
|
||||
}
|
||||
|
||||
uint8_t Device::readR820TReg(uint8_t reg) {
|
||||
// Read registers up to it (can't read single)
|
||||
uint8_t regs[0x20];
|
||||
readI2C(R820T_I2C_ADDR, regs, reg+1);
|
||||
|
||||
// Invert bit order
|
||||
return bitrev(regs[reg]);
|
||||
}
|
||||
|
||||
void Device::writeR820TReg(uint8_t reg, uint8_t val) {
|
||||
// Write register id then value
|
||||
uint8_t cmd[2] = { reg, val };
|
||||
writeI2C(R820T_I2C_ADDR, cmd, 2);
|
||||
}
|
||||
|
||||
int Device::startADC() {
|
||||
return libusb_control_transfer(dev, (2 << 5), CMD_ADC_START, 0, 0, NULL, 0, 1000);
|
||||
}
|
||||
|
||||
int Device::stopADC() {
|
||||
return libusb_control_transfer(dev, (2 << 5), CMD_ADC_STOP, 0, 0, NULL, 0, 1000);
|
||||
}
|
||||
|
||||
void Device::worker() {
|
||||
// Allocate sample buffer
|
||||
uint8_t* buffer = new uint8_t[bufferSize];
|
||||
int sampleCount = 0;
|
||||
|
||||
while (run) {
|
||||
// Receive data with bulk transfer
|
||||
int recvLen = 0;
|
||||
int val = libusb_bulk_transfer(dev, LIBUSB_ENDPOINT_IN | 1, &buffer[sampleCount], bufferSize - sampleCount, &recvLen, 1000);
|
||||
|
||||
// If timed out, try again. Otherwise, if an error occur, stop the thread
|
||||
if (val == LIBUSB_ERROR_TIMEOUT) {
|
||||
continue;
|
||||
}
|
||||
else if (val) {
|
||||
flog::error("USB Error: {}", val);
|
||||
break;
|
||||
}
|
||||
|
||||
// Increment sample count
|
||||
if (recvLen) { sampleCount += recvLen; }
|
||||
|
||||
// If the buffer is full, call handler and reset sample count
|
||||
if (sampleCount >= bufferSize) {
|
||||
callback(buffer, sampleCount, ctx);
|
||||
sampleCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Free buffer
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
std::vector<DeviceInfo> list() {
|
||||
// Init libusb if done yet
|
||||
if (!ctx) {
|
||||
libusb_init(&ctx);
|
||||
libusb_set_debug(ctx, LIBUSB_LOG_LEVEL_WARNING);
|
||||
}
|
||||
|
||||
// List devices
|
||||
std::vector<DeviceInfo> devList;
|
||||
libusb_device** devices;
|
||||
int devCount = libusb_get_device_list(ctx, &devices);
|
||||
for (int i = 0; i < devCount; i++) {
|
||||
// Get device info
|
||||
DeviceInfo devInfo;
|
||||
devInfo.dev = devices[i];
|
||||
libusb_device_descriptor desc;
|
||||
libusb_get_device_descriptor(devInfo.dev, &desc);
|
||||
|
||||
// Check the VID/PID and give up if not the right ones
|
||||
if (desc.idVendor != 0xCAFE || desc.idProduct != 0x4010) {
|
||||
libusb_unref_device(devInfo.dev);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Open devices
|
||||
libusb_device_handle* openDev;
|
||||
int err = libusb_open(devInfo.dev, &openDev);
|
||||
if (err) {
|
||||
libusb_unref_device(devInfo.dev);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get serial number
|
||||
char serial[128];
|
||||
libusb_get_string_descriptor_ascii(openDev, desc.iSerialNumber, (uint8_t*)serial, sizeof(serial));
|
||||
devInfo.serialNumber = serial;
|
||||
|
||||
// TODO: The libusb device should be unreffed but would need to know when the list disappears
|
||||
|
||||
// Close device
|
||||
libusb_close(openDev);
|
||||
|
||||
// Add to list
|
||||
devList.push_back(devInfo);
|
||||
}
|
||||
|
||||
// Return devices
|
||||
return devList;
|
||||
}
|
||||
|
||||
std::shared_ptr<Device> open(const DeviceInfo& dev) {
|
||||
// Open device
|
||||
libusb_device_handle* openDev;
|
||||
int err = libusb_open(dev.dev, &openDev);
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to open device");
|
||||
}
|
||||
|
||||
// Claim the bulk transfer interface
|
||||
if (libusb_claim_interface(openDev, 0)) {
|
||||
throw std::runtime_error("Failed to claim bulk interface");
|
||||
}
|
||||
|
||||
// Create device
|
||||
return std::make_shared<Device>(openDev);
|
||||
}
|
||||
}
|
53
source_modules/badgesdr_source/src/badgesdr.h
Normal file
53
source_modules/badgesdr_source/src/badgesdr.h
Normal file
@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <libusb.h>
|
||||
#include "r820t.h"
|
||||
|
||||
namespace BadgeSDR {
|
||||
struct DeviceInfo {
|
||||
std::string serialNumber;
|
||||
libusb_device* dev;
|
||||
bool operator==(const DeviceInfo& b) const;
|
||||
};
|
||||
|
||||
class Device {
|
||||
public:
|
||||
Device(libusb_device_handle* dev);
|
||||
~Device();
|
||||
|
||||
void setFrequency(double freq);
|
||||
void setLNAGain(int gain);
|
||||
void setMixerGain(int gain);
|
||||
void setVGAGain(int gain);
|
||||
|
||||
void start(void (*callback)(const uint8_t* samples, int count, void* ctx), void* ctx = NULL, int minBufferSize = 2500);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
int getI2CStatus();
|
||||
int readI2C(uint8_t addr, uint8_t* data, int len);
|
||||
int writeI2C(uint8_t addr, const uint8_t* data, int len);
|
||||
uint8_t readR820TReg(uint8_t reg);
|
||||
void writeR820TReg(uint8_t reg, uint8_t val);
|
||||
int startADC();
|
||||
int stopADC();
|
||||
void worker();
|
||||
|
||||
libusb_device_handle* dev;
|
||||
std::thread workerThread;
|
||||
bool run = false;
|
||||
int bufferSize = 0; // Must be multiple of 64 for best performance
|
||||
void* ctx = NULL;
|
||||
void (*callback)(const uint8_t* samples, int count, void* ctx);
|
||||
|
||||
static void write_reg(uint8_t reg, uint8_t value, void* ctx);
|
||||
static void read_reg(uint8_t* data, int len, void* ctx);
|
||||
r820t_priv_t r820t;
|
||||
};
|
||||
|
||||
std::vector<DeviceInfo> list();
|
||||
std::shared_ptr<Device> open(const DeviceInfo& dev);
|
||||
}
|
272
source_modules/badgesdr_source/src/main.cpp
Normal file
272
source_modules/badgesdr_source/src/main.cpp
Normal file
@ -0,0 +1,272 @@
|
||||
#include <imgui.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/smgui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <dsp/channel/rx_vfo.h>
|
||||
#include <dsp/correction/dc_blocker.h>
|
||||
#include "badgesdr.h"
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "badgesdr_source",
|
||||
/* Description: */ "BadgeSDR Source Module",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
class BadgeSDRSourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
BadgeSDRSourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
sampleRate = 250000.0;
|
||||
|
||||
// Initialize DSP
|
||||
dcBlock.init(&input, 0.001);
|
||||
ddc.init(&dcBlock.out, 500000, 250000, 250000, 125000);
|
||||
|
||||
handler.ctx = this;
|
||||
handler.selectHandler = menuSelected;
|
||||
handler.deselectHandler = menuDeselected;
|
||||
handler.menuHandler = menuHandler;
|
||||
handler.startHandler = start;
|
||||
handler.stopHandler = stop;
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &ddc.out;
|
||||
|
||||
// Refresh devices
|
||||
refresh();
|
||||
|
||||
// Select first (TODO: Select from config)
|
||||
select("");
|
||||
|
||||
sigpath::sourceManager.registerSource("BadgeSDR", &handler);
|
||||
}
|
||||
|
||||
~BadgeSDRSourceModule() {
|
||||
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
void refresh() {
|
||||
devices.clear();
|
||||
auto list = BadgeSDR::list();
|
||||
for (const auto& info : list) {
|
||||
// Format device name
|
||||
std::string devName = "BadgeSDR ";
|
||||
devName += " [";
|
||||
devName += info.serialNumber;
|
||||
devName += ']';
|
||||
|
||||
// Save device
|
||||
devices.define(info.serialNumber, devName, info);
|
||||
}
|
||||
}
|
||||
|
||||
void select(const std::string& serial) {
|
||||
// If there are no devices, give up
|
||||
if (devices.empty()) {
|
||||
selectedSerial.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the serial was not found, select the first available serial
|
||||
if (!devices.keyExists(serial)) {
|
||||
select(devices.key(0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Save serial number
|
||||
selectedSerial = serial;
|
||||
selectedDev = devices.value(devices.keyId(serial));
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx;
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
flog::info("BadgeSDRSourceModule '{0}': Menu Select!", _this->name);
|
||||
}
|
||||
|
||||
static void menuDeselected(void* ctx) {
|
||||
BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx;
|
||||
flog::info("BadgeSDRSourceModule '{0}': Menu Deselect!", _this->name);
|
||||
}
|
||||
|
||||
static void start(void* ctx) {
|
||||
BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx;
|
||||
if (_this->running) { return; }
|
||||
|
||||
// Open the device
|
||||
_this->openDev = BadgeSDR::open(_this->selectedDev);
|
||||
|
||||
// Configure the device
|
||||
_this->openDev->setFrequency(_this->freq);
|
||||
_this->openDev->setLNAGain(_this->lnaGain);
|
||||
_this->openDev->setMixerGain(_this->mixerGain);
|
||||
_this->openDev->setVGAGain(_this->vgaGain);
|
||||
|
||||
// Start DSP
|
||||
_this->dcBlock.start();
|
||||
_this->ddc.start();
|
||||
|
||||
// Start device
|
||||
_this->openDev->start(callback, _this, 500000/200);
|
||||
|
||||
_this->running = true;
|
||||
flog::info("BadgeSDRSourceModule '{0}': Start!", _this->name);
|
||||
}
|
||||
|
||||
static void stop(void* ctx) {
|
||||
BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx;
|
||||
if (!_this->running) { return; }
|
||||
_this->running = false;
|
||||
|
||||
// Stop worker
|
||||
_this->openDev->stop();
|
||||
|
||||
// Stop DSP
|
||||
_this->dcBlock.stop();
|
||||
_this->ddc.stop();
|
||||
|
||||
// Close device
|
||||
_this->openDev.reset();
|
||||
|
||||
flog::info("BadgeSDRSourceModule '{0}': Stop!", _this->name);
|
||||
}
|
||||
|
||||
static void tune(double freq, void* ctx) {
|
||||
BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
_this->openDev->setFrequency(freq);
|
||||
}
|
||||
_this->freq = freq;
|
||||
flog::info("BadgeSDRSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx;
|
||||
|
||||
if (_this->running) { SmGui::BeginDisabled(); }
|
||||
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo(CONCAT("##_badgesdr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
|
||||
_this->select(_this->devices.key(_this->devId));
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Button(CONCAT("Refresh##_badgesdr_refr_", _this->name))) {
|
||||
_this->refresh();
|
||||
_this->select(_this->selectedSerial);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
if (_this->running) { SmGui::EndDisabled(); }
|
||||
|
||||
SmGui::LeftLabel("LNA Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_badgesdr_lna_gain_", _this->name), &_this->lnaGain, 0, 15)) {
|
||||
if (_this->running) {
|
||||
_this->openDev->setLNAGain(_this->lnaGain);
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("Mixer Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_badgesdr_mixer_gain_", _this->name), &_this->mixerGain, 0, 15)) {
|
||||
if (_this->running) {
|
||||
_this->openDev->setMixerGain(_this->mixerGain);
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("VGA Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_badgesdr_vga_gain_", _this->name), &_this->vgaGain, 0, 15)) {
|
||||
if (_this->running) {
|
||||
_this->openDev->setVGAGain(_this->vgaGain);
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
}
|
||||
|
||||
static void callback(const uint8_t* samples, int count, void* ctx) {
|
||||
BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx;
|
||||
|
||||
// Convert samples to float
|
||||
dsp::complex_t* out = _this->input.writeBuf;
|
||||
int min = 255, max = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (samples[i] < min) { min = samples[i]; }
|
||||
if (samples[i] > max) { max = samples[i]; }
|
||||
|
||||
out[i].re = ((float)samples[i] - 127.5f) * (1.0f/127.0f);
|
||||
out[i].im = 1.0f;
|
||||
}
|
||||
|
||||
// Send out samples
|
||||
_this->input.swap(count);
|
||||
|
||||
flog::debug("Amplitudes: {} -> {}", min, max);
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
double sampleRate;
|
||||
SourceManager::SourceHandler handler;
|
||||
bool running = false;
|
||||
double freq;
|
||||
|
||||
OptionList<std::string, BadgeSDR::DeviceInfo> devices;
|
||||
|
||||
int devId = 0;
|
||||
int lnaGain = 0;
|
||||
int mixerGain = 0;
|
||||
int vgaGain = 0;
|
||||
std::string selectedSerial;
|
||||
BadgeSDR::DeviceInfo selectedDev;
|
||||
std::shared_ptr<BadgeSDR::Device> openDev;
|
||||
|
||||
dsp::stream<dsp::complex_t> input;
|
||||
dsp::correction::DCBlocker<dsp::complex_t> dcBlock;
|
||||
dsp::channel::RxVFO ddc;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new BadgeSDRSourceModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (BadgeSDRSourceModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
// Nothing here
|
||||
}
|
622
source_modules/badgesdr_source/src/r820t.cpp
Normal file
622
source_modules/badgesdr_source/src/r820t.cpp
Normal file
@ -0,0 +1,622 @@
|
||||
/*
|
||||
* Rafael Micro R820T driver for AIRSPY
|
||||
*
|
||||
* Copyright 2013 Youssef Touil <youssef@airspy.com>
|
||||
* Copyright 2014-2016 Benjamin Vernoux <bvernoux@airspy.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "r820t.h"
|
||||
#include <thread>
|
||||
|
||||
static int r820t_read_cache_reg(r820t_priv_t *priv, int reg);
|
||||
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||
|
||||
/* Tuner frequency ranges */
|
||||
struct r820t_freq_range
|
||||
{
|
||||
uint8_t open_d;
|
||||
uint8_t rf_mux_ploy;
|
||||
uint8_t tf_c;
|
||||
};
|
||||
|
||||
#define R820T_READ_MAX_DATA 32
|
||||
#define R820T_INIT_NB_REGS (32-5)
|
||||
uint8_t r820t_read_data[R820T_READ_MAX_DATA]; /* Buffer for data read from I2C */
|
||||
uint8_t r820t_state_standby = 1; /* 1=standby/power off 0=r820t initialized/power on */
|
||||
|
||||
/* Tuner frequency ranges
|
||||
"Copyright (C) 2013 Mauro Carvalho Chehab"
|
||||
https://stuff.mit.edu/afs/sipb/contrib/linux/drivers/media/tuners/r820t.c
|
||||
part of freq_ranges()
|
||||
*/
|
||||
const struct r820t_freq_range freq_ranges[] =
|
||||
{
|
||||
{
|
||||
/* 0 MHz */
|
||||
/* .open_d = */ 0x08, /* low */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0xdf, /* R27[7:0] band2,band0 */
|
||||
}, {
|
||||
/* 50 MHz */
|
||||
/* .open_d = */ 0x08, /* low */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0xbe, /* R27[7:0] band4,band1 */
|
||||
}, {
|
||||
/* 55 MHz */
|
||||
/* .open_d = */ 0x08, /* low */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x8b, /* R27[7:0] band7,band4 */
|
||||
}, {
|
||||
/* 60 MHz */
|
||||
/* .open_d = */ 0x08, /* low */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x7b, /* R27[7:0] band8,band4 */
|
||||
}, {
|
||||
/* 65 MHz */
|
||||
/* .open_d = */ 0x08, /* low */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x69, /* R27[7:0] band9,band6 */
|
||||
}, {
|
||||
/* 70 MHz */
|
||||
/* .open_d = */ 0x08, /* low */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x58, /* R27[7:0] band10,band7 */
|
||||
}, {
|
||||
/* 75 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x44, /* R27[7:0] band11,band11 */
|
||||
}, {
|
||||
/* 80 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x44, /* R27[7:0] band11,band11 */
|
||||
}, {
|
||||
/* 90 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x34, /* R27[7:0] band12,band11 */
|
||||
}, {
|
||||
/* 100 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x34, /* R27[7:0] band12,band11 */
|
||||
}, {
|
||||
/* 110 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x24, /* R27[7:0] band13,band11 */
|
||||
}, {
|
||||
/* 120 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x24, /* R27[7:0] band13,band11 */
|
||||
}, {
|
||||
/* 140 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x14, /* R27[7:0] band14,band11 */
|
||||
}, {
|
||||
/* 180 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x13, /* R27[7:0] band14,band12 */
|
||||
}, {
|
||||
/* 220 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x13, /* R27[7:0] band14,band12 */
|
||||
}, {
|
||||
/* 250 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x11, /* R27[7:0] highest,highest */
|
||||
}, {
|
||||
/* 280 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */
|
||||
/* .tf_c = */ 0x00, /* R27[7:0] highest,highest */
|
||||
}, {
|
||||
/* 310 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x41, /* R26[7:6]=1 (bypass) R26[1:0]=1 (middle) */
|
||||
/* .tf_c = */ 0x00, /* R27[7:0] highest,highest */
|
||||
}, {
|
||||
/* 450 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x41, /* R26[7:6]=1 (bypass) R26[1:0]=1 (middle) */
|
||||
/* .tf_c = */ 0x00, /* R27[7:0] highest,highest */
|
||||
}, {
|
||||
/* 588 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x40, /* R26[7:6]=1 (bypass) R26[1:0]=0 (highest) */
|
||||
/* .tf_c = */ 0x00, /* R27[7:0] highest,highest */
|
||||
}, {
|
||||
/* 650 MHz */
|
||||
/* .open_d = */ 0x00, /* high */
|
||||
/* .rf_mux_ploy = */ 0x40, /* R26[7:6]=1 (bypass) R26[1:0]=0 (highest) */
|
||||
/* .tf_c = */ 0x00, /* R27[7:0] highest,highest */
|
||||
}
|
||||
};
|
||||
|
||||
#define FREQ_TO_IDX_SIZE (600)
|
||||
const uint8_t freq_to_idx[FREQ_TO_IDX_SIZE]=
|
||||
{
|
||||
/* 50 */ 1,/* 51 */ 1,/* 52 */ 1,/* 53 */ 1,/* 54 */ 1,
|
||||
/* 55 */ 2,/* 56 */ 2,/* 57 */ 2,/* 58 */ 2,/* 59 */ 2,
|
||||
/* 60 */ 3,/* 61 */ 3,/* 62 */ 3,/* 63 */ 3,/* 64 */ 3,
|
||||
/* 65 */ 4,/* 66 */ 4,/* 67 */ 4,/* 68 */ 4,/* 69 */ 4,
|
||||
/* 70 */ 5,/* 71 */ 5,/* 72 */ 5,/* 73 */ 5,/* 74 */ 5,
|
||||
/* 75 */ 6,/* 76 */ 6,/* 77 */ 6,/* 78 */ 6,/* 79 */ 6,
|
||||
/* 80 */ 7,/* 81 */ 7,/* 82 */ 7,/* 83 */ 7,/* 84 */ 7,/* 85 */ 7,/* 86 */ 7,/* 87 */ 7,/* 88 */ 7,/* 89 */ 7,
|
||||
/* 90 */ 8,/* 91 */ 8,/* 92 */ 8,/* 93 */ 8,/* 94 */ 8,/* 95 */ 8,/* 96 */ 8,/* 97 */ 8,/* 98 */ 8,/* 99 */ 8,
|
||||
/* 100 */ 9,/* 101 */ 9,/* 102 */ 9,/* 103 */ 9,/* 104 */ 9,/* 105 */ 9,/* 106 */ 9,/* 107 */ 9,/* 108 */ 9,/* 109 */ 9,
|
||||
/* 110 */ 10,/* 111 */ 10,/* 112 */ 10,/* 113 */ 10,/* 114 */ 10,/* 115 */ 10,/* 116 */ 10,/* 117 */ 10,/* 118 */ 10,/* 119 */ 10,
|
||||
/* 120 */ 11,/* 121 */ 11,/* 122 */ 11,/* 123 */ 11,/* 124 */ 11,/* 125 */ 11,/* 126 */ 11,/* 127 */ 11,/* 128 */ 11,/* 129 */ 11,
|
||||
/* 130 */ 11,/* 131 */ 11,/* 132 */ 11,/* 133 */ 11,/* 134 */ 11,/* 135 */ 11,/* 136 */ 11,/* 137 */ 11,/* 138 */ 11,/* 139 */ 11,
|
||||
/* 140 */ 12,/* 141 */ 12,/* 142 */ 12,/* 143 */ 12,/* 144 */ 12,/* 145 */ 12,/* 146 */ 12,/* 147 */ 12,/* 148 */ 12,/* 149 */ 12,
|
||||
/* 150 */ 12,/* 151 */ 12,/* 152 */ 12,/* 153 */ 12,/* 154 */ 12,/* 155 */ 12,/* 156 */ 12,/* 157 */ 12,/* 158 */ 12,/* 159 */ 12,
|
||||
/* 160 */ 12,/* 161 */ 12,/* 162 */ 12,/* 163 */ 12,/* 164 */ 12,/* 165 */ 12,/* 166 */ 12,/* 167 */ 12,/* 168 */ 12,/* 169 */ 12,
|
||||
/* 170 */ 12,/* 171 */ 12,/* 172 */ 12,/* 173 */ 12,/* 174 */ 12,/* 175 */ 12,/* 176 */ 12,/* 177 */ 12,/* 178 */ 12,/* 179 */ 12,
|
||||
/* 180 */ 13,/* 181 */ 13,/* 182 */ 13,/* 183 */ 13,/* 184 */ 13,/* 185 */ 13,/* 186 */ 13,/* 187 */ 13,/* 188 */ 13,/* 189 */ 13,
|
||||
/* 190 */ 13,/* 191 */ 13,/* 192 */ 13,/* 193 */ 13,/* 194 */ 13,/* 195 */ 13,/* 196 */ 13,/* 197 */ 13,/* 198 */ 13,/* 199 */ 13,
|
||||
/* 200 */ 13,/* 201 */ 13,/* 202 */ 13,/* 203 */ 13,/* 204 */ 13,/* 205 */ 13,/* 206 */ 13,/* 207 */ 13,/* 208 */ 13,/* 209 */ 13,
|
||||
/* 210 */ 13,/* 211 */ 13,/* 212 */ 13,/* 213 */ 13,/* 214 */ 13,/* 215 */ 13,/* 216 */ 13,/* 217 */ 13,/* 218 */ 13,/* 219 */ 13,
|
||||
/* 220 */ 14,/* 221 */ 14,/* 222 */ 14,/* 223 */ 14,/* 224 */ 14,/* 225 */ 14,/* 226 */ 14,/* 227 */ 14,/* 228 */ 14,/* 229 */ 14,
|
||||
/* 230 */ 14,/* 231 */ 14,/* 232 */ 14,/* 233 */ 14,/* 234 */ 14,/* 235 */ 14,/* 236 */ 14,/* 237 */ 14,/* 238 */ 14,/* 239 */ 14,
|
||||
/* 240 */ 14,/* 241 */ 14,/* 242 */ 14,/* 243 */ 14,/* 244 */ 14,/* 245 */ 14,/* 246 */ 14,/* 247 */ 14,/* 248 */ 14,/* 249 */ 14,
|
||||
/* 250 */ 15,/* 251 */ 15,/* 252 */ 15,/* 253 */ 15,/* 254 */ 15,/* 255 */ 15,/* 256 */ 15,/* 257 */ 15,/* 258 */ 15,/* 259 */ 15,
|
||||
/* 260 */ 15,/* 261 */ 15,/* 262 */ 15,/* 263 */ 15,/* 264 */ 15,/* 265 */ 15,/* 266 */ 15,/* 267 */ 15,/* 268 */ 15,/* 269 */ 15,
|
||||
/* 270 */ 15,/* 271 */ 15,/* 272 */ 15,/* 273 */ 15,/* 274 */ 15,/* 275 */ 15,/* 276 */ 15,/* 277 */ 15,/* 278 */ 15,/* 279 */ 15,
|
||||
/* 280 */ 16,/* 281 */ 16,/* 282 */ 16,/* 283 */ 16,/* 284 */ 16,/* 285 */ 16,/* 286 */ 16,/* 287 */ 16,/* 288 */ 16,/* 289 */ 16,
|
||||
/* 290 */ 16,/* 291 */ 16,/* 292 */ 16,/* 293 */ 16,/* 294 */ 16,/* 295 */ 16,/* 296 */ 16,/* 297 */ 16,/* 298 */ 16,/* 299 */ 16,
|
||||
/* 300 */ 16,/* 301 */ 16,/* 302 */ 16,/* 303 */ 16,/* 304 */ 16,/* 305 */ 16,/* 306 */ 16,/* 307 */ 16,/* 308 */ 16,/* 309 */ 16,
|
||||
/* 310 */ 17,/* 311 */ 17,/* 312 */ 17,/* 313 */ 17,/* 314 */ 17,/* 315 */ 17,/* 316 */ 17,/* 317 */ 17,/* 318 */ 17,/* 319 */ 17,
|
||||
/* 320 */ 17,/* 321 */ 17,/* 322 */ 17,/* 323 */ 17,/* 324 */ 17,/* 325 */ 17,/* 326 */ 17,/* 327 */ 17,/* 328 */ 17,/* 329 */ 17,
|
||||
/* 330 */ 17,/* 331 */ 17,/* 332 */ 17,/* 333 */ 17,/* 334 */ 17,/* 335 */ 17,/* 336 */ 17,/* 337 */ 17,/* 338 */ 17,/* 339 */ 17,
|
||||
/* 340 */ 17,/* 341 */ 17,/* 342 */ 17,/* 343 */ 17,/* 344 */ 17,/* 345 */ 17,/* 346 */ 17,/* 347 */ 17,/* 348 */ 17,/* 349 */ 17,
|
||||
/* 350 */ 17,/* 351 */ 17,/* 352 */ 17,/* 353 */ 17,/* 354 */ 17,/* 355 */ 17,/* 356 */ 17,/* 357 */ 17,/* 358 */ 17,/* 359 */ 17,
|
||||
/* 360 */ 17,/* 361 */ 17,/* 362 */ 17,/* 363 */ 17,/* 364 */ 17,/* 365 */ 17,/* 366 */ 17,/* 367 */ 17,/* 368 */ 17,/* 369 */ 17,
|
||||
/* 370 */ 17,/* 371 */ 17,/* 372 */ 17,/* 373 */ 17,/* 374 */ 17,/* 375 */ 17,/* 376 */ 17,/* 377 */ 17,/* 378 */ 17,/* 379 */ 17,
|
||||
/* 380 */ 17,/* 381 */ 17,/* 382 */ 17,/* 383 */ 17,/* 384 */ 17,/* 385 */ 17,/* 386 */ 17,/* 387 */ 17,/* 388 */ 17,/* 389 */ 17,
|
||||
/* 390 */ 17,/* 391 */ 17,/* 392 */ 17,/* 393 */ 17,/* 394 */ 17,/* 395 */ 17,/* 396 */ 17,/* 397 */ 17,/* 398 */ 17,/* 399 */ 17,
|
||||
/* 400 */ 17,/* 401 */ 17,/* 402 */ 17,/* 403 */ 17,/* 404 */ 17,/* 405 */ 17,/* 406 */ 17,/* 407 */ 17,/* 408 */ 17,/* 409 */ 17,
|
||||
/* 410 */ 17,/* 411 */ 17,/* 412 */ 17,/* 413 */ 17,/* 414 */ 17,/* 415 */ 17,/* 416 */ 17,/* 417 */ 17,/* 418 */ 17,/* 419 */ 17,
|
||||
/* 420 */ 17,/* 421 */ 17,/* 422 */ 17,/* 423 */ 17,/* 424 */ 17,/* 425 */ 17,/* 426 */ 17,/* 427 */ 17,/* 428 */ 17,/* 429 */ 17,
|
||||
/* 430 */ 17,/* 431 */ 17,/* 432 */ 17,/* 433 */ 17,/* 434 */ 17,/* 435 */ 17,/* 436 */ 17,/* 437 */ 17,/* 438 */ 17,/* 439 */ 17,
|
||||
/* 440 */ 17,/* 441 */ 17,/* 442 */ 17,/* 443 */ 17,/* 444 */ 17,/* 445 */ 17,/* 446 */ 17,/* 447 */ 17,/* 448 */ 17,/* 449 */ 17,
|
||||
/* 450 */ 18,/* 451 */ 18,/* 452 */ 18,/* 453 */ 18,/* 454 */ 18,/* 455 */ 18,/* 456 */ 18,/* 457 */ 18,/* 458 */ 18,/* 459 */ 18,
|
||||
/* 460 */ 18,/* 461 */ 18,/* 462 */ 18,/* 463 */ 18,/* 464 */ 18,/* 465 */ 18,/* 466 */ 18,/* 467 */ 18,/* 468 */ 18,/* 469 */ 18,
|
||||
/* 470 */ 18,/* 471 */ 18,/* 472 */ 18,/* 473 */ 18,/* 474 */ 18,/* 475 */ 18,/* 476 */ 18,/* 477 */ 18,/* 478 */ 18,/* 479 */ 18,
|
||||
/* 480 */ 18,/* 481 */ 18,/* 482 */ 18,/* 483 */ 18,/* 484 */ 18,/* 485 */ 18,/* 486 */ 18,/* 487 */ 18,/* 488 */ 18,/* 489 */ 18,
|
||||
/* 490 */ 18,/* 491 */ 18,/* 492 */ 18,/* 493 */ 18,/* 494 */ 18,/* 495 */ 18,/* 496 */ 18,/* 497 */ 18,/* 498 */ 18,/* 499 */ 18,
|
||||
/* 500 */ 18,/* 501 */ 18,/* 502 */ 18,/* 503 */ 18,/* 504 */ 18,/* 505 */ 18,/* 506 */ 18,/* 507 */ 18,/* 508 */ 18,/* 509 */ 18,
|
||||
/* 510 */ 18,/* 511 */ 18,/* 512 */ 18,/* 513 */ 18,/* 514 */ 18,/* 515 */ 18,/* 516 */ 18,/* 517 */ 18,/* 518 */ 18,/* 519 */ 18,
|
||||
/* 520 */ 18,/* 521 */ 18,/* 522 */ 18,/* 523 */ 18,/* 524 */ 18,/* 525 */ 18,/* 526 */ 18,/* 527 */ 18,/* 528 */ 18,/* 529 */ 18,
|
||||
/* 530 */ 18,/* 531 */ 18,/* 532 */ 18,/* 533 */ 18,/* 534 */ 18,/* 535 */ 18,/* 536 */ 18,/* 537 */ 18,/* 538 */ 18,/* 539 */ 18,
|
||||
/* 540 */ 18,/* 541 */ 18,/* 542 */ 18,/* 543 */ 18,/* 544 */ 18,/* 545 */ 18,/* 546 */ 18,/* 547 */ 18,/* 548 */ 18,/* 549 */ 18,
|
||||
/* 550 */ 18,/* 551 */ 18,/* 552 */ 18,/* 553 */ 18,/* 554 */ 18,/* 555 */ 18,/* 556 */ 18,/* 557 */ 18,/* 558 */ 18,/* 559 */ 18,
|
||||
/* 560 */ 18,/* 561 */ 18,/* 562 */ 18,/* 563 */ 18,/* 564 */ 18,/* 565 */ 18,/* 566 */ 18,/* 567 */ 18,/* 568 */ 18,/* 569 */ 18,
|
||||
/* 570 */ 18,/* 571 */ 18,/* 572 */ 18,/* 573 */ 18,/* 574 */ 18,/* 575 */ 18,/* 576 */ 18,/* 577 */ 18,/* 578 */ 18,/* 579 */ 18,
|
||||
/* 580 */ 18,/* 581 */ 18,/* 582 */ 18,/* 583 */ 18,/* 584 */ 18,/* 585 */ 18,/* 586 */ 18,/* 587 */ 18,
|
||||
/* 588 */ 19,/* 589 */ 19,/* 590 */ 19,/* 591 */ 19,/* 592 */ 19,/* 593 */ 19,/* 594 */ 19,/* 595 */ 19,/* 596 */ 19,/* 597 */ 19,
|
||||
/* 598 */ 19,/* 599 */ 19,/* 600 */ 19,/* 601 */ 19,/* 602 */ 19,/* 603 */ 19,/* 604 */ 19,/* 605 */ 19,/* 606 */ 19,/* 607 */ 19,
|
||||
/* 608 */ 19,/* 609 */ 19,/* 610 */ 19,/* 611 */ 19,/* 612 */ 19,/* 613 */ 19,/* 614 */ 19,/* 615 */ 19,/* 616 */ 19,/* 617 */ 19,
|
||||
/* 618 */ 19,/* 619 */ 19,/* 620 */ 19,/* 621 */ 19,/* 622 */ 19,/* 623 */ 19,/* 624 */ 19,/* 625 */ 19,/* 626 */ 19,/* 627 */ 19,
|
||||
/* 628 */ 19,/* 629 */ 19,/* 630 */ 19,/* 631 */ 19,/* 632 */ 19,/* 633 */ 19,/* 634 */ 19,/* 635 */ 19,/* 636 */ 19,/* 637 */ 19,
|
||||
/* 638 */ 19,/* 639 */ 19,/* 640 */ 19,/* 641 */ 19,/* 642 */ 19,/* 643 */ 19,/* 644 */ 19,/* 645 */ 19,/* 646 */ 19,/* 647 */ 19,
|
||||
/* 648 */ 19,/* 649 */ 19
|
||||
};
|
||||
|
||||
#define FREQ_50MHZ (50)
|
||||
#define FREQ_TO_IDX_0_TO_49MHZ (0)
|
||||
#define FREQ_TO_IDX_650_TO_1800MHZ (20)
|
||||
|
||||
int r820t_freq_get_idx(uint32_t freq_mhz)
|
||||
{
|
||||
uint32_t freq_mhz_fix;
|
||||
|
||||
if(freq_mhz < FREQ_50MHZ)
|
||||
{
|
||||
/* Frequency Less than 50MHz */
|
||||
return FREQ_TO_IDX_0_TO_49MHZ;
|
||||
}else
|
||||
{
|
||||
/* Frequency Between 50 to 649MHz use table */
|
||||
/* Fix the frequency for the table */
|
||||
freq_mhz_fix = freq_mhz - FREQ_50MHZ;
|
||||
if(freq_mhz_fix < FREQ_TO_IDX_SIZE)
|
||||
{
|
||||
|
||||
return freq_to_idx[freq_mhz_fix];
|
||||
}else
|
||||
{
|
||||
/* Frequency Between 650 to 1800MHz */
|
||||
return FREQ_TO_IDX_650_TO_1800MHZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool r820t_is_power_enabled(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write regs 5 to 32 (R820T_INIT_NB_REGS values) using data parameter and write last reg to 0
|
||||
*/
|
||||
void airspy_r820t_write_init(r820t_priv_t *priv, const uint8_t* data)
|
||||
{
|
||||
for (int i = 0; i < R820T_INIT_NB_REGS; i++) {
|
||||
priv->write_reg(i+REG_SHADOW_START, data[i], priv->ctx);
|
||||
}
|
||||
priv->write_reg(0x1F, 0, priv->ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read from one or more contiguous registers. data[0] should be the first
|
||||
* register number, one or more values follow.
|
||||
*/
|
||||
const uint8_t lut[16] = { 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
|
||||
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf };
|
||||
|
||||
static uint8_t r82xx_bitrev(uint8_t byte)
|
||||
{
|
||||
return (lut[byte & 0xf] << 4) | lut[byte >> 4];
|
||||
}
|
||||
|
||||
static int r820t_write_reg(r820t_priv_t *priv, uint8_t reg, uint8_t val)
|
||||
{
|
||||
if (r820t_read_cache_reg(priv, reg) == val)
|
||||
return 0;
|
||||
priv->write_reg(reg, val, priv->ctx);
|
||||
priv->regs[reg - REG_SHADOW_START] = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int r820t_read_cache_reg(r820t_priv_t *priv, int reg)
|
||||
{
|
||||
reg -= REG_SHADOW_START;
|
||||
|
||||
if (reg >= 0 && reg < NUM_REGS)
|
||||
return priv->regs[reg];
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int r820t_write_reg_mask(r820t_priv_t *priv, uint8_t reg, uint8_t val, uint8_t bit_mask)
|
||||
{
|
||||
int rc = r820t_read_cache_reg(priv, reg);
|
||||
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
val = (rc & ~bit_mask) | (val & bit_mask);
|
||||
|
||||
return r820t_write_reg(priv, reg, val);
|
||||
}
|
||||
|
||||
static int r820t_read(r820t_priv_t *priv, uint8_t *val, int len)
|
||||
{
|
||||
/* reg not used and assumed to be always 0 because start from reg0 to reg0+len */
|
||||
priv->read_reg(val, len, priv->ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* r820t tuning logic
|
||||
*/
|
||||
#ifdef OPTIM_SET_MUX
|
||||
int r820t_set_mux_freq_idx = -1; /* Default set to invalid value in order to force set_mux */
|
||||
#endif
|
||||
|
||||
/*
|
||||
"inspired by Mauro Carvalho Chehab set_mux technique"
|
||||
https://stuff.mit.edu/afs/sipb/contrib/linux/drivers/media/tuners/r820t.c
|
||||
part of r820t_set_mux() (set tracking filter)
|
||||
*/
|
||||
static int r820t_set_tf(r820t_priv_t *priv, uint32_t freq)
|
||||
{
|
||||
const struct r820t_freq_range *range;
|
||||
int freq_idx;
|
||||
int rc = 0;
|
||||
|
||||
/* Get the proper frequency range in MHz instead of Hz */
|
||||
/* Fast divide freq by 1000000 */
|
||||
freq = (uint32_t)((uint64_t)freq * 4295 >> 32);
|
||||
|
||||
freq_idx = r820t_freq_get_idx(freq);
|
||||
range = &freq_ranges[freq_idx];
|
||||
|
||||
/* Only reconfigure mux freq if modified vs previous range */
|
||||
#ifdef OPTIM_SET_MUX
|
||||
if(freq_idx != r820t_set_mux_freq_idx)
|
||||
{
|
||||
#endif
|
||||
/* Open Drain */
|
||||
rc = r820t_write_reg_mask(priv, 0x17, range->open_d, 0x08);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* RF_MUX,Polymux */
|
||||
rc = r820t_write_reg_mask(priv, 0x1a, range->rf_mux_ploy, 0xc3);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* TF BAND */
|
||||
rc = r820t_write_reg(priv, 0x1b, range->tf_c);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* XTAL CAP & Drive */
|
||||
rc = r820t_write_reg_mask(priv, 0x10, 0x08, 0x0b);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = r820t_write_reg_mask(priv, 0x08, 0x00, 0x3f);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = r820t_write_reg_mask(priv, 0x09, 0x00, 0x3f);
|
||||
#ifdef OPTIM_SET_MUX
|
||||
}
|
||||
r820t_set_mux_freq_idx = freq_idx;
|
||||
#endif
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int r820t_set_pll(r820t_priv_t *priv, uint32_t freq)
|
||||
{
|
||||
const uint32_t vco_min = 1770000000;
|
||||
const uint32_t vco_max = 3900000000;
|
||||
uint32_t ref = priv->xtal_freq >> 1;
|
||||
|
||||
int rc;
|
||||
uint32_t div_num;
|
||||
uint32_t vco;
|
||||
uint32_t rem;
|
||||
uint32_t mask;
|
||||
uint16_t sdm;
|
||||
uint8_t nint;
|
||||
uint8_t ni;
|
||||
uint8_t si;
|
||||
uint8_t div_found;
|
||||
|
||||
/* Find a suitable divider */
|
||||
div_found = 0;
|
||||
for (div_num = 0; div_num <= 5; div_num++)
|
||||
{
|
||||
vco = freq << (div_num + 1);
|
||||
if (vco >= vco_min && vco <= vco_max)
|
||||
{
|
||||
div_found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!div_found)
|
||||
return -1;
|
||||
|
||||
vco += ref >> 16;
|
||||
ref <<= 8;
|
||||
mask = 1 << 23;
|
||||
rem = 0;
|
||||
while (mask > 0 && vco > 0)
|
||||
{
|
||||
if (vco >= ref)
|
||||
{
|
||||
rem |= mask;
|
||||
vco -= ref;
|
||||
}
|
||||
ref >>= 1;
|
||||
mask >>= 1;
|
||||
}
|
||||
|
||||
nint = rem >> 16;
|
||||
sdm = rem & 0xffff;
|
||||
|
||||
nint -= 13;
|
||||
ni = nint >> 2;
|
||||
si = nint & 3;
|
||||
|
||||
/* Set the phase splitter */
|
||||
rc = r820t_write_reg_mask(priv, 0x10, (uint8_t) (div_num << 5), 0xe0);
|
||||
if(rc < 0)
|
||||
return rc;
|
||||
|
||||
/* Set the integer part of the PLL */
|
||||
rc = r820t_write_reg(priv, 0x14, (uint8_t) (ni + (si << 6)));
|
||||
if(rc < 0)
|
||||
return rc;
|
||||
|
||||
if (sdm == 0)
|
||||
{
|
||||
/* Disable SDM */
|
||||
rc = r820t_write_reg_mask(priv, 0x12, 0x08, 0x08);
|
||||
if(rc < 0)
|
||||
return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Write SDM */
|
||||
rc = r820t_write_reg(priv, 0x15, (uint8_t)(sdm & 0xff));
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = r820t_write_reg(priv, 0x16, (uint8_t)(sdm >> 8));
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* Enable SDM */
|
||||
rc = r820t_write_reg_mask(priv, 0x12, 0x00, 0x08);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int r820t_set_freq(r820t_priv_t *priv, uint32_t freq)
|
||||
{
|
||||
int rc;
|
||||
uint32_t lo_freq = freq + priv->if_freq;
|
||||
|
||||
rc = r820t_set_tf(priv, freq);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = r820t_set_pll(priv, lo_freq);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
priv->freq = freq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int r820t_set_lna_gain(r820t_priv_t *priv, uint8_t gain_index)
|
||||
{
|
||||
return r820t_write_reg_mask(priv, 0x05, gain_index, 0x0f);
|
||||
}
|
||||
|
||||
int r820t_set_mixer_gain(r820t_priv_t *priv, uint8_t gain_index)
|
||||
{
|
||||
return r820t_write_reg_mask(priv, 0x07, gain_index, 0x0f);
|
||||
}
|
||||
|
||||
int r820t_set_vga_gain(r820t_priv_t *priv, uint8_t gain_index)
|
||||
{
|
||||
return r820t_write_reg_mask(priv, 0x0c, gain_index, 0x0f);
|
||||
}
|
||||
|
||||
int r820t_set_lna_agc(r820t_priv_t *priv, uint8_t value)
|
||||
{
|
||||
value = value != 0 ? 0x00 : 0x10;
|
||||
return r820t_write_reg_mask(priv, 0x05, value, 0x10);
|
||||
}
|
||||
|
||||
int r820t_set_mixer_agc(r820t_priv_t *priv, uint8_t value)
|
||||
{
|
||||
value = value != 0 ? 0x10 : 0x00;
|
||||
return r820t_write_reg_mask(priv, 0x07, value, 0x10);
|
||||
}
|
||||
|
||||
/*
|
||||
"inspired by Mauro Carvalho Chehab calibration technique"
|
||||
https://stuff.mit.edu/afs/sipb/contrib/linux/drivers/media/tuners/r820t.c
|
||||
part of r820t_set_tv_standard()
|
||||
*/
|
||||
int r820t_calibrate(r820t_priv_t *priv)
|
||||
{
|
||||
int i, rc, cal_code;
|
||||
uint8_t data[5];
|
||||
|
||||
for (i = 0; i < 5; i++)
|
||||
{
|
||||
/* Set filt_cap */
|
||||
rc = r820t_write_reg_mask(priv, 0x0b, 0x08, 0x60);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* set cali clk =on */
|
||||
rc = r820t_write_reg_mask(priv, 0x0f, 0x04, 0x04);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* X'tal cap 0pF for PLL */
|
||||
rc = r820t_write_reg_mask(priv, 0x10, 0x00, 0x03);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = r820t_set_pll(priv, CALIBRATION_LO * 1000);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* Start Trigger */
|
||||
rc = r820t_write_reg_mask(priv, 0x0b, 0x10, 0x10);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
/* Stop Trigger */
|
||||
rc = r820t_write_reg_mask(priv, 0x0b, 0x00, 0x10);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* set cali clk =off */
|
||||
rc = r820t_write_reg_mask(priv, 0x0f, 0x00, 0x04);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* Check if calibration worked */
|
||||
rc = r820t_read(priv, data, sizeof(data));
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
cal_code = data[4] & 0x0f;
|
||||
if (cal_code && cal_code != 0x0f)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int r820t_init(r820t_priv_t *priv, const uint32_t if_freq, r820t_write_reg_f write_reg, r820t_read_f read_reg, void* ctx)
|
||||
{
|
||||
int rc;
|
||||
uint32_t saved_freq;
|
||||
|
||||
r820t_state_standby = 0;
|
||||
priv->if_freq = if_freq;
|
||||
priv->write_reg = write_reg;
|
||||
priv->read_reg = read_reg;
|
||||
priv->ctx = ctx;
|
||||
/* Initialize registers */
|
||||
airspy_r820t_write_init(priv, priv->regs);
|
||||
|
||||
r820t_set_freq(priv, priv->freq);
|
||||
|
||||
/* Calibrate the IF filter */
|
||||
saved_freq = priv->freq;
|
||||
rc = r820t_calibrate(priv);
|
||||
priv->freq = saved_freq;
|
||||
if (rc < 0)
|
||||
{
|
||||
saved_freq = priv->freq;
|
||||
r820t_calibrate(priv);
|
||||
priv->freq = saved_freq;
|
||||
}
|
||||
|
||||
/* Restore freq as it has been modified by r820t_calibrate() */
|
||||
rc = r820t_set_freq(priv, priv->freq);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void r820t_set_if_bandwidth(r820t_priv_t *priv, uint8_t bw)
|
||||
{
|
||||
const uint8_t modes[] = { 0xE0, 0x80, 0x60, 0x00 };
|
||||
const uint8_t opt[] = { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
|
||||
uint8_t a = 0xB0 | opt[bw & 0x0F];
|
||||
uint8_t b = 0x0F | modes[bw >> 4];
|
||||
r820t_write_reg(priv, 0x0A, a);
|
||||
r820t_write_reg(priv, 0x0B, b);
|
||||
}
|
58
source_modules/badgesdr_source/src/r820t.h
Normal file
58
source_modules/badgesdr_source/src/r820t.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2013-2016 Benjamin Vernoux <bvernoux@airspy.com>
|
||||
*
|
||||
* This file is part of AirSpy.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define REG_SHADOW_START 5
|
||||
#define NUM_REGS 30
|
||||
|
||||
/* R820T Clock */
|
||||
#define CALIBRATION_LO 88000
|
||||
|
||||
typedef void (*r820t_write_reg_f)(uint8_t reg, uint8_t value, void* ctx);
|
||||
typedef void (*r820t_read_f)(uint8_t* data, int len, void* ctx);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t xtal_freq; /* XTAL_FREQ_HZ */
|
||||
uint32_t freq;
|
||||
uint32_t if_freq;
|
||||
uint8_t regs[NUM_REGS];
|
||||
uint16_t padding;
|
||||
|
||||
r820t_write_reg_f write_reg;
|
||||
r820t_read_f read_reg;
|
||||
void* ctx;
|
||||
|
||||
} r820t_priv_t;
|
||||
|
||||
void airspy_r820t_write_single(r820t_priv_t *priv, uint8_t reg, uint8_t val);
|
||||
uint8_t airspy_r820t_read_single(r820t_priv_t *priv, uint8_t reg);
|
||||
|
||||
int r820t_init(r820t_priv_t *priv, const uint32_t if_freq, r820t_write_reg_f write_reg, r820t_read_f read_reg, void* ctx);
|
||||
int r820t_set_freq(r820t_priv_t *priv, uint32_t freq);
|
||||
int r820t_set_lna_gain(r820t_priv_t *priv, uint8_t gain_index);
|
||||
int r820t_set_mixer_gain(r820t_priv_t *priv, uint8_t gain_index);
|
||||
int r820t_set_vga_gain(r820t_priv_t *priv, uint8_t gain_index);
|
||||
int r820t_set_lna_agc(r820t_priv_t *priv, uint8_t value);
|
||||
int r820t_set_mixer_agc(r820t_priv_t *priv, uint8_t value);
|
||||
void r820t_set_if_bandwidth(r820t_priv_t *priv, uint8_t bw);
|
21
source_modules/fobossdr_source/CMakeLists.txt
Normal file
21
source_modules/fobossdr_source/CMakeLists.txt
Normal file
@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(fobossdr_source)
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_link_directories(fobossdr_source PRIVATE "C:/Program Files/RigExpert/Fobos/lib/")
|
||||
target_include_directories(fobossdr_source PRIVATE "C:/Program Files/RigExpert/Fobos/include/")
|
||||
target_link_libraries(fobossdr_source PRIVATE fobos)
|
||||
else (MSVC)
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(LIBFOBOS REQUIRED libfobos)
|
||||
|
||||
target_include_directories(fobossdr_source PRIVATE ${LIBFOBOS_INCLUDE_DIRS})
|
||||
target_link_directories(fobossdr_source PRIVATE ${LIBFOBOS_LIBRARY_DIRS})
|
||||
target_link_libraries(fobossdr_source PRIVATE ${LIBFOBOS_LIBRARIES})
|
||||
endif ()
|
560
source_modules/fobossdr_source/src/main.cpp
Normal file
560
source_modules/fobossdr_source/src/main.cpp
Normal file
@ -0,0 +1,560 @@
|
||||
#include <imgui.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/smgui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <atomic>
|
||||
#include <fobos.h>
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "fobossdr_source",
|
||||
/* Description: */ "FobosSDR Source Module",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
// Work around for the fobos API not including
|
||||
#define FOBOS_LNA_GAIN_MIN 1
|
||||
#define FOBOS_LNA_GAIN_MAX 3
|
||||
#define FOBOS_VGA_GAIN_MIN 0
|
||||
#define FOBOS_VGA_GAIN_MAX 31
|
||||
|
||||
class FobosSDRSourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
FobosSDRSourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
sampleRate = 50000000.0;
|
||||
|
||||
// Initialize the DDC
|
||||
ddc.init(&ddcIn, 50e6, 50e6, 50e6, 0.0);
|
||||
|
||||
handler.ctx = this;
|
||||
handler.selectHandler = menuSelected;
|
||||
handler.deselectHandler = menuDeselected;
|
||||
handler.menuHandler = menuHandler;
|
||||
handler.startHandler = start;
|
||||
handler.stopHandler = stop;
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &ddc.out;
|
||||
|
||||
// Refresh devices
|
||||
refresh();
|
||||
|
||||
// Select device from config
|
||||
config.acquire();
|
||||
std::string devSerial = config.conf["device"];
|
||||
config.release();
|
||||
select(devSerial);
|
||||
|
||||
sigpath::sourceManager.registerSource("FobosSDR", &handler);
|
||||
}
|
||||
|
||||
~FobosSDRSourceModule() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
enum Port {
|
||||
PORT_RF,
|
||||
PORT_HF1,
|
||||
PORT_HF2
|
||||
};
|
||||
|
||||
private:
|
||||
std::string getBandwdithScaled(double bw) {
|
||||
char buf[1024];
|
||||
if (bw >= 1000000.0) {
|
||||
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
|
||||
}
|
||||
else if (bw >= 1000.0) {
|
||||
sprintf(buf, "%.1lfKHz", bw / 1000.0);
|
||||
}
|
||||
else {
|
||||
sprintf(buf, "%.1lfHz", bw);
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
devices.clear();
|
||||
|
||||
// Get device list
|
||||
char serials[1024];
|
||||
memset(serials, 0, sizeof(serials));
|
||||
int devCount = fobos_rx_list_devices(serials);
|
||||
if (devCount < 0) {
|
||||
flog::error("Failed to get device list: {}", devCount);
|
||||
return;
|
||||
}
|
||||
|
||||
// If no device, give up
|
||||
if (!devCount) { return; }
|
||||
|
||||
// Generate device entries
|
||||
const char* _serials = serials;
|
||||
int index = 0;
|
||||
while (*_serials) {
|
||||
// Read serial until space
|
||||
std::string serial = "";
|
||||
while (*_serials) {
|
||||
// Get a character
|
||||
char c = *(_serials++);
|
||||
|
||||
// If it's a space, we're done
|
||||
if (c == ' ') { break; }
|
||||
|
||||
// Otherwise, add it to the string
|
||||
serial += c;
|
||||
}
|
||||
|
||||
// Create entry
|
||||
devices.define(serial, serial, index++);
|
||||
}
|
||||
}
|
||||
|
||||
void select(const std::string& serial) {
|
||||
// If there are no devices, give up
|
||||
if (devices.empty()) {
|
||||
selectedSerial.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the serial was not found, select the first available serial
|
||||
if (!devices.keyExists(serial)) {
|
||||
select(devices.key(0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the ID in the list
|
||||
int id = devices.keyId(serial);
|
||||
selectedDevId = devices[id];
|
||||
|
||||
// Open the device
|
||||
fobos_dev_t* dev;
|
||||
int err = fobos_rx_open(&dev, selectedDevId);
|
||||
if (err) {
|
||||
flog::error("Failed to open device: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a list of supported samplerates
|
||||
double srList[128];
|
||||
unsigned int srCount;
|
||||
err = fobos_rx_get_samplerates(dev, srList, &srCount);
|
||||
if (err) {
|
||||
flog::error("Failed to get samplerate list: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate samplerate list
|
||||
samplerates.clear();
|
||||
for (int i = 0; i < srCount; i++) {
|
||||
std::string str = getBandwdithScaled(srList[i]);
|
||||
samplerates.define(srList[i], str, srList[i]);
|
||||
}
|
||||
|
||||
// Add some custom samplerates
|
||||
samplerates.define(5e6, "5.0MHz", 5e6);
|
||||
samplerates.define(2.5e6, "2.5MHz", 2.5e6);
|
||||
samplerates.define(1.25e6, "1.25MHz", 1.25e6);
|
||||
|
||||
// Define the ports
|
||||
ports.clear();
|
||||
ports.define("rf", "RF", PORT_RF);
|
||||
ports.define("hf1", "HF1", PORT_HF1);
|
||||
ports.define("hf2", "HF2", PORT_HF2);
|
||||
|
||||
// Define clock sources
|
||||
clockSources.clear();
|
||||
clockSources.define("internal", "Internal", 0);
|
||||
clockSources.define("external", "External", 1);
|
||||
|
||||
// Close the device
|
||||
fobos_rx_close(dev);
|
||||
|
||||
// Save serial number
|
||||
selectedSerial = serial;
|
||||
devId = id;
|
||||
|
||||
// Load default options
|
||||
sampleRate = 50e6;
|
||||
srId = samplerates.valueId(sampleRate);
|
||||
port = PORT_RF;
|
||||
portId = ports.valueId(port);
|
||||
clkSrcId = clockSources.nameId("Internal");
|
||||
lnaGain = 0;
|
||||
vgaGain = 0;
|
||||
|
||||
// Load config
|
||||
config.acquire();
|
||||
if (config.conf["devices"][selectedSerial].contains("samplerate")) {
|
||||
int desiredSr = config.conf["devices"][selectedSerial]["samplerate"];
|
||||
if (samplerates.keyExists(desiredSr)) {
|
||||
srId = samplerates.keyId(desiredSr);
|
||||
sampleRate = samplerates[srId];
|
||||
}
|
||||
}
|
||||
if (config.conf["devices"][selectedSerial].contains("port")) {
|
||||
std::string desiredPort = config.conf["devices"][selectedSerial]["port"];
|
||||
if (ports.keyExists(desiredPort)) {
|
||||
portId = ports.keyId(desiredPort);
|
||||
port = ports[portId];
|
||||
}
|
||||
}
|
||||
if (config.conf["devices"][selectedSerial].contains("clkSrc")) {
|
||||
std::string desiredClkSrc = config.conf["devices"][selectedSerial]["clkSrc"];
|
||||
if (clockSources.keyExists(desiredClkSrc)) {
|
||||
clkSrcId = clockSources.keyId(desiredClkSrc);
|
||||
}
|
||||
}
|
||||
if (config.conf["devices"][selectedSerial].contains("lnaGain")) {
|
||||
lnaGain = std::clamp<int>(config.conf["devices"][selectedSerial]["lnaGain"], FOBOS_LNA_GAIN_MIN, FOBOS_LNA_GAIN_MAX);
|
||||
}
|
||||
if (config.conf["devices"][selectedSerial].contains("vgaGain")) {
|
||||
vgaGain = std::clamp<int>(config.conf["devices"][selectedSerial]["vgaGain"], FOBOS_VGA_GAIN_MIN, FOBOS_VGA_GAIN_MAX);
|
||||
}
|
||||
config.release();
|
||||
|
||||
// Update the samplerate
|
||||
core::setInputSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
flog::info("FobosSDRSourceModule '{0}': Menu Select!", _this->name);
|
||||
}
|
||||
|
||||
static void menuDeselected(void* ctx) {
|
||||
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
|
||||
flog::info("FobosSDRSourceModule '{0}': Menu Deselect!", _this->name);
|
||||
}
|
||||
|
||||
static void start(void* ctx) {
|
||||
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
|
||||
if (_this->running) { return; }
|
||||
|
||||
// Open the device
|
||||
int err = fobos_rx_open(&_this->openDev, _this->selectedDevId);
|
||||
if (err) {
|
||||
flog::error("Failed to open device: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the selected port
|
||||
_this->port = _this->ports[_this->portId];
|
||||
|
||||
// Configure the device
|
||||
double actualSr, actualFreq;
|
||||
fobos_rx_set_samplerate(_this->openDev, (_this->sampleRate >= 50e6) ? _this->sampleRate : 50e6, &actualSr);
|
||||
fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq);
|
||||
fobos_rx_set_direct_sampling(_this->openDev, _this->port != PORT_RF);
|
||||
fobos_rx_set_clk_source(_this->openDev, _this->clockSources[_this->clkSrcId]);
|
||||
fobos_rx_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
fobos_rx_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
|
||||
// Configure the DDC
|
||||
if (_this->port == PORT_RF && _this->sampleRate >= 50e6) {
|
||||
// Set the frequency
|
||||
fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq);
|
||||
}
|
||||
else if (_this->port == PORT_RF) {
|
||||
// Set the frequency
|
||||
fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq);
|
||||
|
||||
// Configure and start the DDC for decimation only
|
||||
_this->ddc.setInSamplerate(actualSr);
|
||||
_this->ddc.setOutSamplerate(_this->sampleRate, _this->sampleRate);
|
||||
_this->ddc.setOffset(0.0);
|
||||
_this->ddc.start();
|
||||
}
|
||||
else {
|
||||
// Configure and start the DDC
|
||||
_this->ddc.setInSamplerate(actualSr);
|
||||
_this->ddc.setOutSamplerate(_this->sampleRate, _this->sampleRate);
|
||||
_this->ddc.setOffset(_this->freq);
|
||||
_this->ddc.start();
|
||||
}
|
||||
|
||||
// Compute buffer size (Lower than usual, but it's a workaround for their API having broken streaming)
|
||||
_this->bufferSize = _this->sampleRate / 400.0;
|
||||
|
||||
// Start streaming
|
||||
err = fobos_rx_start_sync(_this->openDev, _this->bufferSize);
|
||||
if (err) {
|
||||
flog::error("Failed to start stream: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start worker
|
||||
_this->run = true;
|
||||
_this->workerThread = std::thread(&FobosSDRSourceModule::worker, _this);
|
||||
|
||||
_this->running = true;
|
||||
flog::info("FobosSDRSourceModule '{0}': Start!", _this->name);
|
||||
}
|
||||
|
||||
static void stop(void* ctx) {
|
||||
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
|
||||
if (!_this->running) { return; }
|
||||
_this->running = false;
|
||||
|
||||
// Stop worker
|
||||
_this->run = false;
|
||||
if (_this->port == PORT_RF && _this->sampleRate >= 50e6) {
|
||||
_this->ddc.out.stopWriter();
|
||||
if (_this->workerThread.joinable()) { _this->workerThread.join(); }
|
||||
_this->ddc.out.clearWriteStop();
|
||||
}
|
||||
else {
|
||||
_this->ddcIn.stopWriter();
|
||||
if (_this->workerThread.joinable()) { _this->workerThread.join(); }
|
||||
_this->ddcIn.clearWriteStop();
|
||||
}
|
||||
|
||||
// Stop streaming
|
||||
fobos_rx_stop_sync(_this->openDev);
|
||||
|
||||
// Stop the DDC
|
||||
_this->ddc.stop();
|
||||
|
||||
// Close the device
|
||||
fobos_rx_close(_this->openDev);
|
||||
|
||||
flog::info("FobosSDRSourceModule '{0}': Stop!", _this->name);
|
||||
}
|
||||
|
||||
static void tune(double freq, void* ctx) {
|
||||
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
if (_this->port == PORT_RF) {
|
||||
double actual; // Dummy, don't care
|
||||
fobos_rx_set_frequency(_this->openDev, freq, &actual);
|
||||
}
|
||||
else {
|
||||
_this->ddc.setOffset(freq);
|
||||
}
|
||||
}
|
||||
_this->freq = freq;
|
||||
flog::info("FobosSDRSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
|
||||
|
||||
if (_this->running) { SmGui::BeginDisabled(); }
|
||||
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo(CONCAT("##_fobossdr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
|
||||
_this->select(_this->devices.key(_this->devId));
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
config.acquire();
|
||||
config.conf["device"] = _this->selectedSerial;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
if (SmGui::Combo(CONCAT("##_fobossdr_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) {
|
||||
_this->sampleRate = _this->samplerates.value(_this->srId);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (!_this->selectedSerial.empty()) {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["samplerate"] = _this->samplerates.key(_this->srId);
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
SmGui::SameLine();
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Button(CONCAT("Refresh##_fobossdr_refr_", _this->name))) {
|
||||
_this->refresh();
|
||||
_this->select(_this->selectedSerial);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("Antenna Port");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_fobossdr_port_", _this->name), &_this->portId, _this->ports.txt)) {
|
||||
if (!_this->selectedSerial.empty()) {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["port"] = _this->ports.key(_this->portId);
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (_this->running) { SmGui::EndDisabled(); }
|
||||
|
||||
SmGui::LeftLabel("Clock Source");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_fobossdr_clk_", _this->name), &_this->clkSrcId, _this->clockSources.txt)) {
|
||||
if (_this->running) {
|
||||
fobos_rx_set_clk_source(_this->openDev, _this->clockSources[_this->clkSrcId]);
|
||||
}
|
||||
if (!_this->selectedSerial.empty()) {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["clkSrc"] = _this->clockSources.key(_this->clkSrcId);
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (_this->port == PORT_RF) {
|
||||
SmGui::LeftLabel("LNA Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_fobossdr_lna_gain_", _this->name), &_this->lnaGain, FOBOS_LNA_GAIN_MIN, FOBOS_LNA_GAIN_MAX)) {
|
||||
if (_this->running) {
|
||||
fobos_rx_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
if (!_this->selectedSerial.empty()) {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["lnaGain"] = _this->lnaGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("VGA Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_fobossdr_vga_gain_", _this->name), &_this->vgaGain, FOBOS_VGA_GAIN_MIN, FOBOS_VGA_GAIN_MAX)) {
|
||||
if (_this->running) {
|
||||
fobos_rx_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
}
|
||||
if (!_this->selectedSerial.empty()) {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["vgaGain"] = _this->vgaGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void worker() {
|
||||
// Select different processing depending on the mode
|
||||
if (port == PORT_RF && sampleRate >= 50e6) {
|
||||
while (run) {
|
||||
// Read samples
|
||||
unsigned int sampCount = 0;
|
||||
int err = fobos_rx_read_sync(openDev, (float*)ddc.out.writeBuf, &sampCount);
|
||||
if (err) { break; }
|
||||
|
||||
// Send out samples to the core
|
||||
if (!ddc.out.swap(sampCount)) { break; }
|
||||
}
|
||||
}
|
||||
else if (port == PORT_RF) {
|
||||
while (run) {
|
||||
// Read samples
|
||||
unsigned int sampCount = 0;
|
||||
int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount);
|
||||
if (err) { break; }
|
||||
|
||||
// Send samples to the DDC
|
||||
if (!ddcIn.swap(sampCount)) { break; }
|
||||
}
|
||||
}
|
||||
else if (port == PORT_HF1) {
|
||||
while (run) {
|
||||
// Read samples
|
||||
unsigned int sampCount = 0;
|
||||
int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount);
|
||||
if (err) { break; }
|
||||
|
||||
// Null out the HF2 samples
|
||||
for (int i = 0; i < sampCount; i++) {
|
||||
ddcIn.writeBuf[i].im = 0.0f;
|
||||
}
|
||||
|
||||
// Send samples to the DDC
|
||||
if (!ddcIn.swap(sampCount)) { break; }
|
||||
}
|
||||
}
|
||||
else if (port == PORT_HF2) {
|
||||
while (run) {
|
||||
// Read samples
|
||||
unsigned int sampCount = 0;
|
||||
int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount);
|
||||
if (err) { break; }
|
||||
|
||||
// Null out the HF2 samples
|
||||
for (int i = 0; i < sampCount; i++) {
|
||||
ddcIn.writeBuf[i].re = 0.0f;
|
||||
}
|
||||
|
||||
// Send samples to the DDC
|
||||
if (!ddcIn.swap(sampCount)) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
double sampleRate;
|
||||
SourceManager::SourceHandler handler;
|
||||
bool running = false;
|
||||
double freq;
|
||||
|
||||
OptionList<std::string, int> devices;
|
||||
OptionList<int, double> samplerates;
|
||||
OptionList<std::string, Port> ports;
|
||||
OptionList<std::string, int> clockSources;
|
||||
int devId = 0;
|
||||
int srId = 0;
|
||||
int portId = 0;
|
||||
int clkSrcId = 0;
|
||||
Port port;
|
||||
int lnaGain = 0;
|
||||
int vgaGain = 0;
|
||||
std::string selectedSerial;
|
||||
int selectedDevId;
|
||||
|
||||
fobos_dev_t* openDev;
|
||||
|
||||
int bufferSize;
|
||||
std::thread workerThread;
|
||||
std::atomic<bool> run = false;
|
||||
|
||||
dsp::stream<dsp::complex_t> ddcIn;
|
||||
dsp::channel::RxVFO ddc;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
json def = json({});
|
||||
def["devices"] = json({});
|
||||
def["device"] = "";
|
||||
config.setPath(core::args["root"].s() + "/fobossdr_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new FobosSDRSourceModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (FobosSDRSourceModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
@ -304,6 +304,7 @@ private:
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo(CONCAT("##_hackrf_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) {
|
||||
_this->selectBySerial(_this->devList[_this->devId]);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
config.acquire();
|
||||
config.conf["device"] = _this->selectedSerial;
|
||||
config.release(true);
|
||||
|
17
source_modules/harogic_source/CMakeLists.txt
Normal file
17
source_modules/harogic_source/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(harogic_source)
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_link_directories(harogic_source PRIVATE "C:/radio/HTRA_API/x64/htra_api/")
|
||||
target_include_directories(harogic_source PRIVATE "C:/radio/HTRA_API/x64/htra_api/")
|
||||
target_link_libraries(harogic_source PRIVATE htra_api)
|
||||
else (MSVC)
|
||||
target_link_directories(harogic_source PRIVATE "/opt/htraapi/lib/${CMAKE_SYSTEM_PROCESSOR}/")
|
||||
target_include_directories(harogic_source PRIVATE "/opt/htraapi/inc/")
|
||||
target_link_libraries(harogic_source PRIVATE htraapi)
|
||||
endif ()
|
510
source_modules/harogic_source/src/main.cpp
Normal file
510
source_modules/harogic_source/src/main.cpp
Normal file
@ -0,0 +1,510 @@
|
||||
#include <imgui.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/smgui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <htra_api.h>
|
||||
#include <atomic>
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "harogic_source",
|
||||
/* Description: */ "harogic Source Module",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
class HarogicSourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
HarogicSourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
sampleRate = 61440000.0;
|
||||
|
||||
handler.ctx = this;
|
||||
handler.selectHandler = menuSelected;
|
||||
handler.deselectHandler = menuDeselected;
|
||||
handler.menuHandler = menuHandler;
|
||||
handler.startHandler = start;
|
||||
handler.stopHandler = stop;
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &stream;
|
||||
|
||||
// Refresh devices
|
||||
refresh();
|
||||
|
||||
// Select first (TODO: Select from config)
|
||||
select("");
|
||||
|
||||
sigpath::sourceManager.registerSource("Harogic", &handler);
|
||||
}
|
||||
|
||||
~HarogicSourceModule() {
|
||||
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
void refresh() {
|
||||
devices.clear();
|
||||
|
||||
// Set up the device parameters
|
||||
BootProfile_TypeDef profile = {};
|
||||
profile.PhysicalInterface = PhysicalInterface_TypeDef::USB;
|
||||
profile.DevicePowerSupply = DevicePowerSupply_TypeDef::USBPortOnly;
|
||||
|
||||
// Working variables
|
||||
void* dev;
|
||||
BootInfo_TypeDef binfo;
|
||||
|
||||
for (int i = 0; i < 128; i++) {
|
||||
// Attempt to open the device with the given ID
|
||||
int ret = Device_Open(&dev, i, &profile, &binfo);
|
||||
if (ret < 0) { break; }
|
||||
|
||||
// Create serial string
|
||||
char serial[64];
|
||||
sprintf(serial, "%" PRIX64, binfo.DeviceInfo.DeviceUID);
|
||||
|
||||
// Add the device to the list
|
||||
devices.define(serial, serial, i);
|
||||
|
||||
// Close the device
|
||||
Device_Close(&dev);
|
||||
}
|
||||
}
|
||||
|
||||
void select(const std::string& serial) {
|
||||
// If there are no devices, give up
|
||||
if (devices.empty()) {
|
||||
selectedSerial.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the serial was not found, select the first available serial
|
||||
if (!devices.keyExists(serial)) {
|
||||
select(devices.key(0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the menu ID
|
||||
devId = devices.keyId(serial);
|
||||
selectedDevIndex = devices.value(devId);
|
||||
|
||||
// Set up the device parameters
|
||||
BootProfile_TypeDef bprofile = {};
|
||||
bprofile.PhysicalInterface = PhysicalInterface_TypeDef::USB;
|
||||
bprofile.DevicePowerSupply = DevicePowerSupply_TypeDef::USBPortOnly;
|
||||
|
||||
// Working variables
|
||||
BootInfo_TypeDef binfo;
|
||||
|
||||
// Attempt to open the device by ID
|
||||
void* dev;
|
||||
int ret = Device_Open(&dev, selectedDevIndex, &bprofile, &binfo);
|
||||
if (ret < 0) {
|
||||
flog::error("Could not open device: {}", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the default streaming parameters to query some info
|
||||
IQS_Profile_TypeDef profile;
|
||||
IQS_ProfileDeInit(&dev, &profile);
|
||||
|
||||
// Compute all available samplerates
|
||||
samplerates.clear();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
double sr = profile.NativeIQSampleRate_SPS / (double)(1 << i);
|
||||
char buf[128];
|
||||
sprintf(buf, "%.02fMHz", sr / 1e6);
|
||||
samplerates.define(1 << i, buf, sr);
|
||||
}
|
||||
|
||||
// Define RX ports
|
||||
rxPorts.clear();
|
||||
rxPorts.define("external", "External", ExternalPort);
|
||||
rxPorts.define("internal", "Internal", InternalPort);
|
||||
rxPorts.define("ant", "ANT", ANT_Port);
|
||||
rxPorts.define("tr", "T/R", TR_Port);
|
||||
rxPorts.define("swr", "SWR", SWR_Port);
|
||||
rxPorts.define("int", "INT", INT_Port);
|
||||
|
||||
// Define gain strategies
|
||||
gainStategies.clear();
|
||||
gainStategies.define("lowNoise", "Low Noise", LowNoisePreferred);
|
||||
gainStategies.define("highLinearity", "High Linearity", HighLinearityPreferred);
|
||||
|
||||
// Define preamplifier modes
|
||||
preampModes.clear();
|
||||
preampModes.define("auto", "Auto", AutoOn);
|
||||
preampModes.define("off", "Off", ForcedOff);
|
||||
|
||||
// Define LO modes
|
||||
loModes.clear();
|
||||
loModes.define("auto", "Auto", LOOpt_Auto);
|
||||
loModes.define("speed", "Speed", LOOpt_Speed);
|
||||
loModes.define("spurs", "Spurs", LOOpt_Spur);
|
||||
loModes.define("phaseNoise", "Phase Noise", LOOpt_PhaseNoise);
|
||||
|
||||
// Close the device
|
||||
Device_Close(&dev);
|
||||
|
||||
// TODO: Load configuration
|
||||
sampleRate = samplerates.value(0);
|
||||
refLvl = 0;
|
||||
portId = rxPorts.valueId(ExternalPort);
|
||||
gainStratId = gainStategies.valueId(LowNoisePreferred);
|
||||
preampModeId = preampModes.valueId(AutoOn);
|
||||
ifAgc = false;
|
||||
loModeId = loModes.valueId(LOOpt_Auto);
|
||||
|
||||
// Update the samplerate
|
||||
core::setInputSampleRate(sampleRate);
|
||||
|
||||
// Save serial number
|
||||
selectedSerial = serial;
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
HarogicSourceModule* _this = (HarogicSourceModule*)ctx;
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
flog::info("HarogicSourceModule '{0}': Menu Select!", _this->name);
|
||||
}
|
||||
|
||||
static void menuDeselected(void* ctx) {
|
||||
HarogicSourceModule* _this = (HarogicSourceModule*)ctx;
|
||||
flog::info("HarogicSourceModule '{0}': Menu Deselect!", _this->name);
|
||||
}
|
||||
|
||||
static void start(void* ctx) {
|
||||
HarogicSourceModule* _this = (HarogicSourceModule*)ctx;
|
||||
if (_this->running) { return; }
|
||||
|
||||
// Set up the device parameters
|
||||
BootProfile_TypeDef bprofile = {};
|
||||
bprofile.PhysicalInterface = PhysicalInterface_TypeDef::USB;
|
||||
bprofile.DevicePowerSupply = DevicePowerSupply_TypeDef::USBPortAndPowerPort;
|
||||
|
||||
// Working variables
|
||||
BootInfo_TypeDef binfo;
|
||||
|
||||
// Attempt to open the device by ID
|
||||
int ret = Device_Open(&_this->openDev, _this->selectedDevIndex, &bprofile, &binfo);
|
||||
if (ret < 0) {
|
||||
flog::error("Could not open device: {}", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the decimation amount
|
||||
int dec = _this->samplerates.key(_this->samplerates.valueId(_this->sampleRate));
|
||||
flog::debug("Using decimation factor: {}", dec);
|
||||
|
||||
// Decide to use either 8 or 16bit samples
|
||||
_this->sampsInt8 = (_this->sampleRate > 64e6);
|
||||
|
||||
// Get the default configuration
|
||||
IQS_ProfileDeInit(&_this->openDev, &_this->profile);
|
||||
|
||||
// Automatic configuration
|
||||
_this->profile.Atten = -1;
|
||||
_this->profile.BusTimeout_ms = 100;
|
||||
_this->profile.TriggerSource = Bus;
|
||||
_this->profile.TriggerMode = Adaptive;
|
||||
_this->profile.DataFormat = _this->sampsInt8 ? Complex8bit : Complex16bit;
|
||||
|
||||
// User selectable config
|
||||
_this->profile.CenterFreq_Hz = _this->freq;
|
||||
_this->profile.RefLevel_dBm = _this->refLvl;
|
||||
_this->profile.DecimateFactor = dec;
|
||||
_this->profile.RxPort = _this->rxPorts.value(_this->portId);
|
||||
_this->profile.GainStrategy = _this->gainStategies.value(_this->gainStratId);
|
||||
_this->profile.Preamplifier = _this->preampModes.value(_this->preampModeId);
|
||||
_this->profile.EnableIFAGC = _this->ifAgc;
|
||||
_this->profile.LOOptimization = _this->loModes.value(_this->loModeId);
|
||||
|
||||
// Apply the configuration
|
||||
IQS_StreamInfo_TypeDef info;
|
||||
ret = IQS_Configuration(&_this->openDev, &_this->profile, &_this->profile, &info);
|
||||
if (ret < 0) {
|
||||
flog::error("Could not configure device: {}", ret);
|
||||
Device_Close(&_this->openDev);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the stream configuration
|
||||
_this->bufferSize = info.PacketSamples;
|
||||
flog::debug("Got buffer size: {}", _this->bufferSize);
|
||||
|
||||
// Start the stream
|
||||
ret = IQS_BusTriggerStart(&_this->openDev);
|
||||
if (ret < 0) {
|
||||
flog::error("Could not start stream: {}", ret);
|
||||
Device_Close(&_this->openDev);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start worker
|
||||
_this->run = true;
|
||||
_this->workerThread = std::thread(&HarogicSourceModule::worker, _this);
|
||||
|
||||
_this->running = true;
|
||||
flog::info("HarogicSourceModule '{0}': Start!", _this->name);
|
||||
}
|
||||
|
||||
static void stop(void* ctx) {
|
||||
HarogicSourceModule* _this = (HarogicSourceModule*)ctx;
|
||||
if (!_this->running) { return; }
|
||||
_this->running = false;
|
||||
|
||||
// Stop worker
|
||||
_this->run = false;
|
||||
_this->stream.stopWriter();
|
||||
if (_this->workerThread.joinable()) { _this->workerThread.join(); }
|
||||
_this->stream.clearWriteStop();
|
||||
|
||||
// Stop the stream
|
||||
IQS_BusTriggerStop(&_this->openDev);
|
||||
|
||||
// Close the device
|
||||
Device_Close(&_this->openDev);
|
||||
|
||||
flog::info("HarogicSourceModule '{0}': Stop!", _this->name);
|
||||
}
|
||||
|
||||
static void tune(double freq, void* ctx) {
|
||||
HarogicSourceModule* _this = (HarogicSourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
// Update the frequency in the configuration
|
||||
_this->profile.CenterFreq_Hz = freq;
|
||||
_this->applyProfile();
|
||||
}
|
||||
_this->freq = freq;
|
||||
flog::info("HarogicSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
HarogicSourceModule* _this = (HarogicSourceModule*)ctx;
|
||||
|
||||
if (_this->running) { SmGui::BeginDisabled(); }
|
||||
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo(CONCAT("##_harogic_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
|
||||
_this->select(_this->devices.key(_this->devId));
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
if (SmGui::Combo(CONCAT("##_harogic_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) {
|
||||
_this->sampleRate = _this->samplerates.value(_this->srId);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::SameLine();
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Button(CONCAT("Refresh##_harogic_refr_", _this->name))) {
|
||||
_this->refresh();
|
||||
_this->select(_this->selectedSerial);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
if (_this->running) { SmGui::EndDisabled(); }
|
||||
|
||||
SmGui::LeftLabel("RX Port");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_harogic_port_", _this->name), &_this->portId, _this->rxPorts.txt)) {
|
||||
if (_this->running) {
|
||||
_this->profile.RxPort = _this->rxPorts.value(_this->portId);
|
||||
_this->applyProfile();
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("LO Mode");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_lo_mode_", _this->name), &_this->loModeId, _this->loModes.txt)) {
|
||||
if (_this->running) {
|
||||
_this->profile.LOOptimization = _this->loModes.value(_this->loModeId);
|
||||
_this->applyProfile();
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("Gain Mode");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_harogic_gain_mode_", _this->name), &_this->gainStratId, _this->gainStategies.txt)) {
|
||||
if (_this->running) {
|
||||
_this->profile.GainStrategy = _this->gainStategies.value(_this->gainStratId);
|
||||
_this->applyProfile();
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("Preamp Mode");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_harogic_preamp_mode_", _this->name), &_this->preampModeId, _this->preampModes.txt)) {
|
||||
if (_this->running) {
|
||||
_this->profile.Preamplifier = _this->preampModes.value(_this->preampModeId);
|
||||
_this->applyProfile();
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("Reference");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_harogic_ref_", _this->name), &_this->refLvl, _this->minRef, _this->maxRef)) {
|
||||
if (_this->running) {
|
||||
_this->profile.RefLevel_dBm = _this->refLvl;
|
||||
_this->applyProfile();
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
if (SmGui::Checkbox(CONCAT("IF AGC##_harogic_if_agc_", _this->name), &_this->ifAgc)) {
|
||||
if (_this->running) {
|
||||
_this->profile.EnableIFAGC = _this->ifAgc;
|
||||
_this->applyProfile();
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
}
|
||||
|
||||
void applyProfile() {
|
||||
// Acquire device
|
||||
std::lock_guard<std::mutex> lck(devMtx);
|
||||
|
||||
// Configure the device
|
||||
IQS_StreamInfo_TypeDef info;
|
||||
int ret = IQS_Configuration(&openDev, &profile, &profile, &info);
|
||||
if (ret < 0) {
|
||||
flog::error("Failed to apply tuning config: {}", ret);
|
||||
}
|
||||
|
||||
// Re-trigger the stream
|
||||
ret = IQS_BusTriggerStart(&openDev);
|
||||
if (ret < 0) {
|
||||
flog::error("Could not start stream: {}", ret);
|
||||
}
|
||||
}
|
||||
|
||||
void worker() {
|
||||
// Allocate sample buffer
|
||||
int realSamps = bufferSize*2;
|
||||
IQStream_TypeDef iqs;
|
||||
|
||||
// Define number of buffers per swap to maintain 200 fps
|
||||
int maxBufCount = STREAM_BUFFER_SIZE / bufferSize;
|
||||
int bufCount = (sampleRate / bufferSize) / 200;
|
||||
if (bufCount <= 0) { bufCount = 1; }
|
||||
if (bufCount > maxBufCount) { bufCount = maxBufCount; }
|
||||
int count = 0;
|
||||
|
||||
flog::debug("Swapping will be done {} buffers at a time", bufCount);
|
||||
|
||||
// Worker loop
|
||||
while (run) {
|
||||
// Read samples
|
||||
devMtx.lock();
|
||||
int ret = IQS_GetIQStream_PM1(&openDev, &iqs);
|
||||
devMtx.unlock();
|
||||
if (ret < 0) {
|
||||
if (ret == APIRETVAL_WARNING_BusTimeOut) {
|
||||
flog::warn("Stream timed out");
|
||||
continue;
|
||||
}
|
||||
else if (ret <= APIRETVAL_WARNING_IFOverflow && ret >= APIRETVAL_WARNING_ADCConfigError) {
|
||||
// Just warnings, do nothing
|
||||
}
|
||||
else {
|
||||
flog::error("Streaming error: {}", ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert them to floating point
|
||||
if (sampsInt8) {
|
||||
volk_8i_s32f_convert_32f((float*)&stream.writeBuf[(count++)*bufferSize], (int8_t*)iqs.AlternIQStream, 128.0f, realSamps);
|
||||
}
|
||||
else {
|
||||
volk_16i_s32f_convert_32f((float*)&stream.writeBuf[(count++)*bufferSize], (int16_t*)iqs.AlternIQStream, 32768.0f, realSamps);
|
||||
}
|
||||
|
||||
// Send them off if we have enough
|
||||
if (count >= bufCount) {
|
||||
count = 0;
|
||||
if (!stream.swap(bufferSize*bufCount)) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
dsp::stream<dsp::complex_t> stream;
|
||||
double sampleRate;
|
||||
SourceManager::SourceHandler handler;
|
||||
bool running = false;
|
||||
double freq;
|
||||
|
||||
OptionList<std::string, int> devices;
|
||||
OptionList<int, double> samplerates;
|
||||
OptionList<std::string, RxPort_TypeDef> rxPorts;
|
||||
OptionList<std::string, GainStrategy_TypeDef> gainStategies;
|
||||
OptionList<std::string, PreamplifierState_TypeDef> preampModes;
|
||||
OptionList<std::string, LOOptimization_TypeDef> loModes;
|
||||
int devId = 0;
|
||||
int srId = 0;
|
||||
int refLvl = -30;
|
||||
int minRef = -100;
|
||||
int maxRef = 7;
|
||||
int portId = 0;
|
||||
int gainStratId = 0;
|
||||
int preampModeId = 0;
|
||||
int loModeId = 0;
|
||||
bool ifAgc = false;
|
||||
std::string selectedSerial;
|
||||
int selectedDevIndex;
|
||||
|
||||
void* openDev;
|
||||
IQS_Profile_TypeDef profile;
|
||||
|
||||
int bufferSize;
|
||||
std::thread workerThread;
|
||||
std::atomic<bool> run = false;
|
||||
std::mutex devMtx;
|
||||
bool sampsInt8;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new HarogicSourceModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (HarogicSourceModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
// Nothing here
|
||||
}
|
@ -118,7 +118,6 @@ private:
|
||||
|
||||
// Update host samplerate
|
||||
sampleRate = samplerates.key(srId);
|
||||
core::setInputSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
@ -199,6 +198,7 @@ private:
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo(CONCAT("##_hermes_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
|
||||
_this->selectMac(_this->devices.key(_this->devId));
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (!_this->selectedMac.empty()) {
|
||||
config.acquire();
|
||||
config.conf["device"] = _this->devices.key(_this->devId);
|
||||
@ -225,6 +225,7 @@ private:
|
||||
std::string mac = config.conf["device"];
|
||||
config.release();
|
||||
_this->selectMac(mac);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
if (_this->running) { SmGui::EndDisabled(); }
|
||||
|
@ -210,7 +210,6 @@ public:
|
||||
|
||||
// Update samplerate
|
||||
sampleRate = srList[srId];
|
||||
core::setInputSampleRate(sampleRate);
|
||||
|
||||
// Close device
|
||||
perseus_close(dev);
|
||||
@ -329,6 +328,7 @@ private:
|
||||
if (SmGui::Combo(CONCAT("##_airspyhf_dev_sel_", _this->name), &_this->devId, _this->devList.txt)) {
|
||||
std::string serial = _this->devList.key(_this->devId);
|
||||
_this->select(serial);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
config.acquire();
|
||||
config.conf["device"] = serial;
|
||||
config.release(true);
|
||||
|
@ -244,9 +244,6 @@ private:
|
||||
bwId = 0;
|
||||
bandwidth = bandwidths.value(bwId);
|
||||
}
|
||||
|
||||
// Update core samplerate
|
||||
core::setInputSampleRate(samplerate);
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
@ -351,6 +348,7 @@ private:
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo("##plutosdr_dev_sel", &_this->devId, _this->devices.txt)) {
|
||||
_this->select(_this->devices.key(_this->devId));
|
||||
core::setInputSampleRate(_this->samplerate);
|
||||
config.acquire();
|
||||
config.conf["device"] = _this->devices.key(_this->devId);
|
||||
config.release(true);
|
||||
@ -373,7 +371,7 @@ private:
|
||||
if (SmGui::Button(CONCAT("Refresh##_pluto_refr_", _this->name))) {
|
||||
_this->refresh();
|
||||
_this->select(_this->devDesc);
|
||||
|
||||
core::setInputSampleRate(_this->samplerate);
|
||||
}
|
||||
if (_this->running) { SmGui::EndDisabled(); }
|
||||
|
||||
|
21
source_modules/rfnm_source/CMakeLists.txt
Normal file
21
source_modules/rfnm_source/CMakeLists.txt
Normal file
@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(rfnm_source)
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_link_directories(rfnm_source PRIVATE "C:/Program Files/RFNM/lib/")
|
||||
target_include_directories(rfnm_source PUBLIC "C:/Program Files/RFNM/include/")
|
||||
target_link_libraries(rfnm_source PRIVATE rfnm)
|
||||
else (MSVC)
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(LIBRFNM REQUIRED librfnm)
|
||||
|
||||
target_include_directories(rfnm_source PRIVATE ${LIBRFNM_INCLUDE_DIRS})
|
||||
target_link_directories(rfnm_source PRIVATE ${LIBRFNM_LIBRARY_DIRS})
|
||||
target_link_libraries(rfnm_source PRIVATE ${LIBRFNM_LIBRARIES})
|
||||
endif ()
|
512
source_modules/rfnm_source/src/main.cpp
Normal file
512
source_modules/rfnm_source/src/main.cpp
Normal file
@ -0,0 +1,512 @@
|
||||
#include <imgui.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/smgui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <librfnm/librfnm.h>
|
||||
#include <core.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <atomic>
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "rfnm_source",
|
||||
/* Description: */ "RFNM Source Module",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
class RFNMSourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
RFNMSourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
sampleRate = 61440000.0;
|
||||
|
||||
handler.ctx = this;
|
||||
handler.selectHandler = menuSelected;
|
||||
handler.deselectHandler = menuDeselected;
|
||||
handler.menuHandler = menuHandler;
|
||||
handler.startHandler = start;
|
||||
handler.stopHandler = stop;
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &stream;
|
||||
|
||||
// Refresh devices
|
||||
refresh();
|
||||
|
||||
// Select first (TODO: Select from config)
|
||||
select("");
|
||||
|
||||
sigpath::sourceManager.registerSource("RFNM", &handler);
|
||||
}
|
||||
|
||||
~RFNMSourceModule() {
|
||||
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
void refresh() {
|
||||
devices.clear();
|
||||
auto list = librfnm::find(librfnm_transport::LIBRFNM_TRANSPORT_USB);
|
||||
for (const auto& info : list) {
|
||||
// Format device name
|
||||
std::string devName = "RFNM ";
|
||||
devName += info.motherboard.user_readable_name;
|
||||
devName += " [";
|
||||
devName += (char*)info.motherboard.serial_number;
|
||||
devName += ']';
|
||||
|
||||
// Save device
|
||||
devices.define((char*)info.motherboard.serial_number, devName, (char*)info.motherboard.serial_number);
|
||||
}
|
||||
}
|
||||
|
||||
void select(const std::string& serial) {
|
||||
// If there are no devices, give up
|
||||
if (devices.empty()) {
|
||||
selectedSerial.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the serial was not found, select the first available serial
|
||||
if (!devices.keyExists(serial)) {
|
||||
select(devices.key(0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the device
|
||||
librfnm* dev = new librfnm(librfnm_transport::LIBRFNM_TRANSPORT_USB, serial);
|
||||
|
||||
// Define samplerates
|
||||
samplerates.clear();
|
||||
samplerates.define(61440000, "61.44 MHz", 2);
|
||||
samplerates.define(122880000, "122.88 MHz", 1);
|
||||
|
||||
// Define daughterboards
|
||||
daughterboards.clear();
|
||||
for (int i = 0; i < 2; i++) {
|
||||
// If not present, skip
|
||||
if (!dev->s->hwinfo.daughterboard[i].board_id) { continue; }
|
||||
|
||||
// Format the daughterboard name
|
||||
std::string name = (i ? "[SEC] " : "[PRI] ") + std::string(dev->s->hwinfo.daughterboard[i].user_readable_name);
|
||||
|
||||
// Add the daughterboard to the list
|
||||
daughterboards.define(name, name, i);
|
||||
}
|
||||
|
||||
// Load options (TODO)
|
||||
srId = samplerates.keyId(61440000);
|
||||
dgbId = 0;
|
||||
|
||||
// Select the daughterboard
|
||||
selectDaughterboard(dev, 0);
|
||||
|
||||
// Update samplerate
|
||||
sampleRate = samplerates.key(srId);
|
||||
|
||||
// Close device
|
||||
delete dev;
|
||||
|
||||
// Save serial number
|
||||
selectedSerial = serial;
|
||||
}
|
||||
|
||||
struct PathConfig {
|
||||
rfnm_rf_path path;
|
||||
int chId;
|
||||
uint16_t appliesCh;
|
||||
|
||||
bool operator==(const PathConfig& b) const {
|
||||
return b.path == path;
|
||||
}
|
||||
};
|
||||
|
||||
void selectDaughterboard(librfnm* dev, int id) {
|
||||
// If no daugherboard is populated, give up
|
||||
if (!dev->s->hwinfo.daughterboard[0].board_id && !dev->s->hwinfo.daughterboard[1].board_id) {
|
||||
flog::error("The selected device has no daughterboards");
|
||||
return;
|
||||
}
|
||||
|
||||
// If the ID is not populated, select the other one
|
||||
if (id >= 2 || !dev->s->hwinfo.daughterboard[id].board_id) {
|
||||
selectDaughterboard(dev, 1 - id);
|
||||
}
|
||||
|
||||
// Compute the channel offset
|
||||
int offset = 0;
|
||||
for (int i = 0; i < id; i++) {
|
||||
offset += dev->s->hwinfo.daughterboard[i].rx_ch_cnt;
|
||||
}
|
||||
|
||||
// Define antenna paths by going through all channels
|
||||
paths.clear();
|
||||
int count = dev->s->hwinfo.daughterboard[id].rx_ch_cnt;
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Go through each possible path
|
||||
for (int j = 0; j < 10; j++) {
|
||||
// If it's the null path, stop searching
|
||||
rfnm_rf_path path = dev->s->rx.ch[offset + i].path_possible[j];
|
||||
if (path == RFNM_PATH_NULL) { continue; }
|
||||
|
||||
// Get the path
|
||||
PathConfig pc = { path, offset + i, (uint16_t)(1 << (offset + i + 8))};
|
||||
|
||||
// If it's not in the list, add it
|
||||
if (!paths.valueExists(pc)) {
|
||||
std::string name = librfnm::rf_path_to_string(pc.path);
|
||||
std::string capName = name;
|
||||
if (std::islower(capName[0])) { capName[0] = std::toupper(capName[0]); }
|
||||
paths.define(name, capName, pc);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the preferred path
|
||||
PathConfig preferred_pc = { dev->s->rx.ch[offset + i].path_preferred, 0, 0 };
|
||||
|
||||
// Make sure the path is accessible or give up
|
||||
if (!paths.valueExists(preferred_pc)) { continue; }
|
||||
|
||||
// Set this channel as the channel of its prefered path (cursed af but lazy)
|
||||
const PathConfig& pc = paths.value(paths.valueId(preferred_pc));
|
||||
((PathConfig*)&pc)->chId = offset + i;
|
||||
((PathConfig*)&pc)->appliesCh = (uint16_t)(1 << (offset + i + 8));
|
||||
}
|
||||
|
||||
// Dump antenna paths
|
||||
for (int i = 0; i < paths.size(); i++) {
|
||||
flog::debug("PATH[{}]: Name={}, Ch={}, Path={}", i, paths.name(i), paths.value(i).chId, (int)paths.value(i).path);
|
||||
}
|
||||
|
||||
// Load configuration (TODO)
|
||||
selectedPath = paths.key(0);
|
||||
|
||||
// Select antenna path
|
||||
selectPath(dev, id, selectedPath);
|
||||
|
||||
// Save selected daughterboard
|
||||
dgbId = id;
|
||||
}
|
||||
|
||||
void selectPath(librfnm* dev, int dgbId, const std::string& path) {
|
||||
// If the path doesn't exist, select the first path
|
||||
if (!paths.keyExists(path)) {
|
||||
selectPath(dev, dgbId, paths.key(0));
|
||||
}
|
||||
|
||||
// Save selected path
|
||||
selectedPath = path;
|
||||
pathId = paths.keyId(path);
|
||||
currentPath = paths.value(pathId);
|
||||
|
||||
// Define bandwidths
|
||||
bandwidths.clear();
|
||||
bandwidths.define(-1, "Auto", -1);
|
||||
for (int i = 1; i <= 100; i++) {
|
||||
char buf[128];
|
||||
sprintf(buf, "%d MHz", i);
|
||||
bandwidths.define(i, buf, i);
|
||||
}
|
||||
|
||||
// Get gain range
|
||||
gainMin = dev->s->rx.ch[currentPath.chId].gain_range.min;
|
||||
gainMax = dev->s->rx.ch[currentPath.chId].gain_range.max;
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
RFNMSourceModule* _this = (RFNMSourceModule*)ctx;
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
flog::info("RFNMSourceModule '{0}': Menu Select!", _this->name);
|
||||
}
|
||||
|
||||
static void menuDeselected(void* ctx) {
|
||||
RFNMSourceModule* _this = (RFNMSourceModule*)ctx;
|
||||
flog::info("RFNMSourceModule '{0}': Menu Deselect!", _this->name);
|
||||
}
|
||||
|
||||
static void start(void* ctx) {
|
||||
RFNMSourceModule* _this = (RFNMSourceModule*)ctx;
|
||||
if (_this->running) { return; }
|
||||
|
||||
// Open the device
|
||||
_this->openDev = new librfnm(librfnm_transport::LIBRFNM_TRANSPORT_USB, _this->selectedSerial);
|
||||
|
||||
// Configure the stream
|
||||
_this->bufferSize = -1;
|
||||
_this->openDev->rx_stream(librfnm_stream_format::LIBRFNM_STREAM_FORMAT_CS16, &_this->bufferSize);
|
||||
if (_this->bufferSize <= 0) {
|
||||
flog::error("Failed to configure stream");
|
||||
}
|
||||
|
||||
// Allocate and queue buffers
|
||||
flog::debug("BUFFER SIZE: {}", _this->bufferSize);
|
||||
for (int i = 0; i < LIBRFNM_MIN_RX_BUFCNT; i++) {
|
||||
_this->rxBuf[i].buf = dsp::buffer::alloc<uint8_t>(_this->bufferSize);
|
||||
_this->openDev->rx_qbuf(&_this->rxBuf[i]);
|
||||
}
|
||||
|
||||
// Flush buffers
|
||||
_this->openDev->rx_flush();
|
||||
|
||||
// Configure the device
|
||||
_this->openDev->s->rx.ch[_this->currentPath.chId].enable = RFNM_CH_ON;
|
||||
_this->openDev->s->rx.ch[_this->currentPath.chId].samp_freq_div_n = _this->samplerates[_this->srId];
|
||||
_this->openDev->s->rx.ch[_this->currentPath.chId].freq = _this->freq;
|
||||
_this->openDev->s->rx.ch[_this->currentPath.chId].gain = _this->gain;
|
||||
_this->openDev->s->rx.ch[_this->currentPath.chId].rfic_lpf_bw = 100;
|
||||
_this->openDev->s->rx.ch[_this->currentPath.chId].fm_notch = _this->fmNotch ? rfnm_fm_notch::RFNM_FM_NOTCH_ON : rfnm_fm_notch::RFNM_FM_NOTCH_OFF;
|
||||
_this->openDev->s->rx.ch[_this->currentPath.chId].path = _this->currentPath.path;
|
||||
rfnm_api_failcode fail = _this->openDev->set(_this->currentPath.appliesCh);
|
||||
if (fail != rfnm_api_failcode::RFNM_API_OK) {
|
||||
flog::error("Failed to configure device: {}", (int)fail);
|
||||
}
|
||||
|
||||
// Start worker
|
||||
_this->run = true;
|
||||
_this->workerThread = std::thread(&RFNMSourceModule::worker, _this);
|
||||
|
||||
_this->running = true;
|
||||
flog::info("RFNMSourceModule '{0}': Start!", _this->name);
|
||||
}
|
||||
|
||||
static void stop(void* ctx) {
|
||||
RFNMSourceModule* _this = (RFNMSourceModule*)ctx;
|
||||
if (!_this->running) { return; }
|
||||
_this->running = false;
|
||||
|
||||
// Stop worker
|
||||
_this->run = false;
|
||||
_this->stream.stopWriter();
|
||||
if (_this->workerThread.joinable()) { _this->workerThread.join(); }
|
||||
_this->stream.clearWriteStop();
|
||||
|
||||
// Stop the RX streaming
|
||||
_this->openDev->rx_stream_stop();
|
||||
|
||||
// Disable channel
|
||||
_this->openDev->s->rx.ch[_this->currentPath.chId].enable = RFNM_CH_OFF;
|
||||
_this->openDev->set(_this->currentPath.appliesCh);
|
||||
|
||||
// Flush buffers
|
||||
_this->openDev->rx_flush();
|
||||
|
||||
// Close device
|
||||
delete _this->openDev;
|
||||
|
||||
// Free buffers
|
||||
for (int i = 0; i < LIBRFNM_MIN_RX_BUFCNT; i++) {
|
||||
dsp::buffer::free(_this->rxBuf[i].buf);
|
||||
}
|
||||
|
||||
flog::info("RFNMSourceModule '{0}': Stop!", _this->name);
|
||||
}
|
||||
|
||||
static void tune(double freq, void* ctx) {
|
||||
RFNMSourceModule* _this = (RFNMSourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
_this->openDev->s->rx.ch[_this->currentPath.chId].freq = freq;
|
||||
rfnm_api_failcode fail = _this->openDev->set(_this->currentPath.appliesCh);
|
||||
if (fail != rfnm_api_failcode::RFNM_API_OK) {
|
||||
flog::error("Failed to tune: {}", (int)fail);
|
||||
}
|
||||
}
|
||||
_this->freq = freq;
|
||||
flog::info("RFNMSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
RFNMSourceModule* _this = (RFNMSourceModule*)ctx;
|
||||
|
||||
if (_this->running) { SmGui::BeginDisabled(); }
|
||||
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo(CONCAT("##_rfnm_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
|
||||
_this->select(_this->devices.key(_this->devId));
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
if (SmGui::Combo(CONCAT("##_rfnm_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) {
|
||||
_this->sampleRate = _this->samplerates.key(_this->srId);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::SameLine();
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Button(CONCAT("Refresh##_rfnm_refr_", _this->name))) {
|
||||
_this->refresh();
|
||||
_this->select(_this->selectedSerial);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
if (_this->daughterboards.size() > 1) {
|
||||
SmGui::LeftLabel("Daughterboard");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_rfnm_dgb_sel_", _this->name), &_this->dgbId, _this->daughterboards.txt)) {
|
||||
// Open the device
|
||||
librfnm* dev = new librfnm(librfnm_transport::LIBRFNM_TRANSPORT_USB, _this->selectedSerial);
|
||||
|
||||
// Select the daughterboard
|
||||
_this->selectDaughterboard(dev, _this->dgbId);
|
||||
|
||||
// Close device
|
||||
delete dev;
|
||||
|
||||
// TODO: Save
|
||||
}
|
||||
}
|
||||
|
||||
if (_this->paths.size() > 1) {
|
||||
SmGui::LeftLabel("Antenna Path");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_rfnm_path_sel_", _this->name), &_this->pathId, _this->paths.txt)) {
|
||||
// Open the device
|
||||
librfnm* dev = new librfnm(librfnm_transport::LIBRFNM_TRANSPORT_USB, _this->selectedSerial);
|
||||
|
||||
// Select the atennna path
|
||||
_this->selectPath(dev, _this->dgbId, _this->paths.key(_this->pathId));
|
||||
|
||||
// Close device
|
||||
delete dev;
|
||||
|
||||
// TODO: Save
|
||||
}
|
||||
}
|
||||
|
||||
if (_this->running) { SmGui::EndDisabled(); }
|
||||
|
||||
SmGui::LeftLabel("Bandwidth");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_rfnm_bw_sel_", _this->name), &_this->bwId, _this->bandwidths.txt)) {
|
||||
if (_this->running) {
|
||||
// TODO: Set
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_rfnm_gain_", _this->name), &_this->gain, _this->gainMin, _this->gainMax)) {
|
||||
if (_this->running) {
|
||||
_this->openDev->s->rx.ch[_this->currentPath.chId].gain = _this->gain;
|
||||
rfnm_api_failcode fail = _this->openDev->set(_this->currentPath.appliesCh);
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
if (SmGui::Checkbox(CONCAT("FM Notch##_rfnm_", _this->name), &_this->fmNotch)) {
|
||||
if (_this->running) {
|
||||
_this->openDev->s->rx.ch[_this->currentPath.chId].fm_notch = _this->fmNotch ? rfnm_fm_notch::RFNM_FM_NOTCH_ON : rfnm_fm_notch::RFNM_FM_NOTCH_OFF;
|
||||
rfnm_api_failcode fail = _this->openDev->set(_this->currentPath.appliesCh);
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
}
|
||||
|
||||
void worker() {
|
||||
librfnm_rx_buf* lrxbuf;
|
||||
int sampCount = bufferSize/4;
|
||||
uint8_t ch = (1 << currentPath.chId);
|
||||
|
||||
// Define number of buffers per swap to maintain 200 fps
|
||||
int maxBufCount = STREAM_BUFFER_SIZE / sampCount;
|
||||
int bufCount = (sampleRate / sampCount) / 200;
|
||||
if (bufCount <= 0) { bufCount = 1; }
|
||||
if (bufCount > maxBufCount) { bufCount = maxBufCount; }
|
||||
|
||||
int count = 0;
|
||||
while (run) {
|
||||
// Receive a buffer
|
||||
auto fail = openDev->rx_dqbuf(&lrxbuf, ch, 1000);
|
||||
if (fail == rfnm_api_failcode::RFNM_API_DQBUF_NO_DATA) {
|
||||
flog::error("Dequeue buffer didn't have any data");
|
||||
continue;
|
||||
}
|
||||
else if (fail) { break; }
|
||||
|
||||
// Convert buffer to CF32
|
||||
volk_16i_s32f_convert_32f((float*)&stream.writeBuf[(count++)*sampCount], (int16_t*)lrxbuf->buf, 32768.0f, sampCount * 2);
|
||||
|
||||
// Reque buffer
|
||||
openDev->rx_qbuf(lrxbuf);
|
||||
|
||||
// Swap data
|
||||
if (count >= bufCount) {
|
||||
if (!stream.swap(count*sampCount)) { break; }
|
||||
count = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
flog::debug("Worker exiting");
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
dsp::stream<dsp::complex_t> stream;
|
||||
double sampleRate;
|
||||
SourceManager::SourceHandler handler;
|
||||
bool running = false;
|
||||
double freq;
|
||||
|
||||
OptionList<std::string, std::string> devices;
|
||||
OptionList<std::string, int> daughterboards;
|
||||
OptionList<std::string, PathConfig> paths;
|
||||
OptionList<int, int> bandwidths;
|
||||
OptionList<int, int> samplerates;
|
||||
int gainMin = 0;
|
||||
int gainMax = 0;
|
||||
int devId = 0;
|
||||
int dgbId = 0;
|
||||
int pathId = 0;
|
||||
int srId = 0;
|
||||
int bwId = 0;
|
||||
int gain = 0;
|
||||
bool fmNotch = false;
|
||||
std::string selectedSerial;
|
||||
librfnm* openDev;
|
||||
int bufferSize = -1;
|
||||
std::string selectedPath;
|
||||
PathConfig currentPath;
|
||||
librfnm_rx_buf rxBuf[LIBRFNM_MIN_RX_BUFCNT];
|
||||
|
||||
std::atomic<bool> run = false;
|
||||
std::thread workerThread;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new RFNMSourceModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (RFNMSourceModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
// Nothing here
|
||||
}
|
@ -411,8 +411,6 @@ public:
|
||||
agcSetPoint = config.conf["devices"][selectedName]["agcSetPoint"];
|
||||
}
|
||||
|
||||
core::setInputSampleRate(sampleRate);
|
||||
|
||||
// Per device options
|
||||
if (openDev.hwVer == SDRPLAY_RSP1_ID) {
|
||||
// No config to load
|
||||
@ -688,6 +686,7 @@ private:
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo(CONCAT("##sdrplay_dev", _this->name), &_this->devId, _this->devListTxt.c_str())) {
|
||||
_this->selectById(_this->devId);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
config.acquire();
|
||||
config.conf["device"] = _this->devNameList[_this->devId];
|
||||
config.release(true);
|
||||
@ -711,6 +710,7 @@ private:
|
||||
if (SmGui::Button(CONCAT("Refresh##sdrplay_refresh", _this->name))) {
|
||||
_this->refresh();
|
||||
_this->selectByName(_this->selectedName);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("Bandwidth");
|
||||
|
@ -9,8 +9,13 @@
|
||||
#include <gui/smgui.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#include <aaroniartsaapi.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
@ -28,10 +33,13 @@ public:
|
||||
SpectranSourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
if (AARTSAAPI_Init(AARTSAAPI_MEMORY_MEDIUM) != AARTSAAPI_OK) {
|
||||
AARTSAAPI_Result res;
|
||||
if ((res = AARTSAAPI_Init(AARTSAAPI_MEMORY_MEDIUM)) != AARTSAAPI_OK) {
|
||||
flog::error("Failed to initialize the RTSAAPI: {}", (uint32_t)res);
|
||||
return;
|
||||
}
|
||||
if (AARTSAAPI_Open(&api) != AARTSAAPI_OK) {
|
||||
if ((res = AARTSAAPI_Open(&api)) != AARTSAAPI_OK) {
|
||||
flog::error("Failed to open the RTSAAPI: {}", (uint32_t)res);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -189,7 +197,6 @@ public:
|
||||
|
||||
// Set samplerate
|
||||
samplerate = sampleRateList.value(srId);
|
||||
core::setInputSampleRate(samplerate.effective);
|
||||
|
||||
// Close device
|
||||
AARTSAAPI_CloseDevice(&api, &dev);
|
||||
@ -329,9 +336,10 @@ private:
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo(CONCAT("##_spectran_dev_", _this->name), &_this->devId, _this->devList.txt)) {
|
||||
|
||||
_this->selectSerial(_this->devList.key(_this->devId));
|
||||
core::setInputSampleRate(_this->samplerate.effective);
|
||||
}
|
||||
// TODO: SR sel
|
||||
|
||||
if (SmGui::Combo(CONCAT("##_spectran_sr_", _this->name), &_this->srId, _this->sampleRateList.txt)) {
|
||||
_this->samplerate = _this->sampleRateList.value(_this->srId);
|
||||
core::setInputSampleRate(_this->samplerate.effective);
|
||||
@ -447,13 +455,16 @@ private:
|
||||
|
||||
void updateRef() {
|
||||
// Get and update bounds
|
||||
AARTSAAPI_Config config;
|
||||
AARTSAAPI_ConfigInfo refInfo;
|
||||
AARTSAAPI_ConfigFind(&dev, &croot, &config, L"main/reflevel");
|
||||
AARTSAAPI_ConfigGetInfo(&dev, &config, &refInfo);
|
||||
AARTSAAPI_Config config = {};
|
||||
AARTSAAPI_ConfigInfo refInfo = {};
|
||||
auto res = AARTSAAPI_ConfigFind(&dev, &croot, &config, L"main/reflevel");
|
||||
flog::debug("Res A: {}", res);
|
||||
res = AARTSAAPI_ConfigGetInfo(&dev, &config, &refInfo);
|
||||
flog::debug("Res B: {}", res);
|
||||
minRef = refInfo.minValue;
|
||||
maxRef = refInfo.maxValue;
|
||||
refStep = refInfo.stepValue;
|
||||
flog::debug("Gain: {} -> {}", refInfo.minValue, refInfo.maxValue);
|
||||
refLevel = std::clamp<float>(refLevel, minRef, maxRef);
|
||||
|
||||
// Apply new ref level
|
||||
@ -547,4 +558,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +211,6 @@ public:
|
||||
|
||||
// Apply samplerate
|
||||
sampleRate = samplerates.key(srId);
|
||||
core::setInputSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
void setBandwidth(double bw) {
|
||||
@ -335,6 +334,7 @@ private:
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo(CONCAT("##_usrp_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
|
||||
_this->select(_this->devices.key(_this->devId));
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (!_this->selectedSer.empty()) {
|
||||
config.acquire();
|
||||
config.conf["device"] = _this->devices.key(_this->devId);
|
||||
@ -357,10 +357,8 @@ private:
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Button(CONCAT("Refresh##_usrp_refr_", _this->name))) {
|
||||
_this->refresh();
|
||||
config.acquire();
|
||||
std::string ser = config.conf["device"];
|
||||
config.release();
|
||||
_this->select(ser);
|
||||
_this->select(_this->selectedSer);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
if (_this->channels.size() > 1) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include <core.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
return sdrpp_main(argc, argv);
|
||||
|
Reference in New Issue
Block a user