diff --git a/core/src/gui/widgets/image.cpp b/core/src/gui/widgets/image.cpp new file mode 100644 index 00000000..a1f8cbff --- /dev/null +++ b/core/src/gui/widgets/image.cpp @@ -0,0 +1,66 @@ +#include + +namespace ImGui { + ImageDisplay::ImageDisplay(int width, int height, GLenum format) { + _width = width; + _height = height; + _format = format; + buffer = malloc(_width * _height * 4); + activeBuffer = malloc(_width * _height * 4); + memset(buffer, 0, _width * _height * 4); + memset(activeBuffer, 0, _width * _height * 4); + + glGenTextures(1, &textureId); + } + + ImageDisplay::~ImageDisplay() { + free(buffer); + free(activeBuffer); + } + + void ImageDisplay::draw(const ImVec2& size_arg) { + std::lock_guard lck(bufferMtx); + + ImGuiWindow* window = GetCurrentWindow(); + ImGuiStyle& style = GetStyle(); + float pad = style.FramePadding.y; + ImVec2 min = window->DC.CursorPos; + + // Calculate scale + float width = CalcItemWidth(); + float height = roundf((width / (float)_width) * (float)_height); + + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), height); + ImRect bb(min, ImVec2(min.x+size.x, min.y+size.y)); + float lineHeight = size.y; + + ItemSize(size, style.FramePadding.y); + if (!ItemAdd(bb, 0)) { + return; + } + + if (newData) { + newData = false; + updateTexture(); + } + + window->DrawList->AddImage((void*)(intptr_t)textureId, min, ImVec2(min.x + width, min.y + height)); + } + + void ImageDisplay::swap() { + std::lock_guard lck(bufferMtx); + void* tmp = activeBuffer; + activeBuffer = buffer; + buffer = tmp; + newData = true; + memset(buffer, 0, _width * _height * 4); + } + + void ImageDisplay::updateTexture() { + glBindTexture(GL_TEXTURE_2D, textureId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, activeBuffer); + } +} \ No newline at end of file diff --git a/core/src/gui/widgets/image.h b/core/src/gui/widgets/image.h new file mode 100644 index 00000000..eb31d6db --- /dev/null +++ b/core/src/gui/widgets/image.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace ImGui { + class ImageDisplay { + public: + ImageDisplay(int width, int height, GLenum format); + ~ImageDisplay(); + void draw(const ImVec2& size_arg = ImVec2(0, 0)); + void swap(); + + void* buffer; + + private: + void updateTexture(); + + std::mutex bufferMtx; + void* activeBuffer; + + int _width; + int _height; + GLenum _format; + + GLuint textureId; + + bool newData = false; + + }; +} \ No newline at end of file diff --git a/core/src/utils/optionlist.h b/core/src/utils/optionlist.h new file mode 100644 index 00000000..18d47935 --- /dev/null +++ b/core/src/utils/optionlist.h @@ -0,0 +1,119 @@ +#pragma once +#include +#include +#include + +template +class OptionList { +public: + void define(K key, std::string name, T value) { + if (keyExists(key)) { throw std::runtime_error("Key already exists"); } + if (nameExists(name)) { throw std::runtime_error("Name already exists"); } + if (valueExists(value)) { throw std::runtime_error("Value already exists"); } + keys.push_back(key); + names.push_back(name); + values.push_back(value); + updateText(); + } + + void define(std::string name, T value) { + define(name, name, value); + } + + void undefined(int id) { + keys.erase(id); + names.erase(id); + values.erase(id); + updateText(); + } + + void undefineKey(K key) { + undefined(keyId(key)); + } + + void undefineName(std::string name) { + undefined(nameId(name)); + } + + void undefineValue(T value) { + undefined(valueId(value)); + } + + void clear() { + keys.clear(); + names.clear(); + values.clear(); + updateText(); + } + + int size() { + return keys.size(); + } + + bool keyExists(K key) { + if (std::find(keys.begin(), keys.end(), key) != keys.end()) { return true; } + return false; + } + + bool nameExists(std::string name) { + if (std::find(names.begin(), names.end(), name) != names.end()) { return true; } + return false; + } + + bool valueExists(T value) { + if (std::find(values.begin(), values.end(), value) != values.end()) { return true; } + return false; + } + + int keyId(K key) { + auto it = std::find(keys.begin(), keys.end(), key); + if (it == keys.end()) { throw std::runtime_error("Key doesn't exists"); } + return std::distance(keys.begin(), it); + } + + int nameId(std::string name) { + auto it = std::find(names.begin(), names.end(), name); + if (it == names.end()) { throw std::runtime_error("Name doesn't exists"); } + return std::distance(names.begin(), it); + } + + int valueId(T value) { + auto it = std::find(values.begin(), values.end(), value); + if (it == values.end()) { throw std::runtime_error("Value doesn't exists"); } + return std::distance(values.begin(), it); + } + + K key(int id) { + return keys[id]; + } + + std::string name(int id) { + return names[id]; + } + + T value(int id) { + return values[id]; + } + + T operator [](int& id) { + return value(id); + } + + const char* txt = NULL; + +private: + void updateText() { + _txt.clear(); + for (auto& name : names) { + _txt += name; + _txt += '\0'; + } + txt = _txt.c_str(); + } + + std::vector keys; + std::vector names; + std::vector values; + std::string _txt; + +}; \ No newline at end of file diff --git a/decoder_modules/radio/src/demod.h b/decoder_modules/radio/src/demod.h index 18d51c85..64f490d0 100644 --- a/decoder_modules/radio/src/demod.h +++ b/decoder_modules/radio/src/demod.h @@ -6,6 +6,7 @@ #include enum DeemphasisMode { + DEEMP_MODE_22US, DEEMP_MODE_50US, DEEMP_MODE_75US, DEEMP_MODE_NONE, diff --git a/decoder_modules/radio/src/radio_module.h b/decoder_modules/radio/src/radio_module.h index 41b7c9cc..6c253e20 100644 --- a/decoder_modules/radio/src/radio_module.h +++ b/decoder_modules/radio/src/radio_module.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "radio_interface.h" #include "demod.h" @@ -15,18 +16,23 @@ ConfigManager config; #define CONCAT(a, b) ((std::string(a) + b).c_str()) -const double DeemphasisModes[] { - 50e-6, - 75e-6 +std::map deempTaus = { + {DEEMP_MODE_22US, 22e-6}, + {DEEMP_MODE_50US, 50e-6}, + {DEEMP_MODE_75US, 75e-6} }; -const char* DeemhasisModesTxt = "50µs\00075µs\000None\000"; - class RadioModule : public ModuleManager::Instance { public: RadioModule(std::string name) { this->name = name; + // Intialize option lists + deempModes.define("None", DEEMP_MODE_NONE); + deempModes.define("22us", DEEMP_MODE_22US); + deempModes.define("50us", DEEMP_MODE_50US); + deempModes.define("75us", DEEMP_MODE_75US); + // Initialize the config if it doesn't exist bool created = false; config.acquire(); @@ -243,8 +249,8 @@ private: if (_this->deempAllowed) { ImGui::LeftLabel("De-emphasis"); ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); - if (ImGui::Combo(("##_radio_wfm_deemp_" + _this->name).c_str(), &_this->deempMode, DeemhasisModesTxt)) { - _this->setDeemphasisMode(_this->deempMode); + if (ImGui::Combo(("##_radio_wfm_deemp_" + _this->name).c_str(), &_this->deempId, _this->deempModes.txt)) { + _this->setDeemphasisMode(_this->deempModes[_this->deempId]); } } @@ -336,7 +342,7 @@ private: snapInterval = selectedDemod->getDefaultSnapInterval(); squelchLevel = MIN_SQUELCH; deempAllowed = selectedDemod->getDeempAllowed(); - deempMode = selectedDemod->getDefaultDeemphasisMode(); + deempId = deempModes.valueId((DeemphasisMode)selectedDemod->getDefaultDeemphasisMode()); squelchEnabled = false; postProcEnabled = selectedDemod->getPostProcEnabled(); FMIFNRAllowed = selectedDemod->getFMIFNRAllowed(); @@ -359,7 +365,15 @@ private: squelchEnabled = config.conf[name][selectedDemod->getName()]["squelchEnabled"]; } if (config.conf[name][selectedDemod->getName()].contains("deempMode")) { - deempMode = config.conf[name][selectedDemod->getName()]["deempMode"]; + // Upgrade to the text key + if (!config.conf[name][selectedDemod->getName()]["deempMode"].is_string()) { + config.conf[name][selectedDemod->getName()]["deempMode"] = deempModes.key(deempId); + } + + std::string deempOpt = config.conf[name][selectedDemod->getName()]["deempMode"]; + if (deempModes.keyExists(deempOpt)) { + deempId = deempModes.keyId(deempOpt); + } } if (config.conf[name][selectedDemod->getName()].contains("FMIFNREnabled")) { FMIFNREnabled = config.conf[name][selectedDemod->getName()]["FMIFNREnabled"]; @@ -374,7 +388,6 @@ private: nbLevel = config.conf[name][selectedDemod->getName()]["noiseBlankerLevel"]; } config.release(); - deempMode = std::clamp(deempMode, 0, _DEEMP_MODE_COUNT-1); // Configure VFO if (vfo) { @@ -411,7 +424,7 @@ private: afChain.enable(&resamp); // Configure deemphasis - setDeemphasisMode(deempMode); + setDeemphasisMode(deempModes[deempId]); } else { // Disable everyting if post processing is disabled @@ -475,18 +488,16 @@ private: afChain.start(); } - void setDeemphasisMode(int mode) { - deempMode = std::clamp(mode, 0, _DEEMP_MODE_COUNT-1); + void setDeemphasisMode(DeemphasisMode mode) { + deempId = deempModes.valueId(mode); if (!postProcEnabled || !selectedDemod) { return; } - bool deempEnabled = (deempMode != DEEMP_MODE_NONE); - if (deempEnabled) { - deemp.block.setTau(DeemphasisModes[deempMode]); - } + bool deempEnabled = (mode != DEEMP_MODE_NONE); + if (deempEnabled) { deemp.block.setTau(deempTaus[mode]); } afChain.setState(&deemp, deempEnabled); // Save config config.acquire(); - config.conf[name][selectedDemod->getName()]["deempMode"] = deempMode; + config.conf[name][selectedDemod->getName()]["deempMode"] = deempModes.key(deempId); config.release(true); } @@ -643,6 +654,8 @@ private: std::array demods; demod::Demodulator* selectedDemod = NULL; + OptionList deempModes; + double audioSampleRate = 48000.0; float minBandwidth; float maxBandwidth; @@ -655,7 +668,7 @@ private: bool squelchEnabled = false; float squelchLevel; - int deempMode = DEEMP_MODE_NONE; + int deempId = 0; bool deempAllowed; bool FMIFNRAllowed;