mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-10-31 00:48:11 +01:00 
			
		
		
		
	Compare commits
	
		
			42 Commits
		
	
	
		
			fobos_test
			...
			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" | ||||
| @@ -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