From f41cfe8196909c6ace58a9dc7e5a7b4cb24c23ba Mon Sep 17 00:00:00 2001
From: Clyne Sullivan <clyne@bitgloo.com>
Date: Sat, 23 Sep 2023 11:44:50 -0400
Subject: add frequency plot

---
 gui/Makefile              |   7 ++-
 gui/kissfft               |   1 +
 gui/source/device.cpp     |   4 +-
 gui/source/gui_device.cpp | 108 +++++++++++++++++++++++++++++++++++++++++-----
 4 files changed, 106 insertions(+), 14 deletions(-)
 create mode 160000 gui/kissfft

(limited to 'gui')

diff --git a/gui/Makefile b/gui/Makefile
index 0ff9c48..0a5494c 100644
--- a/gui/Makefile
+++ b/gui/Makefile
@@ -1,6 +1,8 @@
 CXX = g++
 
 CXXFILES := \
+    kissfft/kiss_fft.c \
+    kissfft/kiss_fftr.c \
     source/serial/src/serial.cc \
     source/imgui/backends/imgui_impl_sdl2.cpp \
     source/imgui/backends/imgui_impl_opengl2.cpp \
@@ -13,7 +15,8 @@ CXXFILES := \
     $(wildcard source/stmdsp/*.cpp) \
     $(wildcard source/*.cpp)
 
-CXXFLAGS := -std=c++20 -O2 \
+CXXFLAGS := -std=c++20 -Og -ggdb -g3 \
+	    -Ikissfft \
             -Isource -Isource/imgui -Isource/stmdsp -Isource/serial/include \
             -Isource/ImGuiColorTextEdit -Isource/ImGuiFileDialog \
             $(shell sdl2-config --cflags) \
@@ -32,7 +35,7 @@ LDFLAGS = $(shell sdl2-config --libs) -lGL -lpthread
 OUTPUT := stmdspgui
 endif
 
-OFILES := $(patsubst %.cc, %.o, $(patsubst %.cpp, %.o, $(CXXFILES)))
+OFILES := $(patsubst %.c, %.o, $(patsubst %.cc, %.o, $(patsubst %.cpp, %.o, $(CXXFILES))))
 
 all: $(OUTPUT)
 
diff --git a/gui/kissfft b/gui/kissfft
new file mode 160000
index 0000000..8f47a67
--- /dev/null
+++ b/gui/kissfft
@@ -0,0 +1 @@
+Subproject commit 8f47a67f595a6641c566087bf5277034be64f24d
diff --git a/gui/source/device.cpp b/gui/source/device.cpp
index 9c50a0d..d959c30 100644
--- a/gui/source/device.cpp
+++ b/gui/source/device.cpp
@@ -316,7 +316,7 @@ bool deviceConnect()
     return false;
 }
 
-void deviceStart(bool logResults, bool drawSamples)
+void deviceStart(bool fetchSamples)
 {
     if (!m_device) {
         log("No device connected.");
@@ -336,7 +336,7 @@ void deviceStart(bool logResults, bool drawSamples)
         log("Ready.");
     } else {
         m_device->continuous_start();
-        if (drawSamples || logResults || wavOutput.valid())
+        if (fetchSamples || wavOutput.valid())
             std::thread(drawSamplesTask, m_device).detach();
 
         log("Running.");
diff --git a/gui/source/gui_device.cpp b/gui/source/gui_device.cpp
index 82ce5be..bab138f 100644
--- a/gui/source/gui_device.cpp
+++ b/gui/source/gui_device.cpp
@@ -2,7 +2,7 @@
 #include "imgui.h"
 #include "imgui_internal.h"
 #include "ImGuiFileDialog.h"
-
+#include "kiss_fftr.h"
 #include "stmdsp.hpp"
 
 #include <array>
@@ -45,7 +45,7 @@ void deviceLoadAudioFile(const std::string& file);
 void deviceLoadLogFile(const std::string& file);
 void deviceSetSampleRate(unsigned int index);
 void deviceSetInputDrawing(bool enabled);
-void deviceStart(bool logResults, bool drawSamples);
+void deviceStart(bool fetchSamples);
 void deviceStartMeasurement();
 void deviceUpdateDrawBufferSize(double timeframe);
 std::size_t pullFromDrawQueue(
@@ -57,6 +57,7 @@ static std::string sampleRatePreview = "?";
 static bool measureCodeTime = false;
 static bool logResults = false;
 static bool drawSamples = false;
+static bool drawFrequencies = false;
 static bool popupRequestBuffer = false;
 static bool popupRequestSiggen = false;
 static bool popupRequestLog = false;
@@ -74,6 +75,7 @@ void deviceRenderDisconnect()
     measureCodeTime = false;
     logResults = false;
     drawSamples = false;
+    drawFrequencies = false;
 }
 
 void deviceRenderMenu()
@@ -104,7 +106,7 @@ void deviceRenderMenu()
         static std::string startLabel ("Start");
         addMenuItem(startLabel, isConnected, [&] {
                 startLabel = isRunning ? "Start" : "Stop";
-                deviceStart(logResults, drawSamples);
+                deviceStart(logResults || drawSamples || drawFrequencies);
                 if (logResults && isRunning)
                     logResults = false;
             });
@@ -118,7 +120,8 @@ void deviceRenderMenu()
         if (!isConnected || isRunning)
             ImGui::PushDisabled(); // Hey, pushing disabled!
 
-        ImGui::Checkbox("Draw samples", &drawSamples);
+        ImGui::Checkbox("Plot over time", &drawSamples);
+        ImGui::Checkbox("Plot over freq.", &drawFrequencies);
         if (ImGui::Checkbox("Log results...", &logResults)) {
             if (logResults)
                 popupRequestLog = true;
@@ -296,13 +299,16 @@ void deviceRenderWidgets()
 
 void deviceRenderDraw()
 {
-    if (drawSamples) {
-        static std::vector<stmdsp::dacsample_t> buffer;
-        static std::vector<stmdsp::dacsample_t> bufferInput;
-        static auto bufferCirc = CircularBuffer(buffer);
-        static auto bufferInputCirc = CircularBuffer(bufferInput);
+    static std::vector<stmdsp::dacsample_t> buffer;
+    static std::vector<stmdsp::dacsample_t> bufferInput;
+    static std::vector<kiss_fft_scalar> bufferFFTIn;
+    static std::vector<kiss_fft_cpx> bufferFFTOut;
+    static auto bufferCirc = CircularBuffer(buffer);
+    static auto bufferInputCirc = CircularBuffer(bufferInput);
+    static bool drawSamplesInput = false;
+    static kiss_fftr_cfg kisscfg;
 
-        static bool drawSamplesInput = false;
+    if (drawSamples) {
         static unsigned int yMinMax = 4095;
 
         ImGui::Begin("draw", &drawSamples);
@@ -433,6 +439,88 @@ void deviceRenderDraw()
             }
         }
 
+        ImGui::End();
+    } else if (drawFrequencies) {
+        ImGui::Begin("draw", &drawFrequencies);
+
+        ImGui::Text("Time: %0.3f sec", drawSamplesTimeframe);
+        ImGui::SameLine();
+        if (ImGui::Button("-", {30, 0})) {
+            drawSamplesTimeframe = std::max(drawSamplesTimeframe / 2., 0.0078125);
+            deviceUpdateDrawBufferSize(drawSamplesTimeframe);
+        }
+        ImGui::SameLine();
+        if (ImGui::Button("+", {30, 0})) {
+            drawSamplesTimeframe = std::min(drawSamplesTimeframe * 2, 32.);
+            deviceUpdateDrawBufferSize(drawSamplesTimeframe);
+        }
+
+        auto newSize = pullFromDrawQueue(bufferCirc);
+        if (newSize > 0) {
+            buffer.resize(newSize);
+            bufferFFTIn.resize(newSize);
+            bufferFFTOut.resize(newSize);
+            bufferCirc = CircularBuffer(buffer);
+            pullFromDrawQueue(bufferCirc);
+
+            kiss_fftr_free(kisscfg);
+            kisscfg = kiss_fftr_alloc(buffer.size(), false, nullptr, nullptr);
+        }
+
+        std::copy(buffer.begin(), buffer.end(), bufferFFTIn.begin());
+        kiss_fftr(kisscfg, bufferFFTIn.data(), bufferFFTOut.data());
+
+        auto drawList = ImGui::GetWindowDrawList();
+        ImVec2 p0 = ImGui::GetWindowPos();
+        auto size = ImGui::GetWindowSize();
+        p0.y += 65;
+        size.y -= 70;
+        drawList->AddRectFilled(p0, {p0.x + size.x, p0.y + size.y}, IM_COL32_BLACK);
+
+        const auto lcMinor = ImGui::GetColorU32(IM_COL32(40, 40, 40, 255));
+        const auto lcMajor = ImGui::GetColorU32(IM_COL32(140, 140, 140, 255));
+
+        {
+            const float yinc = size.y / 10.f;
+            for (int i = 1; i < 10; ++i) {
+                drawList->AddLine({p0.x, p0.y + i * yinc}, {p0.x + size.x, p0.y + i * yinc}, (i % 2) ? lcMinor : lcMajor);
+            }
+        }
+        {
+            const float xinc = size.x / 10.f;
+            for (int i = 1; i < 10; ++i) {
+                drawList->AddLine({p0.x + i * xinc, p0.y}, {p0.x + i * xinc, p0.y + size.y}, (i % 2) ? lcMinor : lcMajor);
+            }
+        }
+
+        const auto Fs = m_device->get_sample_rate();
+
+        const float di = static_cast<float>(buffer.size() / 2) / size.x;
+        const float dx = std::ceil(size.x / static_cast<float>(buffer.size()));
+        ImVec2 pp = p0;
+        float i = 0;
+        while (pp.x < p0.x + size.x) {
+            unsigned int idx = i;
+            float n = std::clamp(bufferFFTOut[idx].r / Fs / 4.f, 0.f, 1.f);
+            i += di;
+
+            ImVec2 next (pp.x + dx, p0.y + size.y * (1 - n));
+            drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(255, 0, 0, 255)), 2.f);
+            pp = next;
+        }
+
+        const auto mouse = ImGui::GetMousePos();
+        if (mouse.x > p0.x && mouse.x < p0.x + size.x &&
+            mouse.y > p0.y && mouse.y < p0.y + size.y)
+        {
+            char buf[16];
+            drawList->AddLine({mouse.x, p0.y}, {mouse.x, p0.y + size.y}, IM_COL32(255, 255, 0, 255));
+
+            const std::size_t si = (mouse.x - p0.x) / size.x * Fs / 2;
+            snprintf(buf, sizeof(buf), "   %5luHz", si);
+            drawList->AddText(mouse, IM_COL32(255, 0, 0, 255), buf);
+        }
+
         ImGui::End();
     }
 }
-- 
cgit v1.2.3