mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-07-08 18:15:21 +02:00
Compare commits
3 Commits
new_sdrpla
...
dab_experi
Author | SHA1 | Date | |
---|---|---|---|
f8078ac3f0 | |||
064f25ee73 | |||
d87ae23560 |
@ -19,7 +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_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)
|
||||
@ -161,10 +160,6 @@ if (OPT_BUILD_HERMES_SOURCE)
|
||||
add_subdirectory("source_modules/hermes_source")
|
||||
endif (OPT_BUILD_HERMES_SOURCE)
|
||||
|
||||
if (OPT_BUILD_KCSDR_SOURCE)
|
||||
add_subdirectory("source_modules/kcsdr_source")
|
||||
endif (OPT_BUILD_KCSDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_LIMESDR_SOURCE)
|
||||
add_subdirectory("source_modules/limesdr_source")
|
||||
endif (OPT_BUILD_LIMESDR_SOURCE)
|
||||
|
@ -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;
|
||||
@ -183,8 +183,6 @@ int sdrpp_main(int argc, char* argv[]) {
|
||||
defConfig["moduleInstances"]["Hermes 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";
|
||||
@ -232,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;
|
||||
|
||||
@ -325,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()) {
|
||||
|
@ -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(); }
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define VERSION_STR "1.2.1"
|
||||
#define VERSION_STR "1.2.0"
|
@ -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>;
|
||||
|
@ -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;
|
||||
|
324
decoder_modules/dab_decoder/src/ofdm.h
Normal file
324
decoder_modules/dab_decoder/src/ofdm.h
Normal file
@ -0,0 +1,324 @@
|
||||
#pragma once
|
||||
#include <dsp/processor.h>
|
||||
#include <dsp/loop/phase_control_loop.h>
|
||||
#include <dsp/taps/windowed_sinc.h>
|
||||
#include <dsp/multirate/polyphase_bank.h>
|
||||
#include <dsp/math/step.h>
|
||||
|
||||
namespace dsp::ofdm {
|
||||
class CyclicTimeSync : public Processor<complex_t, complex_t> {
|
||||
using base_type = Processor<complex_t, complex_t> ;
|
||||
public:
|
||||
CyclicTimeSync() {}
|
||||
|
||||
CyclicTimeSync(stream<complex_t>* in, int fftSize, int cpSize, double usefulSymbolTime, double samplerate,
|
||||
double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
|
||||
init(in, fftSize, cpSize, usefulSymbolTime, samplerate, omegaGain, muGain, omegaRelLimit, interpPhaseCount, interpTapCount);
|
||||
}
|
||||
|
||||
~CyclicTimeSync() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
dsp::multirate::freePolyphaseBank(interpBank);
|
||||
buffer::free(corrSampCache);
|
||||
buffer::free(corrProdCache);
|
||||
buffer::free(buffer);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, int fftSize, int cpSize, double usefulSymbolTime, double samplerate,
|
||||
double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
|
||||
// Save parameters
|
||||
this->fftSize = fftSize;
|
||||
this->cpSize = cpSize;
|
||||
period = fftSize + cpSize;
|
||||
|
||||
// Compute the interpolator settings
|
||||
omega = (usefulSymbolTime * samplerate) / (double)fftSize;
|
||||
this->omegaGain = omegaGain;
|
||||
this->muGain = muGain;
|
||||
this->omegaRelLimit = omegaRelLimit;
|
||||
this->interpPhaseCount = interpPhaseCount;
|
||||
this->interpTapCount = interpTapCount;
|
||||
|
||||
// Compute the correlator AGC settings
|
||||
// TODO: Compute it using he FFT and CP sizes
|
||||
this->corrAgcRate = 1e-4;
|
||||
corrAgcInvRate = 1.0f - corrAgcRate;
|
||||
|
||||
// Initialize the control loop
|
||||
pcl.init(muGain, omegaGain, 0.0, 0.0, 1.0, omega, omega * (1.0 - omegaRelLimit), omega * (1.0 + omegaRelLimit));
|
||||
|
||||
// Generate the interpolation taps
|
||||
generateInterpTaps();
|
||||
|
||||
// Allocate the buffers
|
||||
corrSampCache = buffer::alloc<complex_t>(fftSize);
|
||||
corrProdCache = buffer::alloc<complex_t>(cpSize);
|
||||
buffer = buffer::alloc<complex_t>(STREAM_BUFFER_SIZE + interpTapCount);
|
||||
bufStart = &buffer[interpTapCount - 1];
|
||||
|
||||
// Clear the buffers
|
||||
buffer::clear(corrSampCache, fftSize);
|
||||
buffer::clear(corrProdCache, cpSize);
|
||||
buffer::clear(buffer, interpTapCount - 1);
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
|
||||
void setOmegaGain(double omegaGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
this->omegaGain = omegaGain;
|
||||
pcl.setCoefficients(muGain, omegaGain);
|
||||
}
|
||||
|
||||
void setMuGain(double muGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
this->muGain = muGain;
|
||||
pcl.setCoefficients(muGain, omegaGain);
|
||||
}
|
||||
|
||||
void setOmegaRelLimit(double omegaRelLimit) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
this->omegaRelLimit = omegaRelLimit;
|
||||
pcl.setFreqLimits(omega * (1.0 - omegaRelLimit), omega * (1.0 + omegaRelLimit));
|
||||
}
|
||||
|
||||
void setInterpParams(int interpPhaseCount, int interpTapCount) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
this->interpPhaseCount = interpPhaseCount;
|
||||
this->interpTapCount = interpTapCount;
|
||||
dsp::multirate::freePolyphaseBank(interpBank);
|
||||
buffer::free(buffer);
|
||||
generateInterpTaps();
|
||||
buffer = buffer::alloc<complex_t>(STREAM_BUFFER_SIZE + interpTapCount);
|
||||
bufStart = &buffer[interpTapCount - 1];
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
offset = 0;
|
||||
pcl.phase = 0.0f;
|
||||
pcl.freq = omega;
|
||||
// TODO: The rest
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// Copy data to work buffer
|
||||
memcpy(bufStart, base_type::_in->readBuf, count * sizeof(complex_t));
|
||||
|
||||
// Process all samples
|
||||
while (offset < count) {
|
||||
// Get the cache slots
|
||||
complex_t* sampSlot = &corrSampCache[corrSampCacheId++];
|
||||
complex_t* prodSlot = &corrProdCache[corrProdCacheId++];
|
||||
corrSampCacheId %= fftSize;
|
||||
corrProdCacheId %= cpSize;
|
||||
|
||||
// Compute the interpolated sample
|
||||
complex_t sample;
|
||||
int phase = std::clamp<int>(floorf(pcl.phase * (float)interpPhaseCount), 0, interpPhaseCount - 1);
|
||||
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&sample, (lv_32fc_t*)&buffer[offset], interpBank.phases[phase], interpTapCount);
|
||||
|
||||
// Write the sample to the output
|
||||
if (outCount >= cpSize) {
|
||||
out.writeBuf[outCount - cpSize] = sample;
|
||||
}
|
||||
|
||||
// Send out a symbol when it's fully received
|
||||
if ((++outCount) >= fftSize+cpSize) {
|
||||
if (!out.swap(outCount)) { break; }
|
||||
outCount = 0;
|
||||
}
|
||||
|
||||
// Run autocorrelation
|
||||
complex_t prod = sample.conj()*(*sampSlot);
|
||||
corr += prod;
|
||||
corr -= *prodSlot;
|
||||
|
||||
// Write back the new sample and product value to the cache
|
||||
*sampSlot = sample;
|
||||
*prodSlot = prod;
|
||||
|
||||
// Compute the correlation level
|
||||
float corrLvl = corr.amplitude();
|
||||
|
||||
// Detect peak in autocorrelation (TODO: level check maybe not needed now that corrPeak is reset to corrLvl)
|
||||
if (corrLvl > corrAvg && corrLvl > corrPeak) {
|
||||
// Save the current correlation as the peak
|
||||
corrPeak = corrLvl;
|
||||
|
||||
// Save the value of the previous correlation as the left side of the peak
|
||||
corrPeakL = corrLast;
|
||||
|
||||
// Reset the peak distance counter
|
||||
sincePeak = 0;
|
||||
}
|
||||
|
||||
// The first sample after a peak is the right-side sample
|
||||
if (sincePeak == 1) {
|
||||
corrPeakR = corrLvl;
|
||||
}
|
||||
else if (sincePeak == cpSize) {
|
||||
// Start the useful symbol counter
|
||||
sinceCp = 0;
|
||||
|
||||
// Compute the fractional error (TODO: Probably very inaccurate with noise, use real slopes instead)
|
||||
if (corrPeakL > corrPeakR) {
|
||||
float maxSlope = corrPeakR - corrPeak;
|
||||
float slope = corrPeak - corrPeakL;
|
||||
fracErr = std::clamp<float>(0.5f * (1.0f + slope / maxSlope), -0.5f, 0.5f);
|
||||
}
|
||||
else {
|
||||
float maxSlope = corrPeak - corrPeakL;
|
||||
float slope = corrPeakR - corrPeak;
|
||||
fracErr = std::clamp<float>(-0.5f * (1.0f + slope / maxSlope), -0.5f, 0.5f);
|
||||
}
|
||||
}
|
||||
else if (sincePeak == fftSize) {
|
||||
// Reset the peak detector
|
||||
corrPeak = corrAvg;
|
||||
}
|
||||
// NOTE: THIS IS ONLY NEEDED FOR DAB
|
||||
// Detect a wider-than-normal distance to adapt the output counter
|
||||
else if (sincePeak == 2656) {
|
||||
// Reset the output counter
|
||||
outCount = 50;
|
||||
}
|
||||
|
||||
// Last sample of useful symbol
|
||||
if (sinceCp == fftSize) {
|
||||
// If the fractional error is valid, run closed-loop
|
||||
float err = 0.0f;
|
||||
if (!std::isnan(fracErr)) {
|
||||
// Compute the measured period using the distance to the last symbol
|
||||
float measuredPeriod = (float)sinceLastSym - fracErr;
|
||||
// NOTE: THIS IS ONLY NEEDED FOR DAB
|
||||
if (measuredPeriod > 3828.0f) {
|
||||
// Null symbol
|
||||
err = measuredPeriod - (2552.0f+2656.0f);
|
||||
}
|
||||
else {
|
||||
// Regular symbol
|
||||
err = measuredPeriod - period;
|
||||
}
|
||||
|
||||
err = std::clamp<float>(err, -10.0f, 10.0f);
|
||||
|
||||
// Run the control loop in closed-loop mode
|
||||
pcl.advance(err);
|
||||
}
|
||||
else {
|
||||
// Otherwise, run open-loop
|
||||
pcl.advancePhase();
|
||||
}
|
||||
|
||||
// printf("%d\n", outCount);
|
||||
|
||||
// Nudge the symbol window if it's too out of sync
|
||||
if (outCount > 100) {
|
||||
// TODO: MOVE THE LAST SAMPLES OR THE SYMBOL WILL BE CORRUPTED!
|
||||
outCount = 50;
|
||||
flog::debug("NUDGE!");
|
||||
}
|
||||
|
||||
// Reset the period counter
|
||||
sinceLastSym = 0;
|
||||
}
|
||||
else {
|
||||
// Run the control loop in open-loop mode
|
||||
pcl.advancePhase();
|
||||
}
|
||||
|
||||
// Update the offset and phase
|
||||
float delta = floorf(pcl.phase);
|
||||
offset += delta;
|
||||
pcl.phase -= delta;
|
||||
|
||||
// Update the last correlation level
|
||||
corrLast = corrLvl;
|
||||
|
||||
// Update correlation AGC
|
||||
corrAvg = corrAvg*corrAgcInvRate + corrLvl*corrAgcRate;
|
||||
|
||||
// Increment the distance counters (TODO: Check if they happen at the right point, eg. after being reset to zero)
|
||||
sincePeak++;
|
||||
sinceLastSym++;
|
||||
sinceCp++;
|
||||
}
|
||||
|
||||
// Prepare offset for next buffer of samples
|
||||
offset -= count;
|
||||
|
||||
// Update delay buffer
|
||||
memmove(buffer, &buffer[count], (interpTapCount - 1) * sizeof(complex_t));
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
protected:
|
||||
void generateInterpTaps() {
|
||||
double bw = 0.5 / (double)interpPhaseCount;
|
||||
dsp::tap<float> lp = dsp::taps::windowedSinc<float>(interpPhaseCount * interpTapCount, dsp::math::hzToRads(bw, 1.0), dsp::window::nuttall, interpPhaseCount);
|
||||
interpBank = dsp::multirate::buildPolyphaseBank<float>(interpPhaseCount, lp);
|
||||
taps::free(lp);
|
||||
}
|
||||
|
||||
// OFDM Configuration
|
||||
int fftSize;
|
||||
int cpSize;
|
||||
float period;
|
||||
|
||||
// Interpolator
|
||||
dsp::multirate::PolyphaseBank<float> interpBank;
|
||||
int interpPhaseCount;
|
||||
int interpTapCount;
|
||||
int offset = 0;
|
||||
complex_t* buffer = NULL;
|
||||
complex_t* bufStart;
|
||||
|
||||
// Control loop
|
||||
loop::PhaseControlLoop<float, false> pcl;
|
||||
double omega;
|
||||
double omegaGain;
|
||||
double muGain;
|
||||
double omegaRelLimit;
|
||||
float fracErr = 0.0f;
|
||||
|
||||
// Autocorrelator
|
||||
complex_t corr = {0.0f, 0.0f};
|
||||
complex_t* corrSampCache = NULL;
|
||||
complex_t* corrProdCache = NULL;
|
||||
int corrSampCacheId = 0;
|
||||
int corrProdCacheId = 0;
|
||||
float corrAgcRate;
|
||||
float corrAgcInvRate;
|
||||
float corrAvg = 0;
|
||||
float corrLast = 0;
|
||||
float corrPeakR = 0;
|
||||
float corrPeak = 0;
|
||||
float corrPeakL = 0;
|
||||
|
||||
// Peak detection
|
||||
int sincePeak = 0;
|
||||
int sinceLastSym = 0;
|
||||
int sinceCp = 0;
|
||||
|
||||
// Other shit to categorize
|
||||
int outCount = 0;
|
||||
};
|
||||
};
|
@ -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
|
31
decoder_modules/dab_decoder/src/optmized.txt
Normal file
31
decoder_modules/dab_decoder/src/optmized.txt
Normal file
@ -0,0 +1,31 @@
|
||||
cyclicLen = 4
|
||||
usefulLen = 12
|
||||
|
||||
A = 0*12 + 1*13 + 2*14 + 3*15
|
||||
B = 1*13 + 2*14 + 3*15 + 4*16 = A - 0*12 + 4*16
|
||||
C = 2*14 + 3*15 + 4*16 + 5*17 = B - 1*13 + 5*17
|
||||
D = 3*15 + 4*16 + 5*17 + 6*18 = C - 2*14 + 6*18
|
||||
E = 4*16 + 5*17 + 6*18 + 7*19 = D - 3*15 + 7*19
|
||||
F = 5*17 + 6*18 + 7*19 + 8*20 = E - 4*16 + 8*20
|
||||
G = 6*18 + 7*19 + 8*20 + 9*21 = F - 5*17 + 9*21
|
||||
H = 7*19 + 8*20 + 9*21 + 10*22 = G - 6*18 + 10*22
|
||||
I = 8*20 + 9*21 + 10*22 + 11*23 = H - 7*19 + 11*23
|
||||
J = 9*21 + 10*22 + 11*23 + 12*24 = I - 8*20 + 12*24
|
||||
K = 10*22 + 11*23 + 12*24 + 13*25 = J - 9*21 + 13*25
|
||||
L = 11*23 + 12*24 + 13*25 + 14*26 = K - 10*22 + 14*26
|
||||
M = 12*24 + 13*25 + 14*26 + 15*27 = L - 11*23 + 15*27
|
||||
N = 13*25 + 14*26 + 15*27 + 16*28 = M - 12*24 + 16*28
|
||||
O = 14*26 + 15*27 + 16*28 + 17*29 = N - 13*25 + 17*29
|
||||
P = 15*27 + 16*28 + 17*29 + 18*30 = O - 14*26 + 18*30
|
||||
Q = 16*28 + 17*29 + 18*30 + 19*31 = P - 15*27 + 19*31
|
||||
R = 17*29 + 18*30 + 19*31 + 20*32 = Q - 16*28 + 20*32
|
||||
S = 18*30 + 19*31 + 20*32 + 21*33 = R - 17*29 + 21*33
|
||||
T = 19*31 + 20*32 + 21*33 + 22*34 = S - 18*30 + 22*34
|
||||
U = 20*32 + 21*33 + 22*34 + 23*35 = T - 19*31 + 23*35
|
||||
|
||||
Conclusion:
|
||||
sampCacheLen = usefulLen
|
||||
prodCacheLen = cyclicLen
|
||||
|
||||
Peak correlation occurs when the current interpolated value is the last FFT sample
|
||||
|
397
decoder_modules/dab_decoder/src/test.txt
Normal file
397
decoder_modules/dab_decoder/src/test.txt
Normal file
@ -0,0 +1,397 @@
|
||||
5209
|
||||
2553
|
||||
2551
|
||||
2551
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2555
|
||||
2549
|
||||
2552
|
||||
2552
|
||||
2554
|
||||
2550
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
2553
|
||||
2552
|
||||
2550
|
||||
2552
|
||||
2548
|
||||
2558
|
||||
2551
|
||||
2546
|
||||
2559
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2551
|
||||
2554
|
||||
2555
|
||||
2549
|
||||
2550
|
||||
2549
|
||||
2557
|
||||
2551
|
||||
2554
|
||||
2550
|
||||
2552
|
||||
2546
|
||||
2559
|
||||
2551
|
||||
2554
|
||||
2550
|
||||
2550
|
||||
2557
|
||||
2550
|
||||
2556
|
||||
2543
|
||||
2556
|
||||
2551
|
||||
2554
|
||||
2551
|
||||
2553
|
||||
2554
|
||||
2550
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2553
|
||||
2550
|
||||
2553
|
||||
2546
|
||||
2558
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2550
|
||||
2555
|
||||
2551
|
||||
2551
|
||||
2554
|
||||
2553
|
||||
2553
|
||||
2549
|
||||
2552
|
||||
5208
|
||||
2554
|
||||
2550
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2551
|
||||
2553
|
||||
2551
|
||||
2553
|
||||
2550
|
||||
2554
|
||||
2553
|
||||
2551
|
||||
2553
|
||||
2552
|
||||
2546
|
||||
2557
|
||||
2551
|
||||
2553
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2551
|
||||
2548
|
||||
2557
|
||||
2553
|
||||
2551
|
||||
2562
|
||||
2539
|
||||
2559
|
||||
2548
|
||||
2551
|
||||
2551
|
||||
2550
|
||||
2556
|
||||
2551
|
||||
2554
|
||||
2550
|
||||
2552
|
||||
2552
|
||||
2548
|
||||
2556
|
||||
2550
|
||||
2554
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
2552
|
||||
2552
|
||||
2550
|
||||
2555
|
||||
2558
|
||||
2545
|
||||
2552
|
||||
2553
|
||||
2548
|
||||
2555
|
||||
2552
|
||||
2552
|
||||
2557
|
||||
2550
|
||||
2548
|
||||
2553
|
||||
2554
|
||||
2550
|
||||
2552
|
||||
2547
|
||||
2558
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2555
|
||||
2549
|
||||
2552
|
||||
5208
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2550
|
||||
2555
|
||||
2550
|
||||
2553
|
||||
2553
|
||||
2551
|
||||
2552
|
||||
2547
|
||||
2557
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2546
|
||||
2558
|
||||
2551
|
||||
2552
|
||||
2553
|
||||
2551
|
||||
2553
|
||||
2553
|
||||
2552
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2556
|
||||
2548
|
||||
2551
|
||||
2555
|
||||
2550
|
||||
2552
|
||||
2555
|
||||
2549
|
||||
2552
|
||||
2552
|
||||
2550
|
||||
2553
|
||||
2551
|
||||
2554
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2553
|
||||
2551
|
||||
2548
|
||||
2550
|
||||
2555
|
||||
2554
|
||||
2553
|
||||
2556
|
||||
2547
|
||||
2552
|
||||
2554
|
||||
2552
|
||||
2550
|
||||
2552
|
||||
2552
|
||||
2553
|
||||
2552
|
||||
2551
|
||||
2552
|
||||
5209
|
||||
2553
|
||||
2551
|
||||
2551
|
||||
2547
|
||||
2558
|
||||
2550
|
||||
2553
|
||||
2552
|
||||
2553
|
||||
2552
|
||||
2549
|
||||
2551
|
||||
2557
|
||||
2549
|
||||
2554
|
||||
2551
|
||||
2554
|
||||
2550
|
||||
2553
|
||||
2553
|
||||
2551
|
||||
2551
|
||||
2548
|
||||
2559
|
||||
2549
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2551
|
||||
2551
|
||||
2550
|
||||
2557
|
||||
2551
|
||||
2552
|
||||
2555
|
||||
2549
|
||||
2557
|
||||
2549
|
||||
2550
|
||||
2553
|
||||
2549
|
||||
2554
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
2552
|
||||
2553
|
||||
2550
|
||||
2553
|
||||
2553
|
||||
2551
|
||||
2547
|
||||
2557
|
||||
2552
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2549
|
||||
2555
|
||||
2556
|
||||
2550
|
||||
2550
|
||||
2553
|
||||
2551
|
||||
2552
|
||||
2553
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2553
|
||||
2551
|
||||
2541
|
||||
2563
|
||||
2552
|
||||
5210
|
||||
2550
|
||||
2551
|
||||
2552
|
||||
2553
|
||||
2553
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2558
|
||||
2547
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2550
|
||||
2554
|
||||
2551
|
||||
2553
|
||||
2552
|
||||
2551
|
||||
2553
|
||||
2551
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
2551
|
||||
2555
|
||||
2550
|
||||
2552
|
||||
2552
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
2552
|
||||
2553
|
||||
2550
|
||||
2553
|
||||
2558
|
||||
2546
|
||||
2553
|
||||
2554
|
||||
2550
|
||||
2552
|
||||
2553
|
||||
2548
|
||||
2554
|
||||
2552
|
||||
2552
|
||||
2552
|
||||
2551
|
||||
2554
|
||||
2550
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
2551
|
||||
2552
|
||||
2554
|
||||
2553
|
||||
2551
|
||||
2551
|
||||
2555
|
||||
2550
|
||||
2553
|
||||
2552
|
||||
2550
|
||||
2554
|
||||
2550
|
||||
2553
|
||||
2553
|
||||
2553
|
||||
2550
|
||||
5210
|
||||
2553
|
||||
2549
|
||||
2553
|
||||
2551
|
||||
2552
|
||||
2552
|
||||
2556
|
||||
2548
|
||||
2549
|
||||
2555
|
||||
2551
|
||||
2557
|
||||
2548
|
||||
2552
|
||||
2552
|
||||
2552
|
@ -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
|
||||
|
@ -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/
|
||||
|
||||
|
@ -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);
|
||||
|
13
readme.md
13
readme.md
@ -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
|
||||
|
||||
@ -419,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
|
||||
|
||||
|
@ -1,201 +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": "10m ham band",
|
||||
"type": "amateur",
|
||||
"start": 28000000,
|
||||
"end": 29700000
|
||||
},
|
||||
{
|
||||
"name": "FM Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 87500000,
|
||||
"end": 108000000
|
||||
},
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
@ -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;
|
||||
|
1
source_modules/kcsdr_source/.gitignore
vendored
1
source_modules/kcsdr_source/.gitignore
vendored
@ -1 +0,0 @@
|
||||
vendor/*
|
@ -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)
|
@ -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;
|
||||
}
|
@ -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
|
@ -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
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
@ -164,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;
|
||||
@ -184,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;
|
||||
@ -256,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];
|
||||
@ -287,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) {
|
||||
@ -296,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"];
|
||||
}
|
||||
@ -354,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")) {
|
||||
@ -376,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"];
|
||||
}
|
||||
@ -412,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"];
|
||||
}
|
||||
@ -427,7 +466,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
config.release();
|
||||
config.release(created);
|
||||
|
||||
if (lnaGain >= lnaSteps) { lnaGain = lnaSteps - 1; }
|
||||
|
||||
@ -575,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;
|
||||
}
|
||||
@ -654,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);
|
||||
}
|
||||
|
||||
@ -676,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);
|
||||
@ -706,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();
|
||||
@ -833,7 +854,6 @@ private:
|
||||
_this->RSP1Menu();
|
||||
break;
|
||||
case SDRPLAY_RSP1A_ID:
|
||||
case SDRPLAY_RSP1B_ID:
|
||||
_this->RSP1AMenu();
|
||||
break;
|
||||
case SDRPLAY_RSP2_ID:
|
||||
@ -843,7 +863,6 @@ private:
|
||||
_this->RSPduoMenu();
|
||||
break;
|
||||
case SDRPLAY_RSPdx_ID:
|
||||
case SDRPLAY_RSPdxR2_ID:
|
||||
_this->RSPdxMenu();
|
||||
break;
|
||||
default:
|
||||
@ -1127,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;
|
||||
@ -1182,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_() {
|
||||
|
Reference in New Issue
Block a user