From 16efeb2fdeaedf4a53d97840786ad39e7e64c07d Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Thu, 4 Jun 2026 14:25:51 +0200 Subject: [PATCH] add shaders for bars, remove waterfall --- CMakeLists.txt | 18 +- src/GradientDialog.cpp | 88 +++++++++ src/GradientDialog.h | 31 +++ src/SpectrogramWidget.cpp | 384 +++++++++++++++++++++++++------------- src/SpectrogramWidget.h | 74 +++++--- src/main.cpp | 5 + src/shaders.qrc | 6 + src/shaders/bars.frag | 12 ++ src/shaders/bars.vert | 38 ++++ 9 files changed, 499 insertions(+), 157 deletions(-) create mode 100644 src/GradientDialog.cpp create mode 100644 src/GradientDialog.h create mode 100644 src/shaders.qrc create mode 100644 src/shaders/bars.frag create mode 100644 src/shaders/bars.vert diff --git a/CMakeLists.txt b/CMakeLists.txt index 5266fb0..61db41f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) # ── Find Qt6 ───────────────────────────────────────────────────────────────── -find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia) +find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia OpenGL OpenGLWidgets) # ── Find FFmpeg via pkg-config ─────────────────────────────────────────────── find_package(PkgConfig REQUIRED) @@ -16,10 +16,11 @@ pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET libswresample ) -# Auto-generate MOC files from Q_OBJECT headers +# Auto-generate MOC, and now also RCC for resource files set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) -# ── Sources (all located in src/ relative to this file) ────────────────────── +# ── Sources ────────────────────────────────────────────────────────────────── set(SOURCES src/main.cpp src/MetadataReader.cpp @@ -28,6 +29,7 @@ set(SOURCES src/MusicLibrary.cpp src/CoverArtWidget.cpp src/SpectrogramWidget.cpp + src/GradientDialog.cpp src/EqualizerWidget.cpp src/TrackDelegate.cpp src/PlaylistModel.cpp @@ -42,22 +44,26 @@ set(HEADERS src/MusicLibrary.h src/CoverArtWidget.h src/SpectrogramWidget.h + src/GradientDialog.h src/EqualizerWidget.h src/TrackDelegate.h src/PlaylistModel.h src/MainWindow.h ) -add_executable(SubWave ${SOURCES} ${HEADERS}) +set(RESOURCES src/shaders.qrc) + +add_executable(SubWave ${SOURCES} ${HEADERS} ${RESOURCES}) -# ── Link libraries ─────────────────────────────────────────────────────────── target_link_libraries(SubWave PRIVATE Qt6::Widgets Qt6::Multimedia + Qt6::OpenGL + Qt6::OpenGLWidgets PkgConfig::FFMPEG ) -# ── (Optional) Copy FFmpeg shared libs next to the binary on Windows ───────── +# ── Copy FFmpeg shared libs next to the binary on Windows ──────────────────── if(WIN32) add_custom_command(TARGET SubWave POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different diff --git a/src/GradientDialog.cpp b/src/GradientDialog.cpp new file mode 100644 index 0000000..d5f4604 --- /dev/null +++ b/src/GradientDialog.cpp @@ -0,0 +1,88 @@ +#include "GradientDialog.h" +#include +#include +#include +#include +#include +#include +#include + +GradientDialog::GradientDialog(const QVector &stops, + QWidget *parent) + : QDialog(parent), m_stops(stops) +{ + setWindowTitle("Bar Colour Gradient"); + setMinimumSize(420, 180); + + auto *mainLay = new QVBoxLayout(this); + + // Preview + m_preview = new QLabel; + m_preview->setFixedHeight(40); + m_preview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + mainLay->addWidget(m_preview); + + // Colour stop buttons + auto *btnLay = new QHBoxLayout; + QStringList labels = { "Low", "Mid", "High", "Peak" }; + for (int i = 0; i < m_stops.size(); ++i) { + auto *btn = new QPushButton(labels.value(i, QString::number(i))); + btn->setFixedSize(70, 30); + connect(btn, &QPushButton::clicked, this, [this, i]{ onStopClicked(i); }); + btnLay->addWidget(btn); + m_buttons.append(btn); + } + mainLay->addLayout(btnLay); + + // Action buttons + auto *dialogBtns = new QHBoxLayout; + auto *resetBtn = new QPushButton("Reset to Default"); + connect(resetBtn, &QPushButton::clicked, this, &GradientDialog::onResetToDefault); + dialogBtns->addWidget(resetBtn); + dialogBtns->addStretch(); + auto *ok = new QPushButton("Apply"); + connect(ok, &QPushButton::clicked, this, &QDialog::accept); + dialogBtns->addWidget(ok); + mainLay->addLayout(dialogBtns); + + updatePreview(); +} + +void GradientDialog::onStopClicked(int index) +{ + QColor c = QColorDialog::getColor(m_stops[index], this, + QString("Choose %1 colour").arg(index)); + if (c.isValid()) { + m_stops[index] = c; + updatePreview(); + } +} + +void GradientDialog::onResetToDefault() +{ + m_stops = { + QColor("#078D70"), // Low + QColor("#26CEAA"), // Mid + QColor("#98E8C1"), // High + QColor("#FFFFFF") // Peak + }; + updatePreview(); +} + +void GradientDialog::updatePreview() +{ + QPixmap pix(m_preview->size()); + if (pix.width() == 0) pix = QPixmap(400, 40); // fallback size + pix.fill(Qt::transparent); + QPainter p(&pix); + QLinearGradient grad(0, 0, pix.width(), 0); + for (int i = 0; i < m_stops.size(); ++i) { + qreal pos = (m_stops.size() == 1) ? 0.5 + : qreal(i) / (m_stops.size() - 1); + grad.setColorAt(pos, m_stops[i]); + } + p.setBrush(grad); + p.drawRect(pix.rect()); + p.end(); + m_preview->setPixmap(pix); +} \ No newline at end of file diff --git a/src/GradientDialog.h b/src/GradientDialog.h new file mode 100644 index 0000000..a5e8b82 --- /dev/null +++ b/src/GradientDialog.h @@ -0,0 +1,31 @@ +#ifndef GRADIENTDIALOG_H +#define GRADIENTDIALOG_H + +#include +#include +#include + +class QPushButton; +class QLabel; + +class GradientDialog : public QDialog +{ + Q_OBJECT +public: + explicit GradientDialog(const QVector &stops, + QWidget *parent = nullptr); + + QVector stops() const { return m_stops; } + +private slots: + void onStopClicked(int index); + void onResetToDefault(); + +private: + QVector m_stops; + QVector m_buttons; + QLabel *m_preview; + void updatePreview(); +}; + +#endif \ No newline at end of file diff --git a/src/SpectrogramWidget.cpp b/src/SpectrogramWidget.cpp index 49a786c..7db39de 100644 --- a/src/SpectrogramWidget.cpp +++ b/src/SpectrogramWidget.cpp @@ -1,166 +1,292 @@ #include "SpectrogramWidget.h" -#include -#include +#include "GradientDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include + +SpectrogramWidget::SpectrogramWidget(QWidget *parent) + : QOpenGLWidget(parent) +{ + setAttribute(Qt::WA_OpaquePaintEvent); + + QSurfaceFormat fmt; + fmt.setVersion(3, 3); + fmt.setProfile(QSurfaceFormat::CoreProfile); + fmt.setSwapInterval(1); + fmt.setSwapBehavior(QSurfaceFormat::DoubleBuffer); + setFormat(fmt); -SpectrogramWidget::SpectrogramWidget(QWidget *parent) : QWidget(parent) { setMinimumSize(400, 200); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - buildHeatmap(); - m_renderTimer.setInterval(16); - connect(&m_renderTimer, &QTimer::timeout, this, [this]{ - bool updated = false; - QVector mag; - { - QMutexLocker lk(&m_queueMtx); - while (!m_fftQueue.isEmpty()) { - mag = m_fftQueue.dequeue(); - updated = true; - } - } - if (updated) { - m_currentMag = mag; - scrollWaterfall(); - update(); - } - }); - m_renderTimer.start(); + + m_barHeights .resize(BAR_COUNT); + m_barPeaksVec.resize(BAR_COUNT); + + loadBarColors(); } -void SpectrogramWidget::receiveFft(const QVector &mag) { +SpectrogramWidget::~SpectrogramWidget() +{ + makeCurrent(); + glDeleteTextures(1, &m_heatmapTex); + glDeleteVertexArrays(1, &m_vaoBars); + doneCurrent(); +} + +void SpectrogramWidget::receiveFft(const QVector &mag) +{ QMutexLocker lk(&m_queueMtx); - if (m_fftQueue.size() < 16) m_fftQueue.enqueue(mag); -} - -void SpectrogramWidget::reset() { - m_currentMag.fill(0.f, 2048); - m_barSmoothed.fill(0.f, BAR_COUNT); - m_barPeak.fill(0.f, BAR_COUNT); - m_peakTimer.fill(0, BAR_COUNT); - m_waterfall = QImage{}; + while (m_fftQueue.size() >= 4) + m_fftQueue.dequeue(); + m_fftQueue.enqueue(mag); update(); } -void SpectrogramWidget::paintEvent(QPaintEvent *) { - QPainter p(this); - int w = width(), h = height(); - if (w < 8 || h < 8) return; - int wfH = h / 2; - int barH = h - wfH; - if (!m_waterfall.isNull()) - p.drawImage(0, 0, m_waterfall); - p.setPen(QColor(100,100,100)); - p.drawLine(0, wfH-1, w, wfH-1); - drawSpectrum(p, 0, wfH, w, barH); +void SpectrogramWidget::reset() +{ + { + QMutexLocker lk(&m_queueMtx); + m_fftQueue.clear(); + } + m_currentMag .fill(0.f); + m_barSmoothed.fill(0.f); + m_barPeak .fill(0.f); + m_peakTimer .fill(0); + update(); } -void SpectrogramWidget::resizeEvent(QResizeEvent *) { - m_waterfall = QImage{}; +void SpectrogramWidget::initializeGL() +{ + initializeOpenGLFunctions(); + initShaders(); + buildHeatmapTexture(); + + glGenVertexArrays(1, &m_vaoBars); + glDisable(GL_BLEND); } -void SpectrogramWidget::buildHeatmap() { - struct Stop { int r,g,b; }; - Stop stops[] = {{0,0,0},{0,0,180},{0,255,255},{255,255,0},{255,0,0}}; - int nSegs = 4; +void SpectrogramWidget::initShaders() +{ + auto load = [](const QString &path) -> QByteArray { + QFile f(path); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "Cannot open shader:" << path; + return {}; + } + return f.readAll(); + }; + + 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"))) + qWarning() << "Bars frag error:" << m_barsProg.log(); + if (!m_barsProg.link()) + qWarning() << "Bars link error:" << m_barsProg.log(); + + m_uBarHeights = m_barsProg.uniformLocation("barHeights"); + m_uBarPeaks = m_barsProg.uniformLocation("barPeaks"); + m_uBarCount = m_barsProg.uniformLocation("barCount"); + m_uBarWidth = m_barsProg.uniformLocation("barWidth"); + m_uGap = m_barsProg.uniformLocation("gap"); + m_uBottomY = m_barsProg.uniformLocation("bottomY"); + m_uTopClip = m_barsProg.uniformLocation("topClip"); + m_uHeatmap = m_barsProg.uniformLocation("heatmap"); +} + +void SpectrogramWidget::buildHeatmapTexture() +{ + updateHeatmapTexture(); // use current m_stops +} + +void SpectrogramWidget::updateHeatmapTexture() +{ + 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 t = float(i) / 255.f * nSegs; + int seg = std::min(int(t), nSegs - 1); float f = t - seg; - auto a = stops[seg], b = stops[seg+1]; - auto lerp = [](int x, int y, float f){ return std::clamp(int(x+(y-x)*f),0,255); }; - m_heatmap[i] = qRgb(lerp(a.r,b.r,f), lerp(a.g,b.g,f), lerp(a.b,b.b,f)); + 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)); } -} -void SpectrogramWidget::ensureWaterfall(int w, int wfH) { - if (m_waterfall.width() == w && m_waterfall.height() == wfH) return; - QImage fresh(w, std::max(1,wfH), QImage::Format_RGB32); - fresh.fill(Qt::black); - if (!m_waterfall.isNull()) - QPainter(&fresh).drawImage(0,0,m_waterfall); - m_waterfall = std::move(fresh); -} - -void SpectrogramWidget::scrollWaterfall() { - int w = width(), h = height(); - int wfH = h / 2; - if (w < 2 || wfH < 2) return; - ensureWaterfall(w, wfH); - - memmove(m_waterfall.bits(), - m_waterfall.bits() + m_waterfall.bytesPerLine(), - static_cast((wfH-1) * m_waterfall.bytesPerLine())); - - computeBars(); - - int fftLen = m_currentMag.size(); - double logMin = std::log10(20.0), logMax = std::log10(22050.0); - QRgb *row = reinterpret_cast( - m_waterfall.bits() + (wfH-1)*m_waterfall.bytesPerLine()); - - for (int px = 0; px < w; px++) { - double freq = std::pow(10.0, logMin + (logMax-logMin)*px/w); - int bin = std::min(fftLen-1, int(freq/22050.0*fftLen)); - int binL = std::max(0, bin-1), binR = std::min(fftLen-1, bin+1); - float mag = 0.f; - for (int k=binL; k<=binR; k++) mag += m_currentMag[k]; - mag /= (binR-binL+1); - float db = mag>0 ? 20.f*std::log10(mag) : DB_MIN; - float val = std::clamp((db-DB_MIN)/(DB_MAX-DB_MIN), 0.f, 1.f); - row[px] = m_heatmap[int(val*255)]; + glGenTextures(1, &m_heatmapTex); + glBindTexture(GL_TEXTURE_1D, m_heatmapTex); + std::vector 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); } -void SpectrogramWidget::computeBars() { - int fftLen = m_currentMag.size(); - double logMin = std::log10(20.0), logMax = std::log10(22050.0); +void SpectrogramWidget::setStops(const QVector &stops) +{ + if (stops.size() < 2) return; + m_stops = stops; + makeCurrent(); + updateHeatmapTexture(); + doneCurrent(); + update(); + saveBarColors(); +} + +void SpectrogramWidget::loadBarColors() +{ + QString configDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.subwave"; + QDir().mkpath(configDir); + QString path = configDir + "/barcolors.json"; + QFile f(path); + if (!f.open(QIODevice::ReadOnly)) + return; + + QJsonDocument doc = QJsonDocument::fromJson(f.readAll()); + f.close(); + if (!doc.isArray()) + return; + + QJsonArray arr = doc.array(); + QVector loaded; + for (const auto &v : arr) { + 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)); + } + if (loaded.size() >= 2) + m_stops = loaded; +} + +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; + + QJsonArray arr; + for (const QColor &c : m_stops) { + QJsonObject obj; + obj["r"] = c.red(); + obj["g"] = c.green(); + obj["b"] = c.blue(); + arr.append(obj); + } + QJsonDocument doc(arr); + f.write(doc.toJson()); + f.close(); +} + +void SpectrogramWidget::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) { + GradientDialog dlg(m_stops, this); + if (dlg.exec() == QDialog::Accepted) { + setStops(dlg.stops()); + } + } + QOpenGLWidget::mousePressEvent(e); +} + +void SpectrogramWidget::computeBars() +{ + const int fftLen = m_currentMag.size(); + const double logMin = std::log10(20.0), logMax = std::log10(22050.0); QVector 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::max(0, std::min(fftLen-1, int(f1/22050.0*fftLen))); - int bin2 = std::max(bin1, std::min(fftLen-1, int(f2/22050.0*fftLen))); - 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; + 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; b0 ? 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++) { + 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 m_barPeak[b]) { - m_barPeak[b] = m_barSmoothed[b]; m_peakTimer[b] = 25; + 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]--; } else { - m_barPeak[b] = std::max(0.f, m_barPeak[b]-0.006f); + m_barPeak[b] = std::max(0.f, m_barPeak[b] - 0.006f); } + m_barHeights [b] = m_barSmoothed[b]; + m_barPeaksVec[b] = m_barPeak[b]; } } -void SpectrogramWidget::drawSpectrum(QPainter &p, int x, int y, int w, int h) { - int barW = std::max(1, w/BAR_COUNT); - int gap = std::max(1, barW/5); - for (int i=0; i 0.01f) { - int ph = int(m_barPeak[i]*(h-12)); - p.fillRect(bx+gap/2, y+h-12-ph-1, barW-gap, 2, Qt::white); - } +void SpectrogramWidget::paintGL() +{ + { + QMutexLocker lk(&m_queueMtx); + while (!m_fftQueue.isEmpty()) + m_currentMag = m_fftQueue.dequeue(); } - p.setPen(palette().windowText().color()); - QFont f = p.font(); f.setPointSizeF(7); p.setFont(f); - const char *lbls[] = {"32","125","500","2k","8k","16k"}; - float freqs[]= {32,125,500,2000,8000,16000}; - double logMin=std::log10(20.0), logMax=std::log10(22050.0); - for (int i=0; i<6; i++) { - double nx = (std::log10(freqs[i])-logMin)/(logMax-logMin)*w; - p.drawText(x+int(nx), y+h-1, lbls[i]); + + computeBars(); + + 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; + + 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(); } + + update(); } \ No newline at end of file diff --git a/src/SpectrogramWidget.h b/src/SpectrogramWidget.h index 0f68079..f128c02 100644 --- a/src/SpectrogramWidget.h +++ b/src/SpectrogramWidget.h @@ -1,38 +1,68 @@ #pragma once -#include -#include +#include +#include +#include #include #include -#include #include +#include +#include -class SpectrogramWidget : public QWidget { +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; public: explicit SpectrogramWidget(QWidget *parent = nullptr); + ~SpectrogramWidget(); + public slots: void receiveFft(const QVector &mag); void reset(); -protected: - void paintEvent(QPaintEvent *) override; - void resizeEvent(QResizeEvent *) override; -private: - void buildHeatmap(); - void ensureWaterfall(int w, int wfH); - void scrollWaterfall(); - void computeBars(); - void drawSpectrum(QPainter &p, int x, int y, int w, int h); - QTimer m_renderTimer; - QMutex m_queueMtx; - QQueue> m_fftQueue; - QVector m_currentMag = QVector(2048, 0.f); - QVector m_barSmoothed = QVector(BAR_COUNT, 0.f); - QVector m_barPeak = QVector(BAR_COUNT, 0.f); - QVector m_peakTimer = QVector(BAR_COUNT, 0); - QImage m_waterfall; - std::array m_heatmap {}; +protected: + void initializeGL() override; + void paintGL() override; + void mousePressEvent(QMouseEvent *e) override; + +private: + void initShaders(); + void buildHeatmapTexture(); + void updateHeatmapTexture(); + void setStops(const QVector &stops); + void loadBarColors(); + void saveBarColors(); + void computeBars(); + + QMutex m_queueMtx; + QQueue> m_fftQueue; + QVector m_currentMag = QVector(2048, 0.f); + + QVector m_barSmoothed = QVector(BAR_COUNT, 0.f); + QVector m_barPeak = QVector(BAR_COUNT, 0.f); + QVector m_peakTimer = QVector (BAR_COUNT, 0); + QVector m_barHeights; + QVector m_barPeaksVec; + + QVector m_stops = { QColor("#078D70"), + QColor("#26CEAA"), + QColor("#98E8C1"), + QColor("#FFFFFF") }; + + std::array m_heatmap{}; + + QOpenGLShaderProgram m_barsProg; + GLuint m_heatmapTex = 0; + GLuint m_vaoBars = 0; + + int m_uBarHeights = -1; + int m_uBarPeaks = -1; + int m_uBarCount = -1; + int m_uBarWidth = -1; + int m_uGap = -1; + int m_uBottomY = -1; + int m_uTopClip = -1; + int m_uHeatmap = -1; }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 256af53..f6fc9ac 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include "MainWindow.h" int main(int argc, char *argv[]) @@ -7,7 +9,10 @@ int main(int argc, char *argv[]) app.setApplicationName("SubWave"); app.setOrganizationName("SubWave"); + qDebug() << QResource(":/shaders/bars.vert").isValid(); + MainWindow w; w.show(); + return app.exec(); } \ No newline at end of file diff --git a/src/shaders.qrc b/src/shaders.qrc new file mode 100644 index 0000000..40784c2 --- /dev/null +++ b/src/shaders.qrc @@ -0,0 +1,6 @@ + + + shaders/bars.vert + shaders/bars.frag + + \ No newline at end of file diff --git a/src/shaders/bars.frag b/src/shaders/bars.frag new file mode 100644 index 0000000..1f86f55 --- /dev/null +++ b/src/shaders/bars.frag @@ -0,0 +1,12 @@ +#version 330 core + +in float vVal; +out vec4 fragColor; + +uniform sampler1D heatmap; + +void main() +{ + vec3 col = texture(heatmap, vVal).rgb; + fragColor = vec4(col, 1.0); +} diff --git a/src/shaders/bars.vert b/src/shaders/bars.vert new file mode 100644 index 0000000..9229be5 --- /dev/null +++ b/src/shaders/bars.vert @@ -0,0 +1,38 @@ +#version 330 core + +uniform float barHeights[96]; +uniform int barCount; +uniform float barWidth; +uniform float gap; +uniform float bottomY; +uniform float topClip; + +out float vVal; + +void main() +{ + int bar = gl_InstanceID; + int vertex = gl_VertexID; + + float h = barHeights[bar]; + + float xLeft = -1.0 + float(bar) * (barWidth + gap); + float xRight = xLeft + barWidth; + + float regionH = topClip - bottomY; + float yTop = bottomY + h * regionH; + + vec2 corners[6]; + corners[0] = vec2(xLeft, bottomY); + corners[1] = vec2(xRight, bottomY); + corners[2] = vec2(xRight, yTop); + corners[3] = vec2(xLeft, bottomY); + corners[4] = vec2(xRight, yTop); + corners[5] = vec2(xLeft, yTop); + + vec2 pos = corners[vertex]; + + vVal = (pos.y - bottomY) / regionH; + + gl_Position = vec4(pos, 0.0, 1.0); +}