chore: gitignore build-viz/, remove accidentally committed build artifacts

This commit is contained in:
joren
2026-03-25 18:25:39 +01:00
parent 3e96b6d7a8
commit b3cc2e3def
144 changed files with 1 additions and 63786 deletions

View File

@@ -1,163 +0,0 @@
#include "spectrumwidget.hpp"
#include <QPainter>
#include <QTimerEvent>
#include <cmath>
#include <algorithm>
static constexpr float M_PI_F = static_cast<float>(M_PI);
SpectrumWidget::SpectrumWidget(QobuzBackend *backend, QWidget *parent)
: QWidget(parent)
, m_backend(backend)
, m_pcmBuf(FFT_SIZE * 2, 0.0f) // enough for stereo
, m_fftReal(FFT_SIZE, 0.0f)
, m_fftImag(FFT_SIZE, 0.0f)
, m_bars(NUM_BARS, 0.0f)
, m_window(FFT_SIZE)
{
// Build Hann window
for (int i = 0; i < FFT_SIZE; ++i)
m_window[i] = 0.5f * (1.0f - std::cos(2.0f * M_PI_F * i / (FFT_SIZE - 1)));
setMinimumHeight(48);
setAttribute(Qt::WA_OpaquePaintEvent);
m_timerId = startTimer(33); // ~30 fps
}
// ---- Cooley-Tukey in-place radix-2 DIT FFT --------------------------------
void SpectrumWidget::fft(std::vector<float> &re, std::vector<float> &im)
{
const int n = static_cast<int>(re.size());
// Bit-reversal permutation
for (int i = 1, j = 0; i < n; ++i) {
int bit = n >> 1;
for (; j & bit; bit >>= 1)
j ^= bit;
j ^= bit;
if (i < j) {
std::swap(re[i], re[j]);
std::swap(im[i], im[j]);
}
}
// Butterfly stages
for (int len = 2; len <= n; len <<= 1) {
const float ang = -2.0f * M_PI_F / len;
const float wRe = std::cos(ang);
const float wIm = std::sin(ang);
for (int i = 0; i < n; i += len) {
float curRe = 1.0f, curIm = 0.0f;
for (int k = 0; k < len / 2; ++k) {
const float uRe = re[i + k];
const float uIm = im[i + k];
const float vRe = re[i + k + len/2] * curRe - im[i + k + len/2] * curIm;
const float vIm = re[i + k + len/2] * curIm + im[i + k + len/2] * curRe;
re[i + k] = uRe + vRe;
im[i + k] = uIm + vIm;
re[i + k + len/2] = uRe - vRe;
im[i + k + len/2] = uIm - vIm;
const float nextRe = curRe * wRe - curIm * wIm;
curIm = curRe * wIm + curIm * wRe;
curRe = nextRe;
}
}
}
}
// ---- Sample processing -----------------------------------------------------
void SpectrumWidget::processNewSamples()
{
const quint32 channels = m_backend->vizChannels();
if (channels == 0) return;
const quint32 n = m_backend->vizRead(m_pcmBuf.data(),
static_cast<quint32>(m_pcmBuf.size()));
if (n == 0) return;
// Mix to mono into the FFT real buffer (wrap-around ring fill)
const int ch = static_cast<int>(channels);
const int frames = static_cast<int>(n) / ch;
for (int f = 0; f < frames && f < FFT_SIZE; ++f) {
float mono = 0.0f;
for (int c = 0; c < ch; ++c)
mono += m_pcmBuf[f * ch + c];
m_fftReal[f] = mono / ch * m_window[f];
}
// Zero-pad if we got fewer frames than FFT_SIZE
for (int f = frames; f < FFT_SIZE; ++f)
m_fftReal[f] = 0.0f;
std::fill(m_fftImag.begin(), m_fftImag.end(), 0.0f);
fft(m_fftReal, m_fftImag);
// Map FFT bins to bars using logarithmic frequency spacing
// Bins 1..FFT_SIZE/2 cover 0..Nyquist; skip DC (bin 0)
const int halfBins = FFT_SIZE / 2;
// Log-spaced bin boundaries: bin = 1 * (halfBins)^(i/NUM_BARS)
for (int b = 0; b < NUM_BARS; ++b) {
const float lo = std::pow(static_cast<float>(halfBins), static_cast<float>(b) / NUM_BARS);
const float hi = std::pow(static_cast<float>(halfBins), static_cast<float>(b + 1) / NUM_BARS);
const int binLo = std::max(1, static_cast<int>(lo));
const int binHi = std::min(halfBins - 1, static_cast<int>(hi));
float peak = 0.0f;
for (int k = binLo; k <= binHi; ++k) {
const float mag = std::sqrt(m_fftReal[k] * m_fftReal[k] + m_fftImag[k] * m_fftImag[k]);
if (mag > peak) peak = mag;
}
// Convert to dB-ish (log scale), normalised to [0,1]
float level = std::log1p(peak * 5.0f) / std::log1p(5.0f * FFT_SIZE / 4.0f);
level = std::clamp(level, 0.0f, 1.0f);
// Smooth: fast attack, slow decay
const float alpha = level > m_bars[b] ? 0.6f : 0.15f;
m_bars[b] = m_bars[b] * (1.0f - alpha) + level * alpha;
}
}
// ---- Qt overrides ----------------------------------------------------------
void SpectrumWidget::timerEvent(QTimerEvent *event)
{
if (event->timerId() == m_timerId) {
processNewSamples();
update();
}
}
void SpectrumWidget::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.fillRect(rect(), palette().window());
const int w = width();
const int h = height();
if (w <= 0 || h <= 0) return;
const QColor barColor = palette().highlight().color();
const QColor peakColor = barColor.lighter(150);
const float barW = static_cast<float>(w) / NUM_BARS;
const float gap = std::max(1.0f, barW * 0.15f);
for (int b = 0; b < NUM_BARS; ++b) {
const int x = static_cast<int>(b * barW);
const int bw = static_cast<int>(barW - gap);
const int bh = static_cast<int>(m_bars[b] * h);
if (bh <= 0) continue;
// Gradient-ish: draw in two colours split at 70% height
const int split = static_cast<int>(h * 0.7f);
if (bh <= split) {
p.fillRect(x, h - bh, bw, bh, barColor);
} else {
p.fillRect(x, h - split, bw, split, barColor);
p.fillRect(x, h - bh, bw, bh - split, peakColor);
}
}
}

View File

@@ -1,42 +0,0 @@
#pragma once
#include "../backend/qobuzbackend.hpp"
#include <QWidget>
#include <QTimer>
#include <vector>
/// Dock-friendly spectrum bar visualizer.
/// Reads PCM from the Rust viz ring buffer, runs a simple FFT,
/// and draws frequency bars with QPainter.
class SpectrumWidget : public QWidget
{
Q_OBJECT
public:
explicit SpectrumWidget(QobuzBackend *backend, QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
void timerEvent(QTimerEvent *event) override;
private:
void processNewSamples();
QobuzBackend *m_backend = nullptr;
int m_timerId = 0;
// FFT size — power of two
static constexpr int FFT_SIZE = 1024;
static constexpr int NUM_BARS = 32;
std::vector<float> m_pcmBuf; // raw interleaved PCM from ring
std::vector<float> m_fftReal; // FFT input (mono mix)
std::vector<float> m_fftImag;
std::vector<float> m_bars; // smoothed bar heights [0..1]
// Simple Hann window
std::vector<float> m_window;
static void fft(std::vector<float> &re, std::vector<float> &im);
};