Compare commits

...

6 Commits

3
.gitmodules vendored

@ -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

Binary file not shown.

@ -162,10 +162,10 @@ INCDIR = $(CONFDIR) $(ALLINC) $(TESTINC) \
source source/periph
# Define C warning options here.
CWARN = -Wall -Wextra -Wundef -Wstrict-prototypes -pedantic
CWARN = -Wall -Wextra -Wundef -Wstrict-prototypes -pedantic -Werror
# Define C++ warning options here.
CPPWARN = -Wall -Wextra -Wundef -pedantic -Wno-volatile
CPPWARN = -Wall -Wextra -Wundef -pedantic -Wno-volatile -Werror -Wconversion
#
# Project, target, sources and paths
@ -217,6 +217,17 @@ include $(RULESPATH)/rules.mk
# Custom rules
#
check:
cppcheck --std=c++17 --enable=warning,style,performance,portability --force $(shell find source/ -name "*.cpp" -or -name "*.hpp" -or -name "*.c" -or -name "*.h" -type f)
tidy:
clang-tidy \
-warnings-as-errors=* \
--extra-arg=-I --extra-arg=/usr/lib/gcc/arm-none-eabi/13/include/g++-v13 \
--extra-arg=-I --extra-arg=/usr/lib/gcc/arm-none-eabi/13/include/g++-v13/arm-none-eabi \
$(CPPSRC)
#
# Custom rules
##############################################################################

@ -223,7 +223,7 @@ void sampleRate(unsigned char *cmd)
{
if (EM.assert(USBSerial::read(&cmd[1], 1) == 1, Error::BadParamSize)) {
if (cmd[1] == 0xFF) {
unsigned char r = SClock::getRate();
auto r = static_cast<unsigned char>(SClock::getRate());
USBSerial::write(&r, 1);
} else {
auto r = static_cast<SClock::Rate>(cmd[1]);

@ -2,7 +2,7 @@
* @file communication.hpp
* @brief Manages communication with the host computer.
*
* Copyright (C) 2021 Clyne Sullivan
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
@ -17,9 +17,17 @@
class CommunicationManager
{
public:
/**
* Starts the communication manager thread (threadComm).
*/
static void begin();
private:
/**
* Continuously polls the USB serial connection, handling received commands
* as necessary.
* Does not return.
*/
static void threadComm(void *);
static std::array<char, 4096> m_thread_stack;

@ -156,6 +156,7 @@ void ConversionManager::threadRunner(void *)
samples = entry(samples, size);
asm("mov sp, %0" :: "r" (sp));
volatile auto testRead = *samples;
(void)testRead;
} else {
// Start execution timer:
asm("mov %0, sp; eor r0, r0; svc 2" : "=r" (sp));
@ -163,6 +164,7 @@ void ConversionManager::threadRunner(void *)
// Stop execution timer:
asm("mov r0, #1; svc 2; mov sp, %0" :: "r" (sp));
volatile auto testRead = *samples;
(void)testRead;
}
}

@ -2,7 +2,7 @@
* @file conversion.hpp
* @brief Manages algorithm application (converts input samples to output).
*
* Copyright (C) 2021 Clyne Sullivan
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
@ -27,6 +27,10 @@ constexpr unsigned int CONVERSION_THREAD_STACK_SIZE =
class ConversionManager
{
public:
/**
* Starts two threads: the privileged monitor thread and the unprivileged
* algorithm execution thread.
*/
static void begin();
// Begins sample conversion.

@ -2,7 +2,7 @@
* @file elf.h
* @brief Defines ELF binary format info.
*
* Free to use, written by Clyne Sullivan.
* Public domain, written by Clyne Sullivan.
*/
#ifndef STMDSP_ELF_HPP

@ -2,7 +2,7 @@
* @file elfload.hpp
* @brief Loads ELF binary data into memory for execution.
*
* Copyright (C) 2021 Clyne Sullivan
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
@ -24,9 +24,26 @@ class ELFManager
public:
using EntryFunc = Sample *(*)(Sample *, size_t);
/**
* Attempts to parse the ELF binary loaded in the file buffer.
* Returns true if successful.
*/
static bool loadFromInternalBuffer();
/**
* Returns a function pointer to the loaded ELF's entry point.
* Returns nullptr if a valid ELF is not loaded.
*/
static EntryFunc loadedElf();
/**
* Returns the address of the ELF file buffer (copy ELF binary to here).
*/
static unsigned char *fileBuffer();
/**
* "Unloads" the loaded binary by invalidating the entry pointer.
*/
static void unload();
private:

@ -2,7 +2,7 @@
* @file error.hpp
* @brief Tracks and reports non-critical errors.
*
* Copyright (C) 2021 Clyne Sullivan
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
@ -31,9 +31,25 @@ class ErrorManager
constexpr static unsigned int MAX_ERROR_QUEUE_SIZE = 8;
public:
/**
* Adds the given error to the error queue.
*/
void add(Error error);
/**
* If condition is false, add the given error to the error queue.
* Returns condition.
*/
bool assert(bool condition, Error error);
/**
* Returns true if the error queue is not empty.
*/
bool hasError();
/**
* Returns the oldest error queue entry after removing it from the queue.
*/
Error pop();
private:

@ -2,7 +2,7 @@
* @file handlers.hpp
* @brief Interrupt service routine handlers.
*
* Copyright (C) 2021 Clyne Sullivan
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
@ -16,12 +16,15 @@
extern "C" {
// Service call handler ("svc" calls)
__attribute__((naked))
void port_syscall(struct port_extctx *ctxp, uint32_t n);
// Handle memory faults possibly caused by the algorithm.
__attribute__((naked))
void MemManage_Handler();
// Handle execution faults possibly caused by the algorithm.
__attribute__((naked))
void HardFault_Handler();

@ -2,7 +2,7 @@
* @file monitor.hpp
* @brief Manages the device monitoring thread (status LEDs, etc.).
*
* Copyright (C) 2021 Clyne Sullivan
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
@ -19,9 +19,15 @@
class Monitor
{
public:
/**
* Starts the monitor thread.
*/
static void begin();
private:
/**
* Updates status LEDs. Does not return.
*/
static void threadMonitor(void *);
static std::array<char, THD_WORKING_AREA_SIZE(256)> m_thread_stack;

@ -2,7 +2,7 @@
* @file adc.hpp
* @brief Manages signal reading through the ADC.
*
* Copyright (C) 2021 Clyne Sullivan
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
@ -22,18 +22,47 @@ class ADC
public:
using Operation = void (*)(adcsample_t *buffer, size_t count);
/**
* Initializes analog input pins and the microcontoller's ADC peripheral.
*/
static void begin();
/**
* Begins continuous ADC sampling triggered by SClock at the set sampling
* rate.
* @param buffer Pointer to buffer for sample data.
* @param count Number of samples that the buffer can hold.
* @param operation Handler function to operate on filled half-buffers.
*/
static void start(adcsample_t *buffer, size_t count, Operation operation);
/**
* Stops the continuous ADC sampling.
*/
static void stop();
/**
* Runs a single conversion on the "alt" inputs (parameter knobs).
* @param id The ID of the desired "alt" input (zero or one).
* @return The sampled value for the given input.
*/
static adcsample_t readAlt(unsigned int id);
/**
* Sets the desired sampling rate for the ADC to operate at.
*/
static void setRate(SClock::Rate rate);
/**
* Used to override the handler function for the currently running
* conversion.
*/
static void setOperation(Operation operation);
private:
// ADC driver for signal input.
static ADCDriver *m_driver;
// ADC driver for "alt" inputs.
static ADCDriver *m_driver2;
static const ADCConfig m_config;

@ -1,3 +1,14 @@
/**
* @file cordic.cpp
* @brief Provides mathematical functions for algorithms.
*
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include "cordic.hpp"
#include "hal.h"

@ -1,11 +1,33 @@
/**
* @file cordic.hpp
* @brief Provides mathematical functions for algorithms.
*
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef CORDIC_HPP_
#define CORDIC_HPP_
/**
* Named after the hardware CORDIC peripheral even though software
* implementations may be used.
*/
namespace cordic {
// Provides pi in case cordic functions require it.
constexpr double PI = 3.1415926535L;
/**
* Prepares cordic functions for use.
*/
void init();
// mod - Calculates remainder for given fraction.
// cos, sin, tan - The trig functions.
#if !defined(TARGET_PLATFORM_L4)
double mod(double n, double d);

@ -2,7 +2,7 @@
* @file dac.hpp
* @brief Manages signal creation using the DAC.
*
* Copyright (C) 2021 Clyne Sullivan
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
@ -18,12 +18,35 @@
class DAC
{
public:
/**
* Initializes DAC output pins and peripheral.
*/
static void begin();
/**
* Begins continuous DAC conversion on the given channel, running at the
* current SClock sampling rate.
* @param channel Selected output channel (0 = sig. out, 1 = sig. gen.).
* @param buffer Buffer of sample data to output.
* @param count Number of samples in sample buffer.
*/
static void start(int channel, dacsample_t *buffer, size_t count);
/**
* Stops DAC conversion on the given channel.
*/
static void stop(int channel);
/**
* Determines if signal generator needs more sample data for streamed
* output (i.e. audio file streaming).
* @return >0 if samples needed, <0 on initial run.
*/
static int sigGenWantsMore();
/**
* Returns true if signal generator is currently running.
*/
static int isSigGenRunning();
private:

@ -264,7 +264,6 @@ static const USBEndpointConfig ep2config = {
* Handles the USB driver global events.
*/
static void usb_event(USBDriver *usbp, usbevent_t event) {
extern SerialUSBDriver SDU1;
switch (event) {
case USB_EVENT_ADDRESS:

@ -2,7 +2,7 @@
* @file usbserial.hpp
* @brief Wrapper for ChibiOS's SerialUSBDriver.
*
* Copyright (C) 2021 Clyne Sullivan
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
@ -17,11 +17,30 @@
class USBSerial
{
public:
/**
* Prepares for USB serial communication.
*/
static void begin();
/**
* Returns true if input data has been received.
*/
static bool isActive();
/**
* Reads received input data into the given buffer.
* @param buffer Buffer to store input data.
* @param count Number of bytes to read.
* @return Number of bytes actually read.
*/
static size_t read(unsigned char *buffer, size_t count);
/**
* Writes data to serial output.
* @param buffer Buffer of output data.
* @param count Number of bytes to write.
* @return Number of bytes actually written.
*/
static size_t write(const unsigned char *buffer, size_t count);
private:

@ -1,5 +1,4 @@
// Run status
//
enum class RunStatus : char
{
Idle = '1',

@ -19,12 +19,12 @@ void SampleBuffer::clear() {
}
__attribute__((section(".convcode")))
void SampleBuffer::modify(Sample *data, unsigned int srcsize) {
auto size = srcsize < m_size ? srcsize : m_size;
size = (size + 15) & (~15);
auto dsize = srcsize < m_size ? srcsize : m_size;
dsize = (dsize + 15) & (~15);
m_modified = m_buffer;
const int *src = reinterpret_cast<const int *>(data);
const int * const srcend = src + (size / 2);
const int * const srcend = src + (dsize / 2);
int *dst = reinterpret_cast<int *>(m_buffer);
do {
int a = src[0];
@ -49,12 +49,12 @@ void SampleBuffer::modify(Sample *data, unsigned int srcsize) {
}
__attribute__((section(".convcode")))
void SampleBuffer::midmodify(Sample *data, unsigned int srcsize) {
auto size = srcsize < m_size / 2 ? srcsize : m_size / 2;
size = (size + 15) & (~15);
auto dsize = srcsize < m_size / 2 ? srcsize : m_size / 2;
dsize = (dsize + 15) & (~15);
m_modified = middata();
const int *src = reinterpret_cast<const int *>(data);
const int * const srcend = src + (size / 2);
const int * const srcend = src + (dsize / 2);
int *dst = reinterpret_cast<int *>(middata());
do {
int a = src[0];

@ -2,7 +2,7 @@
* @file samplebuffer.hpp
* @brief Manages ADC/DAC buffer data.
*
* Copyright (C) 2021 Clyne Sullivan
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
@ -20,25 +20,79 @@ using Sample = uint16_t;
constexpr unsigned int MAX_SAMPLE_BUFFER_BYTESIZE = sizeof(Sample) * 8192;
constexpr unsigned int MAX_SAMPLE_BUFFER_SIZE = MAX_SAMPLE_BUFFER_BYTESIZE / sizeof(Sample);
/**
* Manages a buffer of sample data from the ADC or DAC with facilities to
* work with separate halves of the total buffer (which is necessary when
* streaming data to or from the buffer).
*/
class SampleBuffer
{
public:
SampleBuffer(Sample *buffer);
// Manage the sample data memory at 'buffer'.
explicit SampleBuffer(Sample *buffer);
/**
* Fill the current buffer with midpoint (2048/0V) values.
*/
void clear();
/**
* Copy 'srcsize' samples from 'data' into the first half of the current
* buffer. Also do equivalent of setModified().
*/
void modify(Sample *data, unsigned int srcsize);
/**
* Copy 'srcsize' samples from 'data' into the second half of the current
* buffer. Also do equivalent of setMidmodified().
*/
void midmodify(Sample *data, unsigned int srcsize);
/**
* Set modified buffer pointer to first half of the current buffer.
*/
void setModified();
/**
* Set modified buffer pointer to second half of the current buffer.
*/
void setMidmodified();
/**
* Return pointer to most recently modified buffer portion.
* Clears this internal pointer when called.
*/
Sample *modified();
/**
* Returns pointer to first half of current buffer.
*/
Sample *data();
/**
* Returns pointer to second half of current buffer.
*/
Sample *middata();
/**
* Returns uint8_t-casted pointer to the current buffer.
*/
uint8_t *bytedata();
/**
* Sets the total working size of the current buffer. Current buffer must
* be able to accommodate.
*/
void setSize(unsigned int size);
/**
* Returns the current total working size (number of samples).
*/
unsigned int size() const;
/**
* Returns the current total working size (number of bytes).
*/
unsigned int bytesize() const;
private:

@ -2,7 +2,7 @@
* @file samples.hpp
* @brief Provides sample buffers for inputs and outputs.
*
* Copyright (C) 2021 Clyne Sullivan
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
@ -14,6 +14,8 @@
#include "samplebuffer.hpp"
// Define sample buffers for the input and output signals and the signal
// generator.
class Samples
{
public:

@ -2,7 +2,7 @@
* @file sclock.hpp
* @brief Manages sampling rate clock speeds.
*
* Copyright (C) 2021 Clyne Sullivan
* Copyright (C) 2023 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
@ -19,6 +19,7 @@
class SClock
{
public:
// These are the supported sampling rates. GUI keeps its own enumeration.
enum class Rate : unsigned int {
R8K = 0,
R16K,
@ -28,11 +29,32 @@ public:
R96K
};
/**
* Initializes the sample clock hardware.
*/
static void begin();
/**
* Starts the sample rate clock if it is not already running.
*/
static void start();
/**
* Indicate that the caller no longer needs the sample clock.
* This decrements an internal counter that is incremented by start()
* calls; if the counter reaches zero, the clock will actually stop.
*/
static void stop();
/**
* Sets the desired sampling rate, used for the next start() call.
*/
static void setRate(Rate rate);
/**
* Gets the desired sampling rate (SClock::Rate value) casted to an
* unsigned int.
*/
static unsigned int getRate();
private:

@ -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)

@ -0,0 +1 @@
Subproject commit 8f47a67f595a6641c566087bf5277034be64f24d

@ -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.");

@ -28,6 +28,7 @@ void codeEditorInit()
{
editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
editor.SetPalette(TextEditor::GetLightPalette());
editor.SetShowWhitespaces(false);
}
void codeRenderMenu()

@ -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();
}
}

Loading…
Cancel
Save