diff --git a/core/src/core.cpp b/core/src/core.cpp index 14e83507..7a1cb96a 100644 --- a/core/src/core.cpp +++ b/core/src/core.cpp @@ -117,6 +117,8 @@ int sdrpp_main(int argc, char* argv[]) { defConfig["colorMap"] = "Classic"; defConfig["fftHold"] = false; defConfig["fftHoldSpeed"] = 60; + defConfig["fftSmoothing"] = false; + defConfig["fftSmoothingSpeed"] = 100; defConfig["fastFFT"] = false; defConfig["fftHeight"] = 300; defConfig["fftRate"] = 20; diff --git a/core/src/gui/menus/display.cpp b/core/src/gui/menus/display.cpp index 11e9eb90..ee730848 100644 --- a/core/src/gui/menus/display.cpp +++ b/core/src/gui/menus/display.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace displaymenu { bool showWaterfall; @@ -22,6 +23,8 @@ namespace displaymenu { bool restartRequired = false; bool fftHold = false; int fftHoldSpeed = 60; + bool fftSmoothing = false; + int fftSmoothingSpeed = 100; OptionList uiScales; @@ -57,8 +60,9 @@ namespace displaymenu { IQFrontEnd::FFTWindow::NUTTALL }; - void updateFFTHoldSpeed() { - gui::waterfall.setFFTHoldSpeed(fftHoldSpeed / (fftRate * 10.0f)); + void updateFFTSpeeds() { + gui::waterfall.setFFTHoldSpeed((float)fftHoldSpeed / ((float)fftRate * 10.0f)); + gui::waterfall.setFFTSmoothingSpeed(std::min((float)fftSmoothingSpeed / (float)(fftRate * 10.0f), 1.0f)); } void init() { @@ -104,7 +108,9 @@ namespace displaymenu { fftHold = core::configManager.conf["fftHold"]; fftHoldSpeed = core::configManager.conf["fftHoldSpeed"]; gui::waterfall.setFFTHold(fftHold); - updateFFTHoldSpeed(); + fftSmoothing = core::configManager.conf["fftSmoothing"]; + fftSmoothingSpeed = core::configManager.conf["fftSmoothingSpeed"]; + updateFFTSpeeds(); // Define and load UI scales uiScales.define(1.0f, "100%", 1.0f); @@ -145,15 +151,32 @@ namespace displaymenu { core::configManager.release(true); } + if (ImGui::Checkbox("FFT Smoothing##_sdrpp", &fftSmoothing)) { + gui::waterfall.setFFTSmoothing(fftSmoothing); + core::configManager.acquire(); + core::configManager.conf["fftSmoothing"] = fftSmoothing; + core::configManager.release(true); + } + ImGui::LeftLabel("FFT Hold Speed"); ImGui::FillWidth(); if (ImGui::InputInt("##sdrpp_fft_hold_speed", &fftHoldSpeed)) { - updateFFTHoldSpeed(); + updateFFTSpeeds(); core::configManager.acquire(); core::configManager.conf["fftHoldSpeed"] = fftHoldSpeed; core::configManager.release(true); } + ImGui::LeftLabel("FFT Smoothing Speed"); + ImGui::FillWidth(); + if (ImGui::InputInt("##sdrpp_fft_smoothing_speed", &fftSmoothingSpeed)) { + fftSmoothingSpeed = std::max(fftSmoothingSpeed, 1); + updateFFTSpeeds(); + core::configManager.acquire(); + core::configManager.conf["fftSmoothingSpeed"] = fftSmoothingSpeed; + core::configManager.release(true); + } + ImGui::LeftLabel("High-DPI Scaling"); ImGui::FillWidth(); if (ImGui::Combo("##sdrpp_ui_scale", &uiScaleId, uiScales.txt)) { @@ -168,7 +191,7 @@ namespace displaymenu { if (ImGui::InputInt("##sdrpp_fft_rate", &fftRate, 1, 10)) { fftRate = std::max(1, fftRate); sigpath::iqFrontEnd.setFFTRate(fftRate); - updateFFTHoldSpeed(); + updateFFTSpeeds(); core::configManager.acquire(); core::configManager.conf["fftRate"] = fftRate; core::configManager.release(true); diff --git a/core/src/gui/widgets/waterfall.cpp b/core/src/gui/widgets/waterfall.cpp index 4fea7632..48404abe 100644 --- a/core/src/gui/widgets/waterfall.cpp +++ b/core/src/gui/widgets/waterfall.cpp @@ -689,6 +689,7 @@ namespace ImGui { void WaterFall::onResize() { std::lock_guard lck(latestFFTMtx); + std::lock_guard lck2(smoothingBufMtx); // return if widget is too small if (widgetSize.x < 100 || widgetSize.y < 100) { return; @@ -740,14 +741,23 @@ namespace ImGui { } latestFFTHold = new float[dataWidth]; + // Reallocate smoothing buffer + if (fftSmoothing) { + if (smoothingBuf) { delete[] smoothingBuf; } + smoothingBuf = new float[dataWidth]; + for (int i = 0; i < dataWidth; i++) { + smoothingBuf[i] = -1000.0f; + } + } + if (waterfallVisible) { delete[] waterfallFb; 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 - latestFFTHold[i] = -1000.0; + latestFFT[i] = -1000.0f; // Hide everything + latestFFTHold[i] = -1000.0f; } fftAreaMin = ImVec2(widgetPos.x + (50.0f * style::uiScale), widgetPos.y + (9.0f * style::uiScale)); @@ -873,6 +883,15 @@ namespace ImGui { fftLines = 1; } + // Apply smoothing if enabled + if (fftSmoothing && latestFFT != NULL && smoothingBuf != NULL && fftLines != 0) { + std::lock_guard lck2(smoothingBufMtx); + volk_32f_s32f_multiply_32f(latestFFT, latestFFT, smoothingAlpha, dataWidth); + volk_32f_s32f_multiply_32f(smoothingBuf, smoothingBuf, smoothingBeta, dataWidth); + volk_32f_x2_add_32f(smoothingBuf, latestFFT, smoothingBuf, dataWidth); + memcpy(latestFFT, smoothingBuf, dataWidth * sizeof(float)); + } + if (selectedVFO != "" && vfos.size() > 0) { float dummy; calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, selectedVFOSNR); @@ -1110,6 +1129,36 @@ namespace ImGui { fftHoldSpeed = speed; } + void WaterFall::setFFTSmoothing(bool enabled) { + std::lock_guard lck(smoothingBufMtx); + fftSmoothing = enabled; + + // Free buffer if not null + if (smoothingBuf) {delete[] smoothingBuf; } + + // If disabled, stop here + if (!enabled) { + smoothingBuf = NULL; + return; + } + + // Allocate and copy existing FFT into it + smoothingBuf = new float[dataWidth]; + if (latestFFT) { + std::lock_guard lck2(latestFFTMtx); + memcpy(smoothingBuf, latestFFT, dataWidth * sizeof(float)); + } + else { + memset(smoothingBuf, 0, dataWidth * sizeof(float)); + } + } + + void WaterFall::setFFTSmoothingSpeed(float speed) { + std::lock_guard lck(smoothingBufMtx); + smoothingAlpha = speed; + smoothingBeta = 1.0f - speed; + } + float* WaterFall::acquireLatestFFT(int& width) { latestFFTMtx.lock(); if (!latestFFT) { diff --git a/core/src/gui/widgets/waterfall.h b/core/src/gui/widgets/waterfall.h index 9bea619e..15748b27 100644 --- a/core/src/gui/widgets/waterfall.h +++ b/core/src/gui/widgets/waterfall.h @@ -169,6 +169,9 @@ namespace ImGui { void setFFTHold(bool hold); void setFFTHoldSpeed(float speed); + void setFFTSmoothing(bool enabled); + void setFFTSmoothingSpeed(float speed); + float* acquireLatestFFT(int& width); void releaseLatestFFT(); @@ -270,6 +273,7 @@ namespace ImGui { std::recursive_mutex buf_mtx; std::recursive_mutex latestFFTMtx; std::mutex texMtx; + std::mutex smoothingBufMtx; float vRange; @@ -304,8 +308,9 @@ namespace ImGui { //std::vector> rawFFTs; int rawFFTSize; float* rawFFTs = NULL; - float* latestFFT; - float* latestFFTHold; + float* latestFFT = NULL; + float* latestFFTHold = NULL; + float* smoothingBuf = NULL; int currentFFTLine = 0; int fftLines = 0; @@ -325,6 +330,10 @@ namespace ImGui { bool fftHold = false; float fftHoldSpeed = 0.3f; + bool fftSmoothing = false; + float smoothingAlpha = 0.5; + float smoothingBeta = 0.5; + // UI Select elements bool fftResizeSelect = false; bool freqScaleSelect = false;