mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-10-31 00:48:11 +01:00 
			
		
		
		
	Compare commits
	
		
			42 Commits
		
	
	
		
			dab_experi
			...
			new_sdrpla
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c266a37a6b | ||
|  | 895199ae94 | ||
|  | d62426364a | ||
|  | 304d5c42cc | ||
|  | 11f87e0fe2 | ||
|  | e192cb963b | ||
|  | fe407a2f27 | ||
|  | 6891d0bb0f | ||
|  | b835d07573 | ||
|  | f205d97b52 | ||
|  | 628dcfcce0 | ||
|  | d1e7cc56b4 | ||
|  | 334860c963 | ||
|  | 69161253e8 | ||
|  | 5ab3428b90 | ||
|  | 7f002f6276 | ||
|  | a728403a3f | ||
|  | 0f1d2da3b7 | ||
|  | 6d0b65c27f | ||
|  | f640cdcb6a | ||
|  | 80a90e13d9 | ||
|  | 3982db73d3 | ||
|  | bd64f07a20 | ||
|  | c9950d9331 | ||
|  | 9bc609f4e4 | ||
|  | bcc8e20e66 | ||
|  | b07e828fed | ||
|  | bd24a4a5eb | ||
|  | fe4a7b32a7 | ||
|  | 0e1ab29b5d | ||
|  | fbbafddd3d | ||
|  | 1cbc8ec6f5 | ||
|  | 9f65e3ec71 | ||
|  | 08f3a7d201 | ||
|  | 9ce62f8885 | ||
|  | caeaa2d46c | ||
|  | 7ae030a3a6 | ||
|  | 1b27379a3d | ||
|  | e52123038e | ||
|  | ec8c60111d | ||
|  | f61799cf5f | ||
|  | e835c8dd9a | 
| @@ -19,6 +19,7 @@ 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) | ||||
| @@ -160,6 +161,10 @@ 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,12 +147,12 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|     defConfig["menuElements"][3]["name"] = "Sinks"; | ||||
|     defConfig["menuElements"][3]["open"] = true; | ||||
|  | ||||
|     defConfig["menuElements"][3]["name"] = "Frequency Manager"; | ||||
|     defConfig["menuElements"][3]["open"] = true; | ||||
|  | ||||
|     defConfig["menuElements"][4]["name"] = "VFO Color"; | ||||
|     defConfig["menuElements"][4]["name"] = "Frequency Manager"; | ||||
|     defConfig["menuElements"][4]["open"] = true; | ||||
|  | ||||
|     defConfig["menuElements"][5]["name"] = "VFO Color"; | ||||
|     defConfig["menuElements"][5]["open"] = true; | ||||
|  | ||||
|     defConfig["menuElements"][6]["name"] = "Band Plan"; | ||||
|     defConfig["menuElements"][6]["open"] = true; | ||||
|  | ||||
| @@ -183,6 +183,8 @@ 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"; | ||||
| @@ -230,12 +232,19 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|  | ||||
|     defConfig["modules"] = json::array(); | ||||
|  | ||||
|     defConfig["offsetMode"] = (int)0; // Off | ||||
|     defConfig["offset"] = 0.0; | ||||
|     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["showMenu"] = true; | ||||
|     defConfig["showWaterfall"] = true; | ||||
|     defConfig["source"] = ""; | ||||
|     defConfig["decimationPower"] = 0; | ||||
|     defConfig["decimation"] = 1; | ||||
|     defConfig["iqCorrection"] = false; | ||||
|     defConfig["invertIQ"] = false; | ||||
|  | ||||
| @@ -316,12 +325,18 @@ 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()); | ||||
|             core::configManager.conf.erase(item.key()); | ||||
|             newConf.erase(item.key()); | ||||
|             configCorrected = true; | ||||
|         } | ||||
|     } | ||||
|     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,113 +5,120 @@ | ||||
| #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; | ||||
|     double customOffset = 0.0; | ||||
|     double effectiveOffset = 0.0; | ||||
|     int decimationPower = 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; | ||||
|  | ||||
|     bool iqCorrection = false; | ||||
|     bool invertIQ = false; | ||||
|  | ||||
|     EventHandler<std::string> sourceRegisteredHandler; | ||||
|     EventHandler<std::string> sourceUnregisterHandler; | ||||
|     EventHandler<std::string> sourceUnregisteredHandler; | ||||
|     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; | ||||
|  | ||||
|     std::vector<std::string> sourceNames; | ||||
|     std::string sourceNamesTxt; | ||||
|     std::string selectedSource; | ||||
|     bool showAddOffsetDialog = false; | ||||
|     char newOffsetName[1024]; | ||||
|     double newOffset = 0.0; | ||||
|  | ||||
|     bool showDelOffsetDialog = false; | ||||
|     std::string delOffsetName = ""; | ||||
|  | ||||
|     // Offset IDs | ||||
|     enum { | ||||
|         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 | ||||
|         OFFSET_ID_NONE, | ||||
|         OFFSET_ID_MANUAL, | ||||
|         OFFSET_ID_CUSTOM_BASE | ||||
|     }; | ||||
|  | ||||
|     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"; | ||||
|  | ||||
|     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 { | ||||
|         // 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; | ||||
|         } | ||||
|  | ||||
|         // Apply it | ||||
|         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() { | ||||
|         sourceNames = sigpath::sourceManager.getSourceNames(); | ||||
|         sourceNamesTxt.clear(); | ||||
|         // Get sources | ||||
|         auto sourceNames = sigpath::sourceManager.getSourceNames(); | ||||
|  | ||||
|         // Define source options | ||||
|         sources.clear(); | ||||
|         for (auto name : sourceNames) { | ||||
|             sourceNamesTxt += name; | ||||
|             sourceNamesTxt += '\0'; | ||||
|             sources.define(name, name, name); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void selectSource(std::string name) { | ||||
|         if (sourceNames.empty()) { | ||||
|         // If there is no source, give up | ||||
|         if (sources.empty()) { | ||||
|             sourceId = 0; | ||||
|             selectedSource.clear(); | ||||
|             return; | ||||
|         } | ||||
|         auto it = std::find(sourceNames.begin(), sourceNames.end(), name); | ||||
|         if (it == sourceNames.end()) { | ||||
|             selectSource(sourceNames[0]); | ||||
|  | ||||
|         // If a source with the given name doesn't exist, select the first source instead | ||||
|         if (!sources.valueExists(name)) { | ||||
|             selectSource(sources.value(0)); | ||||
|             return; | ||||
|         } | ||||
|         sourceId = std::distance(sourceNames.begin(), it); | ||||
|         selectedSource = sourceNames[sourceId]; | ||||
|         sigpath::sourceManager.selectSource(sourceNames[sourceId]); | ||||
|  | ||||
|         // Update the GUI variables | ||||
|         sourceId = sources.valueId(name); | ||||
|         selectedSource = name; | ||||
|  | ||||
|         // Select the source module | ||||
|         sigpath::sourceManager.selectSource(name); | ||||
|     } | ||||
|  | ||||
|     void onSourceRegistered(std::string name, void* ctx) { | ||||
|     void onSourcesChanged(std::string name, void* ctx) { | ||||
|         // Update the source list | ||||
|         refreshSources(); | ||||
|  | ||||
|         if (selectedSource.empty()) { | ||||
|             sourceId = 0; | ||||
|             selectSource(sourceNames[0]); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource)); | ||||
|         // Reselect the current source | ||||
|         selectSource(selectedSource); | ||||
|     } | ||||
|  | ||||
|     void onSourceUnregister(std::string name, void* ctx) { | ||||
| @@ -120,60 +127,173 @@ namespace sourcemenu { | ||||
|         // TODO: Stop everything | ||||
|     } | ||||
|  | ||||
|     void onSourceUnregistered(std::string name, void* ctx) { | ||||
|         refreshSources(); | ||||
|     void reloadOffsets() { | ||||
|         // Clear list | ||||
|         offsets.clear(); | ||||
|         namedOffsets.clear(); | ||||
|  | ||||
|         if (sourceNames.empty()) { | ||||
|             selectedSource = ""; | ||||
|             return; | ||||
|         // 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 (name == selectedSource) { | ||||
|             sourceId = std::clamp<int>(sourceId, 0, sourceNames.size() - 1); | ||||
|             selectSource(sourceNames[sourceId]); | ||||
|             return; | ||||
|         // Define custom offsets | ||||
|         for (auto& [name, offset] : namedOffsets) { | ||||
|             offsets.define(name, offsets.size()); | ||||
|         } | ||||
|  | ||||
|         sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource)); | ||||
|         // Release the config file | ||||
|         core::configManager.release(); | ||||
|     } | ||||
|  | ||||
|     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(); | ||||
|         std::string selected = core::configManager.conf["source"]; | ||||
|         customOffset = core::configManager.conf["offset"]; | ||||
|         offsetMode = core::configManager.conf["offsetMode"]; | ||||
|         decimationPower = core::configManager.conf["decimationPower"]; | ||||
|  | ||||
|         // Load other settings | ||||
|         std::string selectedSource = core::configManager.conf["source"]; | ||||
|         manualOffset = core::configManager.conf["manualOffset"]; | ||||
|         std::string selectedOffset = core::configManager.conf["selectedOffset"]; | ||||
|         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); | ||||
|         updateOffset(); | ||||
|         sigpath::iqFrontEnd.setDecimation(decimations.value(decimId)); | ||||
|         selectOffsetByName(selectedOffset); | ||||
|  | ||||
|         refreshSources(); | ||||
|         selectSource(selected); | ||||
|         sigpath::iqFrontEnd.setDecimation(1 << decimationPower); | ||||
|  | ||||
|         sourceRegisteredHandler.handler = onSourceRegistered; | ||||
|         // Register handlers | ||||
|         sourcesChangedHandler.handler = onSourcesChanged; | ||||
|         sourceUnregisterHandler.handler = onSourceUnregister; | ||||
|         sourceUnregisteredHandler.handler = onSourceUnregistered; | ||||
|         sigpath::sourceManager.onSourceRegistered.bindHandler(&sourceRegisteredHandler); | ||||
|         sigpath::sourceManager.onSourceRegistered.bindHandler(&sourcesChangedHandler); | ||||
|         sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler); | ||||
|         sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourceUnregisteredHandler); | ||||
|         sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourcesChangedHandler); | ||||
|     } | ||||
|  | ||||
|         core::configManager.release(); | ||||
|     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; | ||||
|     } | ||||
|  | ||||
|     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, sourceNamesTxt.c_str())) { | ||||
|             selectSource(sourceNames[sourceId]); | ||||
|         if (ImGui::Combo("##source", &sourceId, sources.txt)) { | ||||
|             std::string newSource = sources.value(sourceId); | ||||
|             selectSource(newSource); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["source"] = sourceNames[sourceId]; | ||||
|             core::configManager.conf["source"] = newSource; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
| @@ -196,21 +316,45 @@ namespace sourcemenu { | ||||
|         } | ||||
|  | ||||
|         ImGui::LeftLabel("Offset mode"); | ||||
|         ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo("##_sdrpp_offset_mode", &offsetMode, offsetModesTxt)) { | ||||
|             updateOffset(); | ||||
|         ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX() - 2.0f*(lineHeight + 1.5f*spacing)); | ||||
|         if (ImGui::Combo("##_sdrpp_offset", &offsetId, offsets.txt)) { | ||||
|             selectOffsetById(offsetId); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["offsetMode"] = offsetMode; | ||||
|             core::configManager.conf["selectedOffset"] = offsets.key(offsetId); | ||||
|             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::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); | ||||
|         if (offsetMode == OFFSET_MODE_CUSTOM) { | ||||
|             if (ImGui::InputDouble("##freq_offset", &customOffset, 1.0, 100.0)) { | ||||
|         ImGui::FillWidth(); | ||||
|         if (offsetId == OFFSET_ID_MANUAL) { | ||||
|             if (ImGui::InputDouble("##freq_offset", &manualOffset, 1.0, 100.0)) { | ||||
|                 updateOffset(); | ||||
|                 core::configManager.acquire(); | ||||
|                 core::configManager.conf["offset"] = customOffset; | ||||
|                 core::configManager.conf["manualOffset"] = manualOffset; | ||||
|                 core::configManager.release(true); | ||||
|             } | ||||
|         } | ||||
| @@ -222,11 +366,11 @@ namespace sourcemenu { | ||||
|  | ||||
|         if (running) { style::beginDisabled(); } | ||||
|         ImGui::LeftLabel("Decimation"); | ||||
|         ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo("##source_decim", &decimationPower, decimationStages)) { | ||||
|             sigpath::iqFrontEnd.setDecimation(1 << decimationPower); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::Combo("##source_decim", &decimId, decimations.txt)) { | ||||
|             sigpath::iqFrontEnd.setDecimation(decimations.value(decimId)); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["decimationPower"] = decimationPower; | ||||
|             core::configManager.conf["decimation"] = decimations.key(decimId); | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         if (running) { style::endDisabled(); } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| #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 | ||||
| @@ -90,6 +91,7 @@ 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; | ||||
| @@ -132,7 +134,7 @@ void FrequencySelect::draw() { | ||||
|         ImVec2 mousePos = ImGui::GetMousePos(); | ||||
|         bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); | ||||
|         bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right); | ||||
|         int mw = ImGui::GetIO().MouseWheel; | ||||
|         int mw = io.MouseWheel; | ||||
|         bool onDigit = false; | ||||
|         bool hovered = false; | ||||
|  | ||||
| @@ -174,7 +176,7 @@ void FrequencySelect::draw() { | ||||
|                     moveCursorToDigit(i + 1); | ||||
|                 } | ||||
|  | ||||
|                 auto chars = ImGui::GetIO().InputQueueCharacters; | ||||
|                 auto chars = io.InputQueueCharacters; | ||||
|  | ||||
|                 // For each keyboard characters, type it | ||||
|                 for (int j = 0; j < chars.Size; j++) { | ||||
| @@ -194,6 +196,34 @@ 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; | ||||
|   | ||||
							
								
								
									
										120
									
								
								core/src/utils/hrfreq.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								core/src/utils/hrfreq.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| #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 | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								core/src/utils/hrfreq.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								core/src/utils/hrfreq.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| #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.0" | ||||
| #define VERSION_STR "1.2.1" | ||||
| @@ -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,7 +14,6 @@ | ||||
| #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()) | ||||
|  | ||||
| @@ -36,7 +35,7 @@ public: | ||||
|     M17DecoderModule(std::string name)  { | ||||
|         this->name = name; | ||||
|  | ||||
|         file = std::ofstream("sync5.f32", std::ios::out | std::ios::binary); | ||||
|         file = std::ofstream("sync4.f32", std::ios::out | std::ios::binary); | ||||
|  | ||||
|         // Load config | ||||
|         config.acquire(); | ||||
| @@ -48,7 +47,7 @@ public: | ||||
|         vfo->setSnapInterval(250); | ||||
|  | ||||
|         // Initialize DSP here | ||||
|         csync.init(vfo->output, 2048, 504, 1e-3, INPUT_SAMPLE_RATE, 1e-6, 0.01, 0.005); | ||||
|         csync.init(vfo->output, 1e-3, 246e-6, INPUT_SAMPLE_RATE); | ||||
|         ffsync.init(&csync.out); | ||||
|         ns.init(&ffsync.out, handler, this); | ||||
|  | ||||
| @@ -132,9 +131,8 @@ 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; | ||||
|   | ||||
| @@ -1,324 +0,0 @@ | ||||
| #pragma once | ||||
| #include <dsp/processor.h> | ||||
| #include <dsp/loop/phase_control_loop.h> | ||||
| #include <dsp/taps/windowed_sinc.h> | ||||
| #include <dsp/multirate/polyphase_bank.h> | ||||
| #include <dsp/math/step.h> | ||||
|  | ||||
| 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; | ||||
|     }; | ||||
| }; | ||||
							
								
								
									
										34
									
								
								decoder_modules/dab_decoder/src/optimized_algo.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								decoder_modules/dab_decoder/src/optimized_algo.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| 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 | ||||
| @@ -1,31 +0,0 @@ | ||||
| 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 | ||||
|  | ||||
| @@ -1,397 +0,0 @@ | ||||
| 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,10 +35,11 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/airspyhf_source/airspyhf_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/bladerf_source/bladerf_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/file_source/file_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/file_source/fobossdr_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/fobossdr_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,6 +35,8 @@ 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,10 +168,9 @@ 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, type, vfoName) + extension); | ||||
|         std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, recMode, vfoName) + extension); | ||||
|         if (!writer.open(expandedPath)) { | ||||
|             flog::error("Failed to open file for recording: {0}", expandedPath); | ||||
|             return; | ||||
| @@ -452,7 +451,7 @@ private: | ||||
|         { RADIO_IFACE_MODE_RAW, "RAW" } | ||||
|     }; | ||||
|  | ||||
|     std::string genFileName(std::string templ, std::string type, std::string name) { | ||||
|     std::string genFileName(std::string templ, int mode, std::string name) { | ||||
|         // Get data | ||||
|         time_t now = time(0); | ||||
|         tm* ltm = localtime(&now); | ||||
| @@ -462,6 +461,9 @@ 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]; | ||||
| @@ -470,7 +472,7 @@ private: | ||||
|         char dayStr[128]; | ||||
|         char monStr[128]; | ||||
|         char yearStr[128]; | ||||
|         const char* modeStr = "Unknown"; | ||||
|         const char* modeStr = (recMode == RECORDER_MODE_AUDIO) ? "Unknown" : "IQ"; | ||||
|         sprintf(freqStr, "%.0lfHz", freq); | ||||
|         sprintf(hourStr, "%02d", ltm->tm_hour); | ||||
|         sprintf(minStr, "%02d", ltm->tm_min); | ||||
|   | ||||
							
								
								
									
										13
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								readme.md
									
									
									
									
									
								
							| @@ -324,12 +324,13 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
| | audio_source         | Working    | rtaudio           | OPT_BUILD_AUDIO_SOURCE         | ✅              | ✅                     | ✅                         | | ||||
| | bladerf_source       | Working    | libbladeRF        | OPT_BUILD_BLADERF_SOURCE       | ⛔              | ✅ (not Debian Buster) | ✅                         | | ||||
| | file_source          | Working    | -                 | OPT_BUILD_FILE_SOURCE          | ✅              | ✅                     | ✅                         | | ||||
| | fobossdr_source      | Beta       | libfobos          | OPT_BUILD_FOBOSSDR_SOURCE      | ✅              | ✅                     | ✅                         | | ||||
| | fobossdr_source      | Working    | 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       | Unfinished | -                 | OPT_BUILD_NETWORK_SOURCE       | ✅              | ✅                     | ⛔                         | | ||||
| | network_source       | Beta       | -                 | 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          | ⛔              | ✅                     | ✅                         | | ||||
| @@ -351,8 +352,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 | Beta       | portaudio    | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔              | ✅              | ⛔                         | | ||||
| | portaudio_sink     | Beta       | portaudio    | OPT_BUILD_PORTAUDIO_SINK     | ⛔              | ✅              | ⛔                         | | ||||
| | new_portaudio_sink | Working    | portaudio    | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔              | ✅              | ⛔                         | | ||||
| | portaudio_sink     | Working    | portaudio    | OPT_BUILD_PORTAUDIO_SINK     | ⛔              | ✅              | ⛔                         | | ||||
|  | ||||
| ## Decoders | ||||
|  | ||||
| @@ -418,8 +419,8 @@ If you still have an issue, please open an issue about it or ask on the discord. | ||||
|  | ||||
| # Contributing | ||||
|  | ||||
| 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. | ||||
| Feel free to submit band plans via the GitHub issue tracker. | ||||
| For code changes, please create a feature request instead. | ||||
|  | ||||
| # Credits | ||||
|  | ||||
|   | ||||
							
								
								
									
										201
									
								
								root/res/bandplans/ireland.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								root/res/bandplans/ireland.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,201 @@ | ||||
| { | ||||
|     "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 | ||||
|         } | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										549
									
								
								root/res/bandplans/republic-of-korea.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										549
									
								
								root/res/bandplans/republic-of-korea.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,549 @@ | ||||
| { | ||||
|     "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,6 +10,7 @@ | ||||
| #include <libbladeRF.h> | ||||
| #include <gui/smgui.h> | ||||
| #include <algorithm> | ||||
| #include <utils/optionlist.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| @@ -37,6 +38,10 @@ 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; | ||||
| @@ -267,6 +272,15 @@ 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"]; | ||||
| @@ -364,6 +378,7 @@ 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); | ||||
| @@ -486,6 +501,19 @@ 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(); | ||||
| @@ -537,6 +565,15 @@ 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; | ||||
| @@ -565,6 +602,7 @@ private: | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|     int bwId = 0; | ||||
|     int clkId = 0; | ||||
|     int chanId = 0; | ||||
|     int gainMode = 0; | ||||
|     bool streamingEnabled = false; | ||||
| @@ -580,8 +618,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
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								source_modules/kcsdr_source/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| vendor/* | ||||
							
								
								
									
										10
									
								
								source_modules/kcsdr_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								source_modules/kcsdr_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| 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) | ||||
							
								
								
									
										209
									
								
								source_modules/kcsdr_source/src/kcsdr.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								source_modules/kcsdr_source/src/kcsdr.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | ||||
| #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; | ||||
| } | ||||
							
								
								
									
										150
									
								
								source_modules/kcsdr_source/src/kcsdr.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								source_modules/kcsdr_source/src/kcsdr.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| #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 | ||||
							
								
								
									
										324
									
								
								source_modules/kcsdr_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								source_modules/kcsdr_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,324 @@ | ||||
| #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[] { | ||||
|     sizeof(int8_t)*2, | ||||
|     sizeof(int16_t)*2, | ||||
|     sizeof(int32_t)*2, | ||||
|     sizeof(float)*2, | ||||
|     2*sizeof(int8_t), | ||||
|     2*sizeof(int16_t), | ||||
|     2*sizeof(int32_t), | ||||
|     2*sizeof(float), | ||||
| }; | ||||
|  | ||||
| class NetworkSourceModule : public ModuleManager::Instance { | ||||
| @@ -58,20 +58,6 @@ 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); | ||||
| @@ -86,8 +72,8 @@ public: | ||||
|         // Load config | ||||
|         config.acquire(); | ||||
|         if (config.conf[name].contains("samplerate")) { | ||||
|             int sr = config.conf[name]["samplerate"]; | ||||
|             if (samplerates.keyExists(sr)) { samplerate = samplerates.value(samplerates.keyId(sr)); } | ||||
|             samplerate = config.conf[name]["samplerate"]; | ||||
|             tempSamplerate = samplerate; | ||||
|         } | ||||
|         if (config.conf[name].contains("protocol")) { | ||||
|             std::string protoStr = config.conf[name]["protocol"]; | ||||
| @@ -108,7 +94,6 @@ public: | ||||
|         config.release(); | ||||
|  | ||||
|         // Set menu IDs | ||||
|         srId = samplerates.valueId(samplerate); | ||||
|         protoId = protocols.valueId(proto); | ||||
|         sampTypeId = sampleTypes.valueId(sampType); | ||||
|  | ||||
| @@ -228,35 +213,24 @@ private: | ||||
|         if (_this->running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         // Hostname and port field | ||||
|         if (ImGui::InputText(("##iq_exporter_host_" + _this->name).c_str(), _this->hostname, sizeof(_this->hostname))) { | ||||
|         if (SmGui::InputText(("##network_source_host_" + _this->name).c_str(), _this->hostname, sizeof(_this->hostname))) { | ||||
|             config.acquire(); | ||||
|             config.conf[_this->name]["host"] = _this->hostname; | ||||
|             config.release(true); | ||||
|         } | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::InputInt(("##iq_exporter_port_" + _this->name).c_str(), &_this->port, 0, 0)) { | ||||
|         SmGui::SameLine(); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::InputInt(("##network_source_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 | ||||
|         ImGui::LeftLabel("Protocol"); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::Combo(("##iq_exporter_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) { | ||||
|         SmGui::LeftLabel("Protocol"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(("##network_source_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); | ||||
| @@ -264,15 +238,38 @@ private: | ||||
|         } | ||||
|  | ||||
|         // Sample type selector | ||||
|         ImGui::LeftLabel("Sample type"); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::Combo(("##iq_exporter_samp_" + _this->name).c_str(), &_this->sampTypeId, _this->sampleTypes.txt)) { | ||||
|         SmGui::LeftLabel("Sample type"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(("##network_source_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(); } | ||||
|     } | ||||
|  | ||||
| @@ -280,14 +277,17 @@ private: | ||||
|         // Compute sizes | ||||
|         int blockSize = samplerate / 200; | ||||
|         int sampleSize = SAMPLE_TYPE_SIZE[sampType]; | ||||
|         int frameSize = blockSize*sampleSize; | ||||
|  | ||||
|         // Chose amount of bytes to attempt to read | ||||
|         bool forceSize = (proto != PROTOCOL_UDP); | ||||
|         int frameSize = sampleSize * (forceSize ? blockSize : STREAM_BUFFER_SIZE); | ||||
|  | ||||
|         // Allocate receive buffer | ||||
|         uint8_t* buffer = dsp::buffer::alloc<uint8_t>(frameSize); | ||||
|  | ||||
|         while (true) { | ||||
|             // Read samples from socket | ||||
|             int bytes = sock->recv(buffer, frameSize, true); | ||||
|             int bytes = sock->recv(buffer, frameSize, forceSize); | ||||
|             if (bytes <= 0) { break; } | ||||
|  | ||||
|             // Convert to CF32 (note: problem if partial sample) | ||||
| @@ -325,7 +325,7 @@ private: | ||||
|     double freq; | ||||
|      | ||||
|     int samplerate = 1000000; | ||||
|     int srId; | ||||
|     int tempSamplerate = 1000000; | ||||
|     Protocol proto = PROTOCOL_UDP; | ||||
|     int protoId; | ||||
|     SampleType sampType = SAMPLE_TYPE_INT16; | ||||
| @@ -333,7 +333,6 @@ private: | ||||
|     char hostname[1024] = "localhost"; | ||||
|     int port = 1234; | ||||
|  | ||||
|     OptionList<int, int> samplerates; | ||||
|     OptionList<std::string, Protocol> protocols; | ||||
|     OptionList<std::string, SampleType> sampleTypes; | ||||
|  | ||||
|   | ||||
| @@ -23,6 +23,12 @@ SDRPP_MOD_INFO{ | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| const std::vector<const char*> deviceWhiteList = { | ||||
|     "PlutoSDR", | ||||
|     "ANTSDR", | ||||
|     "LibreSDR" | ||||
| }; | ||||
|  | ||||
| class PlutoSDRSourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     PlutoSDRSourceModule(std::string name) { | ||||
| @@ -130,7 +136,14 @@ private: | ||||
|             std::string duri = iio_context_info_get_uri(info); | ||||
|  | ||||
|             // If the device is not a plutosdr, don't include it | ||||
|             if (desc.find("PlutoSDR") == std::string::npos) { | ||||
|             bool isPluto = false; | ||||
|             for (const auto type : deviceWhiteList) { | ||||
|                 if (desc.find(type) != std::string::npos) { | ||||
|                     isPluto = true; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if (!isPluto) { | ||||
|                 flog::warn("Ignored IIO device: [{}] {}", duri, desc); | ||||
|                 continue; | ||||
|             } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| #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()) | ||||
|  | ||||
| @@ -15,57 +16,12 @@ SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "sdrplay_source", | ||||
|     /* Description:     */ "SDRplay source module for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Version:         */ 0, 2, 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, | ||||
| @@ -208,6 +164,11 @@ 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; | ||||
| @@ -223,6 +184,11 @@ 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; | ||||
| @@ -290,6 +256,30 @@ 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]; | ||||
| @@ -297,7 +287,7 @@ public: | ||||
|         if (openDev.hwVer == SDRPLAY_RSP1_ID) { | ||||
|             lnaSteps = 4; | ||||
|         } | ||||
|         else if (openDev.hwVer == SDRPLAY_RSP1A_ID) { | ||||
|         else if (openDev.hwVer == SDRPLAY_RSP1A_ID || openDev.hwVer == SDRPLAY_RSP1B_ID) { | ||||
|             lnaSteps = 10; | ||||
|         } | ||||
|         else if (openDev.hwVer == SDRPLAY_RSP2_ID) { | ||||
| @@ -306,78 +296,54 @@ public: | ||||
|         else if (openDev.hwVer == SDRPLAY_RSPduo_ID) { | ||||
|             lnaSteps = 10; | ||||
|         } | ||||
|         else if (openDev.hwVer == SDRPLAY_RSPdx_ID) { | ||||
|         else if (openDev.hwVer == SDRPLAY_RSPdx_ID || openDev.hwVer == SDRPLAY_RSPdxR2_ID) { | ||||
|             lnaSteps = 28; | ||||
|         } | ||||
|  | ||||
|         bool created = false; | ||||
|         // 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; | ||||
|  | ||||
|         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; | ||||
|  | ||||
|             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("samplerate")) { | ||||
|             int sr = config.conf["devices"][selectedName]["samplerate"]; | ||||
|             if (samplerates.keyExists(sr)) { | ||||
|                 srId = samplerates.keyId(sr); | ||||
|                 sampleRate = samplerates[srId]; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         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"]; | ||||
|         } | ||||
| @@ -388,11 +354,6 @@ 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")) { | ||||
| @@ -415,7 +376,7 @@ public: | ||||
|         if (openDev.hwVer == SDRPLAY_RSP1_ID) { | ||||
|             // No config to load | ||||
|         } | ||||
|         else if (openDev.hwVer == SDRPLAY_RSP1A_ID) { | ||||
|         else if (openDev.hwVer == SDRPLAY_RSP1A_ID || openDev.hwVer == SDRPLAY_RSP1B_ID) { | ||||
|             if (config.conf["devices"][selectedName].contains("fmmwNotch")) { | ||||
|                 rsp1a_fmmwNotch = config.conf["devices"][selectedName]["fmmwNotch"]; | ||||
|             } | ||||
| @@ -451,7 +412,7 @@ public: | ||||
|                 rspduo_biasT = config.conf["devices"][selectedName]["biast"]; | ||||
|             } | ||||
|         } | ||||
|         else if (openDev.hwVer == SDRPLAY_RSPdx_ID) { | ||||
|         else if (openDev.hwVer == SDRPLAY_RSPdx_ID || openDev.hwVer == SDRPLAY_RSPdxR2_ID) { | ||||
|             if (config.conf["devices"][selectedName].contains("antenna")) { | ||||
|                 rspdx_antennaPort = config.conf["devices"][selectedName]["antenna"]; | ||||
|             } | ||||
| @@ -466,7 +427,7 @@ public: | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         config.release(created); | ||||
|         config.release(); | ||||
|  | ||||
|         if (lnaGain >= lnaSteps) { lnaGain = lnaSteps - 1; } | ||||
|  | ||||
| @@ -614,7 +575,7 @@ private: | ||||
|  | ||||
|         // General options | ||||
|         if (_this->ifModeId == 0) { | ||||
|             _this->bandwidth = (_this->bandwidthId == 8) ? preferedBandwidth[_this->srId] : bandwidths[_this->bandwidthId]; | ||||
|             _this->bandwidth = (_this->bandwidthId == 8) ? preferedBandwidth[_this->srId] : _this->bandwidths[_this->bandwidthId]; | ||||
|             _this->openDevParams->devParams->fsFreq.fsHz = _this->sampleRate; | ||||
|             _this->channelParams->tunerParams.bwType = _this->bandwidth; | ||||
|         } | ||||
| @@ -693,14 +654,14 @@ private: | ||||
|         } | ||||
|  | ||||
|         if (_this->ifModeId == 0) { | ||||
|             if (SmGui::Combo(CONCAT("##sdrplay_sr", _this->name), &_this->srId, sampleRatesTxt)) { | ||||
|                 _this->sampleRate = sampleRates[_this->srId]; | ||||
|             if (SmGui::Combo(CONCAT("##sdrplay_sr", _this->name), &_this->srId, _this->samplerates.txt)) { | ||||
|                 _this->sampleRate = _this->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->sampleRate; | ||||
|                 config.conf["devices"][_this->selectedName]["samplerate"] = _this->samplerates.key(_this->srId); | ||||
|                 config.release(true); | ||||
|             } | ||||
|  | ||||
| @@ -715,8 +676,8 @@ private: | ||||
|  | ||||
|             SmGui::LeftLabel("Bandwidth"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::Combo(CONCAT("##sdrplay_bw", _this->name), &_this->bandwidthId, bandwidthsTxt)) { | ||||
|                 _this->bandwidth = (_this->bandwidthId == 8) ? preferedBandwidth[_this->srId] : bandwidths[_this->bandwidthId]; | ||||
|             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 (_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); | ||||
| @@ -745,10 +706,28 @@ private: | ||||
|             } | ||||
|             else { | ||||
|                 config.acquire(); | ||||
|                 _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]; | ||||
|                 // 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]; | ||||
|             } | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             config.acquire(); | ||||
| @@ -854,6 +833,7 @@ private: | ||||
|                 _this->RSP1Menu(); | ||||
|                 break; | ||||
|             case SDRPLAY_RSP1A_ID: | ||||
|             case SDRPLAY_RSP1B_ID: | ||||
|                 _this->RSP1AMenu(); | ||||
|                 break; | ||||
|             case SDRPLAY_RSP2_ID: | ||||
| @@ -863,6 +843,7 @@ private: | ||||
|                 _this->RSPduoMenu(); | ||||
|                 break; | ||||
|             case SDRPLAY_RSPdx_ID: | ||||
|             case SDRPLAY_RSPdxR2_ID: | ||||
|                 _this->RSPdxMenu(); | ||||
|                 break; | ||||
|             default: | ||||
| @@ -1146,7 +1127,7 @@ private: | ||||
|     sdrplay_api_RxChannelParamsT* channelParams; | ||||
|  | ||||
|     sdrplay_api_Bw_MHzT bandwidth; | ||||
|     int bandwidthId = 0; | ||||
|     int bandwidthId = 8; // Auto | ||||
|  | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
| @@ -1201,6 +1182,9 @@ 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