SDRPlusPlus/core/src/gui/widgets/waterfall.cpp

1286 wiersze
51 KiB
C++
Czysty Zwykły widok Historia

#include <gui/widgets/waterfall.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <imutils.h>
#include <algorithm>
#include <volk/volk.h>
#include <spdlog/spdlog.h>
2021-06-17 18:14:23 +00:00
#include <gui/gui.h>
#include <gui/style.h>
2022-01-29 19:35:08 +00:00
#include <keybinds.h>
float DEFAULT_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 }
2020-06-10 02:13:56 +00:00
};
// 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
2020-07-11 19:15:10 +00:00
};
2020-06-15 13:53:45 +00:00
2021-04-16 17:53:47 +00:00
inline double findBestRange(double bandwidth, int maxSteps) {
2020-07-19 19:26:37 +00:00
for (int i = 0; i < 32; i++) {
if (bandwidth / freq_ranges[i] < (double)maxSteps) {
2020-07-11 19:15:10 +00:00
return freq_ranges[i];
}
}
return 50000000.0;
2020-07-11 19:15:10 +00:00
}
2020-06-15 13:53:45 +00:00
2021-04-16 17:53:47 +00:00
inline void printAndScale(double freq, char* buf) {
2021-02-20 14:27:43 +00:00
double freqAbs = fabs(freq);
if (freqAbs < 1000) {
sprintf(buf, "%.6g", freq);
2020-07-11 19:15:10 +00:00
}
2021-02-20 14:27:43 +00:00
else if (freqAbs < 1000000) {
sprintf(buf, "%.6lgK", freq / 1000.0);
2020-07-11 19:15:10 +00:00
}
2021-02-20 14:27:43 +00:00
else if (freqAbs < 1000000000) {
sprintf(buf, "%.6lgM", freq / 1000000.0);
2020-07-11 19:15:10 +00:00
}
2021-02-20 14:27:43 +00:00
else if (freqAbs < 1000000000000) {
sprintf(buf, "%.6lgG", freq / 1000000000.0);
2020-07-11 19:15:10 +00:00
}
}
2020-06-15 13:53:45 +00:00
2020-07-11 19:15:10 +00:00
namespace ImGui {
2020-06-10 02:13:56 +00:00
WaterFall::WaterFall() {
fftMin = -70.0;
fftMax = 0.0;
waterfallMin = -70.0;
waterfallMax = 0.0;
2020-08-20 16:29:23 +00:00
FFTAreaHeight = 300;
newFFTAreaHeight = FFTAreaHeight;
fftHeight = FFTAreaHeight - 50;
2020-07-11 19:15:10 +00:00
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;
2020-07-11 19:15:10 +00:00
updatePallette(DEFAULT_COLOR_MAP, 13);
2020-06-10 02:13:56 +00:00
}
2020-10-22 01:16:11 +00:00
void WaterFall::init() {
glGenTextures(1, &textureId);
}
2020-07-11 19:15:10 +00:00
void WaterFall::drawFFT() {
// Calculate scaling factor
2020-07-19 19:26:37 +00:00
float startLine = floorf(fftMax / vRange) * vRange;
2020-07-11 19:15:10 +00:00
float vertRange = fftMax - fftMin;
float scaleFactor = fftHeight / vertRange;
2020-06-10 02:13:56 +00:00
char buf[100];
2020-08-17 00:39:56 +00:00
ImU32 trace = ImGui::GetColorU32(ImGuiCol_PlotLines);
ImU32 shadow = ImGui::GetColorU32(ImGuiCol_PlotLines, 0.2);
2021-06-23 19:45:38 +00:00
ImU32 text = ImGui::GetColorU32(ImGuiCol_Text);
float textVOffset = 10.0f * style::uiScale;
2020-06-10 02:13:56 +00:00
2020-06-10 16:52:07 +00:00
// Vertical scale
2020-07-19 19:26:37 +00:00
for (float line = startLine; line > fftMin; line -= vRange) {
float yPos = fftAreaMax.y - ((line - fftMin) * scaleFactor);
window->DrawList->AddLine(ImVec2(fftAreaMin.x, roundf(yPos)),
ImVec2(fftAreaMax.x, roundf(yPos)),
IM_COL32(50, 50, 50, 255), style::uiScale);
2020-07-11 19:15:10 +00:00
sprintf(buf, "%d", (int)line);
2020-06-10 16:52:07 +00:00
ImVec2 txtSz = ImGui::CalcTextSize(buf);
window->DrawList->AddText(ImVec2(fftAreaMin.x - txtSz.x - textVOffset, roundf(yPos - (txtSz.y / 2.0))), text, buf);
2020-06-10 02:13:56 +00:00
}
2020-06-10 16:52:07 +00:00
// Horizontal scale
double startFreq = ceilf(lowerFreq / range) * range;
double horizScale = (double)dataWidth / viewBandwidth;
float scaleVOfsset = 7 * style::uiScale;
for (double freq = startFreq; freq < upperFreq; freq += range) {
double xPos = fftAreaMin.x + ((freq - lowerFreq) * horizScale);
window->DrawList->AddLine(ImVec2(roundf(xPos), fftAreaMin.y + 1),
ImVec2(roundf(xPos), fftAreaMax.y),
IM_COL32(50, 50, 50, 255), style::uiScale);
window->DrawList->AddLine(ImVec2(roundf(xPos), fftAreaMax.y),
ImVec2(roundf(xPos), fftAreaMax.y + scaleVOfsset),
text, style::uiScale);
2020-07-11 19:15:10 +00:00
printAndScale(freq, buf);
2020-06-10 16:52:07 +00:00
ImVec2 txtSz = ImGui::CalcTextSize(buf);
window->DrawList->AddText(ImVec2(roundf(xPos - (txtSz.x / 2.0)), fftAreaMax.y + txtSz.y), text, buf);
2020-07-11 19:15:10 +00:00
}
// Data
if (latestFFT != NULL && fftLines != 0) {
for (int i = 1; i < dataWidth; i++) {
double aPos = fftAreaMax.y - ((latestFFT[i - 1] - fftMin) * scaleFactor);
double bPos = fftAreaMax.y - ((latestFFT[i] - fftMin) * scaleFactor);
aPos = std::clamp<double>(aPos, fftAreaMin.y + 1, fftAreaMax.y);
bPos = std::clamp<double>(bPos, fftAreaMin.y + 1, fftAreaMax.y);
window->DrawList->AddLine(ImVec2(fftAreaMin.x + i - 1, roundf(aPos)),
ImVec2(fftAreaMin.x + i, roundf(bPos)), trace, 1.0);
window->DrawList->AddLine(ImVec2(fftAreaMin.x + i, roundf(bPos)),
ImVec2(fftAreaMin.x + i, fftAreaMax.y), shadow, 1.0);
}
2020-07-11 19:15:10 +00:00
}
2020-06-10 02:13:56 +00:00
FFTRedrawArgs args;
args.min = fftAreaMin;
args.max = fftAreaMax;
args.lowFreq = lowerFreq;
args.highFreq = upperFreq;
args.freqToPixelRatio = horizScale;
args.pixelToFreqRatio = viewBandwidth / (double)dataWidth;
args.window = window;
onFFTRedraw.emit(args);
2020-07-11 19:15:10 +00:00
// X Axis
window->DrawList->AddLine(ImVec2(fftAreaMin.x, fftAreaMax.y),
ImVec2(fftAreaMax.x, fftAreaMax.y),
text, style::uiScale);
2020-07-11 19:15:10 +00:00
// Y Axis
window->DrawList->AddLine(ImVec2(fftAreaMin.x, fftAreaMin.y),
ImVec2(fftAreaMin.x, fftAreaMax.y - 1),
text, style::uiScale);
2020-07-11 19:15:10 +00:00
}
void WaterFall::drawWaterfall() {
if (waterfallUpdate) {
waterfallUpdate = false;
updateWaterfallTexture();
2020-06-10 16:52:07 +00:00
}
2021-02-09 01:11:40 +00:00
window->DrawList->AddImage((void*)(intptr_t)textureId, wfMin, wfMax);
ImVec2 mPos = ImGui::GetMousePos();
if (IS_IN_AREA(mPos, wfMin, wfMax) && !gui::mainWindow.lockWaterfallControls && !inputHandled) {
2021-02-09 01:11:40 +00:00
for (auto const& [name, vfo] : vfos) {
2021-05-04 18:41:23 +00:00
window->DrawList->AddRectFilled(vfo->wfRectMin, vfo->wfRectMax, vfo->color);
if (!vfo->lineVisible) { continue; }
window->DrawList->AddLine(vfo->wfLineMin, vfo->wfLineMax, (name == selectedVFO) ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255), style::uiScale);
2021-02-09 01:11:40 +00:00
}
}
2020-07-11 19:15:10 +00:00
}
2020-06-10 16:52:07 +00:00
2020-08-10 00:30:25 +00:00
void WaterFall::drawVFOs() {
for (auto const& [name, vfo] : vfos) {
vfo->draw(window, name == selectedVFO);
}
}
2020-07-19 13:59:44 +00:00
2020-08-11 16:33:42 +00:00
void WaterFall::selectFirstVFO() {
2020-12-08 03:36:37 +00:00
bool available = false;
2020-08-11 16:33:42 +00:00
for (auto const& [name, vfo] : vfos) {
2020-12-08 03:36:37 +00:00
available = true;
2020-08-11 16:33:42 +00:00
selectedVFO = name;
selectedVFOChanged = true;
2020-08-11 16:33:42 +00:00
return;
}
2020-12-08 03:36:37 +00:00
if (!available) {
selectedVFO = "";
selectedVFOChanged = true;
2020-12-08 03:36:37 +00:00
}
2020-08-11 16:33:42 +00:00
}
2020-08-10 00:30:25 +00:00
void WaterFall::processInputs() {
2021-04-17 20:37:50 +00:00
// Pre calculate useful values
WaterfallVFO* selVfo = NULL;
2020-12-08 03:36:37 +00:00
if (selectedVFO != "") {
2021-04-17 20:37:50 +00:00
selVfo = vfos[selectedVFO];
2020-12-08 03:36:37 +00:00
}
2020-07-19 13:59:44 +00:00
ImVec2 mousePos = ImGui::GetMousePos();
ImVec2 drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left);
ImVec2 dragOrigin(mousePos.x - drag.x, mousePos.y - drag.y);
2020-08-16 01:39:05 +00:00
bool mouseHovered, mouseHeld;
bool mouseClicked = ImGui::ButtonBehavior(ImRect(fftAreaMin, wfMax), GetID("WaterfallID"), &mouseHovered, &mouseHeld,
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_PressedOnClick);
2021-02-09 01:11:40 +00:00
mouseInFFTResize = (dragOrigin.x > widgetPos.x && dragOrigin.x < widgetPos.x + widgetSize.x && dragOrigin.y >= widgetPos.y + newFFTAreaHeight - (2.0f * style::uiScale) && dragOrigin.y <= widgetPos.y + newFFTAreaHeight + (2.0f * style::uiScale));
2021-04-23 23:24:27 +00:00
mouseInFreq = IS_IN_AREA(dragOrigin, freqAreaMin, freqAreaMax);
mouseInFFT = IS_IN_AREA(dragOrigin, fftAreaMin, fftAreaMax);
mouseInWaterfall = IS_IN_AREA(dragOrigin, wfMin, wfMax);
int mouseWheel = ImGui::GetIO().MouseWheel;
2020-07-19 13:59:44 +00:00
2021-05-18 00:26:55 +00:00
bool mouseMoved = false;
if (mousePos.x != lastMousePos.x || mousePos.y != lastMousePos.y) { mouseMoved = true; }
lastMousePos = mousePos;
std::string hoveredVFOName = "";
for (auto const& [name, _vfo] : vfos) {
if (ImGui::IsMouseHoveringRect(_vfo->rectMin, _vfo->rectMax) || ImGui::IsMouseHoveringRect(_vfo->wfRectMin, _vfo->wfRectMax)) {
hoveredVFOName = name;
break;
}
}
2021-04-17 20:37:50 +00:00
// Deselect everything if the mouse is released
if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
2021-09-13 21:31:01 +00:00
if (fftResizeSelect) {
FFTAreaHeight = newFFTAreaHeight;
onResize();
}
fftResizeSelect = false;
2021-04-17 20:37:50 +00:00
freqScaleSelect = false;
vfoSelect = false;
vfoBorderSelect = false;
lastDrag = 0;
}
2021-09-13 21:31:01 +00:00
bool targetFound = false;
// If the mouse was clicked anywhere in the waterfall, check if the resize was clicked
if (mouseInFFTResize) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
fftResizeSelect = true;
targetFound = true;
}
}
// If mouse was clicked inside the central part, check what was clicked
if (mouseClicked && !targetFound) {
2021-04-17 20:37:50 +00:00
mouseDownPos = mousePos;
// First, check if a VFO border was selected
2020-08-11 16:33:42 +00:00
for (auto const& [name, _vfo] : vfos) {
2021-04-17 20:37:50 +00:00
if (_vfo->bandwidthLocked) { continue; }
if (_vfo->rectMax.x - _vfo->rectMin.x < 10) { continue; }
2021-04-17 20:37:50 +00:00
bool resizing = false;
if (_vfo->reference != REF_LOWER) {
if (IS_IN_AREA(mousePos, _vfo->lbwSelMin, _vfo->lbwSelMax)) { resizing = true; }
else if (IS_IN_AREA(mousePos, _vfo->wfLbwSelMin, _vfo->wfLbwSelMax)) {
resizing = true;
}
2020-08-11 16:33:42 +00:00
}
2021-04-17 20:37:50 +00:00
if (_vfo->reference != REF_UPPER) {
if (IS_IN_AREA(mousePos, _vfo->rbwSelMin, _vfo->rbwSelMax)) { resizing = true; }
else if (IS_IN_AREA(mousePos, _vfo->wfRbwSelMin, _vfo->wfRbwSelMax)) {
resizing = true;
}
}
2021-04-17 20:37:50 +00:00
if (!resizing) { continue; }
relatedVfo = _vfo;
vfoBorderSelect = true;
targetFound = true;
break;
2020-08-11 16:33:42 +00:00
}
2021-04-17 20:37:50 +00:00
// Next, check if a VFO was selected
2021-05-18 00:26:55 +00:00
if (!targetFound && hoveredVFOName != "") {
selectedVFO = hoveredVFOName;
selectedVFOChanged = true;
targetFound = true;
return;
2020-08-11 16:33:42 +00:00
}
2021-04-17 20:37:50 +00:00
// Now, check frequency scale
if (!targetFound && mouseInFreq) {
freqScaleSelect = true;
}
2020-08-10 00:30:25 +00:00
}
2021-09-13 21:31:01 +00:00
// If the FFT resize bar was selected, resize FFT accordingly
if (fftResizeSelect) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
newFFTAreaHeight = mousePos.y - widgetPos.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), style::uiScale);
2021-09-13 21:31:01 +00:00
return;
}
2021-04-17 20:37:50 +00:00
// If a vfo border is selected, resize VFO accordingly
if (vfoBorderSelect) {
2021-04-18 13:59:37 +00:00
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
2021-04-17 22:47:23 +00:00
double dist = (relatedVfo->reference == REF_CENTER) ? fabsf(mousePos.x - relatedVfo->lineMin.x) : (mousePos.x - relatedVfo->lineMin.x);
if (relatedVfo->reference == REF_UPPER) { dist = -dist; }
2021-04-17 20:37:50 +00:00
double hzDist = dist * (viewBandwidth / (double)dataWidth);
if (relatedVfo->reference == REF_CENTER) {
hzDist *= 2.0;
2020-08-11 16:33:42 +00:00
}
2021-04-17 20:37:50 +00:00
hzDist = std::clamp<double>(hzDist, relatedVfo->minBandwidth, relatedVfo->maxBandwidth);
relatedVfo->setBandwidth(hzDist);
relatedVfo->onUserChangedBandwidth.emit(hzDist);
2021-04-17 20:37:50 +00:00
return;
}
2020-07-19 13:59:44 +00:00
2021-04-17 20:37:50 +00:00
// If the frequency scale is selected, move it
if (freqScaleSelect) {
2021-04-18 13:59:37 +00:00
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
double deltax = drag.x - lastDrag;
2020-07-19 13:59:44 +00:00
lastDrag = drag.x;
double viewDelta = deltax * (viewBandwidth / (double)dataWidth);
2020-07-19 13:59:44 +00:00
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);
if (!centerFrequencyLocked) {
centerFreq += freqOffset;
centerFreqMoved = true;
}
2020-07-19 13:59:44 +00:00
}
if (viewOffset - (viewBandwidth / 2.0) < -(wholeBandwidth / 2.0)) {
double freqOffset = (viewOffset - (viewBandwidth / 2.0)) + (wholeBandwidth / 2.0);
viewOffset = (viewBandwidth / 2.0) - (wholeBandwidth / 2.0);
if (!centerFrequencyLocked) {
centerFreq += freqOffset;
centerFreqMoved = true;
}
2020-07-19 13:59:44 +00:00
}
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
2021-04-23 23:24:27 +00:00
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
if (viewBandwidth != wholeBandwidth) {
updateAllVFOs();
if (_fullUpdate) { updateWaterfallFb(); };
}
return;
}
// If the mouse wheel is moved on the frequency scale
if (mouseWheel != 0 && mouseInFreq) {
2021-07-28 00:18:54 +00:00
viewOffset -= (double)mouseWheel * viewBandwidth / 20.0;
2021-04-23 23:24:27 +00:00
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);
2021-04-24 02:06:04 +00:00
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
if (viewBandwidth != wholeBandwidth) {
updateAllVFOs();
if (_fullUpdate) { updateWaterfallFb(); };
}
return;
}
// If the left and right keys are pressed while hovering the freq scale, move it too
2022-01-29 19:35:08 +00:00
bool leftKeyPressed = ImGui::IsKeyPressed(KB_KEY_LEFT);
if ((leftKeyPressed || ImGui::IsKeyPressed(KB_KEY_RIGHT)) && mouseInFreq) {
viewOffset += leftKeyPressed ? (viewBandwidth / 20.0) : (-viewBandwidth / 20.0);
2021-04-24 02:06:04 +00:00
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);
if (viewBandwidth != wholeBandwidth) {
updateAllVFOs();
2021-04-13 02:54:47 +00:00
if (_fullUpdate) { updateWaterfallFb(); };
}
2021-04-17 20:37:50 +00:00
return;
2020-07-19 13:59:44 +00:00
}
2021-04-17 20:37:50 +00:00
// Finally, if nothing else was selected, just move the VFO
if ((VFOMoveSingleClick ? ImGui::IsMouseClicked(ImGuiMouseButton_Left) : ImGui::IsMouseDown(ImGuiMouseButton_Left)) && (mouseInFFT | mouseInWaterfall) && (mouseMoved || hoveredVFOName == "")) {
2021-04-17 20:37:50 +00:00
if (selVfo != NULL) {
int refCenter = mousePos.x - fftAreaMin.x;
2021-04-17 20:37:50 +00:00
if (refCenter >= 0 && refCenter < dataWidth) {
double off = ((((double)refCenter / ((double)dataWidth / 2.0)) - 1.0) * (viewBandwidth / 2.0)) + viewOffset;
off += centerFreq;
off = (round(off / selVfo->snapInterval) * selVfo->snapInterval) - centerFreq;
selVfo->setOffset(off);
}
}
2021-04-29 21:29:40 +00:00
}
2021-05-18 00:26:55 +00:00
else if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
2021-04-29 21:29:40 +00:00
// Check if a VFO is hovered. If yes, show tooltip
for (auto const& [name, _vfo] : vfos) {
if (ImGui::IsMouseHoveringRect(_vfo->rectMin, _vfo->rectMax) || ImGui::IsMouseHoveringRect(_vfo->wfRectMin, _vfo->wfRectMax)) {
char buf[128];
ImGui::BeginTooltip();
ImGui::TextUnformatted(name.c_str());
2021-04-29 21:29:40 +00:00
2022-01-29 19:35:08 +00:00
if (ImGui::IsKeyDown(KB_KEY_LCTRL) || ImGui::IsKeyDown(KB_KEY_RCTRL)) {
2021-04-29 21:29:40 +00:00
ImGui::Separator();
printAndScale(_vfo->generalOffset + centerFreq, buf);
ImGui::Text("Frequency: %sHz", buf);
printAndScale(_vfo->bandwidth, buf);
ImGui::Text("Bandwidth: %sHz", buf);
ImGui::Text("Bandwidth Locked: %s", _vfo->bandwidthLocked ? "Yes" : "No");
2021-04-30 02:28:08 +00:00
float strength, snr;
if (calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, _vfo, strength, snr)) {
2021-04-30 02:28:08 +00:00
ImGui::Text("Strength: %0.1fdBFS", strength);
ImGui::Text("SNR: %0.1fdB", snr);
}
else {
2022-01-26 13:50:16 +00:00
ImGui::TextUnformatted("Strength: ---.-dBFS");
ImGui::TextUnformatted("SNR: ---.-dB");
2021-04-30 02:28:08 +00:00
}
2021-04-29 21:29:40 +00:00
}
ImGui::EndTooltip();
break;
}
}
}
// Handle Page Up to cycle through VFOs
2022-01-29 19:35:08 +00:00
if (ImGui::IsKeyPressed(KB_KEY_PG_UP) && selVfo != NULL) {
std::string next = (--vfos.end())->first;
std::string lowest = "";
double lowestOffset = INFINITY;
double firstVfoOffset = selVfo->generalOffset;
double smallestDistance = INFINITY;
bool found = false;
for (auto& [_name, _vfo] : vfos) {
if (_vfo->generalOffset > firstVfoOffset && (_vfo->generalOffset - firstVfoOffset) < smallestDistance) {
next = _name;
smallestDistance = (_vfo->generalOffset - firstVfoOffset);
found = true;
}
if (_vfo->generalOffset < lowestOffset) {
lowestOffset = _vfo->generalOffset;
lowest = _name;
}
}
selectedVFO = found ? next : lowest;
selectedVFOChanged = true;
}
// Handle Page Down to cycle through VFOs
2022-01-29 19:35:08 +00:00
if (ImGui::IsKeyPressed(KB_KEY_PG_DOWN) && selVfo != NULL) {
std::string next = (--vfos.end())->first;
std::string highest = "";
double highestOffset = -INFINITY;
double firstVfoOffset = selVfo->generalOffset;
double smallestDistance = INFINITY;
bool found = false;
for (auto& [_name, _vfo] : vfos) {
if (_vfo->generalOffset < firstVfoOffset && (firstVfoOffset - _vfo->generalOffset) < smallestDistance) {
next = _name;
smallestDistance = (firstVfoOffset - _vfo->generalOffset);
found = true;
}
if (_vfo->generalOffset > highestOffset) {
highestOffset = _vfo->generalOffset;
highest = _name;
}
}
selectedVFO = found ? next : highest;
selectedVFOChanged = true;
}
2020-07-19 13:59:44 +00:00
}
bool WaterFall::calculateVFOSignalInfo(float* fftLine, WaterfallVFO* _vfo, float& strength, float& snr) {
if (fftLine == NULL || fftLines <= 0) { return false; }
2021-04-30 02:28:08 +00:00
// Calculate FFT index data
double vfoMinSizeFreq = _vfo->centerOffset - _vfo->bandwidth;
double vfoMinFreq = _vfo->centerOffset - (_vfo->bandwidth / 2.0);
double vfoMaxFreq = _vfo->centerOffset + (_vfo->bandwidth / 2.0);
2021-04-30 02:28:08 +00:00
double vfoMaxSizeFreq = _vfo->centerOffset + _vfo->bandwidth;
int vfoMinSideOffset = std::clamp<int>(((vfoMinSizeFreq / (wholeBandwidth / 2.0)) * (double)(rawFFTSize / 2)) + (rawFFTSize / 2), 0, rawFFTSize);
int vfoMinOffset = std::clamp<int>(((vfoMinFreq / (wholeBandwidth / 2.0)) * (double)(rawFFTSize / 2)) + (rawFFTSize / 2), 0, rawFFTSize);
int vfoMaxOffset = std::clamp<int>(((vfoMaxFreq / (wholeBandwidth / 2.0)) * (double)(rawFFTSize / 2)) + (rawFFTSize / 2), 0, rawFFTSize);
int vfoMaxSideOffset = std::clamp<int>(((vfoMaxSizeFreq / (wholeBandwidth / 2.0)) * (double)(rawFFTSize / 2)) + (rawFFTSize / 2), 0, rawFFTSize);
2021-04-30 02:28:08 +00:00
double avg = 0;
float max = -INFINITY;
int avgCount = 0;
// Calculate Left average
for (int i = vfoMinSideOffset; i < vfoMinOffset; i++) {
avg += fftLine[i];
avgCount++;
}
// Calculate Right average
for (int i = vfoMaxOffset + 1; i < vfoMaxSideOffset; i++) {
avg += fftLine[i];
avgCount++;
}
avg /= (double)(avgCount);
// Calculate max
for (int i = vfoMinOffset; i <= vfoMaxOffset; i++) {
if (fftLine[i] > max) { max = fftLine[i]; }
}
strength = max;
snr = max - avg;
return true;
}
2021-04-12 21:02:45 +00:00
void WaterFall::setFastFFT(bool fastFFT) {
2021-11-03 15:30:32 +00:00
std::lock_guard<std::recursive_mutex> lck(buf_mtx);
2021-04-12 21:02:45 +00:00
_fastFFT = fastFFT;
}
2020-07-11 19:15:10 +00:00
void WaterFall::updateWaterfallFb() {
if (!waterfallVisible || rawFFTs == NULL) {
2020-08-20 16:29:23 +00:00
return;
}
double offsetRatio = viewOffset / (wholeBandwidth / 2.0);
2020-07-11 19:15:10 +00:00
int drawDataSize;
int drawDataStart;
// TODO: Maybe put on the stack for faster alloc?
2020-07-11 19:15:10 +00:00
float* tempData = new float[dataWidth];
float pixel;
float dataRange = waterfallMax - waterfallMin;
int count = std::min<float>(waterfallHeight, fftLines);
2021-07-28 20:53:38 +00:00
if (rawFFTs != NULL && fftLines >= 0) {
for (int i = 0; i < count; i++) {
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
2021-04-12 21:02:45 +00:00
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[((i + currentFFTLine) % waterfallHeight) * rawFFTSize], tempData, _fastFFT);
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))];
}
2020-06-15 13:53:45 +00:00
}
2021-07-28 20:53:38 +00:00
for (int i = count; i < waterfallHeight; i++) {
for (int j = 0; j < dataWidth; j++) {
waterfallFb[(i * dataWidth) + j] = (uint32_t)255 << 24;
}
}
2020-06-15 13:53:45 +00:00
}
2020-07-11 19:15:10 +00:00
delete[] tempData;
waterfallUpdate = true;
}
2020-06-10 02:13:56 +00:00
2020-08-05 19:13:53 +00:00
void WaterFall::drawBandPlan() {
int count = bandplan->bands.size();
double horizScale = (double)dataWidth / viewBandwidth;
double start, end, center, aPos, bPos, cPos, width;
2020-08-05 19:13:53 +00:00
ImVec2 txtSz;
bool startVis, endVis;
uint32_t color, colorTrans;
2021-04-13 23:45:21 +00:00
float height = ImGui::CalcTextSize("0").y * 2.5f;
float bpBottom;
if (bandPlanPos == BANDPLAN_POS_BOTTOM) {
bpBottom = fftAreaMax.y;
2021-04-13 23:45:21 +00:00
}
else {
bpBottom = fftAreaMin.y + height + 1;
2021-04-13 23:45:21 +00:00
}
2021-04-13 23:45:21 +00:00
2020-08-05 19:13:53 +00:00
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 = fftAreaMin.x + ((start - lowerFreq) * horizScale);
bPos = fftAreaMin.x + ((end - lowerFreq) * horizScale);
cPos = fftAreaMin.x + ((center - lowerFreq) * horizScale);
2020-08-05 19:13:53 +00:00
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 <= fftAreaMin.x) {
aPos = fftAreaMin.x + 1;
2020-08-05 19:13:53 +00:00
}
if (bPos <= fftAreaMin.x) {
bPos = fftAreaMin.x + 1;
2020-08-05 19:13:53 +00:00
}
if (width >= 1.0) {
window->DrawList->AddRectFilled(ImVec2(roundf(aPos), bpBottom - height),
ImVec2(roundf(bPos), bpBottom), colorTrans);
2020-08-05 19:13:53 +00:00
if (startVis) {
window->DrawList->AddLine(ImVec2(roundf(aPos), bpBottom - height - 1),
ImVec2(roundf(aPos), bpBottom - 1), color, style::uiScale);
2020-08-05 19:13:53 +00:00
}
if (endVis) {
window->DrawList->AddLine(ImVec2(roundf(bPos), bpBottom - height - 1),
ImVec2(roundf(bPos), bpBottom - 1), color, style::uiScale);
2020-08-05 19:13:53 +00:00
}
}
if (txtSz.x <= width) {
window->DrawList->AddText(ImVec2(cPos - (txtSz.x / 2.0), bpBottom - (height / 2.0f) - (txtSz.y / 2.0f)),
IM_COL32(255, 255, 255, 255), bandplan->bands[i].name.c_str());
2020-08-05 19:13:53 +00:00
}
}
}
2020-07-11 19:15:10 +00:00
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() {
2020-08-04 19:34:56 +00:00
// Nothing to see here...
2020-07-11 19:15:10 +00:00
}
void WaterFall::onResize() {
2020-08-20 16:29:23 +00:00
// return if widget is too small
if (widgetSize.x < 100 || widgetSize.y < 100) {
return;
}
int lastWaterfallHeight = waterfallHeight;
2020-08-20 16:29:23 +00:00
if (waterfallVisible) {
FFTAreaHeight = std::min<int>(FFTAreaHeight, widgetSize.y - (50.0f * style::uiScale));
2021-08-03 22:14:55 +00:00
newFFTAreaHeight = FFTAreaHeight;
fftHeight = FFTAreaHeight - (50.0f * style::uiScale);
waterfallHeight = widgetSize.y - fftHeight - (50.0f * style::uiScale) - 2;
2020-08-20 16:29:23 +00:00
}
else {
fftHeight = widgetSize.y - (50.0f * style::uiScale);
2020-08-20 16:29:23 +00:00
}
dataWidth = widgetSize.x - (60.0f * style::uiScale);
2020-08-20 16:29:23 +00:00
if (waterfallVisible) {
// Raw FFT resize
fftLines = std::min<int>(fftLines, waterfallHeight) - 1;
if (rawFFTs != NULL) {
if (currentFFTLine != 0) {
float* tempWF = new float[currentFFTLine * rawFFTSize];
int moveCount = lastWaterfallHeight - currentFFTLine;
memcpy(tempWF, rawFFTs, currentFFTLine * rawFFTSize * sizeof(float));
memmove(rawFFTs, &rawFFTs[currentFFTLine * rawFFTSize], moveCount * rawFFTSize * sizeof(float));
memcpy(&rawFFTs[moveCount * rawFFTSize], tempWF, currentFFTLine * rawFFTSize * sizeof(float));
delete[] tempWF;
}
currentFFTLine = 0;
rawFFTs = (float*)realloc(rawFFTs, waterfallHeight * rawFFTSize * sizeof(float));
}
else {
rawFFTs = (float*)malloc(waterfallHeight * rawFFTSize * sizeof(float));
}
// ==============
}
2020-08-17 00:39:56 +00:00
if (latestFFT != NULL) {
delete[] latestFFT;
}
2020-07-11 19:15:10 +00:00
latestFFT = new float[dataWidth];
2020-08-20 16:29:23 +00:00
if (waterfallVisible) {
delete[] waterfallFb;
2020-08-20 16:29:23 +00:00
waterfallFb = new uint32_t[dataWidth * waterfallHeight];
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
}
2020-07-11 19:15:10 +00:00
for (int i = 0; i < dataWidth; i++) {
latestFFT[i] = -1000.0; // Hide everything
2020-06-10 02:13:56 +00:00
}
2020-07-19 13:59:44 +00:00
fftAreaMin = ImVec2(widgetPos.x + (50.0f * style::uiScale), widgetPos.y + (9.0f * style::uiScale));
fftAreaMax = ImVec2(fftAreaMin.x + dataWidth, fftAreaMin.y + fftHeight + 1);
freqAreaMin = ImVec2(fftAreaMin.x, fftAreaMax.y + 1);
freqAreaMax = ImVec2(fftAreaMax.x, fftAreaMax.y + (40.0f * style::uiScale));
wfMin = ImVec2(fftAreaMin.x, freqAreaMax.y + 1);
wfMax = ImVec2(fftAreaMin.x + dataWidth, wfMin.y + waterfallHeight);
2020-07-19 13:59:44 +00:00
2020-08-16 01:39:05 +00:00
maxHSteps = dataWidth / (ImGui::CalcTextSize("000.000").x + 10);
maxVSteps = fftHeight / (ImGui::CalcTextSize("000.000").y);
2020-07-19 19:26:37 +00:00
range = findBestRange(viewBandwidth, maxHSteps);
vRange = findBestRange(fftMax - fftMin, maxVSteps);
2020-07-11 19:15:10 +00:00
updateWaterfallFb();
2020-08-10 00:30:25 +00:00
updateAllVFOs();
2020-07-11 19:15:10 +00:00
}
2020-06-10 02:13:56 +00:00
2020-07-11 19:15:10 +00:00
void WaterFall::draw() {
buf_mtx.lock();
window = GetCurrentWindow();
2020-08-17 00:39:56 +00:00
2020-07-11 19:15:10 +00:00
widgetPos = ImGui::GetWindowContentRegionMin();
2020-08-20 16:29:23 +00:00
widgetEndPos = ImGui::GetWindowContentRegionMax();
2020-07-11 19:15:10 +00:00
widgetPos.x += window->Pos.x;
widgetPos.y += window->Pos.y;
2020-08-17 00:39:56 +00:00
widgetEndPos.x += window->Pos.x - 4; // Padding
2020-07-11 19:15:10 +00:00
widgetEndPos.y += window->Pos.y;
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
2020-12-08 03:36:37 +00:00
if (selectedVFO == "" && vfos.size() > 0) {
selectFirstVFO();
}
2020-08-17 00:39:56 +00:00
2020-07-11 19:15:10 +00:00
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
lastWidgetPos = widgetPos;
onPositionChange();
}
if (widgetSize.x != lastWidgetSize.x || widgetSize.y != lastWidgetSize.y) {
lastWidgetSize = widgetSize;
onResize();
}
2020-06-10 02:13:56 +00:00
2021-06-23 19:45:38 +00:00
//window->DrawList->AddRectFilled(widgetPos, widgetEndPos, IM_COL32( 0, 0, 0, 255 ));
ImU32 bg = ImGui::ColorConvertFloat4ToU32(gui::themeManager.waterfallBg);
window->DrawList->AddRectFilled(widgetPos, widgetEndPos, bg);
window->DrawList->AddRect(widgetPos, widgetEndPos, IM_COL32(50, 50, 50, 255), 0.0, 0, style::uiScale);
window->DrawList->AddLine(ImVec2(widgetPos.x, freqAreaMax.y), ImVec2(widgetPos.x + widgetSize.x, freqAreaMax.y), IM_COL32(50, 50, 50, 255), style::uiScale);
2020-06-10 16:52:07 +00:00
if (!gui::mainWindow.lockWaterfallControls) {
inputHandled = false;
InputHandlerArgs args;
args.fftRectMin = fftAreaMin;
args.fftRectMax = fftAreaMax;
args.freqScaleRectMin = freqAreaMin;
args.freqScaleRectMax = freqAreaMax;
args.waterfallRectMin = wfMin;
args.waterfallRectMax = wfMax;
args.lowFreq = lowerFreq;
args.highFreq = upperFreq;
args.freqToPixelRatio = (double)dataWidth / viewBandwidth;
args.pixelToFreqRatio = viewBandwidth / (double)dataWidth;
onInputProcess.emit(args);
if (!inputHandled) { processInputs(); }
}
2021-05-04 00:52:59 +00:00
updateAllVFOs(true);
2020-07-11 19:15:10 +00:00
drawFFT();
2020-08-20 16:29:23 +00:00
if (waterfallVisible) {
drawWaterfall();
}
2020-08-10 00:30:25 +00:00
drawVFOs();
if (bandplan != NULL && bandplanVisible) {
2020-08-05 19:13:53 +00:00
drawBandPlan();
}
2020-07-19 13:59:44 +00:00
2020-08-20 16:29:23 +00:00
if (!waterfallVisible) {
buf_mtx.unlock();
return;
}
2020-07-11 19:15:10 +00:00
buf_mtx.unlock();
}
2020-06-10 16:52:07 +00:00
float* WaterFall::getFFTBuffer() {
if (rawFFTs == NULL) { return NULL; }
2020-07-11 19:15:10 +00:00
buf_mtx.lock();
if (waterfallVisible) {
currentFFTLine--;
fftLines++;
currentFFTLine = ((currentFFTLine + waterfallHeight) % waterfallHeight);
fftLines = std::min<float>(fftLines, waterfallHeight);
return &rawFFTs[currentFFTLine * rawFFTSize];
}
return rawFFTs;
}
void WaterFall::pushFFT() {
if (rawFFTs == NULL) { return; }
double offsetRatio = viewOffset / (wholeBandwidth / 2.0);
int drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
2021-04-12 21:02:45 +00:00
// If in fast mode, apply IIR filtering
float* buf = &rawFFTs[currentFFTLine * rawFFTSize];
if (_fastFFT) {
float last = buf[0];
for (int i = 0; i < rawFFTSize; i++) {
last = (buf[i] * 0.1f) + (last * 0.9f);
buf[i] = last;
}
}
2020-06-15 13:53:45 +00:00
2020-08-20 16:29:23 +00:00
if (waterfallVisible) {
2021-04-12 21:02:45 +00:00
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT, _fastFFT);
2020-08-20 16:29:23 +00:00
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;
2020-06-15 13:53:45 +00:00
}
else {
2021-04-12 21:02:45 +00:00
doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs, latestFFT, _fastFFT);
fftLines = 1;
}
2021-04-30 02:28:08 +00:00
if (selectedVFO != "" && vfos.size() > 0) {
float dummy;
calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, selectedVFOSNR);
2021-04-30 02:28:08 +00:00
}
2020-07-11 19:15:10 +00:00
buf_mtx.unlock();
2020-06-10 02:13:56 +00:00
}
2020-07-11 19:15:10 +00:00
void WaterFall::updatePallette(float colors[][3], int colorCount) {
2021-11-03 15:30:32 +00:00
std::lock_guard<std::recursive_mutex> lck(buf_mtx);
2020-07-11 19:15:10 +00:00
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);
2020-07-20 11:36:16 +00:00
lowerId = std::clamp<int>(lowerId, 0, colorCount - 1);
upperId = std::clamp<int>(upperId, 0, colorCount - 1);
2020-07-11 19:15:10 +00:00
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));
2020-07-11 19:15:10 +00:00
waterfallPallet[i] = ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
}
updateWaterfallFb();
}
void WaterFall::updatePalletteFromArray(float* colors, int colorCount) {
2021-11-03 15:30:32 +00:00
std::lock_guard<std::recursive_mutex> lck(buf_mtx);
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 * 3) + 0] * (1.0 - ratio)) + (colors[(upperId * 3) + 0] * (ratio));
float g = (colors[(lowerId * 3) + 1] * (1.0 - ratio)) + (colors[(upperId * 3) + 1] * (ratio));
float b = (colors[(lowerId * 3) + 2] * (1.0 - ratio)) + (colors[(upperId * 3) + 2] * (ratio));
waterfallPallet[i] = ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
}
updateWaterfallFb();
2020-07-11 19:15:10 +00:00
}
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;
}
2020-06-10 02:13:56 +00:00
void WaterFall::setCenterFrequency(double freq) {
2020-07-11 19:15:10 +00:00
centerFreq = freq;
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
2020-08-10 00:30:25 +00:00
updateAllVFOs();
2020-07-11 19:15:10 +00:00
}
2020-06-10 02:13:56 +00:00
double WaterFall::getCenterFrequency() {
2020-07-11 19:15:10 +00:00
return centerFreq;
}
2020-06-10 02:13:56 +00:00
void WaterFall::setBandwidth(double bandWidth) {
double currentRatio = viewBandwidth / wholeBandwidth;
2020-07-11 19:15:10 +00:00
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);
}
}
2020-08-10 00:30:25 +00:00
updateAllVFOs();
2020-06-10 02:13:56 +00:00
}
double WaterFall::getBandwidth() {
2020-07-11 19:15:10 +00:00
return wholeBandwidth;
}
2020-06-10 02:13:56 +00:00
void WaterFall::setViewBandwidth(double bandWidth) {
2021-11-03 15:30:32 +00:00
std::lock_guard<std::recursive_mutex> lck(buf_mtx);
2020-07-11 19:15:10 +00:00
if (bandWidth == viewBandwidth) {
return;
}
if (abs(viewOffset) + (bandWidth / 2.0) > wholeBandwidth / 2.0) {
2020-07-11 19:15:10 +00:00
if (viewOffset < 0) {
viewOffset = (bandWidth / 2.0) - (wholeBandwidth / 2.0);
2020-06-10 02:13:56 +00:00
}
else {
viewOffset = (wholeBandwidth / 2.0) - (bandWidth / 2.0);
2020-06-10 02:13:56 +00:00
}
2020-07-11 19:15:10 +00:00
}
viewBandwidth = bandWidth;
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
2020-07-19 19:26:37 +00:00
range = findBestRange(bandWidth, maxHSteps);
2021-04-13 02:54:47 +00:00
if (_fullUpdate) { updateWaterfallFb(); };
2020-08-10 00:30:25 +00:00
updateAllVFOs();
2020-07-11 19:15:10 +00:00
}
2020-06-10 02:13:56 +00:00
double WaterFall::getViewBandwidth() {
2020-07-19 13:59:44 +00:00
return viewBandwidth;
}
void WaterFall::setViewOffset(double offset) {
2021-11-03 15:30:32 +00:00
std::lock_guard<std::recursive_mutex> lck(buf_mtx);
2020-07-11 19:15:10 +00:00
if (offset == viewOffset) {
return;
2020-06-10 02:13:56 +00:00
}
if (offset - (viewBandwidth / 2.0) < -(wholeBandwidth / 2.0)) {
offset = (viewBandwidth / 2.0) - (wholeBandwidth / 2.0);
2020-07-19 13:59:44 +00:00
}
if (offset + (viewBandwidth / 2.0) > (wholeBandwidth / 2.0)) {
offset = (wholeBandwidth / 2.0) - (viewBandwidth / 2.0);
2020-07-11 19:15:10 +00:00
}
viewOffset = offset;
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
2021-04-13 02:54:47 +00:00
if (_fullUpdate) { updateWaterfallFb(); };
2020-08-10 00:30:25 +00:00
updateAllVFOs();
2020-07-11 19:15:10 +00:00
}
2020-07-19 13:59:44 +00:00
double WaterFall::getViewOffset() {
2020-07-19 13:59:44 +00:00
return viewOffset;
}
2020-07-11 19:15:10 +00:00
void WaterFall::setFFTMin(float min) {
fftMin = min;
2020-07-19 19:26:37 +00:00
vRange = findBestRange(fftMax - fftMin, maxVSteps);
2020-07-11 19:15:10 +00:00
}
2020-06-10 02:13:56 +00:00
2020-07-11 19:15:10 +00:00
float WaterFall::getFFTMin() {
return fftMin;
}
2020-06-10 02:13:56 +00:00
2020-07-11 19:15:10 +00:00
void WaterFall::setFFTMax(float max) {
fftMax = max;
2020-07-19 19:26:37 +00:00
vRange = findBestRange(fftMax - fftMin, maxVSteps);
2020-06-10 02:13:56 +00:00
}
2020-07-11 19:15:10 +00:00
float WaterFall::getFFTMax() {
return fftMax;
}
2020-06-10 02:13:56 +00:00
2021-04-13 02:54:47 +00:00
void WaterFall::setFullWaterfallUpdate(bool fullUpdate) {
2021-11-03 15:30:32 +00:00
std::lock_guard<std::recursive_mutex> lck(buf_mtx);
2021-04-13 02:54:47 +00:00
_fullUpdate = fullUpdate;
}
2020-07-11 19:15:10 +00:00
void WaterFall::setWaterfallMin(float min) {
2021-11-03 15:30:32 +00:00
std::lock_guard<std::recursive_mutex> lck(buf_mtx);
2020-07-11 19:15:10 +00:00
if (min == waterfallMin) {
return;
}
waterfallMin = min;
2021-04-13 02:54:47 +00:00
if (_fullUpdate) { updateWaterfallFb(); };
2020-07-11 19:15:10 +00:00
}
2020-06-10 02:13:56 +00:00
2020-07-11 19:15:10 +00:00
float WaterFall::getWaterfallMin() {
return waterfallMin;
}
void WaterFall::setWaterfallMax(float max) {
2021-11-03 15:30:32 +00:00
std::lock_guard<std::recursive_mutex> lck(buf_mtx);
2020-07-11 19:15:10 +00:00
if (max == waterfallMax) {
return;
2020-06-10 02:13:56 +00:00
}
2020-07-11 19:15:10 +00:00
waterfallMax = max;
2021-04-13 02:54:47 +00:00
if (_fullUpdate) { updateWaterfallFb(); };
2020-06-10 02:13:56 +00:00
}
2020-07-11 19:15:10 +00:00
float WaterFall::getWaterfallMax() {
return waterfallMax;
2020-06-10 02:13:56 +00:00
}
2020-08-10 00:30:25 +00:00
2021-05-04 00:52:59 +00:00
void WaterFall::updateAllVFOs(bool checkRedrawRequired) {
2020-08-10 00:30:25 +00:00
for (auto const& [name, vfo] : vfos) {
2021-05-04 00:52:59 +00:00
if (checkRedrawRequired && !vfo->redrawRequired) { continue; }
2020-08-10 00:30:25 +00:00
vfo->updateDrawingVars(viewBandwidth, dataWidth, viewOffset, widgetPos, fftHeight);
2021-02-10 20:35:56 +00:00
vfo->wfRectMin = ImVec2(vfo->rectMin.x, wfMin.y);
vfo->wfRectMax = ImVec2(vfo->rectMax.x, wfMax.y);
vfo->wfLineMin = ImVec2(vfo->lineMin.x, wfMin.y - 1);
vfo->wfLineMax = ImVec2(vfo->lineMax.x, wfMax.y - 1);
2021-04-17 20:37:50 +00:00
vfo->wfLbwSelMin = ImVec2(vfo->wfRectMin.x - 2, vfo->wfRectMin.y);
vfo->wfLbwSelMax = ImVec2(vfo->wfRectMin.x + 2, vfo->wfRectMax.y);
vfo->wfRbwSelMin = ImVec2(vfo->wfRectMax.x - 2, vfo->wfRectMin.y);
vfo->wfRbwSelMax = ImVec2(vfo->wfRectMax.x + 2, vfo->wfRectMax.y);
vfo->redrawRequired = false;
2020-08-10 00:30:25 +00:00
}
}
2021-11-03 15:30:32 +00:00
void WaterFall::setRawFFTSize(int size) {
std::lock_guard<std::recursive_mutex> lck(buf_mtx);
rawFFTSize = size;
2021-07-28 20:53:38 +00:00
int wfSize = std::max<int>(1, waterfallHeight);
if (rawFFTs != NULL) {
rawFFTs = (float*)realloc(rawFFTs, rawFFTSize * wfSize * sizeof(float));
}
else {
rawFFTs = (float*)malloc(rawFFTSize * wfSize * sizeof(float));
}
2021-07-28 20:53:38 +00:00
fftLines = 0;
memset(rawFFTs, 0, rawFFTSize * waterfallHeight * sizeof(float));
2021-07-28 20:53:38 +00:00
updateWaterfallFb();
}
2020-08-10 00:30:25 +00:00
2021-04-13 23:45:21 +00:00
void WaterFall::setBandPlanPos(int pos) {
bandPlanPos = pos;
}
void WaterfallVFO::setOffset(double offset) {
2020-08-10 00:30:25 +00:00
generalOffset = offset;
if (reference == REF_CENTER) {
centerOffset = offset;
lowerOffset = offset - (bandwidth / 2.0);
upperOffset = offset + (bandwidth / 2.0);
2020-08-10 00:30:25 +00:00
}
else if (reference == REF_LOWER) {
lowerOffset = offset;
centerOffset = offset + (bandwidth / 2.0);
2020-08-10 00:30:25 +00:00
upperOffset = offset + bandwidth;
}
else if (reference == REF_UPPER) {
upperOffset = offset;
centerOffset = offset - (bandwidth / 2.0);
2020-08-10 00:30:25 +00:00
lowerOffset = offset - bandwidth;
}
centerOffsetChanged = true;
upperOffsetChanged = true;
lowerOffsetChanged = true;
redrawRequired = true;
}
void WaterfallVFO::setCenterOffset(double offset) {
2020-08-10 00:30:25 +00:00
if (reference == REF_CENTER) {
generalOffset = offset;
}
else if (reference == REF_LOWER) {
generalOffset = offset - (bandwidth / 2.0);
2020-08-10 00:30:25 +00:00
}
else if (reference == REF_UPPER) {
generalOffset = offset + (bandwidth / 2.0);
2020-08-10 00:30:25 +00:00
}
centerOffset = offset;
lowerOffset = offset - (bandwidth / 2.0);
upperOffset = offset + (bandwidth / 2.0);
2020-08-10 00:30:25 +00:00
centerOffsetChanged = true;
upperOffsetChanged = true;
lowerOffsetChanged = true;
redrawRequired = true;
}
void WaterfallVFO::setBandwidth(double bw) {
2020-08-10 00:30:25 +00:00
if (bandwidth == bw || bw < 0) {
return;
}
bandwidth = bw;
if (reference == REF_CENTER) {
lowerOffset = centerOffset - (bandwidth / 2.0);
upperOffset = centerOffset + (bandwidth / 2.0);
2020-08-10 00:30:25 +00:00
}
else if (reference == REF_LOWER) {
centerOffset = lowerOffset + (bandwidth / 2.0);
2020-08-10 00:30:25 +00:00
upperOffset = lowerOffset + bandwidth;
2020-08-21 13:34:50 +00:00
centerOffsetChanged = true;
2020-08-10 00:30:25 +00:00
}
else if (reference == REF_UPPER) {
centerOffset = upperOffset - (bandwidth / 2.0);
2020-08-10 00:30:25 +00:00
lowerOffset = upperOffset - bandwidth;
2020-08-21 13:34:50 +00:00
centerOffsetChanged = true;
2020-08-10 00:30:25 +00:00
}
2021-04-17 20:37:50 +00:00
bandwidthChanged = true;
2020-08-10 00:30:25 +00:00
redrawRequired = true;
}
void WaterfallVFO::setReference(int ref) {
if (reference == ref || ref < 0 || ref >= _REF_COUNT) {
return;
}
reference = ref;
2020-08-12 14:43:44 +00:00
setOffset(generalOffset);
2020-08-10 00:30:25 +00:00
}
void WaterfallVFO::setNotchOffset(double offset) {
notchOffset = offset;
redrawRequired = true;
}
void WaterfallVFO::setNotchVisible(bool visible) {
notchVisible = visible;
redrawRequired = true;
}
void WaterfallVFO::updateDrawingVars(double viewBandwidth, float dataWidth, double viewOffset, ImVec2 widgetPos, int fftHeight) {
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));
int notch = roundf((((notchOffset + centerOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
2020-08-10 00:30:25 +00:00
// Check weather the line is visible
2020-08-10 00:30:25 +00:00
if (left >= 0 && left < dataWidth && reference == REF_LOWER) {
lineVisible = true;
}
else if (center >= 0 && center < dataWidth && reference == REF_CENTER) {
lineVisible = true;
}
else if (right >= 0 && right < dataWidth && reference == REF_UPPER) {
lineVisible = true;
}
else {
lineVisible = false;
}
// Calculate the position of the line
if (reference == REF_LOWER) {
lineMin = ImVec2(gui::waterfall.fftAreaMin.x + left, gui::waterfall.fftAreaMin.y);
lineMax = ImVec2(gui::waterfall.fftAreaMin.x + left, gui::waterfall.fftAreaMax.y - 1);
}
else if (reference == REF_CENTER) {
lineMin = ImVec2(gui::waterfall.fftAreaMin.x + center, gui::waterfall.fftAreaMin.y);
lineMax = ImVec2(gui::waterfall.fftAreaMin.x + center, gui::waterfall.fftAreaMax.y - 1);
}
else if (reference == REF_UPPER) {
lineMin = ImVec2(gui::waterfall.fftAreaMin.x + right, gui::waterfall.fftAreaMin.y);
lineMax = ImVec2(gui::waterfall.fftAreaMin.x + right, gui::waterfall.fftAreaMax.y - 1);
}
int _left = left;
int _right = right;
2020-08-10 00:30:25 +00:00
left = std::clamp<int>(left, 0, dataWidth - 1);
right = std::clamp<int>(right, 0, dataWidth - 1);
2021-07-10 19:15:20 +00:00
leftClamped = (left != _left);
rightClamped = (right != _right);
2020-08-10 00:30:25 +00:00
rectMin = ImVec2(gui::waterfall.fftAreaMin.x + left, gui::waterfall.fftAreaMin.y + 1);
rectMax = ImVec2(gui::waterfall.fftAreaMin.x + right + 1, gui::waterfall.fftAreaMax.y);
2021-04-17 01:38:48 +00:00
float gripSize = 2.0f * style::uiScale;
lbwSelMin = ImVec2(rectMin.x - gripSize, rectMin.y);
lbwSelMax = ImVec2(rectMin.x + gripSize, rectMax.y);
rbwSelMin = ImVec2(rectMax.x - gripSize, rectMin.y);
rbwSelMax = ImVec2(rectMax.x + gripSize, rectMax.y);
notchMin = ImVec2(gui::waterfall.fftAreaMin.x + notch - gripSize, gui::waterfall.fftAreaMin.y);
notchMax = ImVec2(gui::waterfall.fftAreaMin.x + notch + gripSize, gui::waterfall.fftAreaMax.y - 1);
2020-08-10 00:30:25 +00:00
}
void WaterfallVFO::draw(ImGuiWindow* window, bool selected) {
2021-05-04 18:41:23 +00:00
window->DrawList->AddRectFilled(rectMin, rectMax, color);
2020-08-10 00:30:25 +00:00
if (lineVisible) {
window->DrawList->AddLine(lineMin, lineMax, selected ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255), style::uiScale);
2020-08-10 00:30:25 +00:00
}
2021-04-17 20:37:50 +00:00
if (notchVisible) {
window->DrawList->AddRectFilled(notchMin, notchMax, IM_COL32(255, 0, 0, 127));
}
if (!gui::mainWindow.lockWaterfallControls && !gui::waterfall.inputHandled) {
2021-06-23 19:45:38 +00:00
ImVec2 mousePos = ImGui::GetMousePos();
if (rectMax.x - rectMin.x < 10) { return; }
if (reference != REF_LOWER && !bandwidthLocked && !leftClamped) {
2021-06-23 19:45:38 +00:00
if (IS_IN_AREA(mousePos, lbwSelMin, lbwSelMax)) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); }
else if (IS_IN_AREA(mousePos, wfLbwSelMin, wfLbwSelMax)) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
}
2021-06-23 19:45:38 +00:00
}
if (reference != REF_UPPER && !bandwidthLocked && !rightClamped) {
2021-06-23 19:45:38 +00:00
if (IS_IN_AREA(mousePos, rbwSelMin, rbwSelMax)) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); }
else if (IS_IN_AREA(mousePos, wfRbwSelMin, wfRbwSelMax)) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
}
2021-06-23 19:45:38 +00:00
}
2021-05-04 18:41:23 +00:00
}
2020-08-10 00:30:25 +00:00
};
2020-08-20 16:29:23 +00:00
void WaterFall::showWaterfall() {
buf_mtx.lock();
if (rawFFTs == NULL) {
spdlog::error("Null rawFFT");
}
waterfallVisible = true;
2020-08-20 16:29:23 +00:00
onResize();
memset(rawFFTs, 0, waterfallHeight * rawFFTSize * sizeof(float));
updateWaterfallFb();
buf_mtx.unlock();
2020-08-20 16:29:23 +00:00
}
void WaterFall::hideWaterfall() {
buf_mtx.lock();
waterfallVisible = false;
2020-08-20 16:29:23 +00:00
onResize();
buf_mtx.unlock();
2020-08-20 16:29:23 +00:00
}
void WaterFall::setFFTHeight(int height) {
FFTAreaHeight = height;
2020-08-21 13:34:50 +00:00
newFFTAreaHeight = height;
buf_mtx.lock();
2020-08-20 16:29:23 +00:00
onResize();
buf_mtx.unlock();
2020-08-20 16:29:23 +00:00
}
2020-08-20 16:29:23 +00:00
int WaterFall::getFFTHeight() {
return FFTAreaHeight;
}
void WaterFall::showBandplan() {
bandplanVisible = true;
}
void WaterFall::hideBandplan() {
bandplanVisible = false;
}
2020-10-20 12:59:42 +00:00
void WaterfallVFO::setSnapInterval(double interval) {
snapInterval = interval;
}
2020-06-10 02:13:56 +00:00
};