diff --git a/.gitmodules b/.gitmodules index 57329a9..2decf58 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ [submodule "gui/source/ImGuiColorTextEdit"] path = gui/source/ImGuiColorTextEdit url = https://github.com/BalazsJako/ImGuiColorTextEdit +[submodule "gui/kissfft"] + path = gui/kissfft + url = https://github.com/mborgerding/kissfft 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 @@ -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 buffer; - static std::vector bufferInput; - static auto bufferCirc = CircularBuffer(buffer); - static auto bufferInputCirc = CircularBuffer(bufferInput); + static std::vector buffer; + static std::vector bufferInput; + static std::vector bufferFFTIn; + static std::vector 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(buffer.size() / 2) / size.x; + const float dx = std::ceil(size.x / static_cast(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(); } }