mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-07-15 05:23:30 +02:00
More bugfix + folder selection in recorder
This commit is contained in:
127
core/src/gui/widgets/bandplan.cpp
Normal file
127
core/src/gui/widgets/bandplan.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
#include <gui/widgets/bandplan.h>
|
||||
#include <fstream>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
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;
|
||||
file >> data;
|
||||
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;
|
||||
file >> data;
|
||||
file.close();
|
||||
|
||||
colorTable = data.get<std::map<std::string, BandPlanColor_t>>();
|
||||
}
|
||||
};
|
47
core/src/gui/widgets/bandplan.h
Normal file
47
core/src/gui/widgets/bandplan.h
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <json.hpp>
|
||||
#include <imgui/imgui.h>
|
||||
#include <stdint.h>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
namespace bandplan {
|
||||
struct Band_t {
|
||||
std::string name;
|
||||
std::string type;
|
||||
double start;
|
||||
double 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;
|
||||
};
|
165
core/src/gui/widgets/frequency_select.cpp
Normal file
165
core/src/gui/widgets/frequency_select.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
#include <gui/widgets/frequency_select.h>
|
||||
#include <config.h>
|
||||
#include <gui/style.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() {
|
||||
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(style::bigFont);
|
||||
|
||||
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;
|
||||
}
|
38
core/src/gui/widgets/frequency_select.h
Normal file
38
core/src/gui/widgets/frequency_select.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <stdint.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;
|
||||
|
||||
int digits[12];
|
||||
ImVec2 digitBottomMins[12];
|
||||
ImVec2 digitTopMins[12];
|
||||
ImVec2 digitBottomMaxs[12];
|
||||
ImVec2 digitTopMaxs[12];
|
||||
|
||||
char buf[100];
|
||||
};
|
43
core/src/gui/widgets/menu.cpp
Normal file
43
core/src/gui/widgets/menu.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include <gui/widgets/menu.h>
|
||||
#include <imgui/imgui.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;
|
||||
if (!isInOrderList(name)) {
|
||||
order.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::removeEntry(std::string name) {
|
||||
items.erase(name);
|
||||
}
|
||||
|
||||
void Menu::draw() {
|
||||
MenuItem_t item;
|
||||
for (std::string name : order) {
|
||||
if (items.find(name) == items.end()) {
|
||||
continue;
|
||||
}
|
||||
item = items[name];
|
||||
if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
item.drawHandler(item.ctx);
|
||||
ImGui::Spacing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Menu::isInOrderList(std::string name) {
|
||||
for (std::string _name : order) {
|
||||
if (_name == name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
25
core/src/gui/widgets/menu.h
Normal file
25
core/src/gui/widgets/menu.h
Normal file
@ -0,0 +1,25 @@
|
||||
#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:
|
||||
bool isInOrderList(std::string name);
|
||||
|
||||
std::map<std::string, MenuItem_t> items;
|
||||
};
|
819
core/src/gui/widgets/waterfall.cpp
Normal file
819
core/src/gui/widgets/waterfall.cpp
Normal file
@ -0,0 +1,819 @@
|
||||
#include <gui/widgets/waterfall.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <GL/glew.h>
|
||||
#include <imutils.h>
|
||||
#include <algorithm>
|
||||
|
||||
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)];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fix this hacky BS
|
||||
|
||||
double freq_ranges[] = {
|
||||
1.0, 2.0, 2.5, 5.0,
|
||||
10.0, 20.0, 25.0, 50.0,
|
||||
100.0, 200.0, 250.0, 500.0,
|
||||
1000.0, 2000.0, 2500.0, 5000.0,
|
||||
10000.0, 20000.0, 25000.0, 50000.0,
|
||||
100000.0, 200000.0, 250000.0, 500000.0,
|
||||
1000000.0, 2000000.0, 2500000.0, 5000000.0,
|
||||
10000000.0, 20000000.0, 25000000.0, 50000000.0
|
||||
};
|
||||
|
||||
double findBestRange(double bandwidth, int maxSteps) {
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (bandwidth / freq_ranges[i] < (double)maxSteps) {
|
||||
return freq_ranges[i];
|
||||
}
|
||||
}
|
||||
return 50000000.0;
|
||||
}
|
||||
|
||||
void printAndScale(double freq, char* buf) {
|
||||
if (freq < 1000) {
|
||||
sprintf(buf, "%.3lf", freq);
|
||||
}
|
||||
else if (freq < 1000000) {
|
||||
sprintf(buf, "%.3lfK", freq / 1000.0);
|
||||
}
|
||||
else if (freq < 1000000000) {
|
||||
sprintf(buf, "%.3lfM", freq / 1000000.0);
|
||||
}
|
||||
else if (freq < 1000000000000) {
|
||||
sprintf(buf, "%.3lfG", freq / 1000000000.0);
|
||||
}
|
||||
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.0;
|
||||
fftMax = 0.0;
|
||||
waterfallMin = -70.0;
|
||||
waterfallMax = 0.0;
|
||||
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.0;
|
||||
wholeBandwidth = 1.0;
|
||||
|
||||
updatePallette(COLOR_MAP, 13);
|
||||
}
|
||||
|
||||
void WaterFall::init() {
|
||||
glGenTextures(1, &textureId);
|
||||
}
|
||||
|
||||
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.0);
|
||||
sprintf(buf, "%d", (int)line);
|
||||
ImVec2 txtSz = ImGui::CalcTextSize(buf);
|
||||
window->DrawList->AddText(ImVec2(widgetPos.x + 40 - txtSz.x, roundf(yPos - (txtSz.y / 2.0))), IM_COL32( 255, 255, 255, 255 ), buf);
|
||||
}
|
||||
|
||||
// Horizontal scale
|
||||
double startFreq = ceilf(lowerFreq / range) * range;
|
||||
double horizScale = (double)dataWidth / viewBandwidth;
|
||||
for (double freq = startFreq; freq < upperFreq; freq += range) {
|
||||
double 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.0);
|
||||
window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10),
|
||||
ImVec2(roundf(xPos), widgetPos.y + fftHeight + 17),
|
||||
IM_COL32(255, 255, 255, 255), 1.0);
|
||||
printAndScale(freq, buf);
|
||||
ImVec2 txtSz = ImGui::CalcTextSize(buf);
|
||||
window->DrawList->AddText(ImVec2(roundf(xPos - (txtSz.x / 2.0)), widgetPos.y + fftHeight + 10 + txtSz.y), IM_COL32( 255, 255, 255, 255 ), buf);
|
||||
}
|
||||
|
||||
// Data
|
||||
for (int i = 1; i < dataWidth; i++) {
|
||||
double aPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i - 1] - fftMin) * scaleFactor);
|
||||
double bPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i] - fftMin) * scaleFactor);
|
||||
if (aPos < fftMin && bPos < fftMin) {
|
||||
continue;
|
||||
}
|
||||
aPos = std::clamp<double>(aPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10);
|
||||
bPos = std::clamp<double>(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.0);
|
||||
window->DrawList->AddLine(ImVec2(widgetPos.x + 50 + i, roundf(bPos)),
|
||||
ImVec2(widgetPos.x + 50 + i, widgetPos.y + fftHeight + 10), shadow, 1.0);
|
||||
}
|
||||
|
||||
// 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.0);
|
||||
// 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.0);
|
||||
|
||||
|
||||
}
|
||||
|
||||
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), GetID("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)) {
|
||||
double off = ((((double)refCenter / ((double)dataWidth / 2.0)) - 1.0) * (viewBandwidth / 2.0)) + viewOffset;
|
||||
off += centerFreq;
|
||||
off = (round(off / vfo->snapInterval) * vfo->snapInterval) - centerFreq;
|
||||
vfo->setOffset(off);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
double off = ((((double)refCenter / ((double)dataWidth / 2.0)) - 1.0) * (viewBandwidth / 2.0)) + viewOffset;
|
||||
off += centerFreq;
|
||||
off = (round(off / vfo->snapInterval) * vfo->snapInterval) - centerFreq;
|
||||
vfo->setOffset(off);
|
||||
}
|
||||
}
|
||||
|
||||
// Draging frequency scale
|
||||
if (draging && mouseInFreq) {
|
||||
double deltax = drag.x - lastDrag;
|
||||
lastDrag = drag.x;
|
||||
double viewDelta = deltax * (viewBandwidth / (double)dataWidth);
|
||||
|
||||
viewOffset -= viewDelta;
|
||||
|
||||
if (viewOffset + (viewBandwidth / 2.0) > wholeBandwidth / 2.0) {
|
||||
double freqOffset = (viewOffset + (viewBandwidth / 2.0)) - (wholeBandwidth / 2.0);
|
||||
viewOffset = (wholeBandwidth / 2.0) - (viewBandwidth / 2.0);
|
||||
centerFreq += freqOffset;
|
||||
centerFreqMoved = true;
|
||||
}
|
||||
if (viewOffset - (viewBandwidth / 2.0) < -(wholeBandwidth / 2.0)) {
|
||||
double freqOffset = (viewOffset - (viewBandwidth / 2.0)) + (wholeBandwidth / 2.0);
|
||||
viewOffset = (viewBandwidth / 2.0) - (wholeBandwidth / 2.0);
|
||||
centerFreq += freqOffset;
|
||||
centerFreqMoved = true;
|
||||
}
|
||||
|
||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
|
||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
|
||||
updateWaterfallFb();
|
||||
}
|
||||
else {
|
||||
lastDrag = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void WaterFall::updateWaterfallFb() {
|
||||
if (!waterfallVisible) {
|
||||
return;
|
||||
}
|
||||
double offsetRatio = viewOffset / (wholeBandwidth / 2.0);
|
||||
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 = (((double)size / 2.0) * (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();
|
||||
double horizScale = (double)dataWidth / viewBandwidth;
|
||||
double 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<double>(start, lowerFreq, upperFreq);
|
||||
end = std::clamp<double>(end, lowerFreq, upperFreq);
|
||||
center = (start + end) / 2.0;
|
||||
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.0) {
|
||||
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.0), 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.0;
|
||||
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.0; // 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.0;
|
||||
|
||||
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.0);
|
||||
|
||||
processInputs();
|
||||
|
||||
drawFFT();
|
||||
if (waterfallVisible) {
|
||||
drawWaterfall();
|
||||
}
|
||||
drawVFOs();
|
||||
if (bandplan != NULL && bandplanVisible) {
|
||||
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();
|
||||
double offsetRatio = viewOffset / (wholeBandwidth / 2.0);
|
||||
int drawDataSize = (viewBandwidth / wholeBandwidth) * data.size();
|
||||
int drawDataStart = (((double)data.size() / 2.0) * (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.0 - ratio)) + (colors[upperId][0] * (ratio));
|
||||
float g = (colors[lowerId][1] * (1.0 - ratio)) + (colors[upperId][1] * (ratio));
|
||||
float b = (colors[lowerId][2] * (1.0 - 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(double freq) {
|
||||
centerFreq = freq;
|
||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
|
||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
double WaterFall::getCenterFrequency() {
|
||||
return centerFreq;
|
||||
}
|
||||
|
||||
void WaterFall::setBandwidth(double bandWidth) {
|
||||
double currentRatio = viewBandwidth / wholeBandwidth;
|
||||
wholeBandwidth = bandWidth;
|
||||
setViewBandwidth(bandWidth * currentRatio);
|
||||
for (auto const& [name, vfo] : vfos) {
|
||||
if (vfo->lowerOffset < -(bandWidth / 2)) {
|
||||
vfo->setCenterOffset(-(bandWidth / 2));
|
||||
}
|
||||
if (vfo->upperOffset > (bandWidth / 2)) {
|
||||
vfo->setCenterOffset(bandWidth / 2);
|
||||
}
|
||||
}
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
double WaterFall::getBandwidth() {
|
||||
return wholeBandwidth;
|
||||
}
|
||||
|
||||
void WaterFall::setViewBandwidth(double bandWidth) {
|
||||
if (bandWidth == viewBandwidth) {
|
||||
return;
|
||||
}
|
||||
if (abs(viewOffset) + (bandWidth / 2.0) > wholeBandwidth / 2.0) {
|
||||
if (viewOffset < 0) {
|
||||
viewOffset = (bandWidth / 2.0) - (wholeBandwidth / 2.0);
|
||||
}
|
||||
else {
|
||||
viewOffset = (wholeBandwidth / 2.0) - (bandWidth / 2.0);
|
||||
}
|
||||
}
|
||||
viewBandwidth = bandWidth;
|
||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
|
||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
|
||||
range = findBestRange(bandWidth, maxHSteps);
|
||||
updateWaterfallFb();
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
double WaterFall::getViewBandwidth() {
|
||||
return viewBandwidth;
|
||||
}
|
||||
|
||||
void WaterFall::setViewOffset(double offset) {
|
||||
if (offset == viewOffset) {
|
||||
return;
|
||||
}
|
||||
if (offset - (viewBandwidth / 2.0) < -(wholeBandwidth / 2.0)) {
|
||||
offset = (viewBandwidth / 2.0) - (wholeBandwidth / 2.0);
|
||||
}
|
||||
if (offset + (viewBandwidth / 2.0) > (wholeBandwidth / 2.0)) {
|
||||
offset = (wholeBandwidth / 2.0) - (viewBandwidth / 2.0);
|
||||
}
|
||||
viewOffset = offset;
|
||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
|
||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
|
||||
updateWaterfallFb();
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
double 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(double offset) {
|
||||
generalOffset = offset;
|
||||
if (reference == REF_CENTER) {
|
||||
centerOffset = offset;
|
||||
lowerOffset = offset - (bandwidth / 2.0);
|
||||
upperOffset = offset + (bandwidth / 2.0);
|
||||
}
|
||||
else if (reference == REF_LOWER) {
|
||||
lowerOffset = offset;
|
||||
centerOffset = offset + (bandwidth / 2.0);
|
||||
upperOffset = offset + bandwidth;
|
||||
}
|
||||
else if (reference == REF_UPPER) {
|
||||
upperOffset = offset;
|
||||
centerOffset = offset - (bandwidth / 2.0);
|
||||
lowerOffset = offset - bandwidth;
|
||||
}
|
||||
centerOffsetChanged = true;
|
||||
upperOffsetChanged = true;
|
||||
lowerOffsetChanged = true;
|
||||
redrawRequired = true;
|
||||
}
|
||||
|
||||
void WaterfallVFO::setCenterOffset(double offset) {
|
||||
if (reference == REF_CENTER) {
|
||||
generalOffset = offset;
|
||||
}
|
||||
else if (reference == REF_LOWER) {
|
||||
generalOffset = offset - (bandwidth / 2.0);
|
||||
}
|
||||
else if (reference == REF_UPPER) {
|
||||
generalOffset = offset + (bandwidth / 2.0);
|
||||
}
|
||||
centerOffset = offset;
|
||||
lowerOffset = offset - (bandwidth / 2.0);
|
||||
upperOffset = offset + (bandwidth / 2.0);
|
||||
centerOffsetChanged = true;
|
||||
upperOffsetChanged = true;
|
||||
lowerOffsetChanged = true;
|
||||
redrawRequired = true;
|
||||
}
|
||||
|
||||
void WaterfallVFO::setBandwidth(double bw) {
|
||||
if (bandwidth == bw || bw < 0) {
|
||||
return;
|
||||
}
|
||||
bandwidth = bw;
|
||||
if (reference == REF_CENTER) {
|
||||
lowerOffset = centerOffset - (bandwidth / 2.0);
|
||||
upperOffset = centerOffset + (bandwidth / 2.0);
|
||||
}
|
||||
else if (reference == REF_LOWER) {
|
||||
centerOffset = lowerOffset + (bandwidth / 2.0);
|
||||
upperOffset = lowerOffset + bandwidth;
|
||||
centerOffsetChanged = true;
|
||||
}
|
||||
else if (reference == REF_UPPER) {
|
||||
centerOffset = upperOffset - (bandwidth / 2.0);
|
||||
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(double viewBandwidth, float dataWidth, double viewOffset, ImVec2 widgetPos, int fftHeight) {
|
||||
double width = (bandwidth / viewBandwidth) * (double)dataWidth;
|
||||
int center = roundf((((centerOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
|
||||
int left = roundf((((lowerOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
|
||||
int right = roundf((((upperOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void WaterFall::showBandplan() {
|
||||
bandplanVisible = true;
|
||||
}
|
||||
|
||||
void WaterFall::hideBandplan() {
|
||||
bandplanVisible = false;
|
||||
}
|
||||
|
||||
void WaterfallVFO::setSnapInterval(double interval) {
|
||||
snapInterval = interval;
|
||||
}
|
||||
};
|
||||
|
196
core/src/gui/widgets/waterfall.h
Normal file
196
core/src/gui/widgets/waterfall.h
Normal file
@ -0,0 +1,196 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <gui/widgets/bandplan.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
#include <GL/glew.h>
|
||||
|
||||
#define WATERFALL_RESOLUTION 1000000
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
class WaterfallVFO {
|
||||
public:
|
||||
void setOffset(double offset);
|
||||
void setCenterOffset(double offset);
|
||||
void setBandwidth(double bw);
|
||||
void setReference(int ref);
|
||||
void setSnapInterval(double interval);
|
||||
void updateDrawingVars(double viewBandwidth, float dataWidth, double viewOffset, ImVec2 widgetPos, int fftHeight); // NOTE: Datawidth double???
|
||||
void draw(ImGuiWindow* window, bool selected);
|
||||
|
||||
enum {
|
||||
REF_LOWER,
|
||||
REF_CENTER,
|
||||
REF_UPPER,
|
||||
_REF_COUNT
|
||||
};
|
||||
|
||||
double generalOffset;
|
||||
double centerOffset;
|
||||
double lowerOffset;
|
||||
double upperOffset;
|
||||
double bandwidth;
|
||||
double snapInterval = 5000;
|
||||
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 init();
|
||||
|
||||
void draw();
|
||||
void pushFFT(std::vector<float> data, int n);
|
||||
|
||||
void updatePallette(float colors[][3], int colorCount);
|
||||
|
||||
void setCenterFrequency(double freq);
|
||||
double getCenterFrequency();
|
||||
|
||||
void setBandwidth(double bandWidth);
|
||||
double getBandwidth();
|
||||
|
||||
void setViewBandwidth(double bandWidth);
|
||||
double getViewBandwidth();
|
||||
|
||||
void setViewOffset(double offset);
|
||||
double 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(double zoomLevel);
|
||||
void setOffset(double zoomOffset);
|
||||
|
||||
void autoRange();
|
||||
|
||||
void selectFirstVFO();
|
||||
|
||||
void showWaterfall();
|
||||
void hideWaterfall();
|
||||
|
||||
void showBandplan();
|
||||
void hideBandplan();
|
||||
|
||||
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
|
||||
|
||||
double viewBandwidth;
|
||||
double viewOffset;
|
||||
|
||||
double lowerFreq;
|
||||
double upperFreq;
|
||||
double range;
|
||||
|
||||
float lastDrag;
|
||||
|
||||
int vfoRef = REF_CENTER;
|
||||
|
||||
// Absolute values
|
||||
double centerFreq;
|
||||
double 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;
|
||||
bool bandplanVisible = false;
|
||||
};
|
||||
};
|
Reference in New Issue
Block a user