3 Commits

Author SHA1 Message Date
f8078ac3f0 more work on DAB decoding
Some checks failed
Build Binaries / build_debian_buster (push) Failing after 4s
Build Binaries / build_debian_bullseye (push) Failing after 5s
Build Binaries / build_debian_bookworm (push) Failing after 4s
Build Binaries / build_debian_sid (push) Failing after 4s
Build Binaries / build_ubuntu_focal (push) Failing after 4s
Build Binaries / build_ubuntu_jammy (push) Failing after 4s
Build Binaries / build_ubuntu_mantic (push) Failing after 4s
Build Binaries / build_ubuntu_noble (push) Failing after 5s
Build Binaries / build_android (push) Failing after 5s
Build Binaries / check_spelling (push) Failing after 3s
Build Binaries / check_formatting (push) Successful in 3s
Build Binaries / build_windows (push) Has been cancelled
Build Binaries / build_macos_intel (push) Has been cancelled
Build Binaries / build_macos_arm (push) Has been cancelled
Build Binaries / build_raspios_bullseye_armhf (push) Has been cancelled
Build Binaries / create_full_archive (push) Has been cancelled
Build Binaries / update_nightly_release (push) Has been cancelled
2024-09-23 00:51:06 +02:00
064f25ee73 work on the time sync algo
Some checks failed
Build Binaries / build_debian_buster (push) Failing after 5s
Build Binaries / build_debian_bullseye (push) Failing after 4s
Build Binaries / build_debian_bookworm (push) Failing after 4s
Build Binaries / build_debian_sid (push) Failing after 5s
Build Binaries / build_ubuntu_focal (push) Failing after 4s
Build Binaries / build_ubuntu_jammy (push) Failing after 4s
Build Binaries / build_ubuntu_mantic (push) Failing after 5s
Build Binaries / build_ubuntu_noble (push) Failing after 5s
Build Binaries / build_android (push) Failing after 5s
Build Binaries / check_spelling (push) Failing after 4s
Build Binaries / check_formatting (push) Successful in 4s
Build Binaries / build_windows (push) Has been cancelled
Build Binaries / build_macos_intel (push) Has been cancelled
Build Binaries / build_macos_arm (push) Has been cancelled
Build Binaries / build_raspios_bullseye_armhf (push) Has been cancelled
Build Binaries / create_full_archive (push) Has been cancelled
Build Binaries / update_nightly_release (push) Has been cancelled
2024-09-19 17:28:03 +02:00
d87ae23560 beginning of cleaner OFDM demod code 2024-09-19 05:47:58 +02:00
90 changed files with 3101 additions and 8329 deletions

View File

@ -50,7 +50,7 @@ jobs:
run: 7z x ${{runner.workspace}}/SDRplay.zip -o"C:/Program Files/"
- name: Download codec2
run: git clone https://github.com/drowe67/codec2
run: git clone https://github.com/AlexandreRouma/codec2
- name: Prepare MinGW
run: C:/msys64/msys2_shell.cmd -defterm -here -no-start -mingw64 -c "pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja"
@ -98,7 +98,7 @@ jobs:
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
build_macos_intel:
runs-on: macos-13
runs-on: macos-12
steps:
- uses: actions/checkout@v4
@ -211,7 +211,7 @@ jobs:
name: sdrpp_macos_arm
path: ${{runner.workspace}}/sdrpp_macos_arm.zip
build_debian_buster_amd64:
build_debian_buster:
runs-on: ubuntu-latest
steps:
@ -233,29 +233,7 @@ jobs:
name: sdrpp_debian_buster_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_buster_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_buster_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bullseye_amd64:
build_debian_bullseye:
runs-on: ubuntu-latest
steps:
@ -277,29 +255,7 @@ jobs:
name: sdrpp_debian_bullseye_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bullseye_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_bullseye_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bookworm_amd64:
build_debian_bookworm:
runs-on: ubuntu-latest
steps:
@ -321,29 +277,7 @@ jobs:
name: sdrpp_debian_bookworm_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bookworm_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bookworm && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_bookworm_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_sid_amd64:
build_debian_sid:
runs-on: ubuntu-latest
steps:
@ -365,29 +299,7 @@ jobs:
name: sdrpp_debian_sid_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_sid_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_sid_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_focal_amd64:
build_ubuntu_focal:
runs-on: ubuntu-latest
steps:
@ -409,29 +321,7 @@ jobs:
name: sdrpp_ubuntu_focal_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_focal_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_focal_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_jammy_amd64:
build_ubuntu_jammy:
runs-on: ubuntu-latest
steps:
@ -452,15 +342,15 @@ jobs:
with:
name: sdrpp_ubuntu_jammy_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_jammy_aarch64:
runs-on: ubuntu-24.04-arm
build_ubuntu_mantic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_mantic && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
@ -472,10 +362,10 @@ jobs:
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_jammy_aarch64
name: sdrpp_ubuntu_mantic_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_noble_amd64:
build_ubuntu_noble:
runs-on: ubuntu-latest
steps:
@ -497,72 +387,6 @@ jobs:
name: sdrpp_ubuntu_noble_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_noble_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_noble_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_oracular_amd64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_oracular && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_oracular_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_oracular_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_oracular && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_oracular_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_raspios_bullseye_armhf:
runs-on: ARM
@ -618,29 +442,7 @@ jobs:
path: ${{runner.workspace}}/sdrpp.apk
create_full_archive:
needs: [
'build_windows',
'build_macos_intel',
'build_macos_arm',
'build_debian_buster_amd64',
'build_debian_buster_aarch64',
'build_debian_bullseye_amd64',
'build_debian_bullseye_aarch64',
'build_debian_bookworm_amd64',
'build_debian_bookworm_aarch64',
'build_debian_sid_amd64',
'build_debian_sid_aarch64',
'build_ubuntu_focal_amd64',
'build_ubuntu_focal_aarch64',
'build_ubuntu_jammy_amd64',
'build_ubuntu_jammy_aarch64',
'build_ubuntu_noble_amd64',
'build_ubuntu_noble_aarch64',
'build_ubuntu_oracular_amd64',
'build_ubuntu_oracular_aarch64',
'build_raspios_bullseye_armhf',
'build_android'
]
needs: ['build_windows', 'build_macos_intel', 'build_macos_arm', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_ubuntu_noble', 'build_raspios_bullseye_armhf', 'build_android']
runs-on: ubuntu-latest
steps:
@ -654,21 +456,13 @@ jobs:
mv sdrpp_macos_intel/sdrpp_macos_intel.zip sdrpp_all/ &&
mv sdrpp_macos_arm/sdrpp_macos_arm.zip sdrpp_all/ &&
mv sdrpp_debian_buster_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_buster_amd64.deb &&
mv sdrpp_debian_buster_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_buster_aarch64.deb &&
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&
mv sdrpp_debian_bullseye_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_aarch64.deb &&
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&
mv sdrpp_debian_bookworm_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_amd64.deb &&
mv sdrpp_debian_bookworm_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_aarch64.deb &&
mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
mv sdrpp_debian_sid_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_aarch64.deb &&
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
mv sdrpp_ubuntu_focal_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_aarch64.deb &&
mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb &&
mv sdrpp_ubuntu_jammy_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_aarch64.deb &&
mv sdrpp_ubuntu_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_ubuntu_noble_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_aarch64.deb &&
mv sdrpp_ubuntu_oracular_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_oracular_amd64.deb &&
mv sdrpp_ubuntu_oracular_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_oracular_aarch64.deb &&
mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb &&
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk

6
.gitignore vendored
View File

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

View File

@ -19,8 +19,6 @@ option(OPT_BUILD_FOBOSSDR_SOURCE "Build FobosSDR Source Module (Dependencies: li
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON)
option(OPT_BUILD_HAROGIC_SOURCE "Build Harogic Source Module (Dependencies: htra_api)" OFF)
option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON)
option(OPT_BUILD_HYDRASDR_SOURCE "Build HydraSDR Source Module (Dependencies: libhydrasdr)" OFF)
option(OPT_BUILD_KCSDR_SOURCE "Build KCSDR Source Module (Dependencies: libkcsdr)" OFF)
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF)
option(OPT_BUILD_NETWORK_SOURCE "Build Network Source Module (no dependencies required)" ON)
option(OPT_BUILD_PERSEUS_SOURCE "Build Perseus Source Module (Dependencies: libperseus-sdr)" OFF)
@ -54,7 +52,6 @@ option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dep
option(OPT_BUILD_PAGER_DECODER "Build the pager decoder module (no dependencies required)" ON)
option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
option(OPT_BUILD_RYFI_DECODER "RyFi data link decoder" OFF)
option(OPT_BUILD_VOR_RECEIVER "VOR beacon receiver" OFF)
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
# Misc
@ -163,14 +160,6 @@ if (OPT_BUILD_HERMES_SOURCE)
add_subdirectory("source_modules/hermes_source")
endif (OPT_BUILD_HERMES_SOURCE)
if (OPT_BUILD_HYDRASDR_SOURCE)
add_subdirectory("source_modules/hydrasdr_source")
endif (OPT_BUILD_HYDRASDR_SOURCE)
if (OPT_BUILD_KCSDR_SOURCE)
add_subdirectory("source_modules/kcsdr_source")
endif (OPT_BUILD_KCSDR_SOURCE)
if (OPT_BUILD_LIMESDR_SOURCE)
add_subdirectory("source_modules/limesdr_source")
endif (OPT_BUILD_LIMESDR_SOURCE)
@ -203,10 +192,6 @@ if (OPT_BUILD_RTL_TCP_SOURCE)
add_subdirectory("source_modules/rtl_tcp_source")
endif (OPT_BUILD_RTL_TCP_SOURCE)
if (OPT_BUILD_SDDC_SOURCE)
add_subdirectory("source_modules/sddc_source")
endif (OPT_BUILD_SDDC_SOURCE)
if (OPT_BUILD_SDRPP_SERVER_SOURCE)
add_subdirectory("source_modules/sdrpp_server_source")
endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
@ -295,10 +280,6 @@ if (OPT_BUILD_RYFI_DECODER)
add_subdirectory("decoder_modules/ryfi_decoder")
endif (OPT_BUILD_RYFI_DECODER)
if (OPT_BUILD_VOR_RECEIVER)
add_subdirectory("decoder_modules/vor_receiver")
endif (OPT_BUILD_VOR_RECEIVER)
if (OPT_BUILD_WEATHER_SAT_DECODER)
add_subdirectory("decoder_modules/weather_sat_decoder")
endif (OPT_BUILD_WEATHER_SAT_DECODER)

View File

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

View File

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

View File

@ -147,11 +147,11 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["menuElements"][3]["name"] = "Sinks";
defConfig["menuElements"][3]["open"] = true;
defConfig["menuElements"][4]["name"] = "Frequency Manager";
defConfig["menuElements"][4]["open"] = true;
defConfig["menuElements"][3]["name"] = "Frequency Manager";
defConfig["menuElements"][3]["open"] = true;
defConfig["menuElements"][5]["name"] = "VFO Color";
defConfig["menuElements"][5]["open"] = true;
defConfig["menuElements"][4]["name"] = "VFO Color";
defConfig["menuElements"][4]["open"] = true;
defConfig["menuElements"][6]["name"] = "Band Plan";
defConfig["menuElements"][6]["open"] = true;
@ -181,12 +181,8 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["moduleInstances"]["Harogic Source"]["enabled"] = true;
defConfig["moduleInstances"]["Hermes Source"]["module"] = "hermes_source";
defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true;
defConfig["moduleInstances"]["HydraSDR Source"]["module"] = "hydrasdr_source";
defConfig["moduleInstances"]["HydraSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["Network Source"]["module"] = "network_source";
defConfig["moduleInstances"]["Network Source"]["enabled"] = true;
defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source";
defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
@ -234,19 +230,12 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["modules"] = json::array();
defConfig["offsets"]["SpyVerter"] = 120000000.0;
defConfig["offsets"]["Ham-It-Up"] = 125000000.0;
defConfig["offsets"]["MMDS S-band (1998MHz)"] = -1998000000.0;
defConfig["offsets"]["DK5AV X-Band"] = -6800000000.0;
defConfig["offsets"]["Ku LNB (9750MHz)"] = -9750000000.0;
defConfig["offsets"]["Ku LNB (10700MHz)"] = -10700000000.0;
defConfig["selectedOffset"] = "None";
defConfig["manualOffset"] = 0.0;
defConfig["offsetMode"] = (int)0; // Off
defConfig["offset"] = 0.0;
defConfig["showMenu"] = true;
defConfig["showWaterfall"] = true;
defConfig["source"] = "";
defConfig["decimation"] = 1;
defConfig["decimationPower"] = 0;
defConfig["iqCorrection"] = false;
defConfig["invertIQ"] = false;
@ -327,18 +316,12 @@ int sdrpp_main(int argc, char* argv[]) {
// Remove unused elements
auto items = core::configManager.conf.items();
auto newConf = core::configManager.conf;
bool configCorrected = false;
for (auto const& item : items) {
if (!defConfig.contains(item.key())) {
flog::info("Unused key in config {0}, repairing", item.key());
newConf.erase(item.key());
configCorrected = true;
core::configManager.conf.erase(item.key());
}
}
if (configCorrected) {
core::configManager.conf = newConf;
}
// Update to new module representation in config if needed
for (auto [_name, inst] : core::configManager.conf["moduleInstances"].items()) {

View File

@ -70,18 +70,15 @@ namespace sdrpp_credits {
"Flinger Films",
"Frank Werner (HB9FXQ)",
"gringogrigio",
"Jandro",
"Jeff Moe",
"Joe Cupano",
"KD1SQ",
"Kezza",
"Krys Kamieniecki",
"Lee Donaghy",
"Lee (KD1SQ)",
"Lee KD1SQ",
".lozenge. (Hank Hill)",
"Martin Herren (HB9FXX)",
"NeoVilsonWong",
"Nitin (VU2JEK)",
"ON4MU",
"Passion-Radio.com",
"Paul Maine",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,139 +5,139 @@
#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() {}
// 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); }
// // 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);
// 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 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);
// // 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 delay input addresses
// delayBufInput = &delayBuf[symbolSamps];
// Compute the correlation AGC configuration
this->agcRate = agcRate;
agcRateInv = 1.0f - agcRate;
// // Compute the correlation AGC configuration
// this->agcRate = agcRate;
// agcRateInv = 1.0f - agcRate;
base_type::init(in);
}
// base_type::init(in);
// }
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
// void reset() {
// assert(base_type::_block_init);
// std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
// base_type::tempStop();
base_type::tempStart();
}
// base_type::tempStart();
// }
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
// 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));
// // 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();
// // 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++];
// // 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;
// // 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;
// // 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];
// // 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 correlation
// *slot = prod;
// Add the new value to the history buffer
corr += prod;
// // Add the new value to the history buffer
// corr += prod;
// Compute sample amplitude
float rcorr = corr.amplitude();
// // 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 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;
}
// // 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;
// // 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;
}
// // 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
// lastCorr = rcorr;
// Update the average correlation value
avgCorr = agcRate*rcorr + agcRateInv*avgCorr;
}
// // Update the average correlation value
// avgCorr = agcRate*rcorr + agcRateInv*avgCorr;
// }
// Move unused data
memmove(delayBuf, &delayBuf[count], symbolSamps * sizeof(dsp::complex_t));
// // Move unused data
// memmove(delayBuf, &delayBuf[count], symbolSamps * sizeof(dsp::complex_t));
return count;
}
// return count;
// }
protected:
int symbolSamps;
int prefixSamps;
// protected:
// int symbolSamps;
// int prefixSamps;
int histId = 0;
dsp::complex_t* histBuf;
// int histId = 0;
// dsp::complex_t* histBuf;
dsp::complex_t* delayBuf;
dsp::complex_t* delayBufInput;
// dsp::complex_t* delayBuf;
// dsp::complex_t* delayBufInput;
dsp::complex_t corr = { 0.0f, 0.0f };
// 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;
// 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;
};
// // 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>;

View File

@ -14,6 +14,7 @@
#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())
@ -35,7 +36,7 @@ public:
M17DecoderModule(std::string name) {
this->name = name;
file = std::ofstream("sync4.f32", std::ios::out | std::ios::binary);
file = std::ofstream("sync5.f32", std::ios::out | std::ios::binary);
// Load config
config.acquire();
@ -47,7 +48,7 @@ public:
vfo->setSnapInterval(250);
// Initialize DSP here
csync.init(vfo->output, 1e-3, 246e-6, INPUT_SAMPLE_RATE);
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);
@ -131,8 +132,9 @@ private:
std::string name;
bool enabled = true;
dab::CyclicSync csync;
//dab::CyclicSync csync;
dab::FrameFreqSync ffsync;
dsp::ofdm::CyclicTimeSync csync;
dsp::sink::Handler<dsp::complex_t> ns;
ImGui::ConstellationDiagram constDiagram;

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -9,11 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
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

View File

@ -9,11 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
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

View File

@ -9,11 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
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

View File

@ -9,11 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev l
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
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

View File

@ -15,11 +15,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev
libcodec2-dev libudev-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
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

View File

@ -9,11 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
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

View File

@ -9,11 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
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

View File

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

View File

@ -9,11 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev l
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
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

View File

@ -9,11 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev l
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
SDRPLAY_ARCH=$(dpkg --print-architecture)
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
7z x ./SDRplay_RSP_API-Linux-3.15.2
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
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

View File

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

View File

@ -8,7 +8,7 @@ mkdir sdrpp_debian_amd64/DEBIAN
# Create package info
echo Create package info
echo Package: sdrpp >> sdrpp_debian_amd64/DEBIAN/control
echo Version: 1.2.1$BUILD_NO >> sdrpp_debian_amd64/DEBIAN/control
echo Version: 1.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

View File

@ -22,7 +22,7 @@ cp -R root/res/* $BUNDLE/Contents/Resources/
bundle_create_icns root/res/icons/sdrpp.macos.png $BUNDLE/Contents/Resources/sdrpp
# Create the property list
bundle_create_plist sdrpp SDR++ org.sdrpp.sdrpp 1.2.1 sdrp sdrpp sdrpp $BUNDLE/Contents/Info.plist
bundle_create_plist sdrpp SDR++ org.sdrpp.sdrpp 1.2.0 sdrp sdrpp sdrpp $BUNDLE/Contents/Info.plist
# ========================= Install binaries =========================
@ -35,11 +35,10 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/airspyhf_source/airspyhf_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/bladerf_source/bladerf_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/file_source/file_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/fobossdr_source/fobossdr_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/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/network_source/network_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/perseus_source/perseus_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/plutosdr_source/plutosdr_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rfnm_source/rfnm_source.dylib

View File

@ -35,8 +35,6 @@ cp $build_dir/source_modules/hermes_source/Release/hermes_source.dll sdrpp_windo
cp $build_dir/source_modules/limesdr_source/Release/limesdr_source.dll sdrpp_windows_x64/modules/
cp 'C:/Program Files/PothosSDR/bin/LimeSuite.dll' sdrpp_windows_x64/
cp $build_dir/source_modules/network_source/Release/network_source.dll sdrpp_windows_x64/modules/
cp $build_dir/source_modules/perseus_source/Release/perseus_source.dll sdrpp_windows_x64/modules/
cp 'C:/Program Files/PothosSDR/bin/perseus-sdr.dll' sdrpp_windows_x64/

View File

@ -168,9 +168,10 @@ public:
writer.setSamplerate(samplerate);
// Open file
std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband";
std::string vfoName = (recMode == RECORDER_MODE_AUDIO) ? selectedStreamName : "";
std::string extension = ".wav";
std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, recMode, vfoName) + extension);
std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, type, vfoName) + extension);
if (!writer.open(expandedPath)) {
flog::error("Failed to open file for recording: {0}", expandedPath);
return;
@ -451,7 +452,7 @@ private:
{ RADIO_IFACE_MODE_RAW, "RAW" }
};
std::string genFileName(std::string templ, int mode, std::string name) {
std::string genFileName(std::string templ, std::string type, std::string name) {
// Get data
time_t now = time(0);
tm* ltm = localtime(&now);
@ -461,9 +462,6 @@ private:
freq += gui::waterfall.vfos[name]->generalOffset;
}
// Select the recording type string
std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband";
// Format to string
char freqStr[128];
char hourStr[128];
@ -472,7 +470,7 @@ private:
char dayStr[128];
char monStr[128];
char yearStr[128];
const char* modeStr = (recMode == RECORDER_MODE_AUDIO) ? "Unknown" : "IQ";
const char* modeStr = "Unknown";
sprintf(freqStr, "%.0lfHz", freq);
sprintf(hourStr, "%02d", ltm->tm_hour);
sprintf(minStr, "%02d", ltm->tm_min);

View File

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

View File

@ -324,13 +324,12 @@ Modules in beta are still included in releases for the most part but not enabled
| audio_source | Working | rtaudio | OPT_BUILD_AUDIO_SOURCE | ✅ | ✅ | ✅ |
| bladerf_source | Working | libbladeRF | OPT_BUILD_BLADERF_SOURCE | ⛔ | ✅ (not Debian Buster) | ✅ |
| file_source | Working | - | OPT_BUILD_FILE_SOURCE | ✅ | ✅ | ✅ |
| fobossdr_source | Working | libfobos | OPT_BUILD_FOBOSSDR_SOURCE | ✅ | ✅ | ✅ |
| 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 | ✅ | ✅ | ✅ |
| kcsdr_source | Unfinished | libkcsdr | OPT_BUILD_KCSDR_SOURCE | ⛔ | ⛔ | ⛔ |
| limesdr_source | Working | liblimesuite | OPT_BUILD_LIMESDR_SOURCE | ⛔ | ✅ | ✅ |
| network_source | Beta | - | OPT_BUILD_NETWORK_SOURCE | ✅ | ✅ | |
| network_source | Unfinished | - | OPT_BUILD_NETWORK_SOURCE | ✅ | ✅ | |
| perseus_source | Beta | libperseus-sdr | OPT_BUILD_PERSEUS_SOURCE | ⛔ | ✅ | ✅ |
| plutosdr_source | Working | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE | ✅ | ✅ | ✅ |
| rfnm_source | Beta | librfnm | OPT_BUILD_RFNM_SOURCE | ⛔ | ✅ | ✅ |
@ -352,8 +351,8 @@ Modules in beta are still included in releases for the most part but not enabled
| android_audio_sink | Working | - | OPT_BUILD_ANDROID_AUDIO_SINK | ⛔ | ✅ | ✅ (Android only) |
| audio_sink | Working | rtaudio | OPT_BUILD_AUDIO_SINK | ✅ | ✅ | ✅ |
| network_sink | Working | - | OPT_BUILD_NETWORK_SINK | ✅ | ✅ | ✅ |
| new_portaudio_sink | Working | portaudio | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
| portaudio_sink | Working | portaudio | OPT_BUILD_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
| new_portaudio_sink | Beta | portaudio | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
| portaudio_sink | Beta | portaudio | OPT_BUILD_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
## Decoders
@ -367,7 +366,6 @@ Modules in beta are still included in releases for the most part but not enabled
| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ |
| pager_decoder | Unfinished | - | OPT_BUILD_PAGER_DECODER | ⛔ | ⛔ | ⛔ |
| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ |
| radio | Unfinished | - | OPT_BUILD_VOR_RECEIVER | ⛔ | ⛔ | ⛔ |
| weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ |
## Misc
@ -420,8 +418,8 @@ If you still have an issue, please open an issue about it or ask on the discord.
# Contributing
Feel free to submit band plans via the GitHub issue tracker.
For code changes, please create a feature request instead.
Feel free to submit pull requests and report bugs via the GitHub issue tracker.
I will soon publish a contributing.md listing the code style to use.
# Credits
@ -441,18 +439,15 @@ For code changes, please create a feature request instead.
* Flinger Films
* [Frank Werner (HB9FXQ)](https://twitter.com/HB9FXQ)
* gringogrigio
* Jandro
* Jeff Moe
* Joe Cupano
* KD1SQ
* Kezza
* Krys Kamieniecki
* Lee Donaghy
* Lee (KD1SQ)
* Lee KD1SQ
* .lozenge. (Hank Hill)
* Martin Herren (HB9FXX)
* NeoVilsonWong
* Nitin (VU2JEK)
* ON4MU
* [Passion-Radio.com](https://passion-radio.com/)
* Paul Maine

View File

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

View File

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

View File

@ -183,9 +183,6 @@ private:
static void start(void* ctx) {
AudioSourceModule* _this = (AudioSourceModule*)ctx;
if (_this->running) { return; }
// If no device is selected, give up
if (_this->selectedDevice.empty()) { return; }
// Stream options
RtAudio::StreamParameters parameters;

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

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

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

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

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

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

View File

@ -10,7 +10,6 @@
#include <libbladeRF.h>
#include <gui/smgui.h>
#include <algorithm>
#include <utils/optionlist.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
@ -38,10 +37,6 @@ public:
BladeRFSourceModule(std::string name) {
this->name = name;
// Define clocks
clocks.define("onboard", "On-Board", CLOCK_SELECT_ONBOARD);
clocks.define("external", "External", CLOCK_SELECT_EXTERNAL);
sampleRate = 1000000.0;
handler.ctx = this;
@ -272,15 +267,6 @@ public:
}
config.release(true);
// Load clock source
clkId = clocks.keyId("onboard");
if (config.conf["devices"][selectedSerial].contains("clock")) {
std::string clkStr = config.conf["devices"][selectedSerial]["clock"];
if (clocks.keyExists(clkStr)) {
clkId = clocks.keyId(clkStr);
}
}
// Load gain mode
if (config.conf["devices"][selectedSerial].contains("gainMode")) {
std::string gm = config.conf["devices"][selectedSerial]["gainMode"];
@ -378,7 +364,6 @@ private:
if (_this->bufferSize < 1024) { _this->bufferSize = 1024; }
// Setup device parameters
_this->setClockSource(_this->clocks[_this->clkId]);
bladerf_set_sample_rate(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->sampleRate, NULL);
bladerf_set_frequency(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->freq);
bladerf_set_bandwidth(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), (_this->bwId == _this->bandwidths.size()) ? std::clamp<uint64_t>(_this->sampleRate, _this->bwRange->min, _this->bwRange->max) : _this->bandwidths[_this->bwId], NULL);
@ -501,19 +486,6 @@ private:
}
}
SmGui::LeftLabel("Clock Source");
SmGui::FillWidth();
if (SmGui::Combo(CONCAT("##_balderf_clk_sel_", _this->name), &_this->clkId, _this->clocks.txt)) {
if (_this->running) {
_this->setClockSource(_this->clocks[_this->clkId]);
}
if (_this->selectedSerial != "") {
config.acquire();
config.conf["devices"][_this->selectedSerial]["clock"] = _this->clocks.key(_this->clkId);
config.release(true);
}
}
// General config BS
SmGui::LeftLabel("Gain control mode");
SmGui::FillWidth();
@ -565,15 +537,6 @@ private:
}
}
void setClockSource(bladerf_clock_select clk) {
if (selectedBladeType == BLADERF_TYPE_V1) {
bladerf_set_smb_mode(openDev, (clk == CLOCK_SELECT_EXTERNAL) ? BLADERF_SMB_MODE_INPUT : BLADERF_SMB_MODE_DISABLED);
}
else {
bladerf_set_clock_select(openDev, clk);
}
}
void worker() {
int16_t* buffer = new int16_t[bufferSize * 2];
bladerf_metadata meta;
@ -602,7 +565,6 @@ private:
int devId = 0;
int srId = 0;
int bwId = 0;
int clkId = 0;
int chanId = 0;
int gainMode = 0;
bool streamingEnabled = false;
@ -618,8 +580,8 @@ private:
std::string sampleRatesTxt;
std::vector<uint64_t> bandwidths;
std::string bandwidthsTxt;
std::string channelNamesTxt;
OptionList<std::string, bladerf_clock_select> clocks;
int bufferSize;
struct bladerf_stream* rxStream;

View File

@ -1,18 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(hydrasdr_source)
file(GLOB SRC "src/*.cpp")
include(${SDRPP_MODULE_CMAKE})
if (MSVC)
target_link_directories(hydrasdr_source PRIVATE "C:/Program Files/HydraSDR/")
target_include_directories(hydrasdr_source PUBLIC "C:/Program Files/HydraSDR/include/")
target_link_libraries(hydrasdr_source PRIVATE hydrasdr)
elseif (ANDROID)
# TODO
else (MSVC)
# TODO
endif ()

View File

@ -1,635 +0,0 @@
#include <imgui.h>
#include <utils/flog.h>
#include <module.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <core.h>
#include <gui/style.h>
#include <config.h>
#include <gui/smgui.h>
#include <hydrasdr.h>
#include <utils/optionlist.h>
#ifdef __ANDROID__
#include <android_backend.h>
#endif
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
/* Name: */ "hydrasdr_source",
/* Description: */ "HydraSDR source module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
ConfigManager config;
class HydraSDRSourceModule : public ModuleManager::Instance {
public:
HydraSDRSourceModule(std::string name) {
this->name = name;
// Define the ports
ports.define("rx0", "RX0", RF_PORT_RX0);
ports.define("rx1", "RX1", RF_PORT_RX1);
ports.define("rx2", "RX2", RF_PORT_RX2);
sampleRate = 10000000.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();
// Select device from config
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
selectByString(devSerial);
sigpath::sourceManager.registerSource("HydraSDR", &handler);
}
~HydraSDRSourceModule() {
stop(this);
sigpath::sourceManager.unregisterSource("HydraSDR");;
}
void postInit() {}
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
void refresh() {
#ifndef __ANDROID__
devices.clear();
uint64_t serials[256];
int n = hydrasdr_list_devices(serials, 256);
char buf[1024];
for (int i = 0; i < n; i++) {
sprintf(buf, "%016" PRIX64, serials[i]);
devices.define(buf, buf, serials[i]);
}
#else
// Check for device presence
int vid, pid;
devFd = backend::getDeviceFD(vid, pid, backend::AIRSPY_VIDPIDS);
if (devFd < 0) { return; }
// Get device info
std::string fakeName = "HydraSDR USB";
devList.push_back(0xDEADBEEF);
devListTxt += fakeName;
devListTxt += '\0';
#endif
}
void selectFirst() {
if (!devices.empty()) {
selectBySerial(devices.value(0));
}
}
void selectByString(std::string serial) {
if (devices.keyExists(serial)) {
selectBySerial(devices.value(devices.keyId(serial)));
return;
}
selectFirst();
}
void selectBySerial(uint64_t serial) {
hydrasdr_device* dev;
try {
#ifndef __ANDROID__
int err = hydrasdr_open_sn(&dev, serial);
#else
int err = hydrasdr_open_fd(&dev, devFd);
#endif
if (err != 0) {
char buf[1024];
sprintf(buf, "%016" PRIX64, serial);
flog::error("Could not open HydraSDR {0}", buf);
selectedSerial = 0;
return;
}
}
catch (const std::exception& e) {
char buf[1024];
sprintf(buf, "%016" PRIX64, serial);
flog::error("Could not open HydraSDR {}", buf);
}
devId = devices.valueId(serial);
selectedSerial = serial;
selectedSerStr = devices.key(devId);
uint32_t sampleRates[256];
hydrasdr_get_samplerates(dev, sampleRates, 0);
int n = sampleRates[0];
hydrasdr_get_samplerates(dev, sampleRates, n);
samplerates.clear();
for (int i = 0; i < n; i++) {
samplerates.define(sampleRates[i], getBandwdithScaled(sampleRates[i]), sampleRates[i]);
}
// Load config here
config.acquire();
bool created = false;
if (!config.conf["devices"].contains(selectedSerStr)) {
created = true;
config.conf["devices"][selectedSerStr]["sampleRate"] = 10000000;
config.conf["devices"][selectedSerStr]["gainMode"] = 0;
config.conf["devices"][selectedSerStr]["sensitiveGain"] = 0;
config.conf["devices"][selectedSerStr]["linearGain"] = 0;
config.conf["devices"][selectedSerStr]["lnaGain"] = 0;
config.conf["devices"][selectedSerStr]["mixerGain"] = 0;
config.conf["devices"][selectedSerStr]["vgaGain"] = 0;
config.conf["devices"][selectedSerStr]["lnaAgc"] = false;
config.conf["devices"][selectedSerStr]["mixerAgc"] = false;
config.conf["devices"][selectedSerStr]["biasT"] = false;
}
// Load sample rate
srId = 0;
sampleRate = samplerates.value(0);
if (config.conf["devices"][selectedSerStr].contains("sampleRate")) {
int selectedSr = config.conf["devices"][selectedSerStr]["sampleRate"];
if (samplerates.keyExists(selectedSr)) {
srId = samplerates.keyId(selectedSr);
sampleRate = samplerates[srId];
}
}
// Load port
if (config.conf["devices"][selectedSerStr].contains("port")) {
std::string portStr = config.conf["devices"][selectedSerStr]["port"];
if (ports.keyExists(portStr)) {
portId = ports.keyId(portStr);
}
}
// Load gains
if (config.conf["devices"][selectedSerStr].contains("gainMode")) {
gainMode = config.conf["devices"][selectedSerStr]["gainMode"];
}
if (config.conf["devices"][selectedSerStr].contains("sensitiveGain")) {
sensitiveGain = config.conf["devices"][selectedSerStr]["sensitiveGain"];
}
if (config.conf["devices"][selectedSerStr].contains("linearGain")) {
linearGain = config.conf["devices"][selectedSerStr]["linearGain"];
}
if (config.conf["devices"][selectedSerStr].contains("lnaGain")) {
lnaGain = config.conf["devices"][selectedSerStr]["lnaGain"];
}
if (config.conf["devices"][selectedSerStr].contains("mixerGain")) {
mixerGain = config.conf["devices"][selectedSerStr]["mixerGain"];
}
if (config.conf["devices"][selectedSerStr].contains("vgaGain")) {
vgaGain = config.conf["devices"][selectedSerStr]["vgaGain"];
}
if (config.conf["devices"][selectedSerStr].contains("lnaAgc")) {
lnaAgc = config.conf["devices"][selectedSerStr]["lnaAgc"];
}
if (config.conf["devices"][selectedSerStr].contains("mixerAgc")) {
mixerAgc = config.conf["devices"][selectedSerStr]["mixerAgc"];
}
// Load Bias-T
if (config.conf["devices"][selectedSerStr].contains("biasT")) {
biasT = config.conf["devices"][selectedSerStr]["biasT"];
}
config.release(created);
hydrasdr_close(dev);
}
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);
}
static void menuSelected(void* ctx) {
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
flog::info("HydraSDRSourceModule '{0}': Menu Select!", _this->name);
}
static void menuDeselected(void* ctx) {
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)ctx;
flog::info("HydraSDRSourceModule '{0}': Menu Deselect!", _this->name);
}
static void start(void* ctx) {
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)ctx;
if (_this->running) { return; }
if (_this->selectedSerial == 0) {
flog::error("Tried to start HydraSDR source with null serial");
return;
}
#ifndef __ANDROID__
int err = hydrasdr_open_sn(&_this->openDev, _this->selectedSerial);
#else
int err = hydrasdr_open_fd(&_this->openDev, _this->devFd);
#endif
if (err != 0) {
char buf[1024];
sprintf(buf, "%016" PRIX64, _this->selectedSerial);
flog::error("Could not open HydraSDR {0}", buf);
return;
}
hydrasdr_set_samplerate(_this->openDev, _this->samplerates[_this->srId]);
hydrasdr_set_freq(_this->openDev, _this->freq);
hydrasdr_set_rf_port(_this->openDev, _this->ports[_this->portId]);
if (_this->gainMode == 0) {
hydrasdr_set_lna_agc(_this->openDev, 0);
hydrasdr_set_mixer_agc(_this->openDev, 0);
hydrasdr_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
}
else if (_this->gainMode == 1) {
hydrasdr_set_lna_agc(_this->openDev, 0);
hydrasdr_set_mixer_agc(_this->openDev, 0);
hydrasdr_set_linearity_gain(_this->openDev, _this->linearGain);
}
else if (_this->gainMode == 2) {
if (_this->lnaAgc) {
hydrasdr_set_lna_agc(_this->openDev, 1);
}
else {
hydrasdr_set_lna_agc(_this->openDev, 0);
hydrasdr_set_lna_gain(_this->openDev, _this->lnaGain);
}
if (_this->mixerAgc) {
hydrasdr_set_mixer_agc(_this->openDev, 1);
}
else {
hydrasdr_set_mixer_agc(_this->openDev, 0);
hydrasdr_set_mixer_gain(_this->openDev, _this->mixerGain);
}
hydrasdr_set_vga_gain(_this->openDev, _this->vgaGain);
}
hydrasdr_set_rf_bias(_this->openDev, _this->biasT);
hydrasdr_start_rx(_this->openDev, callback, _this);
_this->running = true;
flog::info("HydraSDRSourceModule '{0}': Start!", _this->name);
}
static void stop(void* ctx) {
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)ctx;
if (!_this->running) { return; }
_this->running = false;
_this->stream.stopWriter();
hydrasdr_close(_this->openDev);
_this->stream.clearWriteStop();
flog::info("HydraSDRSourceModule '{0}': Stop!", _this->name);
}
static void tune(double freq, void* ctx) {
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)ctx;
if (_this->running) {
hydrasdr_set_freq(_this->openDev, freq);
}
_this->freq = freq;
flog::info("HydraSDRSourceModule '{0}': Tune: {1}!", _this->name, freq);
}
static void menuHandler(void* ctx) {
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)ctx;
if (_this->running) { SmGui::BeginDisabled(); }
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##_hydrasdr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
_this->selectBySerial(_this->devices[_this->devId]);
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["device"] = _this->selectedSerStr;
config.release(true);
}
}
if (SmGui::Combo(CONCAT("##_hydrasdr_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) {
_this->sampleRate = _this->samplerates[_this->srId];
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->samplerates.key(_this->srId);
config.release(true);
}
}
SmGui::SameLine();
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Button(CONCAT("Refresh##_hydrasdr_refr_", _this->name))) {
_this->refresh();
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
_this->selectByString(devSerial);
core::setInputSampleRate(_this->sampleRate);
}
if (_this->running) { SmGui::EndDisabled(); }
SmGui::LeftLabel("Antenna Port");
SmGui::FillWidth();
if (SmGui::Combo(CONCAT("##_hydrasdr_port_", _this->name), &_this->portId, _this->ports.txt)) {
if (_this->running) {
hydrasdr_set_rf_port(_this->openDev, _this->ports[_this->portId]);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["port"] = _this->ports.key(_this->portId);
config.release(true);
}
}
SmGui::BeginGroup();
SmGui::Columns(3, CONCAT("HydraSDRGainModeColumns##_", _this->name), false);
SmGui::ForceSync();
if (SmGui::RadioButton(CONCAT("Sensitive##_hydrasdr_gm_", _this->name), _this->gainMode == 0)) {
_this->gainMode = 0;
if (_this->running) {
hydrasdr_set_lna_agc(_this->openDev, 0);
hydrasdr_set_mixer_agc(_this->openDev, 0);
hydrasdr_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 0;
config.release(true);
}
}
SmGui::NextColumn();
SmGui::ForceSync();
if (SmGui::RadioButton(CONCAT("Linear##_hydrasdr_gm_", _this->name), _this->gainMode == 1)) {
_this->gainMode = 1;
if (_this->running) {
hydrasdr_set_lna_agc(_this->openDev, 0);
hydrasdr_set_mixer_agc(_this->openDev, 0);
hydrasdr_set_linearity_gain(_this->openDev, _this->linearGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 1;
config.release(true);
}
}
SmGui::NextColumn();
SmGui::ForceSync();
if (SmGui::RadioButton(CONCAT("Free##_hydrasdr_gm_", _this->name), _this->gainMode == 2)) {
_this->gainMode = 2;
if (_this->running) {
if (_this->lnaAgc) {
hydrasdr_set_lna_agc(_this->openDev, 1);
}
else {
hydrasdr_set_lna_agc(_this->openDev, 0);
hydrasdr_set_lna_gain(_this->openDev, _this->lnaGain);
}
if (_this->mixerAgc) {
hydrasdr_set_mixer_agc(_this->openDev, 1);
}
else {
hydrasdr_set_mixer_agc(_this->openDev, 0);
hydrasdr_set_mixer_gain(_this->openDev, _this->mixerGain);
}
hydrasdr_set_vga_gain(_this->openDev, _this->vgaGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 2;
config.release(true);
}
}
SmGui::Columns(1, CONCAT("EndHydraSDRGainModeColumns##_", _this->name), false);
SmGui::EndGroup();
// Gain menus
if (_this->gainMode == 0) {
SmGui::LeftLabel("Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_hydrasdr_sens_gain_", _this->name), &_this->sensitiveGain, 0, 21)) {
if (_this->running) {
hydrasdr_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["sensitiveGain"] = _this->sensitiveGain;
config.release(true);
}
}
}
else if (_this->gainMode == 1) {
SmGui::LeftLabel("Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_hydrasdr_lin_gain_", _this->name), &_this->linearGain, 0, 21)) {
if (_this->running) {
hydrasdr_set_linearity_gain(_this->openDev, _this->linearGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["linearGain"] = _this->linearGain;
config.release(true);
}
}
}
else if (_this->gainMode == 2) {
// TODO: Switch to a table for alignment
if (_this->lnaAgc) { SmGui::BeginDisabled(); }
SmGui::LeftLabel("LNA Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_hydrasdr_lna_gain_", _this->name), &_this->lnaGain, 0, 15)) {
if (_this->running) {
hydrasdr_set_lna_gain(_this->openDev, _this->lnaGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["lnaGain"] = _this->lnaGain;
config.release(true);
}
}
if (_this->lnaAgc) { SmGui::EndDisabled(); }
if (_this->mixerAgc) { SmGui::BeginDisabled(); }
SmGui::LeftLabel("Mixer Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_hydrasdr_mix_gain_", _this->name), &_this->mixerGain, 0, 15)) {
if (_this->running) {
hydrasdr_set_mixer_gain(_this->openDev, _this->mixerGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["mixerGain"] = _this->mixerGain;
config.release(true);
}
}
if (_this->mixerAgc) { SmGui::EndDisabled(); }
SmGui::LeftLabel("VGA Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_hydrasdr_vga_gain_", _this->name), &_this->vgaGain, 0, 15)) {
if (_this->running) {
hydrasdr_set_vga_gain(_this->openDev, _this->vgaGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["vgaGain"] = _this->vgaGain;
config.release(true);
}
}
// AGC Control
SmGui::ForceSync();
if (SmGui::Checkbox(CONCAT("LNA AGC##_hydrasdr_", _this->name), &_this->lnaAgc)) {
if (_this->running) {
if (_this->lnaAgc) {
hydrasdr_set_lna_agc(_this->openDev, 1);
}
else {
hydrasdr_set_lna_agc(_this->openDev, 0);
hydrasdr_set_lna_gain(_this->openDev, _this->lnaGain);
}
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["lnaAgc"] = _this->lnaAgc;
config.release(true);
}
}
SmGui::ForceSync();
if (SmGui::Checkbox(CONCAT("Mixer AGC##_hydrasdr_", _this->name), &_this->mixerAgc)) {
if (_this->running) {
if (_this->mixerAgc) {
hydrasdr_set_mixer_agc(_this->openDev, 1);
}
else {
hydrasdr_set_mixer_agc(_this->openDev, 0);
hydrasdr_set_mixer_gain(_this->openDev, _this->mixerGain);
}
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["mixerAgc"] = _this->mixerAgc;
config.release(true);
}
}
}
// Bias T
if (SmGui::Checkbox(CONCAT("Bias T##_hydrasdr_", _this->name), &_this->biasT)) {
if (_this->running) {
hydrasdr_set_rf_bias(_this->openDev, _this->biasT);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["biasT"] = _this->biasT;
config.release(true);
}
}
}
static int callback(hydrasdr_transfer_t* transfer) {
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)transfer->ctx;
memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t));
if (!_this->stream.swap(transfer->sample_count)) { return -1; }
return 0;
}
std::string name;
hydrasdr_device* openDev;
bool enabled = true;
dsp::stream<dsp::complex_t> stream;
double sampleRate;
SourceManager::SourceHandler handler;
bool running = false;
double freq;
uint64_t selectedSerial = 0;
std::string selectedSerStr = "";
int devId = 0;
int srId = 0;
int portId = 0;
bool biasT = false;
int lnaGain = 0;
int vgaGain = 0;
int mixerGain = 0;
int linearGain = 0;
int sensitiveGain = 0;
int gainMode = 0;
bool lnaAgc = false;
bool mixerAgc = false;
#ifdef __ANDROID__
int devFd = 0;
#endif
OptionList<std::string, uint64_t> devices;
OptionList<uint32_t, uint32_t> samplerates;
OptionList<std::string, hydrasdr_rf_port_t> ports;
};
MOD_EXPORT void _INIT_() {
json def = json({});
def["devices"] = json({});
def["device"] = "";
config.setPath(core::args["root"].s() + "/hydrasdr_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new HydraSDRSourceModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
delete (HydraSDRSourceModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -1 +0,0 @@
vendor/*

View File

@ -1,10 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(kcsdr_source)
file(GLOB SRC "src/*.cpp" "src/*.c")
include(${SDRPP_MODULE_CMAKE})
target_link_directories(kcsdr_source PRIVATE "vendor/FTD3XXLibrary_1.3.0.10/x64/DLL")
target_include_directories(kcsdr_source PRIVATE "vendor/FTD3XXLibrary_1.3.0.10")
target_link_libraries(kcsdr_source PRIVATE FTD3XX)

View File

@ -1,209 +0,0 @@
#include "kcsdr.h"
#include <string.h>
#include "../vendor/FTD3XXLibrary_1.3.0.10/FTD3XX.h"
#include <stdio.h>
#include <stddef.h>
#define KCSDR_PKT_EMPTY_LEN 0x0C
#define KCSDR_COMMAND_PIPE 0x02
#define KCSDR_RX_DATA_PIPE 0x83
#define KCSDR_TX_DATA_PIPE 0x03
struct kcsdr {
FT_HANDLE ft;
};
#pragma pack(push, 1)
struct kcsdr_packet {
uint8_t zeros0[4];
uint8_t length;
uint8_t zeros1[2];
uint8_t hex_eighty;
uint32_t command;
uint8_t data[188];
};
typedef struct kcsdr_packet kcsdr_packet_t;
#pragma pack(pop)
enum kcsdr_command {
CMD_NOT_USED_0x00 = 0x00,
CMD_SET_PORT = 0x01,
CMD_SET_FREQUENCY = 0x02,
CMD_SET_ATTENUATION = 0x03,
CMD_SET_AMPLIFIER = 0x04,
CMD_SET_BANDWIDTH = 0x05,
CMD_START = 0x06,
CMD_STOP = 0x07,
CMD_SET_EXT_AMP = 0x08,
CMD_START_REMOTE = 0x09,
CMD_STOP_REMOTE = 0x0A
};
typedef enum kcsdr_command kcsdr_command_t;
int kcsdr_send_command(kcsdr_t* dev, kcsdr_direction_t dir, kcsdr_command_t cmd, const uint8_t* data, int len) {
Sleep(50);
// Create an empty packet
kcsdr_packet_t pkt;
memset(&pkt, 0, sizeof(kcsdr_packet_t));
// Fill out the packet info
pkt.length = len + KCSDR_PKT_EMPTY_LEN;
pkt.hex_eighty = 0x80; // Whatever the fuck that is
pkt.command = (uint32_t)cmd | (uint32_t)dir;
// Copy the data if there is some
if (len) { memcpy(pkt.data, data, len); }
// Dump the bytes
uint8_t* dump = (uint8_t*)&pkt;
printf("Sending:");
for (int i = 0; i < pkt.length; i++) {
printf(" %02X", dump[i]);
}
printf("\n");
// Send the command to endpoint 0
int sent;
FT_STATUS err = FT_WritePipeEx(dev->ft, KCSDR_COMMAND_PIPE, (uint8_t*)&pkt, sizeof(kcsdr_packet_t), &sent, NULL);
if (err != FT_OK) {
return -err;
}
printf("Sent %d bytes (%d)\n", sent, err);
Sleep(50);
// Flush existing commands
FT_FlushPipe(dev->ft, KCSDR_COMMAND_PIPE);
return -(int)err;
}
int kcsdr_list_devices(kcsdr_info_t** devices) {
// Generate a list of FTDI devices
int ftdiDevCount = 0;
FT_STATUS err = FT_CreateDeviceInfoList(&ftdiDevCount);
if (err != FT_OK) {
return -1;
}
// If no device was found, return nothing
if (!ftdiDevCount) {
*devices = NULL;
return 0;
}
// Get said device list
FT_DEVICE_LIST_INFO_NODE* list = malloc(ftdiDevCount * sizeof(FT_DEVICE_LIST_INFO_NODE));
err = FT_GetDeviceInfoList(list, &ftdiDevCount);
if (err != FT_OK) {
return -1;
}
// Allocate the device info list
*devices = malloc(ftdiDevCount * sizeof(kcsdr_info_t));
// Find all KC908s
int kcCount = 0;
for (int i = 0; i < ftdiDevCount; i++) {
strcpy((*devices)[kcCount++].serial, list[i].SerialNumber);
}
// Free the FTDI list
free(list);
return kcCount;
}
void kcsdr_free_device_list(kcsdr_info_t* devices) {
// Free the list
if (devices) { free(devices); }
}
int kcsdr_open(kcsdr_t** dev, const char* serial) {
// Attempt to open the device using the serial number
FT_HANDLE ft;
FT_STATUS err = FT_Create(serial, FT_OPEN_BY_SERIAL_NUMBER, &ft);
if (err != FT_OK) {
return -1;
}
// Set the timeouts for the data pipes
FT_SetPipeTimeout(ft, KCSDR_RX_DATA_PIPE, 1000);
FT_SetPipeTimeout(ft, KCSDR_TX_DATA_PIPE, 1000);
// Allocate the device object
*dev = malloc(sizeof(kcsdr_t));
// Fill out the device object
(*dev)->ft = ft;
// Put device into remote control mode
return kcsdr_send_command(*dev, KCSDR_DIR_RX, CMD_START_REMOTE, NULL, 0);
}
void kcsdr_close(kcsdr_t* dev) {
// Put device back in normal mode
kcsdr_send_command(dev, KCSDR_DIR_RX, CMD_STOP_REMOTE, NULL, 0);
// Close the device
FT_Close(dev->ft);
// Free the device object
free(dev);
}
int kcsdr_set_port(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t port) {
// Send SET_PORT command
return kcsdr_send_command(dev, dir, CMD_SET_PORT, &port, 1);
}
int kcsdr_set_frequency(kcsdr_t* dev, kcsdr_direction_t dir, uint64_t freq) {
// Send SET_FREQUENCY command
return kcsdr_send_command(dev, dir, CMD_SET_FREQUENCY, (uint8_t*)&freq, 8);
}
int kcsdr_set_attenuation(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t att) {
// Send SET_ATTENUATION command
return kcsdr_send_command(dev, dir, CMD_SET_ATTENUATION, &att, 1);
}
int kcsdr_set_amp_gain(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t gain) {
// Send SET_AMPLIFIER command
return kcsdr_send_command(dev, dir, CMD_SET_AMPLIFIER, &gain, 1);
}
int kcsdr_set_rx_ext_amp_gain(kcsdr_t* dev, uint8_t gain) {
// Send CMD_SET_EXT_AMP command
return kcsdr_send_command(dev, KCSDR_DIR_RX, CMD_SET_EXT_AMP, &gain, 1);
}
int kcsdr_set_samplerate(kcsdr_t* dev, kcsdr_direction_t dir, uint32_t samplerate) {
// Set SET_BANDWIDTH command
return kcsdr_send_command(dev, dir, CMD_SET_BANDWIDTH, (uint8_t*)&samplerate, 4);
}
int kcsdr_start(kcsdr_t* dev, kcsdr_direction_t dir) {
// Send START command
return kcsdr_send_command(dev, dir, CMD_START, NULL, 0);
}
int kcsdr_stop(kcsdr_t* dev, kcsdr_direction_t dir) {
// Send STOP command
return kcsdr_send_command(dev, dir, CMD_STOP, NULL, 0);
}
int kcsdr_rx(kcsdr_t* dev, int16_t* samples, int count) {
// Receive samples (TODO: Endpoint might be 0x81)
int received;
FT_STATUS err = FT_ReadPipeEx(dev->ft, KCSDR_RX_DATA_PIPE, (uint8_t*)samples, count*2*sizeof(uint16_t), &received, NULL);
return (err == FT_OK) ? received / (2*sizeof(uint16_t)) : -(int)err;
}
int kcsdr_tx(kcsdr_t* dev, const int16_t* samples, int count) {
// Transmit samples
int sent;
FT_STATUS err = FT_WritePipeEx(dev->ft, KCSDR_TX_DATA_PIPE, (uint8_t*)samples, count*2*sizeof(uint16_t), &sent, NULL);
return (err == FT_OK) ? sent / (2*sizeof(uint16_t)) : -(int)err;
}

View File

@ -1,150 +0,0 @@
#pragma once
#include <stdint.h>
#define KCSDR_SERIAL_LEN 16
#define KCSDR_MAX_PORTS 6
// Detect C++
#ifdef __cplusplus
extern "C" {
#endif
/**
* KCSDR Device.
*/
struct kcsdr;
typedef struct kcsdr kcsdr_t;
/**
* Device Information
*/
struct kcsdr_info {
char serial[KCSDR_SERIAL_LEN+1];
};
typedef struct kcsdr_info kcsdr_info_t;
/**
* RF Direction.
*/
enum kcsdr_direction {
KCSDR_DIR_RX = 0x00,
KCSDR_DIR_TX = 0x80
};
typedef enum kcsdr_direction kcsdr_direction_t;
/**
* Get a list of KCSDR devices on the system.
* @param devices Pointer to an array of device info.
* @return Number of devices found or error code.
*/
int kcsdr_list_devices(kcsdr_info_t** devices);
/**
* Free a device list returned by `kcsdr_list_devices()`.
* @param devices Device list to free.
*/
void kcsdr_free_device_list(kcsdr_info_t* devices);
/**
* Open a KCSDR device.
* @param dev Newly open device.
* @param serial Serial number of the device to open as returned in the device list.
* @return 0 on success, error code otherwise.
*/
int kcsdr_open(kcsdr_t** dev, const char* serial);
/**
* Close a KCSDR device.
* @param dev Device to be closed.
*/
void kcsdr_close(kcsdr_t* dev);
/**
* Select the RF port.
* @param dev Device to control.
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX.
* @param port RF port number to select.
* @return 0 on success, error code otherwise.
*/
int kcsdr_set_port(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t port);
/**
* Set the center frequency.
* @param dev Device to control.
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX
* @param freq Frequency in Hz.
* @return 0 on success, error code otherwise.
*/
int kcsdr_set_frequency(kcsdr_t* dev, kcsdr_direction_t dir, uint64_t freq);
/**
* Set the attenuation.
* @param dev Device to control.
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX
* @param samplerate Attenuation in dB.
* @return 0 on success, error code otherwise.
*/
int kcsdr_set_attenuation(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t att);
/**
* Set the internal amplifier gain.
* @param dev Device to control.
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX
* @param gain Gain in dB.
* @return 0 on success, error code otherwise.
*/
int kcsdr_set_amp_gain(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t gain);
/**
* Set the external amplifier gain.
* @param dev Device to control.
* @param gain Gain in dB.
* @return 0 on success, error code otherwise.
*/
int kcsdr_set_rx_ext_amp_gain(kcsdr_t* dev, uint8_t gain);
/**
* Set the samplerate.
* @param dev Device to control.
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX
* @param samplerate Samplerate in Hz.
* @return 0 on success, error code otherwise.
*/
int kcsdr_set_samplerate(kcsdr_t* dev, kcsdr_direction_t dir, uint32_t samplerate);
/**
* Start streaming samples.
* @param dev Device to control.
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX.
* @return 0 on success, error code otherwise.
*/
int kcsdr_start(kcsdr_t* dev, kcsdr_direction_t dir);
/**
* Stop streaming samples.
* @param dev Device to control.
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX.
* @return 0 on success, error code otherwise.
*/
int kcsdr_stop(kcsdr_t* dev, kcsdr_direction_t dir);
/**
* Receive a buffer of samples.
* @param samples Sample buffer.
* @param count Number of complex samples.
* @return Number of samples received.
*/
int kcsdr_rx(kcsdr_t* dev, int16_t* samples, int count);
/**
* Transmit a buffer of samples.
* @param samples Sample buffer.
* @param count Number of complex samples.
* @return Number of samples transmitted.
*/
int kcsdr_tx(kcsdr_t* dev, const int16_t* samples, int count);
// Detect C++
#ifdef __cplusplus
}
#endif

View File

@ -1,324 +0,0 @@
#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 "kcsdr.h"
#include <atomic>
SDRPP_MOD_INFO{
/* Name: */ "kcsdr_source",
/* Description: */ "KCSDR Source Module",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
#define CONCAT(a, b) ((std::string(a) + b).c_str())
class KCSDRSourceModule : public ModuleManager::Instance {
public:
KCSDRSourceModule(std::string name) {
this->name = name;
sampleRate = 2000000.0;
samplerates.define(40e6, "40MHz", 40e6);
samplerates.define(35e6, "35MHz", 35e6);
samplerates.define(30e6, "30MHz", 30e6);
samplerates.define(25e6, "25MHz", 25e6);
samplerates.define(20e6, "20MHz", 20e6);
samplerates.define(15e6, "15MHz", 15e6);
samplerates.define(10e6, "10MHz", 10e6);
samplerates.define(5e6, "5MHz", 5e6);
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("KCSDR", &handler);
}
~KCSDRSourceModule() {
}
void postInit() {}
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
void refresh() {
devices.clear();
// Get device list
kcsdr_info_t* list;
int count = kcsdr_list_devices(&list);
if (count < 0) {
flog::error("Failed to list devices: {}", count);
return;
}
// Create list
for (int i = 0; i < count; i++) {
devices.define(list[i].serial, list[i].serial, list[i].serial);
}
// Free the device list
kcsdr_free_device_list(list);
}
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);
// TODO
// Update the samplerate
core::setInputSampleRate(sampleRate);
// Save serial number
selectedSerial = serial;
}
static void menuSelected(void* ctx) {
KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
flog::info("KCSDRSourceModule '{0}': Menu Select!", _this->name);
}
static void menuDeselected(void* ctx) {
KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx;
flog::info("KCSDRSourceModule '{0}': Menu Deselect!", _this->name);
}
static void start(void* ctx) {
KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx;
if (_this->running) { return; }
// If no serial is given, do nothing
if (_this->selectedSerial.empty()) { return; }
// Open the device
int err = kcsdr_open(&_this->openDev, _this->selectedSerial.c_str());
if (err) {
flog::error("Failed to open device: {}", err);
return;
}
// Configure the device
kcsdr_set_port(_this->openDev, KCSDR_DIR_RX, 0);
kcsdr_set_frequency(_this->openDev, KCSDR_DIR_RX, _this->freq);
kcsdr_set_attenuation(_this->openDev, KCSDR_DIR_RX, _this->att);
kcsdr_set_amp_gain(_this->openDev, KCSDR_DIR_RX, _this->gain);
kcsdr_set_rx_ext_amp_gain(_this->openDev, _this->extGain);
kcsdr_set_samplerate(_this->openDev, KCSDR_DIR_RX, _this->sampleRate);
// Start the stream
kcsdr_start(_this->openDev, KCSDR_DIR_RX);
// Start worker
_this->run = true;
_this->workerThread = std::thread(&KCSDRSourceModule::worker, _this);
_this->running = true;
flog::info("KCSDRSourceModule '{0}': Start!", _this->name);
}
static void stop(void* ctx) {
KCSDRSourceModule* _this = (KCSDRSourceModule*)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 streaming
kcsdr_stop(_this->openDev, KCSDR_DIR_RX);
// Close the device
kcsdr_close(_this->openDev);
flog::info("KCSDRSourceModule '{0}': Stop!", _this->name);
}
static void tune(double freq, void* ctx) {
KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx;
if (_this->running) {
kcsdr_set_frequency(_this->openDev, KCSDR_DIR_RX, freq);
}
_this->freq = freq;
flog::info("KCSDRSourceModule '{0}': Tune: {1}!", _this->name, freq);
}
static void menuHandler(void* ctx) {
KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx;
if (_this->running) { SmGui::BeginDisabled(); }
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##_kcsdr_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("##_kcsdr_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##_kcsdr_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("##_kcsdr_port_", _this->name), &_this->portId, _this->rxPorts.txt)) {
// if (_this->running) {
// // TODO
// }
// // TODO: Save
// }
SmGui::LeftLabel("Attenuation");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_kcsdr_att_", _this->name), &_this->att, 0, 31)) {
if (_this->running) {
kcsdr_set_attenuation(_this->openDev, KCSDR_DIR_RX, _this->att);
}
// TODO: Save
}
SmGui::LeftLabel("Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_kcsdr_gain_", _this->name), &_this->gain, 0, 31)) {
if (_this->running) {
kcsdr_set_amp_gain(_this->openDev, KCSDR_DIR_RX, _this->gain);
}
// TODO: Save
}
SmGui::LeftLabel("External Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_kcsdr_ext_gain_", _this->name), &_this->extGain, 0, 31)) {
if (_this->running) {
kcsdr_set_rx_ext_amp_gain(_this->openDev, _this->extGain);
}
// TODO: Save
}
}
void worker() {
// Compute the buffer size
int bufferSize = 0x4000/4;//sampleRate / 200;
// Allocate the sample buffer
int16_t* samps = dsp::buffer::alloc<int16_t>(bufferSize*2);
// Loop
while (run) {
// Read samples
int count = kcsdr_rx(openDev, samps, bufferSize);
if (!count) { continue; }
if (count < 0) {
flog::debug("Failed to read samples: {}", count);
break;
}
// Convert the samples to float
volk_16i_s32f_convert_32f((float*)stream.writeBuf, samps, 8192.0f, count*2);
// Send out the samples
if (!stream.swap(count)) { break; }
}
// Free the sample buffer
dsp::buffer::free(samps);
}
std::string name;
bool enabled = true;
dsp::stream<dsp::complex_t> stream;
double sampleRate;
SourceManager::SourceHandler handler;
bool running = false;
double freq;
OptionList<std::string, std::string> devices;
OptionList<int, double> samplerates;
int devId = 0;
int srId = 0;
int att = 0;
int gain = 30;
int extGain = 1;
int portId = 0;
std::string selectedSerial;
kcsdr_t* openDev;
std::thread workerThread;
std::atomic<bool> run = false;
};
MOD_EXPORT void _INIT_() {
// Nothing here
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new KCSDRSourceModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (KCSDRSourceModule*)instance;
}
MOD_EXPORT void _END_() {
// Nothing here
}

View File

@ -36,10 +36,10 @@ enum SampleType {
};
const size_t SAMPLE_TYPE_SIZE[] {
2*sizeof(int8_t),
2*sizeof(int16_t),
2*sizeof(int32_t),
2*sizeof(float),
sizeof(int8_t)*2,
sizeof(int16_t)*2,
sizeof(int32_t)*2,
sizeof(float)*2,
};
class NetworkSourceModule : public ModuleManager::Instance {
@ -58,6 +58,20 @@ public:
handler.tuneHandler = tune;
handler.stream = &stream;
// Define samplerates
for (int i = 3000; i <= 192000; i <<= 1) {
samplerates.define(i, getSrScaled(i), i);
}
for (int i = 250000; i < 1000000; i += 250000) {
samplerates.define(i, getSrScaled(i), i);
}
for (int i = 1000000; i < 10000000; i += 500000) {
samplerates.define(i, getSrScaled(i), i);
}
for (int i = 10000000; i <= 100000000; i += 5000000) {
samplerates.define(i, getSrScaled(i), i);
}
// Define protocols
// protocols.define("TCP (Server)", PROTOCOL_TCP_SERVER);
protocols.define("TCP (Client)", PROTOCOL_TCP_CLIENT);
@ -72,8 +86,8 @@ public:
// Load config
config.acquire();
if (config.conf[name].contains("samplerate")) {
samplerate = config.conf[name]["samplerate"];
tempSamplerate = samplerate;
int sr = config.conf[name]["samplerate"];
if (samplerates.keyExists(sr)) { samplerate = samplerates.value(samplerates.keyId(sr)); }
}
if (config.conf[name].contains("protocol")) {
std::string protoStr = config.conf[name]["protocol"];
@ -94,6 +108,7 @@ public:
config.release();
// Set menu IDs
srId = samplerates.valueId(samplerate);
protoId = protocols.valueId(proto);
sampTypeId = sampleTypes.valueId(sampType);
@ -213,24 +228,35 @@ private:
if (_this->running) { SmGui::BeginDisabled(); }
// Hostname and port field
if (SmGui::InputText(("##network_source_host_" + _this->name).c_str(), _this->hostname, sizeof(_this->hostname))) {
if (ImGui::InputText(("##iq_exporter_host_" + _this->name).c_str(), _this->hostname, sizeof(_this->hostname))) {
config.acquire();
config.conf[_this->name]["host"] = _this->hostname;
config.release(true);
}
SmGui::SameLine();
SmGui::FillWidth();
if (SmGui::InputInt(("##network_source_port_" + _this->name).c_str(), &_this->port, 0, 0)) {
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::InputInt(("##iq_exporter_port_" + _this->name).c_str(), &_this->port, 0, 0)) {
_this->port = std::clamp<int>(_this->port, 1, 65535);
config.acquire();
config.conf[_this->name]["port"] = _this->port;
config.release(true);
}
// Samplerate selector
ImGui::LeftLabel("Samplerate");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_sr_" + _this->name).c_str(), &_this->srId, _this->samplerates.txt)) {
_this->samplerate = _this->samplerates.value(_this->srId);
core::setInputSampleRate(_this->samplerate);
config.acquire();
config.conf[_this->name]["samplerate"] = _this->samplerates.key(_this->srId);
config.release(true);
}
// Mode protocol selector
SmGui::LeftLabel("Protocol");
SmGui::FillWidth();
if (SmGui::Combo(("##network_source_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
ImGui::LeftLabel("Protocol");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
_this->proto = _this->protocols.value(_this->protoId);
config.acquire();
config.conf[_this->name]["protocol"] = _this->protocols.key(_this->protoId);
@ -238,38 +264,15 @@ private:
}
// Sample type selector
SmGui::LeftLabel("Sample type");
SmGui::FillWidth();
if (SmGui::Combo(("##network_source_samp_" + _this->name).c_str(), &_this->sampTypeId, _this->sampleTypes.txt)) {
ImGui::LeftLabel("Sample type");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_samp_" + _this->name).c_str(), &_this->sampTypeId, _this->sampleTypes.txt)) {
_this->sampType = _this->sampleTypes.value(_this->sampTypeId);
config.acquire();
config.conf[_this->name]["sampleType"] = _this->sampleTypes.key(_this->sampTypeId);
config.release(true);
}
// Samplerate selector
SmGui::LeftLabel("Samplerate");
SmGui::FillWidth();
if (SmGui::InputInt(("##network_source_sr_" + _this->name).c_str(), &_this->tempSamplerate)) {
// Prevent silly values from silly users
_this->tempSamplerate = std::max<int>(_this->tempSamplerate, 1000);
}
bool applyEn = (!_this->running && _this->tempSamplerate != _this->samplerate);
if (!applyEn) { SmGui::BeginDisabled(); }
SmGui::FillWidth();
if (SmGui::Button(("Apply##network_source_apply_" + _this->name).c_str())) {
_this->samplerate = _this->tempSamplerate;
core::setInputSampleRate(_this->samplerate);
config.acquire();
config.conf[_this->name]["samplerate"] = _this->samplerate;
config.release(true);
}
if (!applyEn) { SmGui::EndDisabled(); }
if (_this->tempSamplerate != _this->samplerate) {
SmGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Warning: Samplerate not applied yet");
}
if (_this->running) { SmGui::EndDisabled(); }
}
@ -277,17 +280,14 @@ private:
// Compute sizes
int blockSize = samplerate / 200;
int sampleSize = SAMPLE_TYPE_SIZE[sampType];
// Chose amount of bytes to attempt to read
bool forceSize = (proto != PROTOCOL_UDP);
int frameSize = sampleSize * (forceSize ? blockSize : STREAM_BUFFER_SIZE);
int frameSize = blockSize*sampleSize;
// Allocate receive buffer
uint8_t* buffer = dsp::buffer::alloc<uint8_t>(frameSize);
while (true) {
// Read samples from socket
int bytes = sock->recv(buffer, frameSize, forceSize);
int bytes = sock->recv(buffer, frameSize, true);
if (bytes <= 0) { break; }
// Convert to CF32 (note: problem if partial sample)
@ -325,7 +325,7 @@ private:
double freq;
int samplerate = 1000000;
int tempSamplerate = 1000000;
int srId;
Protocol proto = PROTOCOL_UDP;
int protoId;
SampleType sampType = SAMPLE_TYPE_INT16;
@ -333,6 +333,7 @@ private:
char hostname[1024] = "localhost";
int port = 1234;
OptionList<int, int> samplerates;
OptionList<std::string, Protocol> protocols;
OptionList<std::string, SampleType> sampleTypes;

View File

@ -23,12 +23,6 @@ SDRPP_MOD_INFO{
ConfigManager config;
const std::vector<const char*> deviceWhiteList = {
"PlutoSDR",
"ANTSDR",
"LibreSDR"
};
class PlutoSDRSourceModule : public ModuleManager::Instance {
public:
PlutoSDRSourceModule(std::string name) {
@ -136,14 +130,7 @@ private:
std::string duri = iio_context_info_get_uri(info);
// If the device is not a plutosdr, don't include it
bool isPluto = false;
for (const auto type : deviceWhiteList) {
if (desc.find(type) != std::string::npos) {
isPluto = true;
break;
}
}
if (!isPluto) {
if (desc.find("PlutoSDR") == std::string::npos) {
flog::warn("Ignored IIO device: [{}] {}", duri, desc);
continue;
}
@ -176,9 +163,6 @@ private:
// Construct the device name
std::string devName = '(' + backend + ") " + model + " [" + serial + ']';
// Skip duplicate devices
if (devices.keyExists(desc) || devices.nameExists(devName) || devices.valueExists(duri)) { continue; }
// Save device
devices.define(desc, devName, duri);
}

View File

@ -5,7 +5,6 @@
#include <thread>
#include <vector>
#include <mutex>
#include <chrono>
#define RFSPACE_MAX_SIZE 8192
#define RFSPACE_HEARTBEAT_INTERVAL_MS 1000

View File

@ -121,23 +121,14 @@ public:
#ifndef __ANDROID__
devCount = rtlsdr_get_device_count();
char buf[1024];
char venBuf[256];
char prodBuf[256];
char snBuf[256];
char snBuf[1024];
for (int i = 0; i < devCount; i++) {
// Gather device info
const char* devName = rtlsdr_get_device_name(i);
int snErr = rtlsdr_get_device_usb_strings(i, venBuf, prodBuf, snBuf);
int snErr = rtlsdr_get_device_usb_strings(i, NULL, NULL, snBuf);
// Build name
if (venBuf[0] && prodBuf[0]) {
sprintf(buf, "%s %s [%s]##%d", venBuf, prodBuf, (!snErr && snBuf[0]) ? snBuf : "No Serial", i);
}
else {
sprintf(buf, "%s [%s]##%d", devName, (!snErr && snBuf[0]) ? snBuf : "No Serial", i);
}
// Add device to list
sprintf(buf, "[%s] %s##%d", (!snErr && snBuf[0]) ? snBuf : "No Serial", devName, i);
devNames.push_back(buf);
devListTxt += buf;
devListTxt += '\0';
@ -208,6 +199,8 @@ public:
config.conf["devices"][selectedDevName]["tunerAgc"] = tunerAgc;
config.conf["devices"][selectedDevName]["gain"] = gainId;
}
if (gainId >= gainList.size()) { gainId = gainList.size() - 1; }
updateGainTxt();
// Load config
if (config.conf["devices"][selectedDevName].contains("sampleRate")) {
@ -247,11 +240,9 @@ public:
if (config.conf["devices"][selectedDevName].contains("gain")) {
gainId = config.conf["devices"][selectedDevName]["gain"];
updateGainTxt();
}
if (gainId >= gainList.size()) { gainId = gainList.size() - 1; }
updateGainTxt();
config.release(created);
rtlsdr_close(openDev);
@ -604,4 +595,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}
}

View File

@ -1,9 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(sddc_source)
file(GLOB SRC "src/*.cpp")
include(${SDRPP_MODULE_CMAKE})
add_subdirectory("./libsddc")
target_link_libraries(sddc_source PRIVATE sddc)

View File

@ -1,2 +0,0 @@
build/
.vscode/

View File

@ -1,87 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(libsddc VERSION 0.2.0)
# Options
option(BUILD_SDDC_UTILS "Build SDDC utilities such as sddc_info" ON)
option(INSTALL_UDEV_RULES "Install UDEV rules (Linux only)" ON)
# List all source files
file(GLOB_RECURSE SRC "src/*.c")
# On windows, all symbols must be exported
if (MSVC)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif ()
# Create dynamic libs
add_library(sddc SHARED ${SRC})
# # Set optimisation flags
# if (${CMAKE_BUILD_TYPE} MATCHES "Debug")
# # Debug Flags
# if (MSVC)
# target_compile_options(sddc PRIVATE /EHsc)
# else ()
# target_compile_options(sddc PRIVATE -g -Og)
# endif ()
# else()
# # Normal Flags
# if (MSVC)
# target_compile_options(sddc PRIVATE /O2 /Ob2 /EHsc)
# else ()
# target_compile_options(sddc PRIVATE -O3)
# endif ()
# endif()
# Include the include folder
target_include_directories(sddc PUBLIC "include/")
# Find libusb
find_package(PkgConfig REQUIRED)
pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0)
# Link to libusb
target_link_libraries(sddc PRIVATE PkgConfig::libusb)
# TODO: Have it default instead of override
if (MSVC)
set(CMAKE_INSTALL_PREFIX "C:/Program Files/SDDC/")
set(CMAKE_INSTALL_BINDIR "bin")
set(CMAKE_INSTALL_LIBDIR "lib")
set(CMAKE_INSTALL_INCLUDEDIR "include")
else ()
include(GNUInstallDirs)
endif ()
if (NOT MSVC)
# Configure pkgconfig file
configure_file(${CMAKE_SOURCE_DIR}/libsddc.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libsddc.pc @ONLY)
# Install pkgconfig file
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libsddc.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
endif ()
# Install the library
if (MSVC)
install(TARGETS sddc)
else ()
install(TARGETS sddc DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif ()
# Install the headers
install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
# On Windows, install dependencies
if (MSVC)
install(FILES $<TARGET_FILE_DIR:sddc>/libusb-1.0.dll DESTINATION ${CMAKE_INSTALL_BINDIR})
endif ()
# Build utils if enabled
if (BUILD_SDDC_UTILS)
add_subdirectory("utils/sddc_info")
add_subdirectory("utils/sddc_rx")
endif ()
# # Create uninstall target
# configure_file(${CMAKE_SOURCE_DIR}/cmake/uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/uninstall.cmake @ONLY)
# add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/uninstall.cmake)

View File

@ -1,32 +0,0 @@
# http://www.vtk.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F
IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
STRING(REGEX REPLACE "\n" ";" files "${files}")
FOREACH(file ${files})
MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
IF(EXISTS "$ENV{DESTDIR}${file}")
EXEC_PROGRAM(
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
IF(NOT "${rm_retval}" STREQUAL 0)
MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
ENDIF(NOT "${rm_retval}" STREQUAL 0)
ELSEIF(IS_SYMLINK "$ENV{DESTDIR}${file}")
EXEC_PROGRAM(
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
IF(NOT "${rm_retval}" STREQUAL 0)
MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
ENDIF(NOT "${rm_retval}" STREQUAL 0)
ELSE(EXISTS "$ENV{DESTDIR}${file}")
MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.")
ENDIF(EXISTS "$ENV{DESTDIR}${file}")
ENDFOREACH(file)

View File

@ -1,177 +0,0 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
// Handle inclusion from C++ code
#ifdef __cplusplus
extern "C" {
#endif
#define SDDC_SERIAL_MAX_LEN 256
enum sddc_model {
SDDC_MODEL_UNKNOWN = 0x00,
SDDC_MODEL_BBRF103 = 0x01,
SDDC_MODEL_HF103 = 0x02,
SDDC_MODEL_RX888 = 0x03,
SDDC_MODEL_RX888_MK2 = 0x04,
SDDC_MODEL_RX999 = 0x05,
SDDC_MODEL_RXLUCY = 0x06,
SDDC_MODEL_RX888_MK3 = 0x07
};
typedef enum sddc_model sddc_model_t;
enum sddc_error {
SDDC_ERROR_UNKNOWN = -99,
SDDC_ERROR_NOT_IMPLEMENTED = -98,
SDDC_ERROR_FIRMWARE_UPLOAD_FAILED = -4,
SDDC_ERROR_NOT_FOUND = -3,
SDDC_ERROR_USB_ERROR = -2,
SDDC_ERROR_TIMEOUT = -1,
SDDC_SUCCESS = 0
};
typedef enum sddc_error sddc_error_t;
/**
* Device instance.
*/
struct sddc_dev;
typedef struct sddc_dev sddc_dev_t;
/**
* Device information.
*/
struct sddc_devinfo {
const char serial[SDDC_SERIAL_MAX_LEN];
sddc_model_t model;
int firmwareMajor;
int firmwareMinor;
};
typedef struct sddc_devinfo sddc_devinfo_t;
/**
* Parameter range. A step size of zero means infinitely variable.
*/
struct sddc_range {
double start;
double end;
double step;
};
typedef struct sddc_range sddc_range_t;
/**
* Get the string representation of a device model.
* @param model Model to get the string representation of.
* @return String representation of the model.
*/
const char* sddc_model_to_string(sddc_model_t model);
/**
* Get the string representation of an error.
* @param model Error to get the string representation of.
* @return String representation of the error.
*/
const char* sddc_error_to_string(sddc_error_t error);
/**
* Set the path to the firmware image.
* @param path Path to the firmware image.
* @return SDDC_SUCCESS on success or an error code otherwise.
*/
sddc_error_t sddc_set_firmware_path(const char* path);
/**
* Get a list of connected devices. The returned list has to be freed using `sddc_free_device_list()` if it isn't empty.
* @param dev_list Pointer to a list of devices.
* @return Number of devices in the list or an error code if an error occured.
*/
int sddc_get_device_list(sddc_devinfo_t** dev_list);
/**
* Free a device list returned by `sddc_get_device_list()`. Attempting to free a list returned empty has no effect.
* @param dev_list Device list to free.
*/
void sddc_free_device_list(sddc_devinfo_t* dev_list);
/**
* Open a device by its serial number.
* @param serial Serial number of the device to open.
* @param dev Pointer to a SDDC device pointer to populate once open.
* @return SDDC_SUCCESS on success or an error code otherwise.
*/
sddc_error_t sddc_open(const char* serial, sddc_dev_t** dev);
/**
* Close an opened SDDC device.
* @param dev SDDC Device to close.
*/
void sddc_close(sddc_dev_t* dev);
/**
* Get the range of samplerate supported by a device.
* @param dev SDDC device.
* @return Range of supported samplerates.
*/
sddc_range_t sddc_get_samplerate_range(sddc_dev_t* dev);
/**
* Set the device's sampling rate.
* @param dev SDDC device.
* @param samplerate Sampling rate.
* @return SDDC_SUCCESS on success or an error code otherwise.
*/
sddc_error_t sddc_set_samplerate(sddc_dev_t* dev, uint32_t samplerate);
/**
* Enable the ADC's dithering feature.
* @param dev SDDC device.
* @param enabled True to enable dithering, false to disable it.
* @return SDDC_SUCCESS on success or an error code otherwise.
*/
sddc_error_t sddc_set_dithering(sddc_dev_t* dev, bool enabled);
/**
* Enable the ADC's randomizer feature.
* @param dev SDDC device.
* @param enabled True to enable randomization, false to disable it.
* @return SDDC_SUCCESS on success or an error code otherwise.
*/
sddc_error_t sddc_set_randomizer(sddc_dev_t* dev, bool enabled);
/**
* Set the LO of the tuner.
* @param dev SDDC device.
* @param frequency Frequency of the LO.
* @return SDDC_SUCCESS on success or an error code otherwise.
*/
sddc_error_t sddc_set_tuner_frequency(sddc_dev_t* dev, uint64_t frequency);
/**
* Start the device.
* @param dev SDDC device.
* @return SDDC_SUCCESS on success or an error code otherwise.
*/
sddc_error_t sddc_start(sddc_dev_t* dev);
/**
* Stop the device.
* @param dev SDDC device.
* @return SDDC_SUCCESS on success or an error code otherwise.
*/
sddc_error_t sddc_stop(sddc_dev_t* dev);
/**
* Receive samples.
* @param dev SDDC device.
* @param samples Buffer to write the samples to.
* @param count Number of samples to read.
* @return SDDC_SUCCESS on success or an error code otherwise.
*/
sddc_error_t sddc_rx(sddc_dev_t* dev, int16_t* samples, int count);
// Handle inclusion from C++ code
#ifdef __cplusplus
}
#endif

View File

@ -1,165 +0,0 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -1,116 +0,0 @@
#include "fx3_boot.h"
#include <stdio.h>
#define FX3_TIMEOUT 1000
#define FX3_VENDOR_REQUEST 0xA0
#define FX3_MAX_BLOCK_SIZE 0x1000
int sddc_fx3_boot_mem_read(libusb_device_handle* dev, uint32_t addr, uint8_t* data, uint16_t len) {
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, FX3_VENDOR_REQUEST, addr & 0xFFFF, addr >> 16, data, len, FX3_TIMEOUT);
}
int sddc_fx3_boot_mem_write(libusb_device_handle* dev, uint32_t addr, const uint8_t* data, uint16_t len) {
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, FX3_VENDOR_REQUEST, addr & 0xFFFF, addr >> 16, data, len, FX3_TIMEOUT);
}
int sddc_fx3_boot_run(libusb_device_handle* dev, uint32_t entry) {
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, FX3_VENDOR_REQUEST, entry & 0xFFFF, entry >> 16, NULL, 0, FX3_TIMEOUT);
}
int sddc_fx3_boot_upload_firmware(libusb_device_handle* dev, const char* path) {
// Open the firmware image
FILE* fw = fopen(path, "rb");
if (!fw) {
fprintf(stderr, "Failed to open firmware image\n");
return LIBUSB_ERROR_OTHER;
}
// Read the signature
char sign[2];
int read = fread(sign, 2, 1, fw);
if (read != 1) {
fprintf(stderr, "Failed to read firmware image signature: %d\n", read);
fclose(fw);
return LIBUSB_ERROR_OTHER;
}
// Check the signature
if (sign[0] != 'C' || sign[1] != 'Y') {
fprintf(stderr, "Firmware image has invalid signature\n");
fclose(fw);
return LIBUSB_ERROR_OTHER;
}
// Skip useless metadata
int err = fseek(fw, 2, SEEK_CUR);
if (err) {
fprintf(stderr, "Invalid firmware image: %d\n", err);
fclose(fw);
return LIBUSB_ERROR_OTHER;
}
// Preallocate data buffer
int bufferSize = 0x10000;
uint8_t* buffer = malloc(bufferSize);
// Read every section
while (1) {
// Read the section size
uint32_t sizeWords;
read = fread(&sizeWords, sizeof(uint32_t), 1, fw);
if (read != 1) {
fprintf(stderr, "Invalid firmware image section size\n");
fclose(fw);
return LIBUSB_ERROR_OTHER;
}
uint32_t size = sizeWords << 2;
// Read the section address
uint32_t addr;
read = fread(&addr, sizeof(uint32_t), 1, fw);
if (read != 1) {
fprintf(stderr, "Invalid firmware image section address\n");
fclose(fw);
return LIBUSB_ERROR_OTHER;
}
// If the section is a termination section, run the code at the given address
if (!size) {
sddc_fx3_boot_run(dev, addr);
break;
}
// Re-allocate buffer if needed
if (size > bufferSize) {
bufferSize = size;
realloc(buffer, bufferSize);
}
// Read the section data
read = fread(buffer, 1, size, fw);
if (read != size) {
fprintf(stderr, "Failed to read section data: %d\n", read);
fclose(fw);
return LIBUSB_ERROR_OTHER;
}
// Upload it to the chip
for (int i = 0; i < size; i += FX3_MAX_BLOCK_SIZE) {
int left = size - i;
err = sddc_fx3_boot_mem_write(dev, addr + i, &buffer[i], (left > FX3_MAX_BLOCK_SIZE) ? FX3_MAX_BLOCK_SIZE : left);
if (err < LIBUSB_SUCCESS) {
fprintf(stderr, "Failed to write to device memory: %d\n", err);
fclose(fw);
return err;
}
}
}
// TODO: Checksum stuff and verification ideally
// Close the firmware image
fclose(fw);
// Return successfully
return LIBUSB_SUCCESS;
}

View File

@ -1,39 +0,0 @@
#pragma once
#include <libusb.h>
#include <stdint.h>
/**
* Read data from the device's memory.
* @param dev Device to read data from.
* @param addr Start address of the data in the device's memory.
* @param data Buffer to write the data into.
* @param len Number of bytes to read.
* @return libusb error code.
*/
int sddc_fx3_boot_mem_read(libusb_device_handle* dev, uint32_t addr, uint8_t* data, uint16_t len);
/**
* Write data to the device's memory.
* @param dev Device to write data to.
* @param addr Start address of the data in the device's memory.
* @param data Buffer to write the data into.
* @param len Number of bytes to read.
* @return libusb error code.
*/
int sddc_fx3_boot_mem_write(libusb_device_handle* dev, uint32_t addr, const uint8_t* data, uint16_t len);
/**
* Execute code on the device.
* @param dev Device to execute code on.
* @param entry Entry point of the code.
* @return libusb error code.
*/
int sddc_fx3_boot_run(libusb_device_handle* dev, uint32_t entry);
/**
* Parse, upload and execute a firmware image.
* @param dev Device to upload the firmware to.
* @param path Path to the firmware image.
* @return libusb error code.
*/
int sddc_fx3_boot_upload_firmware(libusb_device_handle* dev, const char* path);

View File

@ -1,451 +0,0 @@
#include <sddc.h>
#include <stdlib.h>
#include <stdint.h>
#include <libusb.h>
#include <stdio.h>
#include "usb_interface.h"
struct sddc_dev {
// USB handles
struct libusb_device_handle* openDev;
// Device info
sddc_devinfo_t info;
// Device state
bool running;
uint32_t samplerate;
uint64_t tunerFreq;
sddc_gpio_t gpioState;
};
struct libusb_context* ctx = NULL;
char* sddc_firmware_path = NULL;
bool sddc_is_init = false;
void sddc_init() {
// If already initialized, do nothing
if (sddc_is_init) { return; }
// If the firmware isn't already found, find it
if (!sddc_firmware_path) {
// TODO: Find the firmware
}
// Init libusb
libusb_init(&ctx);
}
const char* sddc_model_to_string(sddc_model_t model) {
switch (model) {
case SDDC_MODEL_BBRF103: return "BBRF103";
case SDDC_MODEL_HF103: return "HF103";
case SDDC_MODEL_RX888: return "RX888";
case SDDC_MODEL_RX888_MK2: return "RX888 MK2";
case SDDC_MODEL_RX999: return "RX999";
case SDDC_MODEL_RXLUCY: return "RXLUCY";
case SDDC_MODEL_RX888_MK3: return "RX888 MK3";
default: return "Unknown";
}
}
const char* sddc_error_to_string(sddc_error_t error) {
switch (error) {
case SDDC_ERROR_NOT_IMPLEMENTED: return "Not Implemented";
case SDDC_ERROR_FIRMWARE_UPLOAD_FAILED: return "Firmware Upload Failed";
case SDDC_ERROR_NOT_FOUND: return "Not Found";
case SDDC_ERROR_USB_ERROR: return "USB Error";
case SDDC_ERROR_TIMEOUT: return "Timeout";
case SDDC_SUCCESS: return "Success";
default: return "Unknown";
}
}
sddc_error_t sddc_set_firmware_path(const char* path) {
// Free the old path if it exists
if (sddc_firmware_path) { free(sddc_firmware_path); }
// Allocate the new path
sddc_firmware_path = malloc(strlen(path) + 1);
// Copy the new path
strcpy(sddc_firmware_path, path);
// TODO: Check if the file path exists
return SDDC_SUCCESS;
}
int sddc_get_device_list(sddc_devinfo_t** dev_list) {
// Initialize libsddc in case it isn't already
sddc_init();
// Get a list of USB devices
libusb_device** devices;
int devCount = libusb_get_device_list(ctx, &devices);
if (devCount < 0 || !devices) { return SDDC_ERROR_USB_ERROR; }
// Initialize all uninitialized devices
bool uninit = false;
for (int i = 0; i < devCount; i++) {
// Get the device from the list
libusb_device* dev = devices[i];
// Get the device descriptor. Fail silently on error as it might not even be a SDDC device.
struct libusb_device_descriptor desc;
int err = libusb_get_device_descriptor(dev, &desc);
if (err != LIBUSB_SUCCESS) { continue; }
// If it's not an uninitialized device, go to next device
if (desc.idVendor != SDDC_UNINIT_VID || desc.idProduct != SDDC_UNINIT_PID) { continue; }
// Initialize the device
printf("Found uninitialized device, initializing...\n");
// TODO: Check that the firmware path is valid
sddc_error_t serr = sddc_init_device(dev, sddc_firmware_path);
if (serr != SDDC_SUCCESS) { continue; }
// Set the flag to wait the devices to start up
uninit = true;
}
// If some uninitialized devices were found
if (uninit) {
// Free the device list
libusb_free_device_list(devices, 1);
// Wait for the devices to show back up
#ifdef _WIN32
Sleep(SDDC_INIT_SEARCH_DELAY_MS);
#else
usleep(SDDC_INIT_SEARCH_DELAY_MS * 1000);
#endif
// Attempt to list devices again
devCount = libusb_get_device_list(ctx, &devices);
if (devCount < 0 || !devices) { return SDDC_ERROR_USB_ERROR; }
}
// Allocate the device list
*dev_list = malloc(devCount * sizeof(sddc_devinfo_t));
// Check each device
int found = 0;
libusb_device_handle* openDev;
for (int i = 0; i < devCount; i++) {
// Get the device from the list
libusb_device* dev = devices[i];
// Get the device descriptor. Fail silently on error as it might not even be a SDDC device.
struct libusb_device_descriptor desc;
int err = libusb_get_device_descriptor(dev, &desc);
if (err != LIBUSB_SUCCESS) { continue; }
// If the device is not an SDDC device, go to next device
if (desc.idVendor != SDDC_VID || desc.idProduct != SDDC_PID) { continue; }
// Open the device
err = libusb_open(dev, &openDev);
if (err != LIBUSB_SUCCESS) {
fprintf(stderr, "Failed to open device: %d\n", err);
continue;
}
// Create entry
sddc_devinfo_t* info = &((*dev_list)[found]);
// Get the serial number
err = libusb_get_string_descriptor_ascii(openDev, desc.iSerialNumber, info->serial, SDDC_SERIAL_MAX_LEN-1);
if (err < LIBUSB_SUCCESS) {
printf("Failed to get descriptor: %d\n", err);
libusb_close(openDev);
continue;
}
// Get the hardware info
sddc_hwinfo_t hwinfo;
err = sddc_fx3_get_info(openDev, &hwinfo, 0);
if (err < LIBUSB_SUCCESS) {
printf("Failed to get device info: %d\n", err);
libusb_close(openDev);
continue;
}
// Save the hardware info
info->model = (sddc_model_t)hwinfo.model;
info->firmwareMajor = hwinfo.firmwareConfigH;
info->firmwareMinor = hwinfo.firmwareConfigL;
// Close the device
libusb_close(openDev);
// Increment device counter
found++;
}
// Free the libusb device list
libusb_free_device_list(devices, 1);
// Return the number of devices found
return found;
}
void sddc_free_device_list(sddc_devinfo_t* dev_list) {
// Free the device list if it exists
if (dev_list) { free(dev_list); };
}
sddc_error_t sddc_open(const char* serial, sddc_dev_t** dev) {
// Initialize libsddc in case it isn't already
sddc_init();
// Get a list of USB devices
libusb_device** devices;
int devCount = libusb_get_device_list(ctx, &devices);
if (devCount < 0 || !devices) { return SDDC_ERROR_USB_ERROR; }
// Initialize all uninitialized devices
bool uninit = false;
for (int i = 0; i < devCount; i++) {
// Get the device from the list
libusb_device* dev = devices[i];
// Get the device descriptor. Fail silently on error as it might not even be a SDDC device.
struct libusb_device_descriptor desc;
int err = libusb_get_device_descriptor(dev, &desc);
if (err != LIBUSB_SUCCESS) { continue; }
// If it's not an uninitialized device, go to next device
if (desc.idVendor != SDDC_UNINIT_VID || desc.idProduct != SDDC_UNINIT_PID) { continue; }
// Initialize the device
printf("Found uninitialized device, initializing...\n");
// TODO: Check that the firmware path is valid
sddc_error_t serr = sddc_init_device(dev, sddc_firmware_path);
if (serr != SDDC_SUCCESS) { continue; }
// Set the flag to wait the devices to start up
uninit = true;
}
// If some uninitialized devices were found
if (uninit) {
// Free the device list
libusb_free_device_list(devices, 1);
// Wait for the devices to show back up
#ifdef _WIN32
Sleep(SDDC_INIT_SEARCH_DELAY_MS);
#else
usleep(SDDC_INIT_SEARCH_DELAY_MS * 1000);
#endif
// Attempt to list devices again
devCount = libusb_get_device_list(ctx, &devices);
if (devCount < 0 || !devices) { return SDDC_ERROR_USB_ERROR; }
}
// Search through all USB device
bool found = false;
libusb_device_handle* openDev;
for (int i = 0; i < devCount; i++) {
// Get the device from the list
libusb_device* dev = devices[i];
// Get the device descriptor. Fail silently on error as it might not even be a SDDC device.
struct libusb_device_descriptor desc;
int err = libusb_get_device_descriptor(dev, &desc);
if (err != LIBUSB_SUCCESS) { continue; }
// If the device is not an SDDC device, go to next device
if (desc.idVendor != SDDC_VID || desc.idProduct != SDDC_PID) { continue; }
// Open the device
err = libusb_open(dev, &openDev);
if (err != LIBUSB_SUCCESS) {
fprintf(stderr, "Failed to open device: %d\n", err);
continue;
}
// Get the serial number
char dserial[SDDC_SERIAL_MAX_LEN];
err = libusb_get_string_descriptor_ascii(openDev, desc.iSerialNumber, dserial, SDDC_SERIAL_MAX_LEN-1);
if (err < LIBUSB_SUCCESS) {
printf("Failed to get descriptor: %d\n", err);
libusb_close(openDev);
continue;
}
// Compare the serial number and give up if not a match
if (strcmp(dserial, serial)) { continue; }
// Get the device info
// TODO
// Set the found flag and stop searching
found = true;
break;
}
// Free the libusb device list
libusb_free_device_list(devices, true);
// If the device was not found, give up
if (!found) { return SDDC_ERROR_NOT_FOUND; }
// Claim the interface
libusb_claim_interface(openDev, 0);
// Allocate the device object
*dev = malloc(sizeof(sddc_dev_t));
// Initialize the device object
(*dev)->openDev = openDev;
//(*dev)->info = ; //TODO
(*dev)->running = false;
(*dev)->samplerate = 128e6;
(*dev)->tunerFreq = 100e6;
(*dev)->gpioState = SDDC_GPIO_SHUTDOWN | SDDC_GPIO_SEL0; // ADC shutdown and HF port selected
// Stop everything in case the device is partially started
printf("Stopping...\n");
sddc_stop(*dev);
// TODO: Setup all of the other state
sddc_gpio_put(*dev, SDDC_GPIO_SEL0, false);
sddc_gpio_put(*dev, SDDC_GPIO_SEL1, true);
sddc_gpio_put(*dev, SDDC_GPIO_VHF_EN, true);
sddc_tuner_start((*dev)->openDev, 16e6);
sddc_tuner_tune((*dev)->openDev, 100e6);
sddc_fx3_set_param((*dev)->openDev, SDDC_PARAM_R82XX_ATT, 15);
sddc_fx3_set_param((*dev)->openDev, SDDC_PARAM_R83XX_VGA, 9);
sddc_fx3_set_param((*dev)->openDev, SDDC_PARAM_AD8340_VGA, 5);
return SDDC_SUCCESS;
}
void sddc_close(sddc_dev_t* dev) {
// Stop everything
sddc_stop(dev);
// Release the interface
libusb_release_interface(dev->openDev, 0);
// Close the USB device
libusb_close(dev->openDev);
// Free the device struct
free(dev);
}
sddc_range_t sddc_get_samplerate_range(sddc_dev_t* dev) {
// All devices have the same samplerate range
sddc_range_t range = { 8e6, 128e6, 0 };
return range;
}
int sddc_gpio_set(sddc_dev_t* dev, sddc_gpio_t gpios) {
// Update the state
dev->gpioState = gpios;
// Push to the device
sddc_fx3_gpio(dev->openDev, gpios);
}
int sddc_gpio_put(sddc_dev_t* dev, sddc_gpio_t gpios, bool value) {
// Update the state of the given GPIOs only
return sddc_gpio_set(dev, (dev->gpioState & (~gpios)) | (value ? gpios : 0));
}
sddc_error_t sddc_set_samplerate(sddc_dev_t* dev, uint32_t samplerate) {
// Update the state
dev->samplerate = samplerate;
// If running, send the new sampling rate to the device
if (dev->running) {
int err = sddc_adc_set_samplerate(dev->openDev, samplerate);
if (err < LIBUSB_SUCCESS) { return SDDC_ERROR_USB_ERROR; }
}
// Return successfully
return SDDC_SUCCESS;
}
sddc_error_t sddc_set_dithering(sddc_dev_t* dev, bool enabled) {
// Update the GPIOs according to the desired state
int err = sddc_gpio_put(dev, SDDC_GPIO_DITHER, enabled);
return (err < LIBUSB_SUCCESS) ? SDDC_ERROR_USB_ERROR : SDDC_SUCCESS;
}
sddc_error_t sddc_set_randomizer(sddc_dev_t* dev, bool enabled) {
// Update the GPIOs according to the desired state
int err = sddc_gpio_put(dev, SDDC_GPIO_RANDOM, enabled);
return (err < LIBUSB_SUCCESS) ? SDDC_ERROR_USB_ERROR : SDDC_SUCCESS;
}
sddc_error_t sddc_set_tuner_frequency(sddc_dev_t* dev, uint64_t frequency) {
// Update the state
dev->tunerFreq = frequency;
// If running, send the new frequency to the device
if (dev->running) {
int err = sddc_tuner_tune(dev->openDev, frequency);
if (err < LIBUSB_SUCCESS) { return SDDC_ERROR_USB_ERROR; }
}
// Return successfully
return SDDC_SUCCESS;
}
sddc_error_t sddc_start(sddc_dev_t* dev) {
// De-assert the shutdown pin
int err = sddc_gpio_put(dev, SDDC_GPIO_SHUTDOWN, false);
if (err < LIBUSB_SUCCESS) { return SDDC_ERROR_USB_ERROR; }
// Start the tuner (TODO: Check if in VHF mode)
// Start the ADC
err = sddc_adc_set_samplerate(dev->openDev, dev->samplerate);
if (err < LIBUSB_SUCCESS) { return SDDC_ERROR_USB_ERROR; }
// Start the FX3
err = sddc_fx3_start(dev->openDev);
if (err < LIBUSB_SUCCESS) { return SDDC_ERROR_USB_ERROR; }
// Update the state
dev->running = true;
// Return successfully
return SDDC_SUCCESS;
}
sddc_error_t sddc_stop(sddc_dev_t* dev) {
// Stop the FX3
int err = sddc_fx3_stop(dev->openDev);
if (err < LIBUSB_SUCCESS) { return SDDC_ERROR_USB_ERROR; }
// Stop the tuner
err = sddc_tuner_stop(dev->openDev);
if (err < LIBUSB_SUCCESS) { return SDDC_ERROR_USB_ERROR; }
// Stop the ADC
err = sddc_adc_set_samplerate(dev->openDev, 0);
if (err < LIBUSB_SUCCESS) { return SDDC_ERROR_USB_ERROR; }
// Set the GPIOs for standby mode
err = sddc_gpio_put(dev, SDDC_GPIO_SHUTDOWN, true);
if (err < LIBUSB_SUCCESS) { return SDDC_ERROR_USB_ERROR; }
// Update the state
dev->running = false;
// Return successfully
return SDDC_SUCCESS;
}
sddc_error_t sddc_rx(sddc_dev_t* dev, int16_t* samples, int count) {
// Read samples from the device
int bytesRead = 0;
int err = libusb_bulk_transfer(dev->openDev, LIBUSB_ENDPOINT_IN | 1, samples, count * sizeof(int16_t), &bytesRead, SDDC_TIMEOUT_MS);
if (err < LIBUSB_SUCCESS) { return SDDC_ERROR_USB_ERROR; }
return SDDC_SUCCESS;
}

View File

@ -1,92 +0,0 @@
#include "usb_interface.h"
#include "fx3_boot.h"
#include <stdio.h>
sddc_error_t sddc_init_device(libusb_device* dev, const char* firmware) {
// Open the device
libusb_device_handle* openDev;
int err = libusb_open(dev, &openDev);
if (err != LIBUSB_SUCCESS) {
printf("Failed to open device: %d\n", err);
return SDDC_ERROR_USB_ERROR;
}
// Attempt to upload the firmware
err = sddc_fx3_boot_upload_firmware(openDev, firmware);
if (err) {
fprintf(stderr, "Failed to upload firmware to uninitialized device\n");
return SDDC_ERROR_FIRMWARE_UPLOAD_FAILED;
}
// Close the device
libusb_close(openDev);
// Return successfully
return SDDC_SUCCESS;
}
int sddc_fx3_start(libusb_device_handle* dev) {
// Send the start command
uint32_t dummy;
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, SDDC_CMD_FX3_START, 0, 0, &dummy, sizeof(uint32_t), SDDC_TIMEOUT_MS);
}
int sddc_fx3_stop(libusb_device_handle* dev) {
// Send the stop command
uint32_t dummy;
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, SDDC_CMD_FX3_STOP, 0, 0, &dummy, sizeof(uint32_t), SDDC_TIMEOUT_MS);
}
int sddc_fx3_get_info(libusb_device_handle* dev, sddc_hwinfo_t* info, char enableDebug) {
// Fetch the info data
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, SDDC_CMD_FX3_GET_INFO, enableDebug, 0, info, sizeof(sddc_hwinfo_t), SDDC_TIMEOUT_MS);
}
int sddc_fx3_gpio(libusb_device_handle* dev, sddc_gpio_t gpio) {
// Send the GPIO state
uint32_t dword = gpio;
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, SDDC_CMD_FX3_GPIO, 0, 0, &dword, sizeof(uint32_t), SDDC_TIMEOUT_MS);
}
int sddc_fx3_i2c_write(libusb_device_handle* dev, uint8_t addr, uint16_t reg, const uint8_t* data, uint16_t len) {
// Send the I2C data
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, SDDC_CMD_FX3_I2C_W, addr, reg, data, len, SDDC_TIMEOUT_MS);
}
int sddc_fx3_i2c_read(libusb_device_handle* dev, uint8_t addr, uint16_t reg, const uint8_t* data, uint16_t len) {
// Fetch the I2C data
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, SDDC_CMD_FX3_I2C_R, addr, reg, data, len, SDDC_TIMEOUT_MS);
}
int sddc_fx3_reset(libusb_device_handle* dev) {
// Send the reset command
uint32_t dummy;
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, SDDC_CMD_FX3_RESET, 0, 0, &dummy, sizeof(uint32_t), SDDC_TIMEOUT_MS);
}
int sddc_fx3_set_param(libusb_device_handle* dev, sddc_param_t param, uint16_t value) {
// Send the parameter
uint32_t dummy;
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, SDDC_CMD_FX3_SET_PARAM, value, param, &dummy, sizeof(uint32_t), SDDC_TIMEOUT_MS);
}
int sddc_adc_set_samplerate(libusb_device_handle* dev, uint32_t samplerate) {
// Send the samplerate
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, SDDC_CMD_ADC_SET_RATE, 0, 0, &samplerate, sizeof(uint32_t), SDDC_TIMEOUT_MS);
}
int sddc_tuner_start(libusb_device_handle* dev, uint32_t refFreq) {
// Send the reset command
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, SDDC_CMD_TUNER_START, 0, 0, &refFreq, sizeof(uint32_t), SDDC_TIMEOUT_MS);
}
int sddc_tuner_tune(libusb_device_handle* dev, uint64_t frequency) {
// Send the reset command
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, SDDC_CMD_TUNER_TUNE, 0, 0, &frequency, sizeof(uint64_t), SDDC_TIMEOUT_MS);
}
int sddc_tuner_stop(libusb_device_handle* dev) {
// Send the reset command
uint32_t dummy; // Because the shitty firmware absolute wants data to stop the tuner, this is dumb...
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, SDDC_CMD_TUNER_STOP, 0, 0, &dummy, sizeof(uint32_t), SDDC_TIMEOUT_MS);
}

View File

@ -1,168 +0,0 @@
#pragma once
#include <sddc.h>
#include <stdint.h>
#include <libusb.h>
#define SDDC_UNINIT_VID 0x04B4
#define SDDC_UNINIT_PID 0x00F3
#define SDDC_VID 0x04B4
#define SDDC_PID 0x00F1
#define SDDC_INIT_SEARCH_DELAY_MS 1000
#define SDDC_TIMEOUT_MS 1000
enum sddc_command {
SDDC_CMD_FX3_START = 0xAA,
SDDC_CMD_FX3_STOP = 0xAB,
SDDC_CMD_FX3_GET_INFO = 0xAC,
SDDC_CMD_FX3_GPIO = 0xAD,
SDDC_CMD_FX3_I2C_W = 0xAE,
SDDC_CMD_FX3_I2C_R = 0xAF,
SDDC_CMD_FX3_RESET = 0xB1,
SDDC_CMD_FX3_SET_PARAM = 0xB6,
SDDC_CMD_ADC_SET_RATE = 0xB2,
SDDC_CMD_TUNER_START = 0xB4,
SDDC_CMD_TUNER_TUNE = 0xB5,
SDDC_CMD_TUNER_STOP = 0xB8,
SDDC_CMD_READ_DEBUG = 0xBA
};
#pragma pack(push, 1)
struct sddc_hwinfo {
uint8_t model;
uint8_t firmwareConfigH;
uint8_t firmwareConfigL;
uint8_t vendorRequestCount;
};
typedef struct sddc_hwinfo sddc_hwinfo_t;
#pragma pack(pop)
enum sddc_gpio {
SDDC_GPIO_NONE = 0,
SDDC_GPIO_ATT_LE = (1 << 0),
SDDC_GPIO_ATT_CLK = (1 << 1),
SDDC_GPIO_ATT_DATA = (1 << 2),
SDDC_GPIO_SEL0 = (1 << 3),
SDDC_GPIO_SEL1 = (1 << 4),
SDDC_GPIO_SHUTDOWN = (1 << 5),
SDDC_GPIO_DITHER = (1 << 6),
SDDC_GPIO_RANDOM = (1 << 7),
SDDC_GPIO_BIAST_HF = (1 << 8),
SDDC_GPIO_BIAST_VHF = (1 << 9),
SDDC_GPIO_LED_YELLOW = (1 << 10),
SDDC_GPIO_LED_RED = (1 << 11),
SDDC_GPIO_LED_BLUE = (1 << 12),
SDDC_GPIO_ATT_SEL0 = (1 << 13),
SDDC_GPIO_ATT_SEL1 = (1 << 14),
SDDC_GPIO_VHF_EN = (1 << 15),
SDDC_GPIO_PGA_EN = (1 << 16),
SDDC_GPIO_ALL = ((1 << 17)-1)
};
typedef enum sddc_gpio sddc_gpio_t;
enum sddc_param {
SDDC_PARAM_R82XX_ATT = 1,
SDDC_PARAM_R83XX_VGA = 2,
SDDC_PARAM_R83XX_SIDEBAND = 3,
SDDC_PARAM_R83XX_HARMONIC = 4,
SDDC_PARAM_DAT31_ATT = 10,
SDDC_PARAM_AD8340_VGA = 11,
SDDC_PARAM_PRESELECTOR = 12,
SDDC_PARAM_VHF_ATT = 13
};
typedef enum sddc_param sddc_param_t;
/**
* Initialize a device with a given firmware.
* @param dev SDDC USB device entry to initialize.
* @param firmware Path to the firmware image.
*/
sddc_error_t sddc_init_device(libusb_device* dev, const char* firmware);
/**
* Start the FX3 streaming.
* @param dev SDDC USB device.
*/
int sddc_fx3_start(libusb_device_handle* dev);
/**
* Stop the FX3 streaming.
* @param dev SDDC USB device.
*/
int sddc_fx3_stop(libusb_device_handle* dev);
/**
* Get the hardware info of a device.
* @param dev SDDC USB device.
* @param info Hardware info struct to write the return info into.
* @param enableDebug 1 to enable hardware debug mode, 0 otherwise.
*/
int sddc_fx3_get_info(libusb_device_handle* dev, sddc_hwinfo_t* info, char enableDebug);
/**
* Set the state of the GPIO pins.
* @param dev SDDC USB device.
* @param gpio State of the GPIO pins.
*/
int sddc_fx3_gpio(libusb_device_handle* dev, sddc_gpio_t gpio);
/**
* Write data to the I2C port.
* @param dev SDDC USB device.
* @param addr Address of the target I2C device.
* @param reg Register to write data to.
* @param data Data buffer to write.
* @param len Number of bytes to write.
*/
int sddc_fx3_i2c_write(libusb_device_handle* dev, uint8_t addr, uint16_t reg, const uint8_t* data, uint16_t len);
/**
* Read data from the I2C port.
* @param dev SDDC USB device.
* @param addr Address of the target I2C device.
* @param reg Register to read data from.
* @param data Data buffer to read data into.
* @param len Number of bytes to read.
*/
int sddc_fx3_i2c_write(libusb_device_handle* dev, uint8_t addr, uint16_t reg, const uint8_t* data, uint16_t len);
/**
* Reset the FX3 into bootloader mode.
* @param dev SDDC USB device.
*/
int sddc_fx3_reset(libusb_device_handle* dev);
/**
* Set a device parameter.
* @param dev SDDC USB device.
* @param arg Parameter to set.
* @param value Value to set the parameter to.
*/
int sddc_fx3_set_param(libusb_device_handle* dev, sddc_param_t param, uint16_t value);
/**
* Set the ADC sampling rate.
* @param dev SDDC USB device.
* @param samplerate Sampling rate. 0 to stop the ADC.
*/
int sddc_adc_set_samplerate(libusb_device_handle* dev, uint32_t samplerate);
/**
* Start the tuner.
* @param dev SDDC USB device.
* @param frequency Initial LO frequency.
*/
int sddc_tuner_start(libusb_device_handle* dev, uint32_t refFreq);
/**
* Set the tuner's LO frequency.
* @param dev SDDC USB device.
* @param frequency New LO frequency.
*/
int sddc_tuner_tune(libusb_device_handle* dev, uint64_t frequency);
/**
* Stop the tuner.
* @param dev SDDC USB device.
*/
int sddc_tuner_stop(libusb_device_handle* dev);

View File

@ -1,14 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(sddc_info)
# Get source files
file(GLOB_RECURSE SRC "src/*.cpp")
# Create executable
add_executable(${PROJECT_NAME} ${SRC})
# Link to librfnm
target_link_libraries(${PROJECT_NAME} PRIVATE sddc)
# Create install directive
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})

View File

@ -1,35 +0,0 @@
#include <stdio.h>
#include <sddc.h>
int main() {
// Set firmware image path for debugging
sddc_set_firmware_path("C:/Users/ryzerth/Downloads/SDDC_FX3 (1).img");
// List available devices
sddc_devinfo_t* devList;
int count = sddc_get_device_list(&devList);
if (count < 0) {
fprintf(stderr, "Failed to list devices: %d\n", count);
return -1;
}
else if (!count) {
printf("No device found.\n");
return 0;
}
// Show the devices in the list
for (int i = 0; i < count; i++) {
printf("Serial: %s\n", devList[i].serial);
printf("Hardware: %s\n", sddc_model_to_string(devList[i].model));
printf("Firmware: v%d.%d\n", devList[i].firmwareMajor, devList[i].firmwareMinor);
// Print separator if needed
if (i != count-1) {
printf("\n================================================\n\n");
}
}
// Free the device list and exit
sddc_free_device_list(devList);
return 0;
}

View File

@ -1,14 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(sddc_rx)
# Get source files
file(GLOB_RECURSE SRC "src/*.cpp")
# Create executable
add_executable(${PROJECT_NAME} ${SRC})
# Link to librfnm
target_link_libraries(${PROJECT_NAME} PRIVATE sddc)
# Create install directive
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})

View File

@ -1,38 +0,0 @@
#include <stdio.h>
#include <sddc.h>
int main() {
// Set firmware image path for debugging
sddc_set_firmware_path("C:/Users/ryzerth/Downloads/SDDC_FX3 (1).img");
// Open the device
sddc_dev_t* dev;
sddc_error_t err = sddc_open("0009072C00C40C32", &dev);
if (err != SDDC_SUCCESS) {
printf("Failed to open device: %s (%d)\n", sddc_error_to_string(err), err);
return -1;
}
// Configure the device
sddc_set_samplerate(dev, 8e6);
// Start the device
sddc_start(dev);
// Continuous read samples
const int bufSize = 1e6;
int16_t* buffer = new int16_t[bufSize];
while (true) {
// Read
sddc_error_t err = sddc_rx(dev, buffer, bufSize);
printf("Samples received: %d\n", err);
}
// Stop the device
sddc_stop(dev);
// Close the device
sddc_close(dev);
return 0;
}

View File

@ -1,512 +0,0 @@
#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 <sddc.h>
SDRPP_MOD_INFO{
/* Name: */ "sddc_source",
/* Description: */ "SDDC Source Module",
/* Author: */ "Ryzerth",
/* Version: */ 0, 2, 0,
/* Max instances */ -1
};
ConfigManager config;
#define CONCAT(a, b) ((std::string(a) + b).c_str())
class SDDCSourceModule : public ModuleManager::Instance {
public:
SDDCSourceModule(std::string name) {
this->name = name;
// Set firmware image path for debugging
sddc_set_firmware_path("C:/Users/ryzerth/Downloads/SDDC_FX3 (1).img");
sampleRate = 128e6;
// 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("SDDC", &handler);
}
~SDDCSourceModule() {
// 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
// sddc_devinfo_t* devList;
// int count = sddc_get_device_list(&devList);
// if (count < 0) {
// flog::error("Failed to list SDDC devices: {}", count);
// return;
// }
// // Add every device found
// for (int i = 0; i < count; i++) {
// // Create device name
// std::string name = sddc_model_to_string(devList[i].model);
// name += '[';
// name += devList[i].serial;
// name += ']';
// // Add an entry to the device list
// devices.define(devList[i].serial, name, devList[i].serial);
// }
devices.define("0009072C00C40C32", "TESTING", "0009072C00C40C32");
// // Free the device list
// sddc_free_device_list(devList);
}
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);
// Open the device
sddc_dev_t* dev;
int err = sddc_open(serial.c_str(), &dev);
if (err) {
flog::error("Failed to open device: {}", err);
return;
}
// Generate samplerate list
samplerates.clear();
samplerates.define(4e6, "4 MHz", 4e6);
samplerates.define(8e6, "8 MHz", 8e6);
samplerates.define(16e6, "16 MHz", 16e6);
samplerates.define(32e6, "32 MHz", 32e6);
samplerates.define(64e6, "64 MHz", 64e6);
// // Define the ports
// ports.clear();
// ports.define("hf", "HF", PORT_RF);
// ports.define("vhf", "VHF", PORT_HF1);
// Close the device
sddc_close(dev);
// Save serial number
selectedSerial = serial;
devId = id;
// Load default options
sampleRate = 64e6;
srId = samplerates.valueId(sampleRate);
// port = PORT_RF;
// portId = ports.valueId(port);
// 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("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) {
SDDCSourceModule* _this = (SDDCSourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
flog::info("SDDCSourceModule '{0}': Menu Select!", _this->name);
}
static void menuDeselected(void* ctx) {
SDDCSourceModule* _this = (SDDCSourceModule*)ctx;
flog::info("SDDCSourceModule '{0}': Menu Deselect!", _this->name);
}
static void start(void* ctx) {
SDDCSourceModule* _this = (SDDCSourceModule*)ctx;
if (_this->running) { return; }
// Open the device
sddc_error_t err = sddc_open(_this->selectedSerial.c_str(), &_this->openDev);
if (err) {
flog::error("Failed to open device: {}", (int)err);
return;
}
// // Get the selected port
// _this->port = _this->ports[_this->portId];
// Configure the device
sddc_set_samplerate(_this->openDev, _this->sampleRate * 2);
// // 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(_this->sampleRate * 2);
_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 / 100.0;
// Start streaming
err = sddc_start(_this->openDev);
if (err) {
flog::error("Failed to start stream: {}", (int)err);
return;
}
// Start worker
_this->run = true;
_this->workerThread = std::thread(&SDDCSourceModule::worker, _this);
_this->running = true;
flog::info("SDDCSourceModule '{0}': Start!", _this->name);
}
static void stop(void* ctx) {
SDDCSourceModule* _this = (SDDCSourceModule*)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
sddc_stop(_this->openDev);
// Stop the DDC
_this->ddc.stop();
// Close the device
sddc_close(_this->openDev);
flog::info("SDDCSourceModule '{0}': Stop!", _this->name);
}
static void tune(double freq, void* ctx) {
SDDCSourceModule* _this = (SDDCSourceModule*)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("SDDCSourceModule '{0}': Tune: {1}!", _this->name, freq);
}
static void menuHandler(void* ctx) {
SDDCSourceModule* _this = (SDDCSourceModule*)ctx;
if (_this->running) { SmGui::BeginDisabled(); }
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##_sddc_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("##_sddc_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##_sddc_refr_", _this->name))) {
_this->refresh();
_this->select(_this->selectedSerial);
core::setInputSampleRate(_this->sampleRate);
}
// SmGui::LeftLabel("Antenna Port");
// SmGui::FillWidth();
// if (SmGui::Combo(CONCAT("##_sddc_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(); }
// if (_this->port == PORT_RF) {
// SmGui::LeftLabel("LNA Gain");
// SmGui::FillWidth();
// if (SmGui::SliderInt(CONCAT("##_sddc_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("##_sddc_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) {
// Allocate the sample buffer
int16_t* buffer = dsp::buffer::alloc<int16_t>(bufferSize);
float* fbuffer = dsp::buffer::alloc<float>(bufferSize);
float* nullBuffer = dsp::buffer::alloc<float>(bufferSize);
// Clear the null buffer
dsp::buffer::clear(nullBuffer, bufferSize);
while (run) {
// Read samples
int err = sddc_rx(openDev, buffer, bufferSize);
if (err) { break; }
// Convert the samples to float
volk_16i_s32f_convert_32f(fbuffer, buffer, 32768.0f, bufferSize);
// Interleave into a complex value
volk_32f_x2_interleave_32fc((lv_32fc_t*)ddcIn.writeBuf, fbuffer, nullBuffer, bufferSize);
// Send samples to the DDC
if (!ddcIn.swap(bufferSize)) { break; }
}
// Free the buffer
dsp::buffer::free(buffer);
// }
}
std::string name;
bool enabled = true;
double sampleRate;
SourceManager::SourceHandler handler;
bool running = false;
double freq;
OptionList<std::string, std::string> devices;
OptionList<int, int> samplerates;
OptionList<std::string, Port> ports;
int devId = 0;
int srId = 0;
int portId = 0;
Port port;
int lnaGain = 0;
int vgaGain = 0;
std::string selectedSerial;
sddc_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() + "/sddc_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new SDDCSourceModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (SDDCSourceModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -8,7 +8,6 @@
#include <config.h>
#include <sdrplay_api.h>
#include <gui/smgui.h>
#include <utils/optionlist.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
@ -16,12 +15,57 @@ SDRPP_MOD_INFO{
/* Name: */ "sdrplay_source",
/* Description: */ "SDRplay source module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 2, 0,
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
ConfigManager config;
unsigned int sampleRates[] = {
2000000,
3000000,
4000000,
5000000,
6000000,
7000000,
8000000,
9000000,
10000000
};
const char* sampleRatesTxt =
"2MHz\0"
"3MHz\0"
"4MHz\0"
"5MHz\0"
"6MHz\0"
"7MHz\0"
"8MHz\0"
"9MHz\0"
"10MHz\0";
sdrplay_api_Bw_MHzT bandwidths[] = {
sdrplay_api_BW_0_200,
sdrplay_api_BW_0_300,
sdrplay_api_BW_0_600,
sdrplay_api_BW_1_536,
sdrplay_api_BW_5_000,
sdrplay_api_BW_6_000,
sdrplay_api_BW_7_000,
sdrplay_api_BW_8_000,
};
const char* bandwidthsTxt =
"200KHz\0"
"300KHz\0"
"600KHz\0"
"1.536MHz\0"
"5MHz\0"
"6MHz\0"
"7MHz\0"
"8MHz\0"
"Auto\0";
sdrplay_api_Bw_MHzT preferedBandwidth[] = {
sdrplay_api_BW_5_000,
sdrplay_api_BW_5_000,
@ -78,8 +122,6 @@ const char* ifModeTxt =
const char* rspduo_antennaPortsTxt = "Tuner 1 (50Ohm)\0Tuner 1 (Hi-Z)\0Tuner 2 (50Ohm)\0";
#define MAX_DEV_COUNT 16
class SDRPlaySourceModule : public ModuleManager::Instance {
public:
SDRPlaySourceModule(std::string name) {
@ -148,9 +190,9 @@ public:
devNameList.clear();
devListTxt = "";
sdrplay_api_DeviceT devArr[MAX_DEV_COUNT];
sdrplay_api_DeviceT devArr[128];
unsigned int numDev = 0;
sdrplay_api_GetDevices(devArr, &numDev, MAX_DEV_COUNT);
sdrplay_api_GetDevices(devArr, &numDev, 128);
for (unsigned int i = 0; i < numDev; i++) {
devList.push_back(devArr[i]);
@ -166,11 +208,6 @@ public:
name += devArr[i].SerNo;
name += ')';
break;
case SDRPLAY_RSP1B_ID:
name = "RSP1B (";
name += devArr[i].SerNo;
name += ')';
break;
case SDRPLAY_RSP2_ID:
name = "RSP2 (";
name += devArr[i].SerNo;
@ -186,11 +223,6 @@ public:
name += devArr[i].SerNo;
name += ')';
break;
case SDRPLAY_RSPdxR2_ID:
name = "RSPdx-R2 (";
name += devArr[i].SerNo;
name += ')';
break;
default:
name = "Unknown (";
name += devArr[i].SerNo;
@ -258,30 +290,6 @@ public:
return;
}
// Define the valid samplerates
samplerates.clear();
samplerates.define(2e6, "2MHz", 2e6);
samplerates.define(3e6, "3MHz", 3e6);
samplerates.define(4e6, "4MHz", 4e6);
samplerates.define(5e6, "5MHz", 5e6);
samplerates.define(6e6, "6MHz", 6e6);
samplerates.define(7e6, "7MHz", 7e6);
samplerates.define(8e6, "8MHz", 8e6);
samplerates.define(9e6, "9MHz", 9e6);
samplerates.define(10e6, "10MHz", 10e6);
// Define the valid bandwidths
bandwidths.clear();
bandwidths.define(200e3, "200KHz", sdrplay_api_BW_0_200);
bandwidths.define(300e3, "300KHz", sdrplay_api_BW_0_300);
bandwidths.define(600e3, "600KHz", sdrplay_api_BW_0_600);
bandwidths.define(1.536e6, "1.536MHz", sdrplay_api_BW_1_536);
bandwidths.define(5e6, "5MHz", sdrplay_api_BW_5_000);
bandwidths.define(6e6, "6MHz", sdrplay_api_BW_6_000);
bandwidths.define(7e6, "7MHz", sdrplay_api_BW_7_000);
bandwidths.define(8e6, "8MHz", sdrplay_api_BW_8_000);
bandwidths.define(0, "Auto", sdrplay_api_BW_Undefined);
channelParams = openDevParams->rxChannelA;
selectedName = devNameList[id];
@ -289,7 +297,7 @@ public:
if (openDev.hwVer == SDRPLAY_RSP1_ID) {
lnaSteps = 4;
}
else if (openDev.hwVer == SDRPLAY_RSP1A_ID || openDev.hwVer == SDRPLAY_RSP1B_ID) {
else if (openDev.hwVer == SDRPLAY_RSP1A_ID) {
lnaSteps = 10;
}
else if (openDev.hwVer == SDRPLAY_RSP2_ID) {
@ -298,54 +306,78 @@ public:
else if (openDev.hwVer == SDRPLAY_RSPduo_ID) {
lnaSteps = 10;
}
else if (openDev.hwVer == SDRPLAY_RSPdx_ID || openDev.hwVer == SDRPLAY_RSPdxR2_ID) {
else if (openDev.hwVer == SDRPLAY_RSPdx_ID) {
lnaSteps = 28;
}
// Select default settings
srId = 0;
sampleRate = samplerates.value(0);
bandwidthId = 8;
lnaGain = lnaSteps - 1;
gain = 59;
agc = false;
agcAttack = 500;
agcDecay = 500;
agcDecayDelay = 200;
agcDecayThreshold = 5;
agcSetPoint = -30;
ifModeId = 0;
rsp1a_fmmwNotch = false;
rsp2_fmmwNotch = false;
rspdx_fmmwNotch = false;
rspduo_fmmwNotch = false;
rsp1a_dabNotch = false;
rspdx_dabNotch = false;
rspduo_dabNotch = false;
rsp1a_biasT = false;
rsp2_biasT = false;
rspdx_biasT = false;
rspduo_biasT = false;
rsp2_antennaPort = 0;
rspdx_antennaPort = 0;
rspduo_antennaPort = 0;
bool created = false;
config.acquire();
if (!config.conf["devices"].contains(selectedName)) {
created = true;
config.conf["devices"][selectedName]["sampleRate"] = sampleRates[0];
config.conf["devices"][selectedName]["bwMode"] = 8; // Auto
config.conf["devices"][selectedName]["lnaGain"] = lnaSteps - 1;
config.conf["devices"][selectedName]["ifGain"] = 59;
config.conf["devices"][selectedName]["agc"] = false;
// General options
if (config.conf["devices"][selectedName].contains("samplerate")) {
int sr = config.conf["devices"][selectedName]["samplerate"];
if (samplerates.keyExists(sr)) {
srId = samplerates.keyId(sr);
sampleRate = samplerates[srId];
config.conf["devices"][selectedName]["agcAttack"] = 500;
config.conf["devices"][selectedName]["agcDecay"] = 500;
config.conf["devices"][selectedName]["agcDecayDelay"] = 200;
config.conf["devices"][selectedName]["agcDecayThreshold"] = 5;
config.conf["devices"][selectedName]["agcSetPoint"] = -30;
config.conf["devices"][selectedName]["ifModeId"] = 0;
if (openDev.hwVer == SDRPLAY_RSP1_ID) {
// No config to load
}
else if (openDev.hwVer == SDRPLAY_RSP1A_ID) {
config.conf["devices"][selectedName]["fmmwNotch"] = false;
config.conf["devices"][selectedName]["dabNotch"] = false;
config.conf["devices"][selectedName]["biast"] = false;
}
else if (openDev.hwVer == SDRPLAY_RSP2_ID) {
config.conf["devices"][selectedName]["antenna"] = 0;
config.conf["devices"][selectedName]["fmmwNotch"] = false;
config.conf["devices"][selectedName]["biast"] = false;
}
else if (openDev.hwVer == SDRPLAY_RSPduo_ID) {
config.conf["devices"][selectedName]["antenna"] = 0;
config.conf["devices"][selectedName]["fmmwNotch"] = false;
config.conf["devices"][selectedName]["dabNotch"] = false;
config.conf["devices"][selectedName]["biast"] = false;
}
else if (openDev.hwVer == SDRPLAY_RSPdx_ID) {
config.conf["devices"][selectedName]["antenna"] = 0;
config.conf["devices"][selectedName]["fmmwNotch"] = false;
config.conf["devices"][selectedName]["dabNotch"] = false;
config.conf["devices"][selectedName]["biast"] = false;
}
}
// General options
if (config.conf["devices"][selectedName].contains("sampleRate")) {
sampleRate = config.conf["devices"][selectedName]["sampleRate"];
bool found = false;
for (int i = 0; i < 9; i++) {
if (sampleRates[i] == sampleRate) {
srId = i;
found = true;
}
}
if (!found) {
sampleRate = sampleRates[0];
srId = 0;
}
}
if (config.conf["devices"][selectedName].contains("ifModeId")) {
ifModeId = config.conf["devices"][selectedName]["ifModeId"];
if (ifModeId != 0) {
sampleRate = ifModes[ifModeId].effectiveSamplerate;
}
}
if (config.conf["devices"][selectedName].contains("bwMode")) {
bandwidthId = config.conf["devices"][selectedName]["bwMode"];
}
@ -356,6 +388,11 @@ public:
gain = config.conf["devices"][selectedName]["ifGain"];
}
if (config.conf["devices"][selectedName].contains("agc")) {
if (!config.conf["devices"][selectedName]["agc"].is_boolean()) {
int oldMode = config.conf["devices"][selectedName]["agc"];
config.conf["devices"][selectedName]["agc"] = (bool)(oldMode != 0);
created = true;
}
agc = config.conf["devices"][selectedName]["agc"];
}
if (config.conf["devices"][selectedName].contains("agcAttack")) {
@ -378,7 +415,7 @@ public:
if (openDev.hwVer == SDRPLAY_RSP1_ID) {
// No config to load
}
else if (openDev.hwVer == SDRPLAY_RSP1A_ID || openDev.hwVer == SDRPLAY_RSP1B_ID) {
else if (openDev.hwVer == SDRPLAY_RSP1A_ID) {
if (config.conf["devices"][selectedName].contains("fmmwNotch")) {
rsp1a_fmmwNotch = config.conf["devices"][selectedName]["fmmwNotch"];
}
@ -414,7 +451,7 @@ public:
rspduo_biasT = config.conf["devices"][selectedName]["biast"];
}
}
else if (openDev.hwVer == SDRPLAY_RSPdx_ID || openDev.hwVer == SDRPLAY_RSPdxR2_ID) {
else if (openDev.hwVer == SDRPLAY_RSPdx_ID) {
if (config.conf["devices"][selectedName].contains("antenna")) {
rspdx_antennaPort = config.conf["devices"][selectedName]["antenna"];
}
@ -429,7 +466,7 @@ public:
}
}
config.release();
config.release(created);
if (lnaGain >= lnaSteps) { lnaGain = lnaSteps - 1; }
@ -534,7 +571,7 @@ private:
_this->bufferSize = (float)_this->sampleRate / 200.0f;
// RSP1A Options
if (_this->openDev.hwVer == SDRPLAY_RSP1A_ID || _this->openDev.hwVer == SDRPLAY_RSP1B_ID) {
if (_this->openDev.hwVer == SDRPLAY_RSP1A_ID) {
_this->openDevParams->devParams->rsp1aParams.rfNotchEnable = _this->rsp1a_fmmwNotch;
_this->openDevParams->devParams->rsp1aParams.rfDabNotchEnable = _this->rsp1a_dabNotch;
_this->channelParams->rsp1aTunerParams.biasTEnable = _this->rsp1a_biasT;
@ -564,7 +601,7 @@ private:
sdrplay_api_Update(_this->openDev.dev, _this->openDev.tuner, sdrplay_api_Update_RspDuo_RfDabNotchControl, sdrplay_api_Update_Ext1_None);
sdrplay_api_Update(_this->openDev.dev, _this->openDev.tuner, sdrplay_api_Update_RspDuo_Tuner1AmNotchControl, sdrplay_api_Update_Ext1_None);
}
else if (_this->openDev.hwVer == SDRPLAY_RSPdx_ID || _this->openDev.hwVer == SDRPLAY_RSPdxR2_ID) {
else if (_this->openDev.hwVer == SDRPLAY_RSPdx_ID) {
_this->openDevParams->devParams->rspDxParams.rfNotchEnable = _this->rspdx_fmmwNotch;
_this->openDevParams->devParams->rspDxParams.rfDabNotchEnable = _this->rspdx_dabNotch;
_this->openDevParams->devParams->rspDxParams.biasTEnable = _this->rspdx_biasT;
@ -577,7 +614,7 @@ private:
// General options
if (_this->ifModeId == 0) {
_this->bandwidth = (_this->bandwidthId == 8) ? preferedBandwidth[_this->srId] : _this->bandwidths[_this->bandwidthId];
_this->bandwidth = (_this->bandwidthId == 8) ? preferedBandwidth[_this->srId] : bandwidths[_this->bandwidthId];
_this->openDevParams->devParams->fsFreq.fsHz = _this->sampleRate;
_this->channelParams->tunerParams.bwType = _this->bandwidth;
}
@ -656,14 +693,14 @@ private:
}
if (_this->ifModeId == 0) {
if (SmGui::Combo(CONCAT("##sdrplay_sr", _this->name), &_this->srId, _this->samplerates.txt)) {
_this->sampleRate = _this->samplerates[_this->srId];
if (SmGui::Combo(CONCAT("##sdrplay_sr", _this->name), &_this->srId, sampleRatesTxt)) {
_this->sampleRate = sampleRates[_this->srId];
if (_this->bandwidthId == 8) {
_this->bandwidth = preferedBandwidth[_this->srId];
}
core::setInputSampleRate(_this->sampleRate);
config.acquire();
config.conf["devices"][_this->selectedName]["samplerate"] = _this->samplerates.key(_this->srId);
config.conf["devices"][_this->selectedName]["sampleRate"] = _this->sampleRate;
config.release(true);
}
@ -678,8 +715,8 @@ private:
SmGui::LeftLabel("Bandwidth");
SmGui::FillWidth();
if (SmGui::Combo(CONCAT("##sdrplay_bw", _this->name), &_this->bandwidthId, _this->bandwidths.txt)) {
_this->bandwidth = (_this->bandwidthId == 8) ? preferedBandwidth[_this->srId] : _this->bandwidths[_this->bandwidthId];
if (SmGui::Combo(CONCAT("##sdrplay_bw", _this->name), &_this->bandwidthId, bandwidthsTxt)) {
_this->bandwidth = (_this->bandwidthId == 8) ? preferedBandwidth[_this->srId] : bandwidths[_this->bandwidthId];
if (_this->running) {
_this->channelParams->tunerParams.bwType = _this->bandwidth;
sdrplay_api_Update(_this->openDev.dev, _this->openDev.tuner, sdrplay_api_Update_Tuner_BwType, sdrplay_api_Update_Ext1_None);
@ -708,28 +745,10 @@ private:
}
else {
config.acquire();
// Reload samplerate
if (config.conf["devices"][_this->selectedName].contains("samplerate")) {
int sr = config.conf["devices"][_this->selectedName]["samplerate"];
if (_this->samplerates.keyExists(sr)) {
_this->srId = _this->samplerates.keyId(sr);
}
}
else {
_this->srId = 0;
}
// Reload bandwidth
if (config.conf["devices"][_this->selectedName].contains("bwMode")) {
_this->bandwidthId = config.conf["devices"][_this->selectedName]["bwMode"];
}
else {
// Auto
_this->bandwidthId = 8;
}
_this->sampleRate = _this->samplerates[_this->srId];
config.release();
_this->bandwidth = (_this->bandwidthId == 8) ? preferedBandwidth[_this->srId] : _this->bandwidths[_this->bandwidthId];
_this->sampleRate = config.conf["devices"][_this->selectedName]["sampleRate"];
_this->bandwidthId = config.conf["devices"][_this->selectedName]["bwMode"];
config.release(false);
_this->bandwidth = (_this->bandwidthId == 8) ? preferedBandwidth[_this->srId] : bandwidths[_this->bandwidthId];
}
core::setInputSampleRate(_this->sampleRate);
config.acquire();
@ -835,7 +854,6 @@ private:
_this->RSP1Menu();
break;
case SDRPLAY_RSP1A_ID:
case SDRPLAY_RSP1B_ID:
_this->RSP1AMenu();
break;
case SDRPLAY_RSP2_ID:
@ -845,7 +863,6 @@ private:
_this->RSPduoMenu();
break;
case SDRPLAY_RSPdx_ID:
case SDRPLAY_RSPdxR2_ID:
_this->RSPdxMenu();
break;
default:
@ -1129,7 +1146,7 @@ private:
sdrplay_api_RxChannelParamsT* channelParams;
sdrplay_api_Bw_MHzT bandwidth;
int bandwidthId = 8; // Auto
int bandwidthId = 0;
int devId = 0;
int srId = 0;
@ -1184,9 +1201,6 @@ private:
std::string devListTxt;
std::vector<std::string> devNameList;
std::string selectedName;
OptionList<int, int> samplerates;
OptionList<int, sdrplay_api_Bw_MHzT> bandwidths;
};
MOD_EXPORT void _INIT_() {

View File

@ -12,7 +12,6 @@
#include <dsp/sink.h>
#include <dsp/routing/stream_link.h>
#include <zstd.h>
#include <chrono>
#define PROTOCOL_TIMEOUT_MS 10000

View File

@ -1,7 +1,6 @@
#include <spyserver_client.h>
#include <volk/volk.h>
#include <cstring>
#include <chrono>
using namespace std::chrono_literals;
@ -170,4 +169,4 @@ namespace spyserver {
}
return SpyServerClient(new SpyServerClientClass(std::move(conn), out));
}
}
}

View File

@ -11,7 +11,6 @@
#include <uhd/device.hpp>
#include <uhd/usrp/multi_usrp.hpp>
#include <utils/optionlist.h>
#include <utils/freq_formatting.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
@ -144,7 +143,7 @@ public:
for (const auto& l : srList) {
double step = (l.step() == 0.0) ? 100e3 : l.step();
for (double f = l.start(); f <= l.stop(); f += step) {
samplerates.define(f, utils::formatFreq(f), f);
samplerates.define(f, getBandwdithScaled(f), f);
}
}
@ -165,7 +164,7 @@ public:
for (const auto& r : bwRange) {
double step = (r.step() == 0.0) ? 100e3 : r.step();
for (double i = r.start(); i <= r.stop(); i += step) {
bandwidths.define((int)i, utils::formatFreq(i), i);
bandwidths.define((int)i, getBandwdithScaled(i), i);
}
}
@ -233,6 +232,20 @@ public:
}
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);
}
static void menuSelected(void* ctx) {
USRPSourceModule* _this = (USRPSourceModule*)ctx;
@ -435,23 +448,18 @@ private:
void worker() {
// TODO: Select a better buffer size that will avoid bad timing
int bufferSize = sampleRate / 200;
try {
while (true) {
uhd::rx_metadata_t meta;
void* ptr[] = { stream.writeBuf };
uhd::rx_streamer::buffs_type buffers(ptr, 1);
int len = streamer->recv(stream.writeBuf, bufferSize, meta, 1.0);
if (len < 0) { break; }
if (len != bufferSize) {
printf("%d\n", len);
}
if (len) {
if (!stream.swap(len)) { break; }
}
while (true) {
uhd::rx_metadata_t meta;
void* ptr[] = { stream.writeBuf };
uhd::rx_streamer::buffs_type buffers(ptr, 1);
int len = streamer->recv(stream.writeBuf, bufferSize, meta, 1.0);
if (len < 0) { break; }
if (len != bufferSize) {
printf("%d\n", len);
}
if (len) {
if (!stream.swap(len)) { break; }
}
}
catch (const std::exception& e) {
flog::error("Failed to receive samples: {}", e.what());
}
}