try to make code performant, lets see if this will work.

This commit is contained in:
2026-06-11 18:45:03 +02:00
parent 677f263648
commit c46caf7533
9 changed files with 306 additions and 230 deletions
+123 -123
View File
@@ -4,14 +4,12 @@
#include <QSurfaceFormat>
#include <QFile>
#include <QMouseEvent>
#include <QColorDialog>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QDir>
#include <QStandardPaths>
#include <cmath>
#include <cstring>
SpectrogramWidget::SpectrogramWidget(QWidget *parent)
: QOpenGLWidget(parent)
@@ -28,15 +26,14 @@ SpectrogramWidget::SpectrogramWidget(QWidget *parent)
setMinimumSize(400, 200);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_barHeights.resize(BAR_COUNT);
m_barPeaksVec.resize(BAR_COUNT);
loadBarColors();
auto *timer = new QTimer(this);
connect(timer, &QTimer::timeout,
this, QOverload<>::of(&SpectrogramWidget::update));
timer->start(16);
timer->start(16);
m_frameTimer.start();
}
SpectrogramWidget::~SpectrogramWidget()
@@ -49,23 +46,23 @@ SpectrogramWidget::~SpectrogramWidget()
void SpectrogramWidget::receiveFft(const QVector<float> &mag)
{
QMutexLocker lk(&m_queueMtx);
while (m_fftQueue.size() >= 4)
m_fftQueue.dequeue();
m_fftQueue.enqueue(mag);
update();
QMutexLocker lk(&m_latestMtx);
m_latestMag = mag;
m_newMagAvailable = true;
}
void SpectrogramWidget::reset()
{
{
QMutexLocker lk(&m_queueMtx);
m_fftQueue.clear();
QMutexLocker lk(&m_latestMtx);
m_latestMag.clear();
m_newMagAvailable = false;
}
m_currentMag .fill(0.f);
m_currentMag.fill(0.f);
m_barSmoothed.fill(0.f);
m_barPeak .fill(0.f);
m_peakTimer .fill(0);
m_barPeak.fill(0.f);
m_peakHoldTimer.fill(0.f);
m_dirty = false;
update();
}
@@ -73,10 +70,22 @@ void SpectrogramWidget::initializeGL()
{
initializeOpenGLFunctions();
initShaders();
buildHeatmapTexture();
updateHeatmapTexture();
glGenVertexArrays(1, &m_vaoBars);
glDisable(GL_BLEND);
const int bins = engine::SpectrumAnalyzer::BIN_COUNT;
const double logMin = std::log10(20.0);
const double logMax = std::log10(22050.0);
const double range = logMax - logMin;
for (int b = 0; b < BAR_COUNT; ++b) {
double f1 = std::pow(10.0, logMin + range * b / BAR_COUNT);
double f2 = std::pow(10.0, logMin + range * (b + 1) / BAR_COUNT);
m_binMap[b].lo = std::clamp(int(f1 / 22050.0 * bins), 0, bins - 1);
m_binMap[b].hi = std::clamp(int(f2 / 22050.0 * bins), m_binMap[b].lo, bins - 1);
}
}
void SpectrogramWidget::initShaders()
@@ -90,9 +99,11 @@ void SpectrogramWidget::initShaders()
return f.readAll();
};
if (!m_barsProg.addShaderFromSourceCode(QOpenGLShader::Vertex, load(":/shaders/bars.vert")))
if (!m_barsProg.addShaderFromSourceCode(QOpenGLShader::Vertex,
load(":/shaders/bars.vert")))
qWarning() << "Bars vert error:" << m_barsProg.log();
if (!m_barsProg.addShaderFromSourceCode(QOpenGLShader::Fragment, load(":/shaders/bars.frag")))
if (!m_barsProg.addShaderFromSourceCode(QOpenGLShader::Fragment,
load(":/shaders/bars.frag")))
qWarning() << "Bars frag error:" << m_barsProg.log();
if (!m_barsProg.link())
qWarning() << "Bars link error:" << m_barsProg.log();
@@ -107,50 +118,43 @@ void SpectrogramWidget::initShaders()
m_uHeatmap = m_barsProg.uniformLocation("heatmap");
}
void SpectrogramWidget::buildHeatmapTexture()
{
updateHeatmapTexture(); // use current m_stops
}
void SpectrogramWidget::updateHeatmapTexture()
{
if (m_stops.size() < 2)
m_stops = { QColor(0, 0, 0), QColor(255, 255, 255) };
const int nSegs = m_stops.size() - 1;
auto lerp = [](int x, int y, float f) {
return std::clamp(int(x + (y - x) * f), 0, 255);
};
for (int i = 0; i < 256; ++i) {
float t = float(i) / 255.f * nSegs;
int seg = std::min(int(t), nSegs - 1);
float f = t - seg;
QColor a = m_stops[seg], b = m_stops[seg + 1];
m_heatmap[i] = qRgb(lerp(a.red(), b.red(), f),
lerp(a.green(), b.green(), f),
lerp(a.blue(), b.blue(), f));
}
std::array<GLubyte, 256 * 3> td;
for (int i = 0; i < 256; ++i) {
td[3 * i + 0] = static_cast<GLubyte>(qRed (m_heatmap[i]));
td[3 * i + 1] = static_cast<GLubyte>(qGreen(m_heatmap[i]));
td[3 * i + 2] = static_cast<GLubyte>(qBlue (m_heatmap[i]));
}
if (m_heatmapTex) {
glDeleteTextures(1, &m_heatmapTex);
m_heatmapTex = 0;
}
if (m_stops.size() < 2)
m_stops = { QColor(0,0,0), QColor(255,255,255) };
const int nSegs = m_stops.size() - 1;
auto lerp = [](int x, int y, float f) {
return std::clamp(int(x + (y - x) * f), 0, 255);
};
for (int i = 0; i < 256; i++) {
float t = float(i) / 255.f * nSegs;
int seg = std::min(int(t), nSegs - 1);
float f = t - seg;
QColor a = m_stops[seg], b = m_stops[seg+1];
m_heatmap[i] = qRgb(lerp(a.red(), b.red(), f),
lerp(a.green(), b.green(), f),
lerp(a.blue(), b.blue(), f));
}
glGenTextures(1, &m_heatmapTex);
glBindTexture(GL_TEXTURE_1D, m_heatmapTex);
std::vector<GLubyte> td(256 * 3);
for (int i = 0; i < 256; i++) {
td[3*i+0] = quint8(qRed (m_heatmap[i]));
td[3*i+1] = quint8(qGreen(m_heatmap[i]));
td[3*i+2] = quint8(qBlue (m_heatmap[i]));
}
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB, 256, 0,
GL_RGB, GL_UNSIGNED_BYTE, td.data());
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
}
void SpectrogramWidget::setStops(const QVector<QColor> &stops)
@@ -166,26 +170,18 @@ void SpectrogramWidget::setStops(const QVector<QColor> &stops)
void SpectrogramWidget::loadBarColors()
{
QString configDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.subwave";
QDir().mkpath(configDir);
QString path = configDir + "/barcolors.json";
QString path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation)
+ "/.subwave/barcolors.json";
QFile f(path);
if (!f.open(QIODevice::ReadOnly))
return;
if (!f.open(QIODevice::ReadOnly)) return;
QJsonDocument doc = QJsonDocument::fromJson(f.readAll());
f.close();
if (!doc.isArray())
return;
if (!doc.isArray()) return;
QJsonArray arr = doc.array();
QVector<QColor> loaded;
for (const auto &v : arr) {
for (const auto &v : doc.array()) {
QJsonObject obj = v.toObject();
int r = obj["r"].toInt(0);
int g = obj["g"].toInt(0);
int b = obj["b"].toInt(0);
loaded.append(QColor(r, g, b));
loaded.append(QColor(obj["r"].toInt(), obj["g"].toInt(), obj["b"].toInt()));
}
if (loaded.size() >= 2)
m_stops = loaded;
@@ -193,12 +189,11 @@ void SpectrogramWidget::loadBarColors()
void SpectrogramWidget::saveBarColors()
{
QString configDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.subwave";
QDir().mkpath(configDir);
QString path = configDir + "/barcolors.json";
QFile f(path);
if (!f.open(QIODevice::WriteOnly))
return;
QString dir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation)
+ "/.subwave";
QDir().mkpath(dir);
QFile f(dir + "/barcolors.json");
if (!f.open(QIODevice::WriteOnly)) return;
QJsonArray arr;
for (const QColor &c : m_stops) {
@@ -208,89 +203,94 @@ void SpectrogramWidget::saveBarColors()
obj["b"] = c.blue();
arr.append(obj);
}
QJsonDocument doc(arr);
f.write(doc.toJson());
f.close();
f.write(QJsonDocument(arr).toJson());
}
void SpectrogramWidget::mousePressEvent(QMouseEvent *e)
{
if (e->button() == Qt::LeftButton) {
GradientDialog dlg(m_stops, this);
if (dlg.exec() == QDialog::Accepted) {
if (dlg.exec() == QDialog::Accepted)
setStops(dlg.stops());
}
}
QOpenGLWidget::mousePressEvent(e);
}
void SpectrogramWidget::computeBars()
void SpectrogramWidget::computeBars(float dt)
{
const int fftLen = m_currentMag.size();
const double logMin = std::log10(20.0), logMax = std::log10(22050.0);
QVector<float> bars(BAR_COUNT, 0.f);
for (int b = 0; b < BAR_COUNT; b++) {
double f1 = std::pow(10.0, logMin + (logMax-logMin)* b /BAR_COUNT);
double f2 = std::pow(10.0, logMin + (logMax-logMin)*(b+1)/BAR_COUNT);
int bin1 = std::clamp(int(f1/22050.0*fftLen), 0, fftLen-1);
int bin2 = std::clamp(int(f2/22050.0*fftLen), bin1, fftLen-1);
float sum = 0.f; int cnt = 0;
for (int k = bin1; k <= bin2; k++) { sum += m_currentMag[k]; cnt++; }
bars[b] = cnt > 0 ? sum/cnt : 0.f;
}
for (int b = 0; b < BAR_COUNT; b++) {
float db = bars[b] > 0 ? 20.f*std::log10(bars[b]) : DB_MIN;
bars[b] = std::clamp((db - DB_MIN) / (DB_MAX - DB_MIN), 0.f, 1.f);
}
for (int b = 0; b < BAR_COUNT; b++) {
m_barSmoothed[b] = m_barSmoothed[b]*0.65f + bars[b]*0.35f;
for (int b = 0; b < BAR_COUNT; ++b) {
const int lo = m_binMap[b].lo;
const int hi = std::min(m_binMap[b].hi, fftLen - 1);
float sum = 0.f;
int cnt = 0;
for (int k = lo; k <= hi; ++k) { sum += m_currentMag[k]; ++cnt; }
float raw = cnt > 0 ? sum / cnt : 0.f;
float db = raw > 0.f ? 20.f * std::log10(raw) : DB_MIN;
float bar = std::clamp((db - DB_MIN) / (DB_MAX - DB_MIN), 0.f, 1.f);
const float coeff = bar > m_barSmoothed[b] ? 0.50f : 0.18f;
m_barSmoothed[b] += (bar - m_barSmoothed[b]) * coeff;
if (m_barSmoothed[b] >= m_barPeak[b]) {
m_barPeak[b] = m_barSmoothed[b];
m_peakTimer[b] = 25;
} else if (m_peakTimer[b] > 0) {
m_peakTimer[b]--;
m_barPeak[b] = m_barSmoothed[b];
m_peakHoldTimer[b] = PEAK_HOLD_SEC;
} else if (m_peakHoldTimer[b] > 0.f) {
m_peakHoldTimer[b] -= dt;
} else {
m_barPeak[b] = std::max(0.f, m_barPeak[b] - 0.006f);
m_barPeak[b] = std::max(0.f, m_barPeak[b] - PEAK_DECAY_SEC * dt);
}
m_barHeights [b] = m_barSmoothed[b];
m_barPeaksVec[b] = m_barPeak[b];
}
}
void SpectrogramWidget::paintGL()
{
const float dt = static_cast<float>(m_frameTimer.restart()) / 1000.f;
{
QMutexLocker lk(&m_queueMtx);
while (!m_fftQueue.isEmpty())
m_currentMag = m_fftQueue.dequeue();
QMutexLocker lk(&m_latestMtx);
if (m_newMagAvailable) {
m_currentMag = std::move(m_latestMag);
m_latestMag = {};
m_newMagAvailable = false;
m_dirty = true;
}
}
computeBars();
computeBars(dt);
QColor bg = palette().color(QPalette::Window);
glClearColor(bg.redF(), bg.greenF(), bg.blueF(), 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (m_barsProg.isLinked()) {
const float barWidth = 2.0f / BAR_COUNT;
const float gap = barWidth * 0.2f;
const float drawWidth = barWidth - gap;
if (!m_barsProg.isLinked()) return;
m_barsProg.bind();
glUniform1fv(m_uBarHeights, BAR_COUNT, m_barHeights .constData());
glUniform1fv(m_uBarPeaks, BAR_COUNT, m_barPeaksVec.constData());
glUniform1i (m_uBarCount, BAR_COUNT);
glUniform1f (m_uBarWidth, drawWidth);
glUniform1f (m_uGap, gap);
glUniform1f (m_uBottomY, -1.0f);
glUniform1f (m_uTopClip, 1.0f);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_1D, m_heatmapTex);
glUniform1i(m_uHeatmap, 0);
glBindVertexArray(m_vaoBars);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, BAR_COUNT);
glBindVertexArray(0);
m_barsProg.release();
}
const float barWidth = 2.0f / BAR_COUNT;
const float gap = barWidth * 0.2f;
const float drawWidth = barWidth - gap;
m_barsProg.bind();
glUniform1fv(m_uBarHeights, BAR_COUNT, m_barHeights .data());
glUniform1fv(m_uBarPeaks, BAR_COUNT, m_barPeaksVec.data());
glUniform1i (m_uBarCount, BAR_COUNT);
glUniform1f (m_uBarWidth, drawWidth);
glUniform1f (m_uGap, gap);
glUniform1f (m_uBottomY, -1.0f);
glUniform1f (m_uTopClip, 1.0f);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_1D, m_heatmapTex);
glUniform1i(m_uHeatmap, 0);
glBindVertexArray(m_vaoBars);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, BAR_COUNT);
glBindVertexArray(0);
m_barsProg.release();
}
+45 -24
View File
@@ -2,60 +2,81 @@
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QElapsedTimer>
#include <QMutex>
#include <QQueue>
#include <array>
#include <atomic>
#include <QColor>
#include <QVector>
#include "SpectrumAnalyzer.h"
class SpectrogramWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{
Q_OBJECT
static constexpr int BAR_COUNT = 96;
static constexpr float DB_MIN = -80.f, DB_MAX = 0.f;
static constexpr float DB_MIN = -80.f;
static constexpr float DB_MAX = 0.f;
static constexpr float PEAK_HOLD_SEC = 0.4f;
static constexpr float PEAK_DECAY_SEC = 0.5f;
public:
explicit SpectrogramWidget(QWidget *parent = nullptr);
~SpectrogramWidget();
~SpectrogramWidget() override;
public slots:
void receiveFft(const QVector<float> &mag);
void reset();
protected:
void initializeGL() override;
void paintGL() override;
void initializeGL() override;
void paintGL() override;
void mousePressEvent(QMouseEvent *e) override;
private:
// GL setup
void initShaders();
void buildHeatmapTexture();
void updateHeatmapTexture();
void updateHeatmapTexture();
// Data
void setStops(const QVector<QColor> &stops);
void loadBarColors();
void saveBarColors();
void computeBars();
void computeBars(float dt);
QMutex m_queueMtx;
QQueue<QVector<float>> m_fftQueue;
QVector<float> m_currentMag = QVector<float>(2048, 0.f);
QMutex m_latestMtx;
QVector<float> m_latestMag; // written by audio thread
bool m_newMagAvailable{false};
QVector<float> m_barSmoothed = QVector<float>(BAR_COUNT, 0.f);
QVector<float> m_barPeak = QVector<float>(BAR_COUNT, 0.f);
QVector<int> m_peakTimer = QVector<int> (BAR_COUNT, 0);
QVector<float> m_barHeights;
QVector<float> m_barPeaksVec;
QVector<float> m_currentMag =
QVector<float>(engine::SpectrumAnalyzer::BIN_COUNT, 0.f);
QVector<QColor> m_stops = { QColor("#078D70"),
QColor("#26CEAA"),
QColor("#98E8C1"),
QColor("#FFFFFF") };
// --- bar state ---
std::array<float, BAR_COUNT> m_barSmoothed{};
std::array<float, BAR_COUNT> m_barPeak{};
std::array<float, BAR_COUNT> m_peakHoldTimer{}; // seconds remaining in hold
std::array<QRgb, 256> m_heatmap{};
// Uploaded to GPU each frame
std::array<float, BAR_COUNT> m_barHeights{};
std::array<float, BAR_COUNT> m_barPeaksVec{};
QOpenGLShaderProgram m_barsProg;
GLuint m_heatmapTex = 0;
GLuint m_vaoBars = 0;
struct BinRange { int lo, hi; };
std::array<BinRange, BAR_COUNT> m_binMap{};
QVector<QColor> m_stops = { QColor("#078D70"),
QColor("#26CEAA"),
QColor("#98E8C1"),
QColor("#FFFFFF") };
std::array<QRgb, 256> m_heatmap{};
QElapsedTimer m_frameTimer;
bool m_dirty{false}; // true when new data arrived since last paint
QOpenGLShaderProgram m_barsProg;
GLuint m_heatmapTex = 0;
GLuint m_vaoBars = 0;
int m_uBarHeights = -1;
int m_uBarPeaks = -1;