mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-06-28 21:37:50 +02:00
Fixed issues with new module system
This commit is contained in:
122
core/src/gui/bandplan.cpp
Normal file
122
core/src/gui/bandplan.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
#include <gui/bandplan.h>
|
||||
|
||||
namespace bandplan {
|
||||
std::map<std::string, BandPlan_t> bandplans;
|
||||
std::vector<std::string> bandplanNames;
|
||||
std::string bandplanNameTxt;
|
||||
std::map<std::string, BandPlanColor_t> colorTable;
|
||||
|
||||
void generateTxt() {
|
||||
bandplanNameTxt = "";
|
||||
for (int i = 0; i < bandplanNames.size(); i++) {
|
||||
bandplanNameTxt += bandplanNames[i];
|
||||
bandplanNameTxt += '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(json& j, const Band_t& b) {
|
||||
j = json{
|
||||
{"name", b.name},
|
||||
{"type", b.type},
|
||||
{"start", b.start},
|
||||
{"end", b.end},
|
||||
};
|
||||
}
|
||||
|
||||
void from_json(const json& j, Band_t& b) {
|
||||
j.at("name").get_to(b.name);
|
||||
j.at("type").get_to(b.type);
|
||||
j.at("start").get_to(b.start);
|
||||
j.at("end").get_to(b.end);
|
||||
}
|
||||
|
||||
void to_json(json& j, const BandPlan_t& b) {
|
||||
j = json{
|
||||
{"name", b.name},
|
||||
{"country_name", b.countryName},
|
||||
{"country_code", b.countryCode},
|
||||
{"author_name", b.authorName},
|
||||
{"author_url", b.authorURL},
|
||||
{"bands", b.bands}
|
||||
};
|
||||
}
|
||||
|
||||
void from_json(const json& j, BandPlan_t& b) {
|
||||
j.at("name").get_to(b.name);
|
||||
j.at("country_name").get_to(b.countryName);
|
||||
j.at("country_code").get_to(b.countryCode);
|
||||
j.at("author_name").get_to(b.authorName);
|
||||
j.at("author_url").get_to(b.authorURL);
|
||||
j.at("bands").get_to(b.bands);
|
||||
}
|
||||
|
||||
void to_json(json& j, const BandPlanColor_t& ct) {
|
||||
spdlog::error("ImGui color to JSON not implemented!!!");
|
||||
}
|
||||
|
||||
void from_json(const json& j, BandPlanColor_t& ct) {
|
||||
std::string col = j.get<std::string>();
|
||||
if (col[0] != '#' || !std::all_of(col.begin() + 1, col.end(), ::isxdigit)) {
|
||||
return;
|
||||
}
|
||||
uint8_t r, g, b, a;
|
||||
r = std::stoi(col.substr(1, 2), NULL, 16);
|
||||
g = std::stoi(col.substr(3, 2), NULL, 16);
|
||||
b = std::stoi(col.substr(5, 2), NULL, 16);
|
||||
a = std::stoi(col.substr(7, 2), NULL, 16);
|
||||
ct.colorValue = IM_COL32(r, g, b, a);
|
||||
ct.transColorValue = IM_COL32(r, g, b, 100);
|
||||
}
|
||||
|
||||
void loadBandPlan(std::string path) {
|
||||
std::ifstream file(path.c_str());
|
||||
json data;
|
||||
data << file;
|
||||
file.close();
|
||||
|
||||
BandPlan_t plan = data.get<BandPlan_t>();
|
||||
if (bandplans.find(plan.name) != bandplans.end()) {
|
||||
spdlog::error("Duplicate band plan name ({0}), not loading.", plan.name);
|
||||
return;
|
||||
}
|
||||
bandplans[plan.name] = plan;
|
||||
bandplanNames.push_back(plan.name);
|
||||
generateTxt();
|
||||
}
|
||||
|
||||
void loadFromDir(std::string path) {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
spdlog::error("Band Plan directory does not exist");
|
||||
return;
|
||||
}
|
||||
if (!std::filesystem::is_directory(path)) {
|
||||
spdlog::error("Band Plan directory isn't a directory...");
|
||||
return;
|
||||
}
|
||||
bandplans.clear();
|
||||
for (const auto & file : std::filesystem::directory_iterator(path)) {
|
||||
std::string path = file.path().generic_string();
|
||||
if (file.path().extension().generic_string() != ".json") {
|
||||
continue;
|
||||
}
|
||||
loadBandPlan(path);
|
||||
}
|
||||
}
|
||||
|
||||
void loadColorTable(std::string path) {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
spdlog::error("Band Plan Color Table file does not exist");
|
||||
return;
|
||||
}
|
||||
if (!std::filesystem::is_regular_file(path)) {
|
||||
spdlog::error("Band Plan Color Table file isn't a file...");
|
||||
return;
|
||||
}
|
||||
std::ifstream file(path.c_str());
|
||||
json data;
|
||||
data << file;
|
||||
file.close();
|
||||
|
||||
colorTable = data.get<std::map<std::string, BandPlanColor_t>>();
|
||||
}
|
||||
};
|
51
core/src/gui/bandplan.h
Normal file
51
core/src/gui/bandplan.h
Normal file
@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
#include <json.hpp>
|
||||
#include <fstream>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
namespace bandplan {
|
||||
struct Band_t {
|
||||
std::string name;
|
||||
std::string type;
|
||||
float start;
|
||||
float end;
|
||||
};
|
||||
|
||||
void to_json(json& j, const Band_t& b);
|
||||
void from_json(const json& j, Band_t& b);
|
||||
|
||||
struct BandPlan_t {
|
||||
std::string name;
|
||||
std::string countryName;
|
||||
std::string countryCode;
|
||||
std::string authorName;
|
||||
std::string authorURL;
|
||||
std::vector<Band_t> bands;
|
||||
};
|
||||
|
||||
void to_json(json& j, const BandPlan_t& b);
|
||||
void from_json(const json& j, BandPlan_t& b);
|
||||
|
||||
struct BandPlanColor_t {
|
||||
uint32_t colorValue;
|
||||
uint32_t transColorValue;
|
||||
};
|
||||
|
||||
void to_json(json& j, const BandPlanColor_t& ct);
|
||||
void from_json(const json& j, BandPlanColor_t& ct);
|
||||
|
||||
void loadBandPlan(std::string path);
|
||||
void loadFromDir(std::string path);
|
||||
void loadColorTable(std::string path);
|
||||
|
||||
extern std::map<std::string, BandPlan_t> bandplans;
|
||||
extern std::vector<std::string> bandplanNames;
|
||||
extern std::string bandplanNameTxt;
|
||||
extern std::map<std::string, BandPlanColor_t> colorTable;
|
||||
};
|
164
core/src/gui/frequency_select.cpp
Normal file
164
core/src/gui/frequency_select.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
#include <gui/frequency_select.h>
|
||||
|
||||
bool isInArea(ImVec2 val, ImVec2 min, ImVec2 max) {
|
||||
return val.x >= min.x && val.x < max.x && val.y >= min.y && val.y < max.y;
|
||||
}
|
||||
|
||||
FrequencySelect::FrequencySelect() {
|
||||
|
||||
}
|
||||
|
||||
void FrequencySelect::init() {
|
||||
font = ImGui::GetIO().Fonts->AddFontFromFileTTF((config::getRootDirectory() + "/res/fonts/Roboto-Medium.ttf").c_str(), 42.0f);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
digits[i] = 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void FrequencySelect::onPosChange() {
|
||||
int digitHeight = ImGui::CalcTextSize("0").y;
|
||||
int commaOffset = 0;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
digitTopMins[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y);
|
||||
digitBottomMins[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y + (digitHeight / 2));
|
||||
|
||||
digitTopMaxs[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset + 22, widgetPos.y + (digitHeight / 2));
|
||||
digitBottomMaxs[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset + 22, widgetPos.y + digitHeight);
|
||||
|
||||
if ((i + 1) % 3 == 0 && i < 11) {
|
||||
commaOffset += 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FrequencySelect::onResize() {
|
||||
|
||||
}
|
||||
|
||||
void FrequencySelect::incrementDigit(int i) {
|
||||
if (i < 0) {
|
||||
return;
|
||||
}
|
||||
if (digits[i] < 9) {
|
||||
digits[i]++;
|
||||
}
|
||||
else {
|
||||
digits[i] = 0;
|
||||
incrementDigit(i - 1);
|
||||
}
|
||||
frequencyChanged = true;
|
||||
}
|
||||
|
||||
void FrequencySelect::decrementDigit(int i) {
|
||||
if (i < 0) {
|
||||
return;
|
||||
}
|
||||
if (digits[i] > 0) {
|
||||
digits[i]--;
|
||||
}
|
||||
else {
|
||||
digits[i] = 9;
|
||||
decrementDigit(i - 1);
|
||||
}
|
||||
frequencyChanged = true;
|
||||
}
|
||||
|
||||
void FrequencySelect::draw() {
|
||||
window = ImGui::GetCurrentWindow();
|
||||
widgetPos = ImGui::GetWindowContentRegionMin();
|
||||
widgetEndPos = ImGui::GetWindowContentRegionMax();
|
||||
ImVec2 cursorPos = ImGui::GetCursorPos();
|
||||
widgetPos.x += window->Pos.x + cursorPos.x;
|
||||
widgetPos.y += window->Pos.y - 3;
|
||||
widgetEndPos.x += window->Pos.x + cursorPos.x;
|
||||
widgetEndPos.y += window->Pos.y - 3;
|
||||
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
|
||||
|
||||
ImGui::PushFont(font);
|
||||
|
||||
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
|
||||
lastWidgetPos = widgetPos;
|
||||
onPosChange();
|
||||
}
|
||||
if (widgetSize.x != lastWidgetSize.x || widgetSize.y != lastWidgetSize.y) {
|
||||
lastWidgetSize = widgetSize;
|
||||
onResize();
|
||||
}
|
||||
|
||||
ImU32 disabledColor = ImGui::GetColorU32(ImGuiCol_Text, 0.3f);
|
||||
ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
|
||||
int commaOffset = 0;
|
||||
bool zeros = true;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
if (digits[i] != 0) {
|
||||
zeros = false;
|
||||
}
|
||||
sprintf(buf, "%d", digits[i]);
|
||||
window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y),
|
||||
zeros ? disabledColor : textColor, buf);
|
||||
if ((i + 1) % 3 == 0 && i < 11) {
|
||||
commaOffset += 12;
|
||||
window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset + 10, widgetPos.y),
|
||||
zeros ? disabledColor : textColor, ".");
|
||||
}
|
||||
}
|
||||
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
|
||||
int mw = ImGui::GetIO().MouseWheel;
|
||||
bool onDigit = false;
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
onDigit = false;
|
||||
if (isInArea(mousePos, digitTopMins[i], digitTopMaxs[i])) {
|
||||
window->DrawList->AddRectFilled(digitTopMins[i], digitTopMaxs[i], IM_COL32(255, 0, 0, 75));
|
||||
if (leftClick) {
|
||||
incrementDigit(i);
|
||||
}
|
||||
onDigit = true;
|
||||
}
|
||||
if (isInArea(mousePos, digitBottomMins[i], digitBottomMaxs[i])) {
|
||||
window->DrawList->AddRectFilled(digitBottomMins[i], digitBottomMaxs[i], IM_COL32(0, 0, 255, 75));
|
||||
if (leftClick) {
|
||||
decrementDigit(i);
|
||||
}
|
||||
onDigit = true;
|
||||
}
|
||||
if (onDigit) {
|
||||
if (rightClick) {
|
||||
for (int j = i; j < 12; j++) {
|
||||
digits[j] = 0;
|
||||
}
|
||||
frequencyChanged = true;
|
||||
}
|
||||
if (mw != 0) {
|
||||
int count = abs(mw);
|
||||
for (int j = 0; j < count; j++) {
|
||||
mw > 0 ? incrementDigit(i) : decrementDigit(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t freq = 0;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
freq += digits[i] * pow(10, 11 - i);
|
||||
}
|
||||
frequency = freq;
|
||||
|
||||
ImGui::PopFont();
|
||||
ImGui::NewLine();
|
||||
}
|
||||
|
||||
void FrequencySelect::setFrequency(uint64_t freq) {
|
||||
int i = 11;
|
||||
for (uint64_t f = freq; i >= 0; i--) {
|
||||
digits[i] = f % 10;
|
||||
f -= digits[i];
|
||||
f /= 10;
|
||||
}
|
||||
frequency = freq;
|
||||
}
|
40
core/src/gui/frequency_select.h
Normal file
40
core/src/gui/frequency_select.h
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <stdint.h>
|
||||
#include <config.h>
|
||||
|
||||
class FrequencySelect {
|
||||
public:
|
||||
FrequencySelect();
|
||||
void init();
|
||||
void draw();
|
||||
void setFrequency(uint64_t freq);
|
||||
|
||||
uint64_t frequency;
|
||||
bool frequencyChanged = false;
|
||||
|
||||
private:
|
||||
void onPosChange();
|
||||
void onResize();
|
||||
void incrementDigit(int i);
|
||||
void decrementDigit(int i);
|
||||
|
||||
ImVec2 widgetPos;
|
||||
ImVec2 widgetEndPos;
|
||||
ImVec2 widgetSize;
|
||||
|
||||
ImVec2 lastWidgetPos;
|
||||
ImVec2 lastWidgetSize;
|
||||
|
||||
ImGuiWindow* window;
|
||||
ImFont* font;
|
||||
|
||||
int digits[12];
|
||||
ImVec2 digitBottomMins[12];
|
||||
ImVec2 digitTopMins[12];
|
||||
ImVec2 digitBottomMaxs[12];
|
||||
ImVec2 digitTopMaxs[12];
|
||||
|
||||
char buf[100];
|
||||
};
|
6
core/src/gui/gui.cpp
Normal file
6
core/src/gui/gui.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include <gui/gui.h>
|
||||
|
||||
namespace gui {
|
||||
ImGui::WaterFall waterfall;
|
||||
FrequencySelect freqSelect;
|
||||
};
|
9
core/src/gui/gui.h
Normal file
9
core/src/gui/gui.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <gui/waterfall.h>
|
||||
#include <gui/frequency_select.h>
|
||||
#include <module.h>
|
||||
|
||||
namespace gui {
|
||||
SDRPP_EXPORT ImGui::WaterFall waterfall;
|
||||
SDRPP_EXPORT FrequencySelect freqSelect;
|
||||
};
|
32
core/src/gui/icons.cpp
Normal file
32
core/src/gui/icons.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
#include <gui/icons.h>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <imgui/stb_image.h>
|
||||
|
||||
namespace icons {
|
||||
ImTextureID LOGO;
|
||||
ImTextureID PLAY;
|
||||
ImTextureID STOP;
|
||||
ImTextureID MENU;
|
||||
|
||||
GLuint loadTexture(std::string path) {
|
||||
int w,h,n;
|
||||
stbi_uc* data = stbi_load(path.c_str(), &w, &h, &n, NULL);
|
||||
GLuint texId;
|
||||
glGenTextures(1, &texId);
|
||||
glBindTexture(GL_TEXTURE_2D, texId);
|
||||
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, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)data);
|
||||
stbi_image_free(data);
|
||||
return texId;
|
||||
}
|
||||
|
||||
void load() {
|
||||
LOGO = (ImTextureID)loadTexture(config::getRootDirectory() + "/res/icons/sdrpp.png");
|
||||
PLAY = (ImTextureID)loadTexture(config::getRootDirectory() + "/res/icons/play.png");
|
||||
STOP = (ImTextureID)loadTexture(config::getRootDirectory() + "/res/icons/stop.png");
|
||||
MENU = (ImTextureID)loadTexture(config::getRootDirectory() + "/res/icons/menu.png");
|
||||
}
|
||||
}
|
15
core/src/gui/icons.h
Normal file
15
core/src/gui/icons.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <imgui/imgui.h>
|
||||
#include <stdint.h>
|
||||
#include <GL/glew.h>
|
||||
#include <config.h>
|
||||
|
||||
namespace icons {
|
||||
extern ImTextureID LOGO;
|
||||
extern ImTextureID PLAY;
|
||||
extern ImTextureID STOP;
|
||||
extern ImTextureID MENU;
|
||||
|
||||
GLuint loadTexture(char* path);
|
||||
void load();
|
||||
}
|
869
core/src/gui/main_window.cpp
Normal file
869
core/src/gui/main_window.cpp
Normal file
@ -0,0 +1,869 @@
|
||||
#include <gui/main_window.h>
|
||||
#include <gui/gui.h>
|
||||
|
||||
std::thread worker;
|
||||
std::mutex fft_mtx;
|
||||
fftwf_complex *fft_in, *fft_out;
|
||||
fftwf_plan p;
|
||||
float* tempData;
|
||||
float* uiGains;
|
||||
char buf[1024];
|
||||
ImFont* bigFont;
|
||||
|
||||
int fftSize = 8192 * 8;
|
||||
|
||||
io::SoapyWrapper soapy;
|
||||
std::vector<float> _data;
|
||||
std::vector<float> fftTaps;
|
||||
void fftHandler(dsp::complex_t* samples) {
|
||||
fftwf_execute(p);
|
||||
int half = fftSize / 2;
|
||||
|
||||
for (int i = 0; i < half; i++) {
|
||||
_data.push_back(log10(std::abs(std::complex<float>(fft_out[half + i][0], fft_out[half + i][1])) / (float)fftSize) * 10.0f);
|
||||
}
|
||||
for (int i = 0; i < half; i++) {
|
||||
_data.push_back(log10(std::abs(std::complex<float>(fft_out[i][0], fft_out[i][1])) / (float)fftSize) * 10.0f);
|
||||
}
|
||||
|
||||
for (int i = 5; i < fftSize; i++) {
|
||||
_data[i] = (_data[i - 4] + _data[i - 3] + _data[i - 2] + _data[i - 1] + _data[i]) / 5.0f;
|
||||
}
|
||||
|
||||
gui::waterfall.pushFFT(_data, fftSize);
|
||||
_data.clear();
|
||||
}
|
||||
|
||||
dsp::NullSink sink;
|
||||
int devId = 0;
|
||||
int srId = 0;
|
||||
watcher<int> bandplanId(0, true);
|
||||
watcher<uint64_t> freq((uint64_t)90500000);
|
||||
int demod = 1;
|
||||
watcher<float> vfoFreq(92000000.0f);
|
||||
float dummyVolume = 1.0f;
|
||||
float* volume = &dummyVolume;
|
||||
float fftMin = -70.0f;
|
||||
float fftMax = 0.0f;
|
||||
watcher<float> offset(0.0f, true);
|
||||
watcher<float> bw(8000000.0f, true);
|
||||
int sampleRate = 8000000;
|
||||
bool playing = false;
|
||||
watcher<bool> dcbias(false, false);
|
||||
watcher<bool> bandPlanEnabled(true, false);
|
||||
bool showCredits = false;
|
||||
std::string audioStreamName = "";
|
||||
std::string sourceName = "";
|
||||
int menuWidth = 300;
|
||||
bool grabbingMenu = false;
|
||||
int newWidth = 300;
|
||||
bool showWaterfall = true;
|
||||
int fftHeight = 300;
|
||||
bool showMenu = true;
|
||||
|
||||
void saveCurrentSource() {
|
||||
int i = 0;
|
||||
for (std::string gainName : soapy.gainList) {
|
||||
config::config["sourceSettings"][sourceName]["gains"][gainName] = uiGains[i];
|
||||
i++;
|
||||
}
|
||||
config::config["sourceSettings"][sourceName]["sampleRate"] = soapy.sampleRates[srId];
|
||||
}
|
||||
|
||||
void loadSourceConfig(std::string name) {
|
||||
json sourceSettings = config::config["sourceSettings"][name];
|
||||
|
||||
sampleRate = sourceSettings["sampleRate"];
|
||||
|
||||
auto _srIt = std::find(soapy.sampleRates.begin(), soapy.sampleRates.end(), sampleRate);
|
||||
// If the sample rate isn't valid, set to minimum
|
||||
if (_srIt == soapy.sampleRates.end()) {
|
||||
srId = 0;
|
||||
sampleRate = soapy.sampleRates[0];
|
||||
}
|
||||
else {
|
||||
srId = std::distance(soapy.sampleRates.begin(), _srIt);
|
||||
}
|
||||
sigpath::signalPath.setSampleRate(sampleRate);
|
||||
soapy.setSampleRate(sampleRate);
|
||||
|
||||
// Set gains
|
||||
delete[] uiGains;
|
||||
uiGains = new float[soapy.gainList.size()];
|
||||
int i = 0;
|
||||
for (std::string gainName : soapy.gainList) {
|
||||
// If gain doesn't exist in config, set it to the minimum value
|
||||
if (!sourceSettings["gains"].contains(gainName)) {
|
||||
uiGains[i] = soapy.gainRanges[i].minimum();
|
||||
soapy.setGain(i, uiGains[i]);
|
||||
continue;
|
||||
}
|
||||
uiGains[i] = sourceSettings["gains"][gainName];
|
||||
soapy.setGain(i, uiGains[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
// Update GUI
|
||||
gui::waterfall.setBandwidth(sampleRate);
|
||||
gui::waterfall.setViewBandwidth(sampleRate);
|
||||
bw.val = sampleRate;
|
||||
}
|
||||
|
||||
void loadAudioConfig(std::string name) {
|
||||
json audioSettings = config::config["audio"][name];
|
||||
std::string devName = audioSettings["device"];
|
||||
auto _devIt = std::find(audio::streams[name]->audio->deviceNames.begin(), audio::streams[name]->audio->deviceNames.end(), devName);
|
||||
|
||||
// If audio device doesn't exist anymore
|
||||
if (_devIt == audio::streams[name]->audio->deviceNames.end()) {
|
||||
audio::streams[name]->audio->setToDefault();
|
||||
int deviceId = audio::streams[name]->audio->getDeviceId();
|
||||
audio::setAudioDevice(name, deviceId, audio::streams[name]->audio->devices[deviceId].sampleRates[0]);
|
||||
audio::streams[name]->sampleRateId = 0;
|
||||
audio::streams[name]->volume = audioSettings["volume"];
|
||||
audio::streams[name]->audio->setVolume(audio::streams[name]->volume);
|
||||
return;
|
||||
}
|
||||
int deviceId = std::distance(audio::streams[name]->audio->deviceNames.begin(), _devIt);
|
||||
float sr = audioSettings["sampleRate"];
|
||||
auto _srIt = std::find(audio::streams[name]->audio->devices[deviceId].sampleRates.begin(), audio::streams[name]->audio->devices[deviceId].sampleRates.end(), sr);
|
||||
|
||||
// If sample rate doesn't exist anymore
|
||||
if (_srIt == audio::streams[name]->audio->devices[deviceId].sampleRates.end()) {
|
||||
audio::streams[name]->sampleRateId = 0;
|
||||
audio::setAudioDevice(name, deviceId, audio::streams[name]->audio->devices[deviceId].sampleRates[0]);
|
||||
audio::streams[name]->volume = audioSettings["volume"];
|
||||
audio::streams[name]->audio->setVolume(audio::streams[name]->volume);
|
||||
return;
|
||||
}
|
||||
|
||||
int samplerateId = std::distance(audio::streams[name]->audio->devices[deviceId].sampleRates.begin(), _srIt);
|
||||
audio::streams[name]->sampleRateId = samplerateId;
|
||||
audio::setAudioDevice(name, deviceId, audio::streams[name]->audio->devices[deviceId].sampleRates[samplerateId]);
|
||||
audio::streams[name]->deviceId = deviceId;
|
||||
audio::streams[name]->volume = audioSettings["volume"];
|
||||
audio::streams[name]->audio->setVolume(audio::streams[name]->volume);
|
||||
}
|
||||
|
||||
void saveAudioConfig(std::string name) {
|
||||
config::config["audio"][name]["device"] = audio::streams[name]->audio->deviceNames[audio::streams[name]->deviceId];
|
||||
config::config["audio"][name]["volume"] = audio::streams[name]->volume;
|
||||
config::config["audio"][name]["sampleRate"] = audio::streams[name]->sampleRate;
|
||||
}
|
||||
|
||||
void windowInit() {
|
||||
spdlog::info("Initializing SoapySDR");
|
||||
soapy.init();
|
||||
|
||||
gui::freqSelect.init();
|
||||
|
||||
fft_in = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
|
||||
fft_out = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
|
||||
p = fftwf_plan_dft_1d(fftSize, fft_in, fft_out, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
|
||||
sigpath::signalPath.init(sampleRate, 20, fftSize, &soapy.output, (dsp::complex_t*)fft_in, fftHandler);
|
||||
sigpath::signalPath.start();
|
||||
|
||||
uiGains = new float[soapy.gainList.size()];
|
||||
|
||||
spdlog::info("Loading modules");
|
||||
mod::initAPI(&gui::waterfall);
|
||||
mod::loadFromList(config::getRootDirectory() + "/module_list.json");
|
||||
|
||||
bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF((config::getRootDirectory() + "/res/fonts/Roboto-Medium.ttf").c_str(), 128.0f);
|
||||
|
||||
// Load last source configuration
|
||||
uint64_t frequency = config::config["frequency"];
|
||||
sourceName = config::config["source"];
|
||||
auto _sourceIt = std::find(soapy.devNameList.begin(), soapy.devNameList.end(), sourceName);
|
||||
if (_sourceIt != soapy.devNameList.end() && config::config["sourceSettings"].contains(sourceName)) {
|
||||
json sourceSettings = config::config["sourceSettings"][sourceName];
|
||||
devId = std::distance(soapy.devNameList.begin(), _sourceIt);
|
||||
soapy.setDevice(soapy.devList[devId]);
|
||||
loadSourceConfig(sourceName);
|
||||
}
|
||||
else {
|
||||
int i = 0;
|
||||
bool settingsFound = false;
|
||||
for (std::string devName : soapy.devNameList) {
|
||||
if (config::config["sourceSettings"].contains(devName)) {
|
||||
sourceName = devName;
|
||||
settingsFound = true;
|
||||
devId = i;
|
||||
soapy.setDevice(soapy.devList[i]);
|
||||
loadSourceConfig(sourceName);
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (!settingsFound) {
|
||||
if (soapy.devNameList.size() > 0) {
|
||||
sourceName = soapy.devNameList[0];
|
||||
}
|
||||
sampleRate = soapy.getSampleRate();
|
||||
sigpath::signalPath.setSampleRate(sampleRate);
|
||||
}
|
||||
// Search for the first source in the list to have a config
|
||||
// If no pre-defined source, selected default device
|
||||
}
|
||||
|
||||
// Also add a loading screen
|
||||
// Adjustable "snap to grid" for each VFO
|
||||
// Finish the recorder module
|
||||
// Add squelsh
|
||||
// Bandwidth ajustment
|
||||
// CW and RAW modes;
|
||||
// Bring VFO to a visible place when changing sample rate if it's smaller
|
||||
// Add save config for modules
|
||||
// Do VFO in two steps: First sample rate conversion, then filtering
|
||||
|
||||
// And a module add/remove/change order menu
|
||||
// get rid of watchers and use if() instead
|
||||
// Switch to double for all frequecies and bandwidth
|
||||
|
||||
// Update UI settings
|
||||
fftMin = config::config["min"];
|
||||
fftMax = config::config["max"];
|
||||
gui::waterfall.setFFTMin(fftMin);
|
||||
gui::waterfall.setWaterfallMin(fftMin);
|
||||
gui::waterfall.setFFTMax(fftMax);
|
||||
gui::waterfall.setWaterfallMax(fftMax);
|
||||
|
||||
bandPlanEnabled.val = config::config["bandPlanEnabled"];
|
||||
bandPlanEnabled.markAsChanged();
|
||||
|
||||
std::string bandPlanName = config::config["bandPlan"];
|
||||
auto _bandplanIt = bandplan::bandplans.find(bandPlanName);
|
||||
if (_bandplanIt != bandplan::bandplans.end()) {
|
||||
bandplanId.val = std::distance(bandplan::bandplans.begin(), bandplan::bandplans.find(bandPlanName));
|
||||
|
||||
if (bandPlanEnabled.val) {
|
||||
gui::waterfall.bandplan = &bandplan::bandplans[bandPlanName];
|
||||
}
|
||||
else {
|
||||
gui::waterfall.bandplan = NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
bandplanId.val = 0;
|
||||
}
|
||||
bandplanId.markAsChanged();
|
||||
|
||||
|
||||
gui::freqSelect.setFrequency(frequency);
|
||||
gui::freqSelect.frequencyChanged = false;
|
||||
soapy.setFrequency(frequency);
|
||||
gui::waterfall.setCenterFrequency(frequency);
|
||||
gui::waterfall.setBandwidth(sampleRate);
|
||||
gui::waterfall.setViewBandwidth(sampleRate);
|
||||
bw.val = sampleRate;
|
||||
gui::waterfall.vfoFreqChanged = false;
|
||||
gui::waterfall.centerFreqMoved = false;
|
||||
gui::waterfall.selectFirstVFO();
|
||||
|
||||
for (auto [name, stream] : audio::streams) {
|
||||
if (config::config["audio"].contains(name)) {
|
||||
bool running = audio::streams[name]->running;
|
||||
audio::stopStream(name);
|
||||
loadAudioConfig(name);
|
||||
if (running) {
|
||||
audio::startStream(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
audioStreamName = audio::getNameFromVFO(gui::waterfall.selectedVFO);
|
||||
if (audioStreamName != "") {
|
||||
volume = &audio::streams[audioStreamName]->volume;
|
||||
}
|
||||
|
||||
menuWidth = config::config["menuWidth"];
|
||||
newWidth = menuWidth;
|
||||
|
||||
showWaterfall = config::config["showWaterfall"];
|
||||
if (!showWaterfall) {
|
||||
gui::waterfall.hideWaterfall();
|
||||
}
|
||||
|
||||
fftHeight = config::config["fftHeight"];
|
||||
gui::waterfall.setFFTHeight(fftHeight);
|
||||
}
|
||||
|
||||
void setVFO(float freq) {
|
||||
ImGui::WaterfallVFO* vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO];
|
||||
|
||||
float currentOff = vfo->centerOffset;
|
||||
float currentTune = gui::waterfall.getCenterFrequency() + vfo->generalOffset;
|
||||
float delta = freq - currentTune;
|
||||
|
||||
float newVFO = currentOff + delta;
|
||||
float vfoBW = vfo->bandwidth;
|
||||
float vfoBottom = newVFO - (vfoBW / 2.0f);
|
||||
float vfoTop = newVFO + (vfoBW / 2.0f);
|
||||
|
||||
float view = gui::waterfall.getViewOffset();
|
||||
float viewBW = gui::waterfall.getViewBandwidth();
|
||||
float viewBottom = view - (viewBW / 2.0f);
|
||||
float viewTop = view + (viewBW / 2.0f);
|
||||
|
||||
float wholeFreq = gui::waterfall.getCenterFrequency();
|
||||
float BW = gui::waterfall.getBandwidth();
|
||||
float bottom = -(BW / 2.0f);
|
||||
float top = (BW / 2.0f);
|
||||
|
||||
// VFO still fints in the view
|
||||
if (vfoBottom > viewBottom && vfoTop < viewTop) {
|
||||
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFO);
|
||||
return;
|
||||
}
|
||||
|
||||
// VFO too low for current SDR tuning
|
||||
if (vfoBottom < bottom) {
|
||||
gui::waterfall.setViewOffset((BW / 2.0f) - (viewBW / 2.0f));
|
||||
float newVFOOffset = (BW / 2.0f) - (vfoBW / 2.0f) - (viewBW / 10.0f);
|
||||
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFOOffset);
|
||||
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
|
||||
soapy.setFrequency(freq - newVFOOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
// VFO too high for current SDR tuning
|
||||
if (vfoTop > top) {
|
||||
gui::waterfall.setViewOffset((viewBW / 2.0f) - (BW / 2.0f));
|
||||
float newVFOOffset = (vfoBW / 2.0f) - (BW / 2.0f) + (viewBW / 10.0f);
|
||||
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFOOffset);
|
||||
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
|
||||
soapy.setFrequency(freq - newVFOOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
// VFO is still without the SDR's bandwidth
|
||||
if (delta < 0) {
|
||||
float newViewOff = vfoTop - (viewBW / 2.0f) + (viewBW / 10.0f);
|
||||
float newViewBottom = newViewOff - (viewBW / 2.0f);
|
||||
float newViewTop = newViewOff + (viewBW / 2.0f);
|
||||
|
||||
if (newViewBottom > bottom) {
|
||||
gui::waterfall.setViewOffset(newViewOff);
|
||||
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFO);
|
||||
return;
|
||||
}
|
||||
|
||||
gui::waterfall.setViewOffset((BW / 2.0f) - (viewBW / 2.0f));
|
||||
float newVFOOffset = (BW / 2.0f) - (vfoBW / 2.0f) - (viewBW / 10.0f);
|
||||
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFOOffset);
|
||||
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
|
||||
soapy.setFrequency(freq - newVFOOffset);
|
||||
}
|
||||
else {
|
||||
float newViewOff = vfoBottom + (viewBW / 2.0f) - (viewBW / 10.0f);
|
||||
float newViewBottom = newViewOff - (viewBW / 2.0f);
|
||||
float newViewTop = newViewOff + (viewBW / 2.0f);
|
||||
|
||||
if (newViewTop < top) {
|
||||
gui::waterfall.setViewOffset(newViewOff);
|
||||
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFO);
|
||||
return;
|
||||
}
|
||||
|
||||
gui::waterfall.setViewOffset((viewBW / 2.0f) - (BW / 2.0f));
|
||||
float newVFOOffset = (vfoBW / 2.0f) - (BW / 2.0f) + (viewBW / 10.0f);
|
||||
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFOOffset);
|
||||
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
|
||||
soapy.setFrequency(freq - newVFOOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void drawWindow() {
|
||||
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
|
||||
|
||||
ImGui::WaterfallVFO* vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO];
|
||||
|
||||
if (vfo->centerOffsetChanged) {
|
||||
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
|
||||
gui::freqSelect.frequencyChanged = false;
|
||||
config::config["frequency"] = gui::freqSelect.frequency;
|
||||
config::configModified = true;
|
||||
}
|
||||
|
||||
sigpath::vfoManager.updateFromWaterfall(&gui::waterfall);
|
||||
|
||||
if (gui::waterfall.selectedVFOChanged) {
|
||||
gui::waterfall.selectedVFOChanged = false;
|
||||
gui::freqSelect.setFrequency(vfo->generalOffset + gui::waterfall.getCenterFrequency());
|
||||
gui::freqSelect.frequencyChanged = false;
|
||||
mod::broadcastEvent(mod::EVENT_SELECTED_VFO_CHANGED);
|
||||
audioStreamName = audio::getNameFromVFO(gui::waterfall.selectedVFO);
|
||||
if (audioStreamName != "") {
|
||||
volume = &audio::streams[audioStreamName]->volume;
|
||||
}
|
||||
config::config["frequency"] = gui::freqSelect.frequency;
|
||||
config::configModified = true;
|
||||
}
|
||||
|
||||
if (gui::freqSelect.frequencyChanged) {
|
||||
gui::freqSelect.frequencyChanged = false;
|
||||
setVFO(gui::freqSelect.frequency);
|
||||
vfo->centerOffsetChanged = false;
|
||||
vfo->lowerOffsetChanged = false;
|
||||
vfo->upperOffsetChanged = false;
|
||||
config::config["frequency"] = gui::freqSelect.frequency;
|
||||
config::configModified = true;
|
||||
}
|
||||
|
||||
if (gui::waterfall.centerFreqMoved) {
|
||||
gui::waterfall.centerFreqMoved = false;
|
||||
soapy.setFrequency(gui::waterfall.getCenterFrequency());
|
||||
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
|
||||
config::config["frequency"] = gui::freqSelect.frequency;
|
||||
config::configModified = true;
|
||||
}
|
||||
|
||||
if (dcbias.changed()) {
|
||||
sigpath::signalPath.setDCBiasCorrection(dcbias.val);
|
||||
}
|
||||
|
||||
if (bandplanId.changed() && bandPlanEnabled.val) {
|
||||
gui::waterfall.bandplan = &bandplan::bandplans[bandplan::bandplanNames[bandplanId.val]];
|
||||
}
|
||||
|
||||
if (bandPlanEnabled.changed()) {
|
||||
gui::waterfall.bandplan = bandPlanEnabled.val ? &bandplan::bandplans[bandplan::bandplanNames[bandplanId.val]] : NULL;
|
||||
}
|
||||
|
||||
int _fftHeight = gui::waterfall.getFFTHeight();
|
||||
if (fftHeight != _fftHeight) {
|
||||
fftHeight = _fftHeight;
|
||||
config::config["fftHeight"] = fftHeight;
|
||||
config::configModified = true;
|
||||
}
|
||||
|
||||
ImVec2 vMin = ImGui::GetWindowContentRegionMin();
|
||||
ImVec2 vMax = ImGui::GetWindowContentRegionMax();
|
||||
|
||||
int width = vMax.x - vMin.x;
|
||||
int height = vMax.y - vMin.y;
|
||||
|
||||
int modCount = mod::moduleNames.size();
|
||||
mod::Module_t mod;
|
||||
for (int i = 0; i < modCount; i++) {
|
||||
mod = mod::modules[mod::moduleNames[i]];
|
||||
mod._NEW_FRAME_(mod.ctx);
|
||||
}
|
||||
|
||||
// To Bar
|
||||
if (ImGui::ImageButton(icons::MENU, ImVec2(40, 40), ImVec2(0, 0), ImVec2(1, 1), 0)) {
|
||||
showMenu = !showMenu;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (playing) {
|
||||
if (ImGui::ImageButton(icons::STOP, ImVec2(40, 40), ImVec2(0, 0), ImVec2(1, 1), 0)) {
|
||||
soapy.stop();
|
||||
playing = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (ImGui::ImageButton(icons::PLAY, ImVec2(40, 40), ImVec2(0, 0), ImVec2(1, 1), 0) && soapy.devList.size() > 0) {
|
||||
soapy.start();
|
||||
soapy.setFrequency(gui::waterfall.getCenterFrequency());
|
||||
playing = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);
|
||||
ImGui::SetNextItemWidth(200);
|
||||
if (ImGui::SliderFloat("##_2_", volume, 0.0f, 1.0f, "")) {
|
||||
if (audioStreamName != "") {
|
||||
if (!config::config["audio"].contains(audioStreamName)) {
|
||||
saveAudioConfig(audioStreamName);
|
||||
}
|
||||
audio::streams[audioStreamName]->audio->setVolume(*volume);
|
||||
config::config["audio"][audioStreamName]["volume"] = *volume;
|
||||
config::configModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::freqSelect.draw();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Logo button
|
||||
ImGui::SetCursorPosX(ImGui::GetWindowSize().x - 48);
|
||||
ImGui::SetCursorPosY(10);
|
||||
if (ImGui::ImageButton(icons::LOGO, ImVec2(32, 32), ImVec2(0, 0), ImVec2(1, 1), 0)) {
|
||||
showCredits = true;
|
||||
}
|
||||
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
||||
showCredits = false;
|
||||
}
|
||||
if (ImGui::IsKeyPressed(GLFW_KEY_ESCAPE)) {
|
||||
showCredits = false;
|
||||
}
|
||||
|
||||
// Handle menu resize
|
||||
float curY = ImGui::GetCursorPosY();
|
||||
ImVec2 winSize = ImGui::GetWindowSize();
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
if (grabbingMenu) {
|
||||
newWidth = mousePos.x;
|
||||
newWidth = std::clamp<float>(newWidth, 250, winSize.x - 250);
|
||||
ImGui::GetForegroundDrawList()->AddLine(ImVec2(newWidth, curY), ImVec2(newWidth, winSize.y - 10), ImGui::GetColorU32(ImGuiCol_SeparatorActive));
|
||||
}
|
||||
if (mousePos.x >= newWidth - 2 && mousePos.x <= newWidth + 2 && mousePos.y > curY) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
|
||||
if (click) {
|
||||
grabbingMenu = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
|
||||
}
|
||||
if(!down && grabbingMenu) {
|
||||
grabbingMenu = false;
|
||||
menuWidth = newWidth;
|
||||
config::config["menuWidth"] = menuWidth;
|
||||
config::configModified = true;
|
||||
}
|
||||
|
||||
// Left Column
|
||||
|
||||
if (showMenu) {
|
||||
ImGui::Columns(3, "WindowColumns", false);
|
||||
ImGui::SetColumnWidth(0, menuWidth);
|
||||
ImGui::SetColumnWidth(1, winSize.x - menuWidth - 60);
|
||||
ImGui::SetColumnWidth(2, 60);
|
||||
ImGui::BeginChild("Left Column");
|
||||
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
if (ImGui::CollapsingHeader("Source", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
if (playing) { style::beginDisabled(); };
|
||||
|
||||
ImGui::PushItemWidth(menuColumnWidth);
|
||||
if (ImGui::Combo("##_0_", &devId, soapy.txtDevList.c_str())) {
|
||||
spdlog::info("Changed input device: {0}", devId);
|
||||
sourceName = soapy.devNameList[devId];
|
||||
soapy.setDevice(soapy.devList[devId]);
|
||||
|
||||
if (config::config["sourceSettings"].contains(sourceName)) {
|
||||
loadSourceConfig(sourceName);
|
||||
}
|
||||
else {
|
||||
srId = 0;
|
||||
sampleRate = soapy.getSampleRate();
|
||||
bw.val = sampleRate;
|
||||
gui::waterfall.setBandwidth(sampleRate);
|
||||
gui::waterfall.setViewBandwidth(sampleRate);
|
||||
sigpath::signalPath.setSampleRate(sampleRate);
|
||||
|
||||
if (soapy.gainList.size() >= 0) {
|
||||
delete[] uiGains;
|
||||
uiGains = new float[soapy.gainList.size()];
|
||||
for (int i = 0; i < soapy.gainList.size(); i++) {
|
||||
uiGains[i] = soapy.currentGains[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
setVFO(gui::freqSelect.frequency);
|
||||
config::config["source"] = sourceName;
|
||||
config::configModified = true;
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
if (ImGui::Combo("##_1_", &srId, soapy.txtSampleRateList.c_str())) {
|
||||
spdlog::info("Changed sample rate: {0}", srId);
|
||||
sampleRate = soapy.sampleRates[srId];
|
||||
soapy.setSampleRate(sampleRate);
|
||||
gui::waterfall.setBandwidth(sampleRate);
|
||||
gui::waterfall.setViewBandwidth(sampleRate);
|
||||
sigpath::signalPath.setSampleRate(sampleRate);
|
||||
bw.val = sampleRate;
|
||||
|
||||
if (!config::config["sourceSettings"].contains(sourceName)) {
|
||||
saveCurrentSource();
|
||||
}
|
||||
config::config["sourceSettings"][sourceName]["sampleRate"] = sampleRate;
|
||||
config::configModified = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
bool noDevice = (soapy.devList.size() == 0);
|
||||
if (ImGui::Button("Refresh", ImVec2(menuColumnWidth - ImGui::GetCursorPosX(), 0.0f))) {
|
||||
soapy.refresh();
|
||||
if (noDevice && soapy.devList.size() > 0) {
|
||||
sourceName = soapy.devNameList[0];
|
||||
soapy.setDevice(soapy.devList[0]);
|
||||
if (config::config["sourceSettings"][sourceName]) {
|
||||
loadSourceConfig(sourceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (playing) { style::endDisabled(); };
|
||||
|
||||
float maxTextLength = 0;
|
||||
float txtLen = 0;
|
||||
char buf[100];
|
||||
|
||||
// Calculate the spacing
|
||||
for (int i = 0; i < soapy.gainList.size(); i++) {
|
||||
sprintf(buf, "%s gain", soapy.gainList[i].c_str());
|
||||
txtLen = ImGui::CalcTextSize(buf).x;
|
||||
if (txtLen > maxTextLength) {
|
||||
maxTextLength = txtLen;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < soapy.gainList.size(); i++) {
|
||||
ImGui::Text("%s gain", soapy.gainList[i].c_str());
|
||||
ImGui::SameLine();
|
||||
sprintf(buf, "##_gain_slide_%d_", i);
|
||||
|
||||
ImGui::SetCursorPosX(maxTextLength + 5);
|
||||
ImGui::PushItemWidth(menuColumnWidth - (maxTextLength + 5));
|
||||
if (ImGui::SliderFloat(buf, &uiGains[i], soapy.gainRanges[i].minimum(), soapy.gainRanges[i].maximum())) {
|
||||
soapy.setGain(i, uiGains[i]);
|
||||
if (!config::config["sourceSettings"].contains(sourceName)) {
|
||||
saveCurrentSource();
|
||||
}
|
||||
config::config["sourceSettings"][sourceName]["gains"][soapy.gainList[i]] = uiGains[i];
|
||||
config::configModified = true;
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
for (int i = 0; i < modCount; i++) {
|
||||
if (ImGui::CollapsingHeader(mod::moduleNames[i].c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
mod = mod::modules[mod::moduleNames[i]];
|
||||
mod._DRAW_MENU_(mod.ctx);
|
||||
ImGui::Spacing();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ImGui::CollapsingHeader("Audio", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
int count = 0;
|
||||
int maxCount = audio::streams.size();
|
||||
for (auto const& [name, stream] : audio::streams) {
|
||||
int deviceId;
|
||||
float vol = 1.0f;
|
||||
deviceId = stream->audio->getDeviceId();
|
||||
|
||||
ImGui::SetCursorPosX((menuColumnWidth / 2.0f) - (ImGui::CalcTextSize(name.c_str()).x / 2.0f));
|
||||
ImGui::Text(name.c_str());
|
||||
|
||||
ImGui::PushItemWidth(menuColumnWidth);
|
||||
bool running = stream->running;
|
||||
if (ImGui::Combo(("##_audio_dev_0_"+ name).c_str(), &stream->deviceId, stream->audio->devTxtList.c_str())) {
|
||||
audio::stopStream(name);
|
||||
audio::setAudioDevice(name, stream->deviceId, stream->audio->devices[deviceId].sampleRates[0]);
|
||||
if (running) {
|
||||
audio::startStream(name);
|
||||
}
|
||||
stream->sampleRateId = 0;
|
||||
|
||||
// Create config if it doesn't exist
|
||||
if (!config::config["audio"].contains(name)) {
|
||||
saveAudioConfig(name);
|
||||
}
|
||||
config::config["audio"][name]["device"] = stream->audio->deviceNames[stream->deviceId];
|
||||
config::config["audio"][name]["sampleRate"] = stream->audio->devices[stream->deviceId].sampleRates[0];
|
||||
config::configModified = true;
|
||||
}
|
||||
if (ImGui::Combo(("##_audio_sr_0_" + name).c_str(), &stream->sampleRateId, stream->audio->devices[deviceId].txtSampleRates.c_str())) {
|
||||
audio::stopStream(name);
|
||||
audio::setSampleRate(name, stream->audio->devices[deviceId].sampleRates[stream->sampleRateId]);
|
||||
if (running) {
|
||||
audio::startStream(name);
|
||||
}
|
||||
|
||||
// Create config if it doesn't exist
|
||||
if (!config::config["audio"].contains(name)) {
|
||||
saveAudioConfig(name);
|
||||
}
|
||||
config::config["audio"][name]["sampleRate"] = stream->audio->devices[deviceId].sampleRates[stream->sampleRateId];
|
||||
config::configModified = true;
|
||||
}
|
||||
if (ImGui::SliderFloat(("##_audio_vol_0_" + name).c_str(), &stream->volume, 0.0f, 1.0f, "")) {
|
||||
stream->audio->setVolume(stream->volume);
|
||||
|
||||
// Create config if it doesn't exist
|
||||
if (!config::config["audio"].contains(name)) {
|
||||
saveAudioConfig(name);
|
||||
}
|
||||
config::config["audio"][name]["volume"] = stream->volume;
|
||||
config::configModified = true;
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
count++;
|
||||
if (count < maxCount) {
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Band Plan", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::PushItemWidth(menuColumnWidth);
|
||||
if (ImGui::Combo("##_4_", &bandplanId.val, bandplan::bandplanNameTxt.c_str())) {
|
||||
config::config["bandPlan"] = bandplan::bandplanNames[bandplanId.val];
|
||||
config::configModified = true;
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
if (ImGui::Checkbox("Enabled", &bandPlanEnabled.val)) {
|
||||
config::config["bandPlanEnabled"] = bandPlanEnabled.val;
|
||||
config::configModified = true;
|
||||
}
|
||||
bandplan::BandPlan_t plan = bandplan::bandplans[bandplan::bandplanNames[bandplanId.val]];
|
||||
ImGui::Text("Country: %s (%s)", plan.countryName.c_str(), plan.countryCode.c_str());
|
||||
ImGui::Text("Author: %s", plan.authorName.c_str());
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Display", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
if (ImGui::Checkbox("Show waterfall", &showWaterfall)) {
|
||||
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
if(ImGui::CollapsingHeader("Debug")) {
|
||||
ImGui::Text("Frame time: %.3f ms/frame", 1000.0f / ImGui::GetIO().Framerate);
|
||||
ImGui::Text("Framerate: %.1f FPS", ImGui::GetIO().Framerate);
|
||||
ImGui::Text("Center Frequency: %.0f Hz", gui::waterfall.getCenterFrequency());
|
||||
ImGui::Text("Source name: %s", sourceName.c_str());
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
else {
|
||||
// When hiding the menu bar
|
||||
ImGui::Columns(3, "WindowColumns", false);
|
||||
ImGui::SetColumnWidth(0, 8);
|
||||
ImGui::SetColumnWidth(1, winSize.x - 8 - 60);
|
||||
ImGui::SetColumnWidth(2, 60);
|
||||
}
|
||||
|
||||
// Right Column
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||
ImGui::NextColumn();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::BeginChild("Waterfall");
|
||||
|
||||
gui::waterfall.draw();
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
|
||||
ImGui::NextColumn();
|
||||
ImGui::BeginChild("WaterfallControls");
|
||||
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - (ImGui::CalcTextSize("Zoom").x / 2.0f));
|
||||
ImGui::Text("Zoom");
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - 10);
|
||||
ImGui::VSliderFloat("##_7_", ImVec2(20.0f, 150.0f), &bw.val, sampleRate, 1000.0f, "");
|
||||
|
||||
ImGui::NewLine();
|
||||
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - (ImGui::CalcTextSize("Max").x / 2.0f));
|
||||
ImGui::Text("Max");
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - 10);
|
||||
if (ImGui::VSliderFloat("##_8_", ImVec2(20.0f, 150.0f), &fftMax, 0.0f, -100.0f, "")) {
|
||||
fftMax = std::max<float>(fftMax, fftMin + 10);
|
||||
config::config["max"] = fftMax;
|
||||
config::configModified = true;
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - (ImGui::CalcTextSize("Min").x / 2.0f));
|
||||
ImGui::Text("Min");
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0f) - 10);
|
||||
if (ImGui::VSliderFloat("##_9_", ImVec2(20.0f, 150.0f), &fftMin, 0.0f, -100.0f, "")) {
|
||||
fftMin = std::min<float>(fftMax - 10, fftMin);
|
||||
config::config["min"] = fftMin;
|
||||
config::configModified = true;
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
if (bw.changed()) {
|
||||
gui::waterfall.setViewBandwidth(bw.val);
|
||||
gui::waterfall.setViewOffset(vfo->centerOffset);
|
||||
}
|
||||
|
||||
gui::waterfall.setFFTMin(fftMin);
|
||||
gui::waterfall.setFFTMax(fftMax);
|
||||
gui::waterfall.setWaterfallMin(fftMin);
|
||||
gui::waterfall.setWaterfallMax(fftMax);
|
||||
|
||||
|
||||
ImGui::End();
|
||||
|
||||
if (showCredits) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
|
||||
ImGui::OpenPopup("Credits");
|
||||
ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
|
||||
|
||||
ImGui::PushFont(bigFont);
|
||||
ImGui::Text("SDR++ ");
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::Image(icons::LOGO, ImVec2(128, 128));
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("This software is brought to you by\n\n");
|
||||
|
||||
ImGui::Columns(3, "CreditColumns", true);
|
||||
|
||||
// Contributors
|
||||
ImGui::Text("Contributors");
|
||||
ImGui::BulletText("Ryzerth (Creator)");
|
||||
ImGui::BulletText("aosync");
|
||||
ImGui::BulletText("Benjamin Kyd");
|
||||
ImGui::BulletText("Tobias Mädel");
|
||||
ImGui::BulletText("Raov");
|
||||
ImGui::BulletText("Howard0su");
|
||||
|
||||
// Libraries
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Libraries");
|
||||
ImGui::BulletText("SoapySDR (PothosWare)");
|
||||
ImGui::BulletText("Dear ImGui (ocornut)");
|
||||
ImGui::BulletText("spdlog (gabime)");
|
||||
ImGui::BulletText("json (nlohmann)");
|
||||
ImGui::BulletText("portaudio (PA Comm.)");
|
||||
|
||||
// Patrons
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Patrons");
|
||||
ImGui::BulletText("SignalsEverywhere");
|
||||
|
||||
ImGui::EndPopup();
|
||||
ImGui::PopStyleVar(1);
|
||||
}
|
||||
}
|
||||
|
||||
void bindVolumeVariable(float* vol) {
|
||||
volume = vol;
|
||||
}
|
||||
|
||||
void unbindVolumeVariable() {
|
||||
volume = &dummyVolume;
|
||||
}
|
36
core/src/gui/main_window.h
Normal file
36
core/src/gui/main_window.h
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <stdio.h>
|
||||
#include <GL/glew.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <imgui_plot.h>
|
||||
#include <dsp/resampling.h>
|
||||
#include <dsp/demodulator.h>
|
||||
#include <dsp/filter.h>
|
||||
#include <thread>
|
||||
#include <complex>
|
||||
#include <dsp/source.h>
|
||||
#include <dsp/math.h>
|
||||
#include <gui/waterfall.h>
|
||||
#include <gui/frequency_select.h>
|
||||
#include <fftw3.h>
|
||||
#include <signal_path/dsp.h>
|
||||
#include <io/soapy.h>
|
||||
#include <gui/icons.h>
|
||||
#include <gui/bandplan.h>
|
||||
#include <watcher.h>
|
||||
#include <module.h>
|
||||
#include <signal_path/vfo_manager.h>
|
||||
#include <signal_path/audio.h>
|
||||
#include <gui/style.h>
|
||||
#include <config.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
|
||||
#define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground
|
||||
|
||||
void windowInit();
|
||||
void drawWindow();
|
||||
void bindVolumeVariable(float* vol);
|
||||
void unbindVolumeVariable();
|
24
core/src/gui/menu.cpp
Normal file
24
core/src/gui/menu.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
#include <gui/menu.h>
|
||||
|
||||
Menu::Menu() {
|
||||
|
||||
}
|
||||
|
||||
void Menu::registerEntry(std::string name, void (*drawHandler)(void* ctx), void* ctx) {
|
||||
MenuItem_t item;
|
||||
item.drawHandler = drawHandler;
|
||||
item.ctx = ctx;
|
||||
items[name] = item;
|
||||
}
|
||||
|
||||
void Menu::removeEntry(std::string name) {
|
||||
items.erase(name);
|
||||
}
|
||||
|
||||
void Menu::draw() {
|
||||
MenuItem_t item;
|
||||
for (std::string name : order) {
|
||||
item = items[name];
|
||||
item.drawHandler(item.ctx);
|
||||
}
|
||||
}
|
23
core/src/gui/menu.h
Normal file
23
core/src/gui/menu.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
class Menu {
|
||||
public:
|
||||
Menu();
|
||||
|
||||
struct MenuItem_t {
|
||||
void (*drawHandler)(void* ctx);
|
||||
void* ctx;
|
||||
};
|
||||
|
||||
void registerEntry(std::string name, void (*drawHandler)(void* ctx), void* ctx = NULL);
|
||||
void removeEntry(std::string name);
|
||||
void draw();
|
||||
|
||||
std::vector<std::string> order;
|
||||
|
||||
private:
|
||||
std::map<std::string, MenuItem_t> items;
|
||||
};
|
93
core/src/gui/style.cpp
Normal file
93
core/src/gui/style.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
#include <gui/style.h>
|
||||
|
||||
namespace style {
|
||||
void setDefaultStyle() {
|
||||
ImGui::GetStyle().WindowRounding = 0.0f;
|
||||
ImGui::GetStyle().ChildRounding = 0.0f;
|
||||
ImGui::GetStyle().FrameRounding = 0.0f;
|
||||
ImGui::GetStyle().GrabRounding = 0.0f;
|
||||
ImGui::GetStyle().PopupRounding = 0.0f;
|
||||
ImGui::GetStyle().ScrollbarRounding = 0.0f;
|
||||
|
||||
ImGui::GetIO().Fonts->AddFontFromFileTTF((config::getRootDirectory() + "/res/fonts/Roboto-Medium.ttf").c_str(), 16.0f);
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
//ImGui::StyleColorsLight();
|
||||
}
|
||||
|
||||
void testtt() {
|
||||
ImGui::StyleColorsLight();
|
||||
}
|
||||
|
||||
void setDarkStyle() {
|
||||
ImGui::GetStyle().WindowRounding = 0.0f;
|
||||
ImGui::GetStyle().ChildRounding = 0.0f;
|
||||
ImGui::GetStyle().FrameRounding = 0.0f;
|
||||
ImGui::GetStyle().GrabRounding = 0.0f;
|
||||
ImGui::GetStyle().PopupRounding = 0.0f;
|
||||
ImGui::GetStyle().ScrollbarRounding = 0.0f;
|
||||
|
||||
ImGui::GetIO().Fonts->AddFontFromFileTTF((config::getRootDirectory() + "/res/fonts/Roboto-Medium.ttf").c_str(), 16.0f);
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
|
||||
ImVec4* colors = style.Colors;
|
||||
|
||||
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f);
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
|
||||
colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.29f, 0.29f, 0.29f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.44f, 0.44f, 0.44f, 0.40f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.45f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.44f, 0.44f, 0.44f, 0.40f);
|
||||
colors[ImGuiCol_Header] = ImVec4(0.63f, 0.63f, 0.70f, 0.31f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.63f, 0.63f, 0.70f, 0.40f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.63f, 0.63f, 0.70f, 0.31f);
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.72f, 0.72f, 0.72f, 0.78f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(0.91f, 0.91f, 0.91f, 0.25f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.81f, 0.81f, 0.81f, 0.67f);
|
||||
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.46f, 0.46f, 0.46f, 0.95f);
|
||||
colors[ImGuiCol_PlotLines] = ImVec4(0.4f, 0.9f, 1.0f, 1.00f);
|
||||
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogram] = ImVec4(0.73f, 0.60f, 0.15f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.87f, 0.87f, 0.87f, 0.35f);
|
||||
colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
|
||||
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
|
||||
colors[ImGuiCol_NavHighlight] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
|
||||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||
}
|
||||
|
||||
void beginDisabled() {
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.44f, 0.44f, 0.44f, 0.15f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.20f, 0.21f, 0.22f, 0.30f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 0.65f));
|
||||
}
|
||||
|
||||
void endDisabled() {
|
||||
ImGui::PopItemFlag();
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
}
|
12
core/src/gui/style.h
Normal file
12
core/src/gui/style.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <config.h>
|
||||
|
||||
namespace style {
|
||||
void setDefaultStyle();
|
||||
void setDarkStyle();
|
||||
void beginDisabled();
|
||||
void endDisabled();
|
||||
void testtt();
|
||||
}
|
785
core/src/gui/waterfall.cpp
Normal file
785
core/src/gui/waterfall.cpp
Normal file
@ -0,0 +1,785 @@
|
||||
#include <gui/waterfall.h>
|
||||
|
||||
float COLOR_MAP[][3] = {
|
||||
{0x00, 0x00, 0x20},
|
||||
{0x00, 0x00, 0x30},
|
||||
{0x00, 0x00, 0x50},
|
||||
{0x00, 0x00, 0x91},
|
||||
{0x1E, 0x90, 0xFF},
|
||||
{0xFF, 0xFF, 0xFF},
|
||||
{0xFF, 0xFF, 0x00},
|
||||
{0xFE, 0x6D, 0x16},
|
||||
{0xFF, 0x00, 0x00},
|
||||
{0xC6, 0x00, 0x00},
|
||||
{0x9F, 0x00, 0x00},
|
||||
{0x75, 0x00, 0x00},
|
||||
{0x4A, 0x00, 0x00}
|
||||
};
|
||||
|
||||
void doZoom(int offset, int width, int outWidth, std::vector<float> data, float* out) {
|
||||
// NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX
|
||||
if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
if (width > 65535) {
|
||||
width = 65535;
|
||||
}
|
||||
|
||||
float factor = (float)width / (float)outWidth;
|
||||
for (int i = 0; i < outWidth; i++) {
|
||||
out[i] = data[offset + ((float)i * factor)];
|
||||
}
|
||||
}
|
||||
|
||||
float freq_ranges[] = {
|
||||
1.0f, 2.0f, 2.5f, 5.0f,
|
||||
10.0f, 20.0f, 25.0f, 50.0f,
|
||||
100.0f, 200.0f, 250.0f, 500.0f,
|
||||
1000.0f, 2000.0f, 2500.0f, 5000.0f,
|
||||
10000.0f, 20000.0f, 25000.0f, 50000.0f,
|
||||
100000.0f, 200000.0f, 250000.0f, 500000.0f,
|
||||
1000000.0f, 2000000.0f, 2500000.0f, 5000000.0f,
|
||||
10000000.0f, 20000000.0f, 25000000.0f, 50000000.0f
|
||||
};
|
||||
|
||||
float findBestRange(float bandwidth, int maxSteps) {
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (bandwidth / freq_ranges[i] < (float)maxSteps) {
|
||||
return freq_ranges[i];
|
||||
}
|
||||
}
|
||||
return 50000000.0f;
|
||||
}
|
||||
|
||||
void printAndScale(float freq, char* buf) {
|
||||
if (freq < 1000) {
|
||||
sprintf(buf, "%.3f", freq);
|
||||
}
|
||||
else if (freq < 1000000) {
|
||||
sprintf(buf, "%.3fK", freq / 1000.0f);
|
||||
}
|
||||
else if (freq < 1000000000) {
|
||||
sprintf(buf, "%.3fM", freq / 1000000.0f);
|
||||
}
|
||||
else if (freq < 1000000000000) {
|
||||
sprintf(buf, "%.3fG", freq / 1000000000.0f);
|
||||
}
|
||||
for (int i = strlen(buf) - 2; i >= 0; i--) {
|
||||
if (buf[i] != '0') {
|
||||
if (buf[i] == '.') {
|
||||
i--;
|
||||
}
|
||||
char scale = buf[strlen(buf) - 1];
|
||||
buf[i + 1] = scale;
|
||||
buf[i + 2] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace ImGui {
|
||||
WaterFall::WaterFall() {
|
||||
fftMin = -70.0f;
|
||||
fftMax = 0.0f;
|
||||
waterfallMin = -70.0f;
|
||||
waterfallMax = 0.0f;
|
||||
FFTAreaHeight = 300;
|
||||
newFFTAreaHeight = FFTAreaHeight;
|
||||
fftHeight = FFTAreaHeight - 50;
|
||||
dataWidth = 600;
|
||||
lastWidgetPos.x = 0;
|
||||
lastWidgetPos.y = 0;
|
||||
lastWidgetSize.x = 0;
|
||||
lastWidgetSize.y = 0;
|
||||
latestFFT = new float[1];
|
||||
waterfallFb = new uint32_t[1];
|
||||
|
||||
viewBandwidth = 1.0f;
|
||||
wholeBandwidth = 1.0f;
|
||||
|
||||
glGenTextures(1, &textureId);
|
||||
|
||||
updatePallette(COLOR_MAP, 13);
|
||||
}
|
||||
|
||||
void WaterFall::drawFFT() {
|
||||
// Calculate scaling factor
|
||||
float startLine = floorf(fftMax / vRange) * vRange;
|
||||
float vertRange = fftMax - fftMin;
|
||||
float scaleFactor = fftHeight / vertRange;
|
||||
char buf[100];
|
||||
|
||||
ImU32 trace = ImGui::GetColorU32(ImGuiCol_PlotLines);
|
||||
ImU32 shadow = ImGui::GetColorU32(ImGuiCol_PlotLines, 0.2);
|
||||
|
||||
// Vertical scale
|
||||
for (float line = startLine; line > fftMin; line -= vRange) {
|
||||
float yPos = widgetPos.y + fftHeight + 10 - ((line - fftMin) * scaleFactor);
|
||||
window->DrawList->AddLine(ImVec2(roundf(widgetPos.x + 50), roundf(yPos)),
|
||||
ImVec2(roundf(widgetPos.x + dataWidth + 50), roundf(yPos)),
|
||||
IM_COL32(50, 50, 50, 255), 1.0f);
|
||||
sprintf(buf, "%d", (int)line);
|
||||
ImVec2 txtSz = ImGui::CalcTextSize(buf);
|
||||
window->DrawList->AddText(ImVec2(widgetPos.x + 40 - txtSz.x, roundf(yPos - (txtSz.y / 2))), IM_COL32( 255, 255, 255, 255 ), buf);
|
||||
}
|
||||
|
||||
// Horizontal scale
|
||||
float startFreq = ceilf(lowerFreq / range) * range;
|
||||
float horizScale = (float)dataWidth / viewBandwidth;
|
||||
for (float freq = startFreq; freq < upperFreq; freq += range) {
|
||||
float xPos = widgetPos.x + 50 + ((freq - lowerFreq) * horizScale);
|
||||
window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + 10),
|
||||
ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10),
|
||||
IM_COL32(50, 50, 50, 255), 1.0f);
|
||||
window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10),
|
||||
ImVec2(roundf(xPos), widgetPos.y + fftHeight + 17),
|
||||
IM_COL32(255, 255, 255, 255), 1.0f);
|
||||
printAndScale(freq, buf);
|
||||
ImVec2 txtSz = ImGui::CalcTextSize(buf);
|
||||
window->DrawList->AddText(ImVec2(roundf(xPos - (txtSz.x / 2.0f)), widgetPos.y + fftHeight + 10 + txtSz.y), IM_COL32( 255, 255, 255, 255 ), buf);
|
||||
}
|
||||
|
||||
// Data
|
||||
for (int i = 1; i < dataWidth; i++) {
|
||||
float aPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i - 1] - fftMin) * scaleFactor);
|
||||
float bPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i] - fftMin) * scaleFactor);
|
||||
if (aPos < fftMin && bPos < fftMin) {
|
||||
continue;
|
||||
}
|
||||
aPos = std::clamp<float>(aPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10);
|
||||
bPos = std::clamp<float>(bPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10);
|
||||
window->DrawList->AddLine(ImVec2(widgetPos.x + 49 + i, roundf(aPos)),
|
||||
ImVec2(widgetPos.x + 50 + i, roundf(bPos)), trace, 1.0f);
|
||||
window->DrawList->AddLine(ImVec2(widgetPos.x + 50 + i, roundf(bPos)),
|
||||
ImVec2(widgetPos.x + 50 + i, widgetPos.y + fftHeight + 10), shadow, 1.0f);
|
||||
}
|
||||
|
||||
// X Axis
|
||||
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 10),
|
||||
ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10),
|
||||
IM_COL32(255, 255, 255, 255), 1.0f);
|
||||
// Y Axis
|
||||
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + 9),
|
||||
ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 9),
|
||||
IM_COL32(255, 255, 255, 255), 1.0f);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void WaterFall::drawWaterfall() {
|
||||
if (waterfallUpdate) {
|
||||
waterfallUpdate = false;
|
||||
updateWaterfallTexture();
|
||||
}
|
||||
window->DrawList->AddImage((void*)(intptr_t)textureId, ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 51),
|
||||
ImVec2(widgetPos.x + 50 + dataWidth, widgetPos.y + fftHeight + 51 + waterfallHeight));
|
||||
}
|
||||
|
||||
void WaterFall::drawVFOs() {
|
||||
for (auto const& [name, vfo] : vfos) {
|
||||
if (vfo->redrawRequired) {
|
||||
vfo->redrawRequired = false;
|
||||
vfo->updateDrawingVars(viewBandwidth, dataWidth, viewOffset, widgetPos, fftHeight);
|
||||
}
|
||||
vfo->draw(window, name == selectedVFO);
|
||||
}
|
||||
}
|
||||
|
||||
void WaterFall::selectFirstVFO() {
|
||||
for (auto const& [name, vfo] : vfos) {
|
||||
selectedVFO = name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void WaterFall::processInputs() {
|
||||
WaterfallVFO* vfo = vfos[selectedVFO];
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
ImVec2 drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left);
|
||||
ImVec2 dragOrigin(mousePos.x - drag.x, mousePos.y - drag.y);
|
||||
|
||||
bool mouseHovered, mouseHeld;
|
||||
bool mouseClicked = ImGui::ButtonBehavior(ImRect(fftAreaMin, fftAreaMax), ImGuiID("WaterfallID"), &mouseHovered, &mouseHeld,
|
||||
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_PressedOnClick);
|
||||
|
||||
bool draging = ImGui::IsMouseDragging(ImGuiMouseButton_Left) && ImGui::IsWindowFocused();
|
||||
bool mouseInFreq = IS_IN_AREA(dragOrigin, freqAreaMin, freqAreaMax);
|
||||
bool mouseInFFT = IS_IN_AREA(dragOrigin, fftAreaMin, fftAreaMax);
|
||||
|
||||
|
||||
// If mouse was clicked on a VFO, select VFO and return
|
||||
// If mouse was clicked but not on a VFO, move selected VFO to position
|
||||
if (mouseClicked) {
|
||||
for (auto const& [name, _vfo] : vfos) {
|
||||
if (name == selectedVFO) {
|
||||
continue;
|
||||
}
|
||||
if (IS_IN_AREA(mousePos, _vfo->rectMin, _vfo->rectMax)) {
|
||||
selectedVFO = name;
|
||||
selectedVFOChanged = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
int refCenter = mousePos.x - (widgetPos.x + 50);
|
||||
if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y)) {
|
||||
vfo->setOffset(((((float)refCenter / ((float)dataWidth / 2.0f)) - 1.0f) * (viewBandwidth / 2.0f)) + viewOffset);
|
||||
}
|
||||
}
|
||||
|
||||
// Draging VFO
|
||||
if (draging && mouseInFFT) {
|
||||
int refCenter = mousePos.x - (widgetPos.x + 50);
|
||||
if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y)) {
|
||||
vfo->setOffset(((((float)refCenter / ((float)dataWidth / 2.0f)) - 1.0f) * (viewBandwidth / 2.0f)) + viewOffset);
|
||||
}
|
||||
}
|
||||
|
||||
// Dragon frequency scale
|
||||
if (draging && mouseInFreq) {
|
||||
float deltax = drag.x - lastDrag;
|
||||
lastDrag = drag.x;
|
||||
float viewDelta = deltax * (viewBandwidth / (float)dataWidth);
|
||||
|
||||
viewOffset -= viewDelta;
|
||||
|
||||
if (viewOffset + (viewBandwidth / 2.0f) > wholeBandwidth / 2.0f) {
|
||||
float freqOffset = (viewOffset + (viewBandwidth / 2.0f)) - (wholeBandwidth / 2.0f);
|
||||
viewOffset = (wholeBandwidth / 2.0f) - (viewBandwidth / 2.0f);
|
||||
centerFreq += freqOffset;
|
||||
centerFreqMoved = true;
|
||||
}
|
||||
if (viewOffset - (viewBandwidth / 2.0f) < -(wholeBandwidth / 2.0f)) {
|
||||
float freqOffset = (viewOffset - (viewBandwidth / 2.0f)) + (wholeBandwidth / 2.0f);
|
||||
viewOffset = (viewBandwidth / 2.0f) - (wholeBandwidth / 2.0f);
|
||||
centerFreq += freqOffset;
|
||||
centerFreqMoved = true;
|
||||
}
|
||||
|
||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f);
|
||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0f);
|
||||
updateWaterfallFb();
|
||||
}
|
||||
else {
|
||||
lastDrag = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void WaterFall::updateWaterfallFb() {
|
||||
if (!waterfallVisible) {
|
||||
return;
|
||||
}
|
||||
float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
|
||||
int drawDataSize;
|
||||
int drawDataStart;
|
||||
int count = std::min<int>(waterfallHeight, rawFFTs.size());
|
||||
float* tempData = new float[dataWidth];
|
||||
float pixel;
|
||||
float dataRange = waterfallMax - waterfallMin;
|
||||
int size;
|
||||
for (int i = 0; i < count; i++) {
|
||||
size = rawFFTs[i].size();
|
||||
drawDataSize = (viewBandwidth / wholeBandwidth) * size;
|
||||
drawDataStart = (((float)size / 2.0f) * (offsetRatio + 1)) - (drawDataSize / 2);
|
||||
doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs[i], tempData);
|
||||
for (int j = 0; j < dataWidth; j++) {
|
||||
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
||||
waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
|
||||
}
|
||||
}
|
||||
delete[] tempData;
|
||||
waterfallUpdate = true;
|
||||
}
|
||||
|
||||
void WaterFall::drawBandPlan() {
|
||||
int count = bandplan->bands.size();
|
||||
float horizScale = (float)dataWidth / viewBandwidth;
|
||||
float start, end, center, aPos, bPos, cPos, width;
|
||||
ImVec2 txtSz;
|
||||
bool startVis, endVis;
|
||||
uint32_t color, colorTrans;
|
||||
for (int i = 0; i < count; i++) {
|
||||
start = bandplan->bands[i].start;
|
||||
end = bandplan->bands[i].end;
|
||||
if (start < lowerFreq && end < lowerFreq) {
|
||||
continue;
|
||||
}
|
||||
if (start > upperFreq && end > upperFreq) {
|
||||
continue;
|
||||
}
|
||||
startVis = (start > lowerFreq);
|
||||
endVis = (end < upperFreq);
|
||||
start = std::clamp<float>(start, lowerFreq, upperFreq);
|
||||
end = std::clamp<float>(end, lowerFreq, upperFreq);
|
||||
center = (start + end) / 2.0f;
|
||||
aPos = widgetPos.x + 50 + ((start - lowerFreq) * horizScale);
|
||||
bPos = widgetPos.x + 50 + ((end - lowerFreq) * horizScale);
|
||||
cPos = widgetPos.x + 50 + ((center - lowerFreq) * horizScale);
|
||||
width = bPos - aPos;
|
||||
txtSz = ImGui::CalcTextSize(bandplan->bands[i].name.c_str());
|
||||
if (bandplan::colorTable.find(bandplan->bands[i].type.c_str()) != bandplan::colorTable.end()) {
|
||||
color = bandplan::colorTable[bandplan->bands[i].type].colorValue;
|
||||
colorTrans = bandplan::colorTable[bandplan->bands[i].type].transColorValue;
|
||||
}
|
||||
else {
|
||||
color = IM_COL32(255, 255, 255, 255);
|
||||
colorTrans = IM_COL32(255, 255, 255, 100);
|
||||
}
|
||||
if (aPos <= widgetPos.x + 50) {
|
||||
aPos = widgetPos.x + 51;
|
||||
}
|
||||
if (bPos <= widgetPos.x + 50) {
|
||||
bPos = widgetPos.x + 51;
|
||||
}
|
||||
if (width >= 1.0f) {
|
||||
window->DrawList->AddRectFilled(ImVec2(roundf(aPos), widgetPos.y + fftHeight - 25),
|
||||
ImVec2(roundf(bPos), widgetPos.y + fftHeight + 10), colorTrans);
|
||||
if (startVis) {
|
||||
window->DrawList->AddLine(ImVec2(roundf(aPos), widgetPos.y + fftHeight - 26),
|
||||
ImVec2(roundf(aPos), widgetPos.y + fftHeight + 9), color);
|
||||
}
|
||||
if (endVis) {
|
||||
window->DrawList->AddLine(ImVec2(roundf(bPos), widgetPos.y + fftHeight - 26),
|
||||
ImVec2(roundf(bPos), widgetPos.y + fftHeight + 9), color);
|
||||
}
|
||||
}
|
||||
if (txtSz.x <= width) {
|
||||
window->DrawList->AddText(ImVec2(cPos - (txtSz.x / 2.0f), widgetPos.y + fftHeight - 17),
|
||||
IM_COL32(255, 255, 255, 255), bandplan->bands[i].name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WaterFall::updateWaterfallTexture() {
|
||||
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, dataWidth, waterfallHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)waterfallFb);
|
||||
}
|
||||
|
||||
void WaterFall::onPositionChange() {
|
||||
// Nothing to see here...
|
||||
}
|
||||
|
||||
void WaterFall::onResize() {
|
||||
// return if widget is too small
|
||||
if (widgetSize.x < 100 || widgetSize.y < 100) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (waterfallVisible) {
|
||||
FFTAreaHeight = std::min<int>(FFTAreaHeight, widgetSize.y - 50);
|
||||
fftHeight = FFTAreaHeight - 50;
|
||||
waterfallHeight = widgetSize.y - fftHeight - 52;
|
||||
}
|
||||
else {
|
||||
fftHeight = widgetSize.y - 50;
|
||||
}
|
||||
dataWidth = widgetSize.x - 60.0f;
|
||||
delete[] latestFFT;
|
||||
|
||||
if (waterfallVisible) {
|
||||
delete[] waterfallFb;
|
||||
}
|
||||
|
||||
latestFFT = new float[dataWidth];
|
||||
if (waterfallVisible) {
|
||||
waterfallFb = new uint32_t[dataWidth * waterfallHeight];
|
||||
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
|
||||
}
|
||||
for (int i = 0; i < dataWidth; i++) {
|
||||
latestFFT[i] = -1000.0f; // Hide everything
|
||||
}
|
||||
|
||||
fftAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + 9);
|
||||
fftAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10);
|
||||
freqAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 11);
|
||||
freqAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 50);
|
||||
|
||||
maxHSteps = dataWidth / (ImGui::CalcTextSize("000.000").x + 10);
|
||||
maxVSteps = fftHeight / (ImGui::CalcTextSize("000.000").y);
|
||||
|
||||
range = findBestRange(viewBandwidth, maxHSteps);
|
||||
vRange = findBestRange(fftMax - fftMin, maxVSteps);
|
||||
vRange = 10.0f;
|
||||
|
||||
updateWaterfallFb();
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
void WaterFall::draw() {
|
||||
buf_mtx.lock();
|
||||
window = GetCurrentWindow();
|
||||
|
||||
widgetPos = ImGui::GetWindowContentRegionMin();
|
||||
widgetEndPos = ImGui::GetWindowContentRegionMax();
|
||||
widgetPos.x += window->Pos.x;
|
||||
widgetPos.y += window->Pos.y;
|
||||
widgetEndPos.x += window->Pos.x - 4; // Padding
|
||||
widgetEndPos.y += window->Pos.y;
|
||||
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
|
||||
|
||||
|
||||
|
||||
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
|
||||
lastWidgetPos = widgetPos;
|
||||
onPositionChange();
|
||||
}
|
||||
if (widgetSize.x != lastWidgetSize.x || widgetSize.y != lastWidgetSize.y) {
|
||||
lastWidgetSize = widgetSize;
|
||||
onResize();
|
||||
}
|
||||
|
||||
window->DrawList->AddRectFilled(widgetPos, widgetEndPos, IM_COL32( 0, 0, 0, 255 ));
|
||||
window->DrawList->AddRect(widgetPos, widgetEndPos, IM_COL32( 50, 50, 50, 255 ));
|
||||
window->DrawList->AddLine(ImVec2(widgetPos.x, widgetPos.y + fftHeight + 50), ImVec2(widgetPos.x + widgetSize.x, widgetPos.y + fftHeight + 50), IM_COL32(50, 50, 50, 255), 1.0f);
|
||||
|
||||
processInputs();
|
||||
|
||||
drawFFT();
|
||||
if (waterfallVisible) {
|
||||
drawWaterfall();
|
||||
}
|
||||
drawVFOs();
|
||||
if (bandplan != NULL) {
|
||||
drawBandPlan();
|
||||
}
|
||||
|
||||
if (!waterfallVisible) {
|
||||
buf_mtx.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle fft resize
|
||||
ImVec2 winSize = ImGui::GetWindowSize();
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
mousePos.x -= widgetPos.x;
|
||||
mousePos.y -= widgetPos.y;
|
||||
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
if (draggingFW) {
|
||||
newFFTAreaHeight = mousePos.y;
|
||||
newFFTAreaHeight = std::clamp<float>(newFFTAreaHeight, 150, widgetSize.y - 50);
|
||||
ImGui::GetForegroundDrawList()->AddLine(ImVec2(widgetPos.x, newFFTAreaHeight + widgetPos.y), ImVec2(widgetEndPos.x, newFFTAreaHeight + widgetPos.y),
|
||||
ImGui::GetColorU32(ImGuiCol_SeparatorActive));
|
||||
}
|
||||
if (mousePos.y >= newFFTAreaHeight - 2 && mousePos.y <= newFFTAreaHeight + 2 && mousePos.x > 0 && mousePos.x < widgetSize.x) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
|
||||
if (click) {
|
||||
draggingFW = true;
|
||||
}
|
||||
}
|
||||
if(!down && draggingFW) {
|
||||
draggingFW = false;
|
||||
FFTAreaHeight = newFFTAreaHeight;
|
||||
onResize();
|
||||
}
|
||||
|
||||
buf_mtx.unlock();
|
||||
}
|
||||
|
||||
void WaterFall::pushFFT(std::vector<float> data, int n) {
|
||||
buf_mtx.lock();
|
||||
float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
|
||||
int drawDataSize = (viewBandwidth / wholeBandwidth) * data.size();
|
||||
int drawDataStart = (((float)data.size() / 2.0f) * (offsetRatio + 1)) - (drawDataSize / 2);
|
||||
|
||||
doZoom(drawDataStart, drawDataSize, dataWidth, data, latestFFT);
|
||||
rawFFTs.insert(rawFFTs.begin(), data);
|
||||
if (rawFFTs.size() > waterfallHeight + 300) {
|
||||
rawFFTs.resize(waterfallHeight);
|
||||
}
|
||||
|
||||
if (waterfallVisible) {
|
||||
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
|
||||
float pixel;
|
||||
float dataRange = waterfallMax - waterfallMin;
|
||||
for (int j = 0; j < dataWidth; j++) {
|
||||
pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
||||
int id = (int)(pixel * (WATERFALL_RESOLUTION - 1));
|
||||
waterfallFb[j] = waterfallPallet[id];
|
||||
}
|
||||
waterfallUpdate = true;
|
||||
}
|
||||
|
||||
buf_mtx.unlock();
|
||||
}
|
||||
|
||||
void WaterFall::updatePallette(float colors[][3], int colorCount) {
|
||||
for (int i = 0; i < WATERFALL_RESOLUTION; i++) {
|
||||
int lowerId = floorf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
|
||||
int upperId = ceilf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
|
||||
lowerId = std::clamp<int>(lowerId, 0, colorCount - 1);
|
||||
upperId = std::clamp<int>(upperId, 0, colorCount - 1);
|
||||
float ratio = (((float)i / (float)WATERFALL_RESOLUTION) * colorCount) - lowerId;
|
||||
float r = (colors[lowerId][0] * (1.0f - ratio)) + (colors[upperId][0] * (ratio));
|
||||
float g = (colors[lowerId][1] * (1.0f - ratio)) + (colors[upperId][1] * (ratio));
|
||||
float b = (colors[lowerId][2] * (1.0f - ratio)) + (colors[upperId][2] * (ratio));
|
||||
waterfallPallet[i] = ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
|
||||
}
|
||||
}
|
||||
|
||||
void WaterFall::autoRange() {
|
||||
float min = INFINITY;
|
||||
float max = -INFINITY;
|
||||
for (int i = 0; i < dataWidth; i++) {
|
||||
if (latestFFT[i] < min) {
|
||||
min = latestFFT[i];
|
||||
}
|
||||
if (latestFFT[i] > max) {
|
||||
max = latestFFT[i];
|
||||
}
|
||||
}
|
||||
fftMin = min - 5;
|
||||
fftMax = max + 5;
|
||||
}
|
||||
|
||||
void WaterFall::setCenterFrequency(float freq) {
|
||||
centerFreq = freq;
|
||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f);
|
||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0f);
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
float WaterFall::getCenterFrequency() {
|
||||
return centerFreq;
|
||||
}
|
||||
|
||||
void WaterFall::setBandwidth(float bandWidth) {
|
||||
float currentRatio = viewBandwidth / wholeBandwidth;
|
||||
wholeBandwidth = bandWidth;
|
||||
setViewBandwidth(bandWidth * currentRatio);
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
float WaterFall::getBandwidth() {
|
||||
return wholeBandwidth;
|
||||
}
|
||||
|
||||
void WaterFall::setViewBandwidth(float bandWidth) {
|
||||
if (bandWidth == viewBandwidth) {
|
||||
return;
|
||||
}
|
||||
if (abs(viewOffset) + (bandWidth / 2.0f) > wholeBandwidth / 2.0f) {
|
||||
if (viewOffset < 0) {
|
||||
viewOffset = (bandWidth / 2.0f) - (wholeBandwidth / 2.0f);
|
||||
}
|
||||
else {
|
||||
viewOffset = (wholeBandwidth / 2.0f) - (bandWidth / 2.0f);
|
||||
}
|
||||
}
|
||||
viewBandwidth = bandWidth;
|
||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f);
|
||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0f);
|
||||
range = findBestRange(bandWidth, maxHSteps);
|
||||
updateWaterfallFb();
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
float WaterFall::getViewBandwidth() {
|
||||
return viewBandwidth;
|
||||
}
|
||||
|
||||
void WaterFall::setViewOffset(float offset) {
|
||||
if (offset == viewOffset) {
|
||||
return;
|
||||
}
|
||||
if (offset - (viewBandwidth / 2.0f) < -(wholeBandwidth / 2.0f)) {
|
||||
offset = (viewBandwidth / 2.0f) - (wholeBandwidth / 2.0f);
|
||||
}
|
||||
if (offset + (viewBandwidth / 2.0f) > (wholeBandwidth / 2.0f)) {
|
||||
offset = (wholeBandwidth / 2.0f) - (viewBandwidth / 2.0f);
|
||||
}
|
||||
viewOffset = offset;
|
||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f);
|
||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0f);
|
||||
updateWaterfallFb();
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
float WaterFall::getViewOffset() {
|
||||
return viewOffset;
|
||||
}
|
||||
|
||||
void WaterFall::setFFTMin(float min) {
|
||||
fftMin = min;
|
||||
vRange = findBestRange(fftMax - fftMin, maxVSteps);
|
||||
}
|
||||
|
||||
float WaterFall::getFFTMin() {
|
||||
return fftMin;
|
||||
}
|
||||
|
||||
void WaterFall::setFFTMax(float max) {
|
||||
fftMax = max;
|
||||
vRange = findBestRange(fftMax - fftMin, maxVSteps);
|
||||
}
|
||||
|
||||
float WaterFall::getFFTMax() {
|
||||
return fftMax;
|
||||
}
|
||||
|
||||
void WaterFall::setWaterfallMin(float min) {
|
||||
if (min == waterfallMin) {
|
||||
return;
|
||||
}
|
||||
waterfallMin = min;
|
||||
updateWaterfallFb();
|
||||
}
|
||||
|
||||
float WaterFall::getWaterfallMin() {
|
||||
return waterfallMin;
|
||||
}
|
||||
|
||||
void WaterFall::setWaterfallMax(float max) {
|
||||
if (max == waterfallMax) {
|
||||
return;
|
||||
}
|
||||
waterfallMax = max;
|
||||
updateWaterfallFb();
|
||||
}
|
||||
|
||||
float WaterFall::getWaterfallMax() {
|
||||
return waterfallMax;
|
||||
}
|
||||
|
||||
void WaterFall::updateAllVFOs() {
|
||||
for (auto const& [name, vfo] : vfos) {
|
||||
vfo->updateDrawingVars(viewBandwidth, dataWidth, viewOffset, widgetPos, fftHeight);
|
||||
}
|
||||
}
|
||||
|
||||
void WaterfallVFO::setOffset(float offset) {
|
||||
printf("WaterfallVFO::SetOffset: %p\n", this);
|
||||
generalOffset = offset;
|
||||
if (reference == REF_CENTER) {
|
||||
centerOffset = offset;
|
||||
lowerOffset = offset - (bandwidth / 2.0f);
|
||||
upperOffset = offset + (bandwidth / 2.0f);
|
||||
}
|
||||
else if (reference == REF_LOWER) {
|
||||
lowerOffset = offset;
|
||||
centerOffset = offset + (bandwidth / 2.0f);
|
||||
upperOffset = offset + bandwidth;
|
||||
}
|
||||
else if (reference == REF_UPPER) {
|
||||
upperOffset = offset;
|
||||
centerOffset = offset - (bandwidth / 2.0f);
|
||||
lowerOffset = offset - bandwidth;
|
||||
}
|
||||
centerOffsetChanged = true;
|
||||
upperOffsetChanged = true;
|
||||
lowerOffsetChanged = true;
|
||||
redrawRequired = true;
|
||||
}
|
||||
|
||||
void WaterfallVFO::setCenterOffset(float offset) {
|
||||
if (reference == REF_CENTER) {
|
||||
generalOffset = offset;
|
||||
}
|
||||
else if (reference == REF_LOWER) {
|
||||
generalOffset = offset - (bandwidth / 2.0f);
|
||||
}
|
||||
else if (reference == REF_UPPER) {
|
||||
generalOffset = offset + (bandwidth / 2.0f);
|
||||
}
|
||||
centerOffset = offset;
|
||||
lowerOffset = offset - (bandwidth / 2.0f);
|
||||
upperOffset = offset + (bandwidth / 2.0f);
|
||||
centerOffsetChanged = true;
|
||||
upperOffsetChanged = true;
|
||||
lowerOffsetChanged = true;
|
||||
redrawRequired = true;
|
||||
}
|
||||
|
||||
void WaterfallVFO::setBandwidth(float bw) {
|
||||
if (bandwidth == bw || bw < 0) {
|
||||
return;
|
||||
}
|
||||
bandwidth = bw;
|
||||
if (reference == REF_CENTER) {
|
||||
lowerOffset = centerOffset - (bandwidth / 2.0f);
|
||||
upperOffset = centerOffset + (bandwidth / 2.0f);
|
||||
}
|
||||
else if (reference == REF_LOWER) {
|
||||
centerOffset = lowerOffset + (bandwidth / 2.0f);
|
||||
upperOffset = lowerOffset + bandwidth;
|
||||
centerOffsetChanged = true;
|
||||
}
|
||||
else if (reference == REF_UPPER) {
|
||||
centerOffset = upperOffset - (bandwidth / 2.0f);
|
||||
lowerOffset = upperOffset - bandwidth;
|
||||
centerOffsetChanged = true;
|
||||
}
|
||||
redrawRequired = true;
|
||||
}
|
||||
|
||||
void WaterfallVFO::setReference(int ref) {
|
||||
if (reference == ref || ref < 0 || ref >= _REF_COUNT) {
|
||||
return;
|
||||
}
|
||||
reference = ref;
|
||||
setOffset(generalOffset);
|
||||
|
||||
}
|
||||
|
||||
void WaterfallVFO::updateDrawingVars(float viewBandwidth, float dataWidth, float viewOffset, ImVec2 widgetPos, int fftHeight) {
|
||||
float width = (bandwidth / viewBandwidth) * (float)dataWidth;
|
||||
int center = roundf((((centerOffset - viewOffset) / (viewBandwidth / 2.0f)) + 1.0f) * ((float)dataWidth / 2.0f));
|
||||
int left = roundf((((lowerOffset - viewOffset) / (viewBandwidth / 2.0f)) + 1.0f) * ((float)dataWidth / 2.0f));
|
||||
int right = roundf((((upperOffset - viewOffset) / (viewBandwidth / 2.0f)) + 1.0f) * ((float)dataWidth / 2.0f));
|
||||
|
||||
if (left >= 0 && left < dataWidth && reference == REF_LOWER) {
|
||||
lineMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 9);
|
||||
lineMax = ImVec2(widgetPos.x + 50 + left, widgetPos.y + fftHeight + 9);
|
||||
lineVisible = true;
|
||||
}
|
||||
else if (center >= 0 && center < dataWidth && reference == REF_CENTER) {
|
||||
lineMin = ImVec2(widgetPos.x + 50 + center, widgetPos.y + 9);
|
||||
lineMax = ImVec2(widgetPos.x + 50 + center, widgetPos.y + fftHeight + 9);
|
||||
lineVisible = true;
|
||||
}
|
||||
else if (right >= 0 && right < dataWidth && reference == REF_UPPER) {
|
||||
lineMin = ImVec2(widgetPos.x + 50 + right, widgetPos.y + 9);
|
||||
lineMax = ImVec2(widgetPos.x + 50 + right, widgetPos.y + fftHeight + 9);
|
||||
lineVisible = true;
|
||||
}
|
||||
else {
|
||||
lineVisible = false;
|
||||
}
|
||||
|
||||
left = std::clamp<int>(left, 0, dataWidth - 1);
|
||||
right = std::clamp<int>(right, 0, dataWidth - 1);
|
||||
|
||||
rectMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 10);
|
||||
rectMax = ImVec2(widgetPos.x + 51 + right, widgetPos.y + fftHeight + 10);
|
||||
}
|
||||
|
||||
void WaterfallVFO::draw(ImGuiWindow* window, bool selected) {
|
||||
window->DrawList->AddRectFilled(rectMin, rectMax, IM_COL32(255, 255, 255, 50));
|
||||
if (lineVisible) {
|
||||
window->DrawList->AddLine(lineMin, lineMax, selected ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255));
|
||||
}
|
||||
};
|
||||
|
||||
void WaterFall::showWaterfall() {
|
||||
waterfallVisible = true;
|
||||
onResize();
|
||||
}
|
||||
|
||||
void WaterFall::hideWaterfall() {
|
||||
waterfallVisible = false;
|
||||
onResize();
|
||||
}
|
||||
|
||||
void WaterFall::setFFTHeight(int height) {
|
||||
FFTAreaHeight = height;
|
||||
newFFTAreaHeight = height;
|
||||
onResize();
|
||||
}
|
||||
|
||||
int WaterFall::getFFTHeight() {
|
||||
return FFTAreaHeight;
|
||||
}
|
||||
};
|
||||
|
191
core/src/gui/waterfall.h
Normal file
191
core/src/gui/waterfall.h
Normal file
@ -0,0 +1,191 @@
|
||||
#pragma once
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <GL/glew.h>
|
||||
#include <imutils.h>
|
||||
#include <gui/bandplan.h>
|
||||
#include <algorithm>
|
||||
|
||||
#define WATERFALL_RESOLUTION 1000000
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
class WaterfallVFO {
|
||||
public:
|
||||
void setOffset(float offset);
|
||||
void setCenterOffset(float offset);
|
||||
void setBandwidth(float bw);
|
||||
void setReference(int ref);
|
||||
void updateDrawingVars(float viewBandwidth, float dataWidth, float viewOffset, ImVec2 widgetPos, int fftHeight);
|
||||
void draw(ImGuiWindow* window, bool selected);
|
||||
|
||||
enum {
|
||||
REF_LOWER,
|
||||
REF_CENTER,
|
||||
REF_UPPER,
|
||||
_REF_COUNT
|
||||
};
|
||||
|
||||
float generalOffset;
|
||||
float centerOffset;
|
||||
float lowerOffset;
|
||||
float upperOffset;
|
||||
float bandwidth;
|
||||
float snapInterval;
|
||||
int reference = REF_CENTER;
|
||||
|
||||
ImVec2 rectMin;
|
||||
ImVec2 rectMax;
|
||||
ImVec2 lineMin;
|
||||
ImVec2 lineMax;
|
||||
|
||||
bool centerOffsetChanged = false;
|
||||
bool lowerOffsetChanged = false;
|
||||
bool upperOffsetChanged = false;
|
||||
bool redrawRequired = true;
|
||||
bool lineVisible = true;
|
||||
};
|
||||
|
||||
class WaterFall {
|
||||
public:
|
||||
WaterFall();
|
||||
|
||||
void draw();
|
||||
void pushFFT(std::vector<float> data, int n);
|
||||
|
||||
void updatePallette(float colors[][3], int colorCount);
|
||||
|
||||
void setCenterFrequency(float freq);
|
||||
float getCenterFrequency();
|
||||
|
||||
void setBandwidth(float bandWidth);
|
||||
float getBandwidth();
|
||||
|
||||
void setViewBandwidth(float bandWidth);
|
||||
float getViewBandwidth();
|
||||
|
||||
void setViewOffset(float offset);
|
||||
float getViewOffset();
|
||||
|
||||
void setFFTMin(float min);
|
||||
float getFFTMin();
|
||||
|
||||
void setFFTMax(float max);
|
||||
float getFFTMax();
|
||||
|
||||
void setWaterfallMin(float min);
|
||||
float getWaterfallMin();
|
||||
|
||||
void setWaterfallMax(float max);
|
||||
float getWaterfallMax();
|
||||
|
||||
void setZoom(float zoomLevel);
|
||||
void setOffset(float zoomOffset);
|
||||
|
||||
void autoRange();
|
||||
|
||||
void selectFirstVFO();
|
||||
|
||||
void showWaterfall();
|
||||
void hideWaterfall();
|
||||
|
||||
void setFFTHeight(int height);
|
||||
int getFFTHeight();
|
||||
|
||||
bool centerFreqMoved = false;
|
||||
bool vfoFreqChanged = false;
|
||||
bool bandplanEnabled = false;
|
||||
bandplan::BandPlan_t* bandplan = NULL;
|
||||
|
||||
std::map<std::string, WaterfallVFO*> vfos;
|
||||
std::string selectedVFO;
|
||||
bool selectedVFOChanged = false;
|
||||
|
||||
enum {
|
||||
REF_LOWER,
|
||||
REF_CENTER,
|
||||
REF_UPPER,
|
||||
_REF_COUNT
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
void drawWaterfall();
|
||||
void drawFFT();
|
||||
void drawVFOs();
|
||||
void drawBandPlan();
|
||||
void processInputs();
|
||||
void onPositionChange();
|
||||
void onResize();
|
||||
void updateWaterfallFb();
|
||||
void updateWaterfallTexture();
|
||||
void updateAllVFOs();
|
||||
|
||||
bool waterfallUpdate = false;
|
||||
|
||||
uint32_t waterfallPallet[WATERFALL_RESOLUTION];
|
||||
|
||||
ImVec2 widgetPos;
|
||||
ImVec2 widgetEndPos;
|
||||
ImVec2 widgetSize;
|
||||
|
||||
ImVec2 lastWidgetPos;
|
||||
ImVec2 lastWidgetSize;
|
||||
|
||||
ImVec2 fftAreaMin;
|
||||
ImVec2 fftAreaMax;
|
||||
ImVec2 freqAreaMin;
|
||||
ImVec2 freqAreaMax;
|
||||
ImVec2 waterfallAreaMin;
|
||||
ImVec2 waterfallAreaMax;
|
||||
|
||||
ImGuiWindow* window;
|
||||
|
||||
GLuint textureId;
|
||||
|
||||
std::mutex buf_mtx;
|
||||
|
||||
float vRange;
|
||||
|
||||
int maxVSteps;
|
||||
int maxHSteps;
|
||||
|
||||
int dataWidth; // Width of the FFT and waterfall
|
||||
int fftHeight; // Height of the fft graph
|
||||
int waterfallHeight; // Height of the waterfall
|
||||
|
||||
float viewBandwidth;
|
||||
float viewOffset;
|
||||
|
||||
float lowerFreq;
|
||||
float upperFreq;
|
||||
float range;
|
||||
|
||||
float lastDrag;
|
||||
|
||||
int vfoRef = REF_CENTER;
|
||||
|
||||
// Absolute values
|
||||
float centerFreq;
|
||||
float wholeBandwidth;
|
||||
|
||||
// Ranges
|
||||
float fftMin;
|
||||
float fftMax;
|
||||
float waterfallMin;
|
||||
float waterfallMax;
|
||||
|
||||
std::vector<std::vector<float>> rawFFTs;
|
||||
float* latestFFT;
|
||||
|
||||
uint32_t* waterfallFb;
|
||||
|
||||
bool draggingFW = false;
|
||||
int FFTAreaHeight;
|
||||
int newFFTAreaHeight;
|
||||
|
||||
bool waterfallVisible = true;
|
||||
};
|
||||
};
|
Reference in New Issue
Block a user