try to make code performant, lets see if this will work.
This commit is contained in:
@@ -96,6 +96,17 @@ set(RESOURCES
|
|||||||
# target
|
# target
|
||||||
add_executable(SubWave ${SOURCES} ${HEADERS} ${RESOURCES})
|
add_executable(SubWave ${SOURCES} ${HEADERS} ${RESOURCES})
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
|
message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.")
|
||||||
|
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Choose the type of build." FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(SubWave PRIVATE /O2 /fp:fast)
|
||||||
|
else()
|
||||||
|
target_compile_options(SubWave PRIVATE -O3 -ffast-math -march=native)
|
||||||
|
endif()
|
||||||
|
|
||||||
# headers
|
# headers
|
||||||
target_include_directories(SubWave PRIVATE
|
target_include_directories(SubWave PRIVATE
|
||||||
src
|
src
|
||||||
|
|||||||
@@ -21,6 +21,17 @@ clean_build() {
|
|||||||
make -j"$(nproc)"
|
make -j"$(nproc)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
release_build() {
|
||||||
|
if [ -d "build" ]; then
|
||||||
|
rm -rf "build"
|
||||||
|
fi
|
||||||
|
mkdir "build"
|
||||||
|
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||||
|
make -j"$(nproc)"
|
||||||
|
}
|
||||||
|
|
||||||
run_executable() {
|
run_executable() {
|
||||||
./build/SubWave
|
./build/SubWave
|
||||||
}
|
}
|
||||||
@@ -38,6 +49,9 @@ case "${1:-build}" in
|
|||||||
run|exec|r)
|
run|exec|r)
|
||||||
run_executable
|
run_executable
|
||||||
;;
|
;;
|
||||||
|
release|rb)
|
||||||
|
release_build
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Usage: $0 {build|clean|cleanbuild|cb}"
|
echo "Usage: $0 {build|clean|cleanbuild|cb}"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ private:
|
|||||||
float m_bqCoeff[EQ_BANDS][5] {};
|
float m_bqCoeff[EQ_BANDS][5] {};
|
||||||
float m_bqState[EQ_BANDS][2][2] {};
|
float m_bqState[EQ_BANDS][2][2] {};
|
||||||
|
|
||||||
static constexpr int FFT_SIZE = 4096;
|
static constexpr int FFT_SIZE = 2048;
|
||||||
float m_fftBuf[FFT_SIZE] {};
|
float m_fftBuf[FFT_SIZE] {};
|
||||||
int m_fftPos { 0 };
|
int m_fftPos { 0 };
|
||||||
};
|
};
|
||||||
|
|||||||
+60
-35
@@ -1,21 +1,23 @@
|
|||||||
#include "EqProcessor.h"
|
#include "EqProcessor.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <numbers>
|
#include <numbers>
|
||||||
#include <cstring>
|
#include <algorithm>
|
||||||
|
|
||||||
namespace engine {
|
namespace engine {
|
||||||
|
|
||||||
EqProcessor::EqProcessor() {
|
EqProcessor::EqProcessor() {
|
||||||
|
std::fill(std::begin(m_gainDb), std::end(m_gainDb), 0.f);
|
||||||
for (int b = 0; b < BANDS; ++b) {
|
for (int b = 0; b < BANDS; ++b) {
|
||||||
m_gainDb[b] = 0.f;
|
|
||||||
recomputeBiquad(b);
|
recomputeBiquad(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EqProcessor::setSampleRate(int sr) {
|
void EqProcessor::setSampleRate(int sr) {
|
||||||
|
if (sr <= 0) return;
|
||||||
m_sampleRate = sr;
|
m_sampleRate = sr;
|
||||||
for (int b = 0; b < BANDS; ++b)
|
for (int b = 0; b < BANDS; ++b) {
|
||||||
recomputeBiquad(b);
|
recomputeBiquad(b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EqProcessor::setBand(int band, float dB) {
|
void EqProcessor::setBand(int band, float dB) {
|
||||||
@@ -29,34 +31,56 @@ float EqProcessor::band(int band) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EqProcessor::reset() {
|
void EqProcessor::reset() {
|
||||||
std::memset(m_state, 0, sizeof(m_state));
|
for (int b = 0; b < BANDS; ++b) {
|
||||||
|
for (int c = 0; c < MAX_CH; ++c) {
|
||||||
|
m_state[b][c] = BiquadState{};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EqProcessor::process(AudioBuffer &a_buf) {
|
void EqProcessor::process(AudioBuffer &a_buf) {
|
||||||
const int channels = a_buf.channels;
|
const int channels = a_buf.channels;
|
||||||
const int count = a_buf.sampleCount();
|
const int count = a_buf.sampleCount();
|
||||||
float *samples = a_buf.samples.data();
|
float *samples = a_buf.samples.data();
|
||||||
|
|
||||||
|
if (channels <= 0 || count <= 0 || !samples) return;
|
||||||
|
|
||||||
|
const int activeChannels = std::min(channels, MAX_CH);
|
||||||
|
|
||||||
|
struct ActiveBand {
|
||||||
|
BiquadCoefficients coeff;
|
||||||
|
int index;
|
||||||
|
};
|
||||||
|
|
||||||
|
ActiveBand activeBands[BANDS];
|
||||||
|
int activeCount = 0;
|
||||||
|
|
||||||
for (int band = 0; band < BANDS; ++band) {
|
for (int band = 0; band < BANDS; ++band) {
|
||||||
if (std::abs(m_gainDb[band]) < 0.01f) {continue;}
|
if (std::abs(m_gainDb[band]) >= 0.01f) {
|
||||||
|
activeBands[activeCount++] = { m_coeff[band], band };
|
||||||
const auto& c = m_coeff[band];
|
}
|
||||||
|
}
|
||||||
for (int idx = 0; idx < count; ++idx) {
|
|
||||||
const int ch = idx % channels;
|
if (activeCount == 0) return;
|
||||||
|
|
||||||
auto &s = m_state[band][ch];
|
for (int frame = 0; frame < count; frame += channels) {
|
||||||
|
for (int ch = 0; ch < activeChannels; ++ch) {
|
||||||
const float x = samples[idx];
|
const int idx = frame + ch;
|
||||||
const float y = c.b0 * x + s.z1;
|
float sample = samples[idx];
|
||||||
|
|
||||||
s.z1 = c.b1 * x - c.a1 * y + s.z2;
|
for (int b = 0; b < activeCount; ++b) {
|
||||||
s.z2 = c.b2 * x - c.a2 * y;
|
const auto& c = activeBands[b].coeff;
|
||||||
|
auto &s = m_state[activeBands[b].index][ch];
|
||||||
samples[idx] = y;
|
|
||||||
|
const float y = c.b0 * sample + s.z1;
|
||||||
|
s.z1 = c.b1 * sample - c.a1 * y + s.z2;
|
||||||
|
s.z2 = c.b2 * sample - c.a2 * y;
|
||||||
|
|
||||||
|
sample = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
samples[idx] = sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,27 +90,28 @@ void EqProcessor::recomputeBiquad(int band) {
|
|||||||
const double gainDb = m_gainDb[band];
|
const double gainDb = m_gainDb[band];
|
||||||
constexpr double Q = std::numbers::sqrt2;
|
constexpr double Q = std::numbers::sqrt2;
|
||||||
|
|
||||||
// RBJ peaking EQ gain factor
|
if (frequency >= sampleRate * 0.5) {
|
||||||
const double A = std::pow(10.0, gainDb / 40.0);
|
auto &c = m_coeff[band];
|
||||||
const double omega = 2.0 * std::numbers::pi * frequency / sampleRate;
|
c.b0 = 1.0f; c.b1 = 0.0f; c.b2 = 0.0f; c.a1 = 0.0f; c.a2 = 0.0f;
|
||||||
const double sinW = std::sin(omega);
|
return;
|
||||||
const double cosW = std::cos(omega);
|
}
|
||||||
|
|
||||||
|
const double A = std::pow(10.0, gainDb / 40.0);
|
||||||
|
const double omega = 2.0 * std::numbers::pi * frequency / sampleRate;
|
||||||
|
const double sinW = std::sin(omega);
|
||||||
|
const double cosW = std::cos(omega);
|
||||||
const double alpha = sinW / (2.0 * Q);
|
const double alpha = sinW / (2.0 * Q);
|
||||||
|
|
||||||
// rbj audio eq cookbook
|
|
||||||
const double b0 = 1.0 + alpha * A;
|
const double b0 = 1.0 + alpha * A;
|
||||||
const double b1 = -2.0 * cosW;
|
const double b1 = -2.0 * cosW;
|
||||||
const double b2 = 1.0 - alpha * A;
|
const double b2 = 1.0 - alpha * A;
|
||||||
|
|
||||||
const double a0 = 1.0 + alpha / A;
|
const double a0 = 1.0 + alpha / A;
|
||||||
const double a1 = -2.0 * cosW;
|
const double a1 = -2.0 * cosW;
|
||||||
const double a2 = 1.0 - alpha / A;
|
const double a2 = 1.0 - alpha / A;
|
||||||
|
|
||||||
const double invA0 = 1.0 / a0;
|
const double invA0 = 1.0 / a0;
|
||||||
|
|
||||||
auto &c = m_coeff[band];
|
auto &c = m_coeff[band];
|
||||||
|
|
||||||
c.b0 = static_cast<float>(b0 * invA0);
|
c.b0 = static_cast<float>(b0 * invA0);
|
||||||
c.b1 = static_cast<float>(b1 * invA0);
|
c.b1 = static_cast<float>(b1 * invA0);
|
||||||
c.b2 = static_cast<float>(b2 * invA0);
|
c.b2 = static_cast<float>(b2 * invA0);
|
||||||
|
|||||||
+15
-17
@@ -1,11 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
namespace engine {
|
namespace engine {
|
||||||
|
|
||||||
class EqProcessor {
|
class EqProcessor {
|
||||||
public:
|
public:
|
||||||
static constexpr int BANDS = 10;
|
static constexpr int BANDS = 10;
|
||||||
static constexpr float FREQS[BANDS] = {
|
static constexpr float FREQS[BANDS] = {
|
||||||
32, 64, 125, 250, 500, 1000, 2000, 4000, 8000, 16000
|
32, 64, 125, 250, 500, 1000, 2000, 4000, 8000, 16000
|
||||||
};
|
};
|
||||||
@@ -14,12 +16,9 @@ public:
|
|||||||
|
|
||||||
void setBand(int band, float dB);
|
void setBand(int band, float dB);
|
||||||
float band(int band) const;
|
float band(int band) const;
|
||||||
|
void process(AudioBuffer &buf);
|
||||||
void process(AudioBuffer &buf);
|
void reset();
|
||||||
|
void setSampleRate(int sr);
|
||||||
void reset();
|
|
||||||
|
|
||||||
void setSampleRate(int sr);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void recomputeBiquad(int band);
|
void recomputeBiquad(int band);
|
||||||
@@ -28,22 +27,21 @@ private:
|
|||||||
float m_gainDb[BANDS] {};
|
float m_gainDb[BANDS] {};
|
||||||
|
|
||||||
struct BiquadCoefficients {
|
struct BiquadCoefficients {
|
||||||
float b0 {};
|
float b0 { 1.0f };
|
||||||
float b1 {};
|
float b1 { 0.0f };
|
||||||
float b2 {};
|
float b2 { 0.0f };
|
||||||
float a1 {};
|
float a1 { 0.0f };
|
||||||
float a2 {};
|
float a2 { 0.0f };
|
||||||
};
|
};
|
||||||
|
|
||||||
BiquadCoefficients m_coeff[BANDS];
|
BiquadCoefficients m_coeff[BANDS];
|
||||||
|
|
||||||
struct BiquadState {
|
struct BiquadState {
|
||||||
float z1;
|
float z1 { 0.0f };
|
||||||
float z2;
|
float z2 { 0.0f };
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr int MAX_CH = 8;
|
static constexpr int MAX_CH = 8;
|
||||||
BiquadState m_state[BANDS][MAX_CH] {};
|
BiquadState m_state[BANDS][MAX_CH] {};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace engine
|
} // namespace engine
|
||||||
@@ -18,27 +18,35 @@ SpectrumAnalyzer::SpectrumAnalyzer(QObject *parent)
|
|||||||
if (!m_plan)
|
if (!m_plan)
|
||||||
throw std::runtime_error("FFTW plan creation failed");
|
throw std::runtime_error("FFTW plan creation failed");
|
||||||
|
|
||||||
|
// Precompute Hann window once — avoid recalculating every FFT frame
|
||||||
|
const double invN1 = 1.0 / static_cast<double>(FFT_SIZE - 1);
|
||||||
|
for (int i = 0; i < FFT_SIZE; ++i)
|
||||||
|
m_hannWindow[i] = 0.5 * (1.0 - std::cos(2.0 * std::numbers::pi * i * invN1));
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
SpectrumAnalyzer::~SpectrumAnalyzer()
|
SpectrumAnalyzer::~SpectrumAnalyzer()
|
||||||
{
|
{
|
||||||
if (m_plan) fftw_destroy_plan(m_plan);
|
if (m_plan) fftw_destroy_plan(m_plan);
|
||||||
if (m_fftIn) fftw_free(m_fftIn);
|
if (m_fftIn) fftw_free(m_fftIn);
|
||||||
if (m_fftOut) fftw_free(m_fftOut);
|
if (m_fftOut) fftw_free(m_fftOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpectrumAnalyzer::reset()
|
void SpectrumAnalyzer::reset()
|
||||||
{
|
{
|
||||||
|
// Fill the whole buffer with silence.
|
||||||
|
// m_pos starts at FFT_SIZE/2 so the first FFT fires after one
|
||||||
|
// half-window of real samples, maintaining 50% overlap from the start.
|
||||||
std::fill(std::begin(m_buf), std::end(m_buf), 0.f);
|
std::fill(std::begin(m_buf), std::end(m_buf), 0.f);
|
||||||
m_pos = FFT_SIZE / 2;
|
m_pos = FFT_SIZE / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpectrumAnalyzer::feed(const AudioBuffer &buf)
|
void SpectrumAnalyzer::feed(const AudioBuffer &buf)
|
||||||
{
|
{
|
||||||
const int channels = buf.channels;
|
const int channels = buf.channels;
|
||||||
const int count = buf.sampleCount();
|
const int count = buf.sampleCount();
|
||||||
const float *src = buf.samples.data();
|
const float *src = buf.samples.data();
|
||||||
|
|
||||||
for (int i = 0; i < count; i += channels) {
|
for (int i = 0; i < count; i += channels) {
|
||||||
float mono = 0.f;
|
float mono = 0.f;
|
||||||
@@ -50,33 +58,28 @@ void SpectrumAnalyzer::feed(const AudioBuffer &buf)
|
|||||||
|
|
||||||
if (m_pos == FFT_SIZE) {
|
if (m_pos == FFT_SIZE) {
|
||||||
emit fftReady(computeFFT(m_buf));
|
emit fftReady(computeFFT(m_buf));
|
||||||
// 50 % overlap
|
// 50% overlap: shift the second half down to the first
|
||||||
std::copy(m_buf + FFT_SIZE / 2, m_buf + FFT_SIZE, m_buf);
|
std::copy(m_buf + FFT_SIZE / 2, m_buf + FFT_SIZE, m_buf);
|
||||||
m_pos = FFT_SIZE / 2;
|
m_pos = FFT_SIZE / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<float> SpectrumAnalyzer::computeFFT(const float *input)
|
QVector<float> SpectrumAnalyzer::computeFFT(const float (&input)[FFT_SIZE])
|
||||||
{
|
{
|
||||||
// Copy input and apply Hann window
|
// Apply precomputed Hann window
|
||||||
const double invN1 = 1.0 / static_cast<double>(FFT_SIZE - 1);
|
for (int i = 0; i < FFT_SIZE; ++i)
|
||||||
for (int i = 0; i < FFT_SIZE; ++i) {
|
m_fftIn[i] = static_cast<double>(input[i]) * m_hannWindow[i];
|
||||||
const double w = 0.5 * (1.0 - std::cos(2.0 * std::numbers::pi * i * invN1));
|
|
||||||
m_fftIn[i] = static_cast<double>(input[i]) * w;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute FFTW plan
|
|
||||||
fftw_execute(m_plan);
|
fftw_execute(m_plan);
|
||||||
|
|
||||||
// Compute magnitude spectrum (bins 0 .. FFT_SIZE/2 - 1)
|
// Magnitude spectrum
|
||||||
const int bins = FFT_SIZE / 2;
|
const float invN = 1.f / static_cast<float>(FFT_SIZE);
|
||||||
QVector<float> mag(bins);
|
QVector<float> mag(BIN_COUNT);
|
||||||
const double invN = 1.0 / static_cast<double>(FFT_SIZE);
|
for (int i = 0; i < BIN_COUNT; ++i) {
|
||||||
for (int i = 0; i < bins; ++i) {
|
const float re = static_cast<float>(m_fftOut[i][0]);
|
||||||
const double re = m_fftOut[i][0];
|
const float im = static_cast<float>(m_fftOut[i][1]);
|
||||||
const double im = m_fftOut[i][1];
|
mag[i] = std::sqrt(re * re + im * im) * invN;
|
||||||
mag[i] = static_cast<float>(std::sqrt(re * re + im * im) * invN);
|
|
||||||
}
|
}
|
||||||
return mag;
|
return mag;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
#include <atomic>
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
#include <fftw3.h>
|
#include <fftw3.h>
|
||||||
|
|
||||||
@@ -9,7 +10,8 @@ namespace engine {
|
|||||||
class SpectrumAnalyzer : public QObject {
|
class SpectrumAnalyzer : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
static constexpr int FFT_SIZE = 4096;
|
static constexpr int FFT_SIZE = 2048;
|
||||||
|
static constexpr int BIN_COUNT = FFT_SIZE / 2;
|
||||||
|
|
||||||
explicit SpectrumAnalyzer(QObject *parent = nullptr);
|
explicit SpectrumAnalyzer(QObject *parent = nullptr);
|
||||||
~SpectrumAnalyzer() override;
|
~SpectrumAnalyzer() override;
|
||||||
@@ -21,15 +23,17 @@ signals:
|
|||||||
void fftReady(QVector<float> magnitudes);
|
void fftReady(QVector<float> magnitudes);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QVector<float> computeFFT(const float *input);
|
QVector<float> computeFFT(const float (&input)[FFT_SIZE]);
|
||||||
|
|
||||||
float m_buf[FFT_SIZE] {};
|
float m_buf[FFT_SIZE]{};
|
||||||
int m_pos { 0 };
|
int m_pos{FFT_SIZE / 2};
|
||||||
|
|
||||||
// FFTW members
|
// Precomputed Hann window coefficients
|
||||||
fftw_plan m_plan = nullptr;
|
double m_hannWindow[FFT_SIZE]{};
|
||||||
double *m_fftIn = nullptr;
|
|
||||||
fftw_complex *m_fftOut = nullptr;
|
fftw_plan m_plan = nullptr;
|
||||||
|
double *m_fftIn = nullptr;
|
||||||
|
fftw_complex *m_fftOut = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace engine
|
} // namespace engine
|
||||||
+123
-123
@@ -4,14 +4,12 @@
|
|||||||
#include <QSurfaceFormat>
|
#include <QSurfaceFormat>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QColorDialog>
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
SpectrogramWidget::SpectrogramWidget(QWidget *parent)
|
SpectrogramWidget::SpectrogramWidget(QWidget *parent)
|
||||||
: QOpenGLWidget(parent)
|
: QOpenGLWidget(parent)
|
||||||
@@ -28,15 +26,14 @@ SpectrogramWidget::SpectrogramWidget(QWidget *parent)
|
|||||||
setMinimumSize(400, 200);
|
setMinimumSize(400, 200);
|
||||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
|
||||||
m_barHeights.resize(BAR_COUNT);
|
|
||||||
m_barPeaksVec.resize(BAR_COUNT);
|
|
||||||
|
|
||||||
loadBarColors();
|
loadBarColors();
|
||||||
|
|
||||||
auto *timer = new QTimer(this);
|
auto *timer = new QTimer(this);
|
||||||
connect(timer, &QTimer::timeout,
|
connect(timer, &QTimer::timeout,
|
||||||
this, QOverload<>::of(&SpectrogramWidget::update));
|
this, QOverload<>::of(&SpectrogramWidget::update));
|
||||||
timer->start(16);
|
timer->start(16);
|
||||||
|
|
||||||
|
m_frameTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
SpectrogramWidget::~SpectrogramWidget()
|
SpectrogramWidget::~SpectrogramWidget()
|
||||||
@@ -49,23 +46,23 @@ SpectrogramWidget::~SpectrogramWidget()
|
|||||||
|
|
||||||
void SpectrogramWidget::receiveFft(const QVector<float> &mag)
|
void SpectrogramWidget::receiveFft(const QVector<float> &mag)
|
||||||
{
|
{
|
||||||
QMutexLocker lk(&m_queueMtx);
|
QMutexLocker lk(&m_latestMtx);
|
||||||
while (m_fftQueue.size() >= 4)
|
m_latestMag = mag;
|
||||||
m_fftQueue.dequeue();
|
m_newMagAvailable = true;
|
||||||
m_fftQueue.enqueue(mag);
|
|
||||||
update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpectrogramWidget::reset()
|
void SpectrogramWidget::reset()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
QMutexLocker lk(&m_queueMtx);
|
QMutexLocker lk(&m_latestMtx);
|
||||||
m_fftQueue.clear();
|
m_latestMag.clear();
|
||||||
|
m_newMagAvailable = false;
|
||||||
}
|
}
|
||||||
m_currentMag .fill(0.f);
|
m_currentMag.fill(0.f);
|
||||||
m_barSmoothed.fill(0.f);
|
m_barSmoothed.fill(0.f);
|
||||||
m_barPeak .fill(0.f);
|
m_barPeak.fill(0.f);
|
||||||
m_peakTimer .fill(0);
|
m_peakHoldTimer.fill(0.f);
|
||||||
|
m_dirty = false;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,10 +70,22 @@ void SpectrogramWidget::initializeGL()
|
|||||||
{
|
{
|
||||||
initializeOpenGLFunctions();
|
initializeOpenGLFunctions();
|
||||||
initShaders();
|
initShaders();
|
||||||
buildHeatmapTexture();
|
updateHeatmapTexture();
|
||||||
|
|
||||||
glGenVertexArrays(1, &m_vaoBars);
|
glGenVertexArrays(1, &m_vaoBars);
|
||||||
glDisable(GL_BLEND);
|
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()
|
void SpectrogramWidget::initShaders()
|
||||||
@@ -90,9 +99,11 @@ void SpectrogramWidget::initShaders()
|
|||||||
return f.readAll();
|
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();
|
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();
|
qWarning() << "Bars frag error:" << m_barsProg.log();
|
||||||
if (!m_barsProg.link())
|
if (!m_barsProg.link())
|
||||||
qWarning() << "Bars link error:" << m_barsProg.log();
|
qWarning() << "Bars link error:" << m_barsProg.log();
|
||||||
@@ -107,50 +118,43 @@ void SpectrogramWidget::initShaders()
|
|||||||
m_uHeatmap = m_barsProg.uniformLocation("heatmap");
|
m_uHeatmap = m_barsProg.uniformLocation("heatmap");
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpectrogramWidget::buildHeatmapTexture()
|
|
||||||
{
|
|
||||||
updateHeatmapTexture(); // use current m_stops
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpectrogramWidget::updateHeatmapTexture()
|
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) {
|
if (m_heatmapTex) {
|
||||||
glDeleteTextures(1, &m_heatmapTex);
|
glDeleteTextures(1, &m_heatmapTex);
|
||||||
m_heatmapTex = 0;
|
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);
|
glGenTextures(1, &m_heatmapTex);
|
||||||
glBindTexture(GL_TEXTURE_1D, 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,
|
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB, 256, 0,
|
||||||
GL_RGB, GL_UNSIGNED_BYTE, td.data());
|
GL_RGB, GL_UNSIGNED_BYTE, td.data());
|
||||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
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_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)
|
void SpectrogramWidget::setStops(const QVector<QColor> &stops)
|
||||||
@@ -166,26 +170,18 @@ void SpectrogramWidget::setStops(const QVector<QColor> &stops)
|
|||||||
|
|
||||||
void SpectrogramWidget::loadBarColors()
|
void SpectrogramWidget::loadBarColors()
|
||||||
{
|
{
|
||||||
QString configDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.subwave";
|
QString path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation)
|
||||||
QDir().mkpath(configDir);
|
+ "/.subwave/barcolors.json";
|
||||||
QString path = configDir + "/barcolors.json";
|
|
||||||
QFile f(path);
|
QFile f(path);
|
||||||
if (!f.open(QIODevice::ReadOnly))
|
if (!f.open(QIODevice::ReadOnly)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(f.readAll());
|
QJsonDocument doc = QJsonDocument::fromJson(f.readAll());
|
||||||
f.close();
|
if (!doc.isArray()) return;
|
||||||
if (!doc.isArray())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QJsonArray arr = doc.array();
|
|
||||||
QVector<QColor> loaded;
|
QVector<QColor> loaded;
|
||||||
for (const auto &v : arr) {
|
for (const auto &v : doc.array()) {
|
||||||
QJsonObject obj = v.toObject();
|
QJsonObject obj = v.toObject();
|
||||||
int r = obj["r"].toInt(0);
|
loaded.append(QColor(obj["r"].toInt(), obj["g"].toInt(), obj["b"].toInt()));
|
||||||
int g = obj["g"].toInt(0);
|
|
||||||
int b = obj["b"].toInt(0);
|
|
||||||
loaded.append(QColor(r, g, b));
|
|
||||||
}
|
}
|
||||||
if (loaded.size() >= 2)
|
if (loaded.size() >= 2)
|
||||||
m_stops = loaded;
|
m_stops = loaded;
|
||||||
@@ -193,12 +189,11 @@ void SpectrogramWidget::loadBarColors()
|
|||||||
|
|
||||||
void SpectrogramWidget::saveBarColors()
|
void SpectrogramWidget::saveBarColors()
|
||||||
{
|
{
|
||||||
QString configDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.subwave";
|
QString dir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation)
|
||||||
QDir().mkpath(configDir);
|
+ "/.subwave";
|
||||||
QString path = configDir + "/barcolors.json";
|
QDir().mkpath(dir);
|
||||||
QFile f(path);
|
QFile f(dir + "/barcolors.json");
|
||||||
if (!f.open(QIODevice::WriteOnly))
|
if (!f.open(QIODevice::WriteOnly)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
QJsonArray arr;
|
QJsonArray arr;
|
||||||
for (const QColor &c : m_stops) {
|
for (const QColor &c : m_stops) {
|
||||||
@@ -208,89 +203,94 @@ void SpectrogramWidget::saveBarColors()
|
|||||||
obj["b"] = c.blue();
|
obj["b"] = c.blue();
|
||||||
arr.append(obj);
|
arr.append(obj);
|
||||||
}
|
}
|
||||||
QJsonDocument doc(arr);
|
f.write(QJsonDocument(arr).toJson());
|
||||||
f.write(doc.toJson());
|
|
||||||
f.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void SpectrogramWidget::mousePressEvent(QMouseEvent *e)
|
void SpectrogramWidget::mousePressEvent(QMouseEvent *e)
|
||||||
{
|
{
|
||||||
if (e->button() == Qt::LeftButton) {
|
if (e->button() == Qt::LeftButton) {
|
||||||
GradientDialog dlg(m_stops, this);
|
GradientDialog dlg(m_stops, this);
|
||||||
if (dlg.exec() == QDialog::Accepted) {
|
if (dlg.exec() == QDialog::Accepted)
|
||||||
setStops(dlg.stops());
|
setStops(dlg.stops());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
QOpenGLWidget::mousePressEvent(e);
|
QOpenGLWidget::mousePressEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpectrogramWidget::computeBars()
|
|
||||||
|
void SpectrogramWidget::computeBars(float dt)
|
||||||
{
|
{
|
||||||
const int fftLen = m_currentMag.size();
|
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++) {
|
for (int b = 0; b < BAR_COUNT; ++b) {
|
||||||
double f1 = std::pow(10.0, logMin + (logMax-logMin)* b /BAR_COUNT);
|
const int lo = m_binMap[b].lo;
|
||||||
double f2 = std::pow(10.0, logMin + (logMax-logMin)*(b+1)/BAR_COUNT);
|
const int hi = std::min(m_binMap[b].hi, fftLen - 1);
|
||||||
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;
|
||||||
float sum = 0.f; int cnt = 0;
|
int cnt = 0;
|
||||||
for (int k = bin1; k <= bin2; k++) { sum += m_currentMag[k]; cnt++; }
|
for (int k = lo; k <= hi; ++k) { sum += m_currentMag[k]; ++cnt; }
|
||||||
bars[b] = cnt > 0 ? sum/cnt : 0.f;
|
float raw = cnt > 0 ? sum / cnt : 0.f;
|
||||||
}
|
|
||||||
for (int b = 0; b < BAR_COUNT; b++) {
|
float db = raw > 0.f ? 20.f * std::log10(raw) : DB_MIN;
|
||||||
float db = bars[b] > 0 ? 20.f*std::log10(bars[b]) : DB_MIN;
|
float bar = std::clamp((db - DB_MIN) / (DB_MAX - DB_MIN), 0.f, 1.f);
|
||||||
bars[b] = std::clamp((db - DB_MIN) / (DB_MAX - DB_MIN), 0.f, 1.f);
|
|
||||||
}
|
const float coeff = bar > m_barSmoothed[b] ? 0.50f : 0.18f;
|
||||||
for (int b = 0; b < BAR_COUNT; b++) {
|
m_barSmoothed[b] += (bar - m_barSmoothed[b]) * coeff;
|
||||||
m_barSmoothed[b] = m_barSmoothed[b]*0.65f + bars[b]*0.35f;
|
|
||||||
if (m_barSmoothed[b] >= m_barPeak[b]) {
|
if (m_barSmoothed[b] >= m_barPeak[b]) {
|
||||||
m_barPeak[b] = m_barSmoothed[b];
|
m_barPeak[b] = m_barSmoothed[b];
|
||||||
m_peakTimer[b] = 25;
|
m_peakHoldTimer[b] = PEAK_HOLD_SEC;
|
||||||
} else if (m_peakTimer[b] > 0) {
|
} else if (m_peakHoldTimer[b] > 0.f) {
|
||||||
m_peakTimer[b]--;
|
m_peakHoldTimer[b] -= dt;
|
||||||
} else {
|
} 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_barHeights [b] = m_barSmoothed[b];
|
||||||
m_barPeaksVec[b] = m_barPeak[b];
|
m_barPeaksVec[b] = m_barPeak[b];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void SpectrogramWidget::paintGL()
|
void SpectrogramWidget::paintGL()
|
||||||
{
|
{
|
||||||
|
const float dt = static_cast<float>(m_frameTimer.restart()) / 1000.f;
|
||||||
|
|
||||||
{
|
{
|
||||||
QMutexLocker lk(&m_queueMtx);
|
QMutexLocker lk(&m_latestMtx);
|
||||||
while (!m_fftQueue.isEmpty())
|
if (m_newMagAvailable) {
|
||||||
m_currentMag = m_fftQueue.dequeue();
|
m_currentMag = std::move(m_latestMag);
|
||||||
|
m_latestMag = {};
|
||||||
|
m_newMagAvailable = false;
|
||||||
|
m_dirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
computeBars();
|
computeBars(dt);
|
||||||
|
|
||||||
QColor bg = palette().color(QPalette::Window);
|
QColor bg = palette().color(QPalette::Window);
|
||||||
glClearColor(bg.redF(), bg.greenF(), bg.blueF(), 1.0f);
|
glClearColor(bg.redF(), bg.greenF(), bg.blueF(), 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
if (m_barsProg.isLinked()) {
|
if (!m_barsProg.isLinked()) return;
|
||||||
const float barWidth = 2.0f / BAR_COUNT;
|
|
||||||
const float gap = barWidth * 0.2f;
|
|
||||||
const float drawWidth = barWidth - gap;
|
|
||||||
|
|
||||||
m_barsProg.bind();
|
const float barWidth = 2.0f / BAR_COUNT;
|
||||||
glUniform1fv(m_uBarHeights, BAR_COUNT, m_barHeights .constData());
|
const float gap = barWidth * 0.2f;
|
||||||
glUniform1fv(m_uBarPeaks, BAR_COUNT, m_barPeaksVec.constData());
|
const float drawWidth = barWidth - gap;
|
||||||
glUniform1i (m_uBarCount, BAR_COUNT);
|
|
||||||
glUniform1f (m_uBarWidth, drawWidth);
|
m_barsProg.bind();
|
||||||
glUniform1f (m_uGap, gap);
|
glUniform1fv(m_uBarHeights, BAR_COUNT, m_barHeights .data());
|
||||||
glUniform1f (m_uBottomY, -1.0f);
|
glUniform1fv(m_uBarPeaks, BAR_COUNT, m_barPeaksVec.data());
|
||||||
glUniform1f (m_uTopClip, 1.0f);
|
glUniform1i (m_uBarCount, BAR_COUNT);
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glUniform1f (m_uBarWidth, drawWidth);
|
||||||
glBindTexture(GL_TEXTURE_1D, m_heatmapTex);
|
glUniform1f (m_uGap, gap);
|
||||||
glUniform1i(m_uHeatmap, 0);
|
glUniform1f (m_uBottomY, -1.0f);
|
||||||
glBindVertexArray(m_vaoBars);
|
glUniform1f (m_uTopClip, 1.0f);
|
||||||
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, BAR_COUNT);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
glBindVertexArray(0);
|
glBindTexture(GL_TEXTURE_1D, m_heatmapTex);
|
||||||
m_barsProg.release();
|
glUniform1i(m_uHeatmap, 0);
|
||||||
}
|
glBindVertexArray(m_vaoBars);
|
||||||
|
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, BAR_COUNT);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
m_barsProg.release();
|
||||||
}
|
}
|
||||||
+45
-24
@@ -2,60 +2,81 @@
|
|||||||
#include <QOpenGLWidget>
|
#include <QOpenGLWidget>
|
||||||
#include <QOpenGLFunctions_3_3_Core>
|
#include <QOpenGLFunctions_3_3_Core>
|
||||||
#include <QOpenGLShaderProgram>
|
#include <QOpenGLShaderProgram>
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QQueue>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
#include "SpectrumAnalyzer.h"
|
||||||
|
|
||||||
class SpectrogramWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
|
class SpectrogramWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
static constexpr int BAR_COUNT = 96;
|
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:
|
public:
|
||||||
explicit SpectrogramWidget(QWidget *parent = nullptr);
|
explicit SpectrogramWidget(QWidget *parent = nullptr);
|
||||||
~SpectrogramWidget();
|
~SpectrogramWidget() override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void receiveFft(const QVector<float> &mag);
|
void receiveFft(const QVector<float> &mag);
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void initializeGL() override;
|
void initializeGL() override;
|
||||||
void paintGL() override;
|
void paintGL() override;
|
||||||
void mousePressEvent(QMouseEvent *e) override;
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// GL setup
|
||||||
void initShaders();
|
void initShaders();
|
||||||
void buildHeatmapTexture();
|
void updateHeatmapTexture();
|
||||||
void updateHeatmapTexture();
|
|
||||||
|
// Data
|
||||||
void setStops(const QVector<QColor> &stops);
|
void setStops(const QVector<QColor> &stops);
|
||||||
void loadBarColors();
|
void loadBarColors();
|
||||||
void saveBarColors();
|
void saveBarColors();
|
||||||
void computeBars();
|
void computeBars(float dt);
|
||||||
|
|
||||||
QMutex m_queueMtx;
|
QMutex m_latestMtx;
|
||||||
QQueue<QVector<float>> m_fftQueue;
|
QVector<float> m_latestMag; // written by audio thread
|
||||||
QVector<float> m_currentMag = QVector<float>(2048, 0.f);
|
bool m_newMagAvailable{false};
|
||||||
|
|
||||||
QVector<float> m_barSmoothed = QVector<float>(BAR_COUNT, 0.f);
|
QVector<float> m_currentMag =
|
||||||
QVector<float> m_barPeak = QVector<float>(BAR_COUNT, 0.f);
|
QVector<float>(engine::SpectrumAnalyzer::BIN_COUNT, 0.f);
|
||||||
QVector<int> m_peakTimer = QVector<int> (BAR_COUNT, 0);
|
|
||||||
QVector<float> m_barHeights;
|
|
||||||
QVector<float> m_barPeaksVec;
|
|
||||||
|
|
||||||
QVector<QColor> m_stops = { QColor("#078D70"),
|
// --- bar state ---
|
||||||
QColor("#26CEAA"),
|
std::array<float, BAR_COUNT> m_barSmoothed{};
|
||||||
QColor("#98E8C1"),
|
std::array<float, BAR_COUNT> m_barPeak{};
|
||||||
QColor("#FFFFFF") };
|
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;
|
struct BinRange { int lo, hi; };
|
||||||
GLuint m_heatmapTex = 0;
|
std::array<BinRange, BAR_COUNT> m_binMap{};
|
||||||
GLuint m_vaoBars = 0;
|
|
||||||
|
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_uBarHeights = -1;
|
||||||
int m_uBarPeaks = -1;
|
int m_uBarPeaks = -1;
|
||||||
|
|||||||
Reference in New Issue
Block a user