visual stuff
This commit is contained in:
parent
b0f33d984a
commit
9ad565ecfb
|
|
@ -1,3 +1,5 @@
|
||||||
|
// src/AudioEngine.cpp
|
||||||
|
|
||||||
#include "AudioEngine.h"
|
#include "AudioEngine.h"
|
||||||
#include <QMediaDevices>
|
#include <QMediaDevices>
|
||||||
#include <QAudioDevice>
|
#include <QAudioDevice>
|
||||||
|
|
@ -7,11 +9,31 @@
|
||||||
#include <QAudioBuffer>
|
#include <QAudioBuffer>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
AudioEngine::AudioEngine(QObject* parent) : QObject(parent) {
|
AudioEngine::AudioEngine(QObject* parent) : QObject(parent) {
|
||||||
|
// Main Processors (Steady State)
|
||||||
m_processors.push_back(new Processor(m_frameSize, m_sampleRate));
|
m_processors.push_back(new Processor(m_frameSize, m_sampleRate));
|
||||||
m_processors.push_back(new Processor(m_frameSize, m_sampleRate));
|
m_processors.push_back(new Processor(m_frameSize, m_sampleRate));
|
||||||
|
|
||||||
|
// Configure Main: Expander + HPF + Moderate Smoothing
|
||||||
|
for(auto p : m_processors) {
|
||||||
|
p->setExpander(1.5f, -50.0f); // Gentle expansion to clean up noise
|
||||||
|
p->setHPF(60.0f); // Cut rumble below 60Hz
|
||||||
|
p->setSmoothing(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transient Processors (Secondary, Fast)
|
||||||
|
int transSize = std::max(64, m_frameSize / 4);
|
||||||
|
m_transientProcessors.push_back(new Processor(transSize, m_sampleRate));
|
||||||
|
m_transientProcessors.push_back(new Processor(transSize, m_sampleRate));
|
||||||
|
|
||||||
|
// Configure Transient: Aggressive expansion, light smoothing
|
||||||
|
for(auto p : m_transientProcessors) {
|
||||||
|
p->setExpander(2.5f, -40.0f); // Expand dynamics around -40dB
|
||||||
|
p->setSmoothing(2); // Fast decay
|
||||||
|
}
|
||||||
|
|
||||||
m_processTimer = new QTimer(this);
|
m_processTimer = new QTimer(this);
|
||||||
m_processTimer->setInterval(16);
|
m_processTimer->setInterval(16);
|
||||||
connect(m_processTimer, &QTimer::timeout, this, &AudioEngine::onProcessTimer);
|
connect(m_processTimer, &QTimer::timeout, this, &AudioEngine::onProcessTimer);
|
||||||
|
|
@ -20,11 +42,13 @@ AudioEngine::AudioEngine(QObject* parent) : QObject(parent) {
|
||||||
AudioEngine::~AudioEngine() {
|
AudioEngine::~AudioEngine() {
|
||||||
stop();
|
stop();
|
||||||
for(auto p : m_processors) delete p;
|
for(auto p : m_processors) delete p;
|
||||||
|
for(auto p : m_transientProcessors) delete p;
|
||||||
if (m_fileSource) delete m_fileSource;
|
if (m_fileSource) delete m_fileSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioEngine::setNumBins(int n) {
|
void AudioEngine::setNumBins(int n) {
|
||||||
for(auto p : m_processors) p->setNumBins(n);
|
for(auto p : m_processors) p->setNumBins(n);
|
||||||
|
for(auto p : m_transientProcessors) p->setNumBins(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioEngine::loadTrack(const QString& filePath) {
|
void AudioEngine::loadTrack(const QString& filePath) {
|
||||||
|
|
@ -32,6 +56,8 @@ void AudioEngine::loadTrack(const QString& filePath) {
|
||||||
m_pcmData.clear();
|
m_pcmData.clear();
|
||||||
m_buffer.close();
|
m_buffer.close();
|
||||||
|
|
||||||
|
m_sampleRate = 48000;
|
||||||
|
|
||||||
if (m_fileSource) {
|
if (m_fileSource) {
|
||||||
m_fileSource->close();
|
m_fileSource->close();
|
||||||
delete m_fileSource;
|
delete m_fileSource;
|
||||||
|
|
@ -42,7 +68,6 @@ void AudioEngine::loadTrack(const QString& filePath) {
|
||||||
m_decoder = new QAudioDecoder(this);
|
m_decoder = new QAudioDecoder(this);
|
||||||
|
|
||||||
QAudioFormat format;
|
QAudioFormat format;
|
||||||
format.setSampleRate(44100);
|
|
||||||
format.setChannelCount(2);
|
format.setChannelCount(2);
|
||||||
format.setSampleFormat(QAudioFormat::Int16);
|
format.setSampleFormat(QAudioFormat::Int16);
|
||||||
m_decoder->setAudioFormat(format);
|
m_decoder->setAudioFormat(format);
|
||||||
|
|
@ -60,7 +85,6 @@ void AudioEngine::loadTrack(const QString& filePath) {
|
||||||
} else {
|
} else {
|
||||||
delete m_fileSource;
|
delete m_fileSource;
|
||||||
m_fileSource = nullptr;
|
m_fileSource = nullptr;
|
||||||
// Fix: Handle content:// URIs correctly
|
|
||||||
if (filePath.startsWith("content://")) {
|
if (filePath.startsWith("content://")) {
|
||||||
m_decoder->setSource(QUrl(filePath));
|
m_decoder->setSource(QUrl(filePath));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -83,7 +107,13 @@ void AudioEngine::onBufferReady() {
|
||||||
QAudioBuffer buffer = m_decoder->read();
|
QAudioBuffer buffer = m_decoder->read();
|
||||||
if (!buffer.isValid()) return;
|
if (!buffer.isValid()) return;
|
||||||
|
|
||||||
// Fix: Explicit cast to int to avoid warning
|
if (buffer.format().sampleRate() != m_sampleRate && buffer.format().sampleRate() > 0) {
|
||||||
|
m_sampleRate = buffer.format().sampleRate();
|
||||||
|
qDebug() << "AudioEngine: Switching sample rate to" << m_sampleRate;
|
||||||
|
for(auto p : m_processors) p->setSampleRate(m_sampleRate);
|
||||||
|
for(auto p : m_transientProcessors) p->setSampleRate(m_sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
const int frames = static_cast<int>(buffer.frameCount());
|
const int frames = static_cast<int>(buffer.frameCount());
|
||||||
const int channels = buffer.format().channelCount();
|
const int channels = buffer.format().channelCount();
|
||||||
auto sampleType = buffer.format().sampleFormat();
|
auto sampleType = buffer.format().sampleFormat();
|
||||||
|
|
@ -153,7 +183,7 @@ void AudioEngine::play() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QAudioFormat format;
|
QAudioFormat format;
|
||||||
format.setSampleRate(44100);
|
format.setSampleRate(m_sampleRate);
|
||||||
format.setChannelCount(2);
|
format.setChannelCount(2);
|
||||||
format.setSampleFormat(QAudioFormat::Float);
|
format.setSampleFormat(QAudioFormat::Float);
|
||||||
|
|
||||||
|
|
@ -164,13 +194,12 @@ void AudioEngine::play() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!device.isFormatSupported(format)) {
|
if (!device.isFormatSupported(format)) {
|
||||||
qWarning() << "AudioEngine: Float format not supported, using preferred format.";
|
qWarning() << "AudioEngine: Format not supported, using preferred format.";
|
||||||
format = device.preferredFormat();
|
format = device.preferredFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_sink = new QAudioSink(device, format, this);
|
m_sink = new QAudioSink(device, format, this);
|
||||||
|
|
||||||
|
|
||||||
connect(m_sink, &QAudioSink::stateChanged, this, [this](QAudio::State state){
|
connect(m_sink, &QAudioSink::stateChanged, this, [this](QAudio::State state){
|
||||||
if (state == QAudio::IdleState && m_sink->error() == QAudio::NoError) {
|
if (state == QAudio::IdleState && m_sink->error() == QAudio::NoError) {
|
||||||
if (m_buffer.bytesAvailable() == 0) {
|
if (m_buffer.bytesAvailable() == 0) {
|
||||||
|
|
@ -208,7 +237,13 @@ void AudioEngine::seek(float position) {
|
||||||
void AudioEngine::setDspParams(int frameSize, int hopSize) {
|
void AudioEngine::setDspParams(int frameSize, int hopSize) {
|
||||||
m_frameSize = frameSize;
|
m_frameSize = frameSize;
|
||||||
m_hopSize = hopSize;
|
m_hopSize = hopSize;
|
||||||
|
|
||||||
|
// Main: Full size
|
||||||
for(auto p : m_processors) p->setFrameSize(frameSize);
|
for(auto p : m_processors) p->setFrameSize(frameSize);
|
||||||
|
|
||||||
|
// Transient: 1/4 size (Minimum 64)
|
||||||
|
int transSize = std::max(64, frameSize / 4);
|
||||||
|
for(auto p : m_transientProcessors) p->setFrameSize(transSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioEngine::onProcessTimer() {
|
void AudioEngine::onProcessTimer() {
|
||||||
|
|
@ -223,6 +258,7 @@ void AudioEngine::onProcessTimer() {
|
||||||
|
|
||||||
if (sampleIdx + m_frameSize * 2 >= totalSamples) return;
|
if (sampleIdx + m_frameSize * 2 >= totalSamples) return;
|
||||||
|
|
||||||
|
// Prepare data for Main Processors
|
||||||
std::vector<float> ch0(m_frameSize), ch1(m_frameSize);
|
std::vector<float> ch0(m_frameSize), ch1(m_frameSize);
|
||||||
for (int i = 0; i < m_frameSize; ++i) {
|
for (int i = 0; i < m_frameSize; ++i) {
|
||||||
ch0[i] = samples[sampleIdx + i*2];
|
ch0[i] = samples[sampleIdx + i*2];
|
||||||
|
|
@ -232,10 +268,31 @@ void AudioEngine::onProcessTimer() {
|
||||||
m_processors[0]->pushData(ch0);
|
m_processors[0]->pushData(ch0);
|
||||||
m_processors[1]->pushData(ch1);
|
m_processors[1]->pushData(ch1);
|
||||||
|
|
||||||
|
// Prepare data for Transient Processors (Smaller window)
|
||||||
|
int transSize = std::max(64, m_frameSize / 4);
|
||||||
|
std::vector<float> tCh0(transSize), tCh1(transSize);
|
||||||
|
int offset = m_frameSize - transSize;
|
||||||
|
for (int i = 0; i < transSize; ++i) {
|
||||||
|
tCh0[i] = ch0[offset + i];
|
||||||
|
tCh1[i] = ch1[offset + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
m_transientProcessors[0]->pushData(tCh0);
|
||||||
|
m_transientProcessors[1]->pushData(tCh1);
|
||||||
|
|
||||||
std::vector<FrameData> results;
|
std::vector<FrameData> results;
|
||||||
for (auto p : m_processors) {
|
for (size_t i = 0; i < m_processors.size(); ++i) {
|
||||||
auto spec = p->getSpectrum();
|
auto specMain = m_processors[i]->getSpectrum();
|
||||||
results.push_back({spec.freqs, spec.db});
|
auto specTrans = m_transientProcessors[i]->getSpectrum();
|
||||||
|
|
||||||
|
// Mix: Overlay the expanded transient peaks onto the main spectrum
|
||||||
|
if (specMain.db.size() == specTrans.db.size()) {
|
||||||
|
for(size_t b = 0; b < specMain.db.size(); ++b) {
|
||||||
|
specMain.db[b] = std::max(specMain.db[b], specTrans.db[b]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push_back({specMain.freqs, specMain.db});
|
||||||
}
|
}
|
||||||
emit spectrumReady(results);
|
emit spectrumReady(results);
|
||||||
}
|
}
|
||||||
|
|
@ -51,9 +51,11 @@ private:
|
||||||
QFile* m_fileSource = nullptr;
|
QFile* m_fileSource = nullptr;
|
||||||
QTimer* m_processTimer = nullptr;
|
QTimer* m_processTimer = nullptr;
|
||||||
|
|
||||||
std::vector<Processor*> m_processors;
|
std::vector<Processor*> m_processors; // Main (Steady)
|
||||||
int m_frameSize = 32768;
|
std::vector<Processor*> m_transientProcessors; // Secondary (Fast/Transient)
|
||||||
|
|
||||||
|
int m_frameSize = 4096;
|
||||||
int m_hopSize = 1024;
|
int m_hopSize = 1024;
|
||||||
int m_sampleRate = 44100;
|
int m_sampleRate = 48000;
|
||||||
int m_channels = 2;
|
int m_channels = 2;
|
||||||
};
|
};
|
||||||
|
|
@ -160,12 +160,15 @@ SettingsWidget::SettingsWidget(QWidget* parent) : QWidget(parent) {
|
||||||
|
|
||||||
m_padDsp = new XYPad("DSP", this);
|
m_padDsp = new XYPad("DSP", this);
|
||||||
m_padDsp->setFormatter([](float x, float y) {
|
m_padDsp->setFormatter([](float x, float y) {
|
||||||
int fft = std::pow(2, 13 + (int)(x * 4.0f + 0.5f));
|
// Range: 2^6 (64) to 2^13 (8192)
|
||||||
|
int power = 6 + (int)(x * 7.0f + 0.5f);
|
||||||
|
int fft = std::pow(2, power);
|
||||||
int hop = 64 + y * (8192 - 64);
|
int hop = 64 + y * (8192 - 64);
|
||||||
return QString("FFT: %1\nHop: %2").arg(fft).arg(hop);
|
return QString("FFT: %1\nHop: %2").arg(fft).arg(hop);
|
||||||
});
|
});
|
||||||
|
|
||||||
m_padDsp->setValues(0.5f, 0.118f);
|
// Default to ~4096 FFT (x approx 0.857) and reasonable hop
|
||||||
|
m_padDsp->setValues(0.857f, 0.118f);
|
||||||
connect(m_padDsp, &XYPad::valuesChanged, this, &SettingsWidget::onDspPadChanged);
|
connect(m_padDsp, &XYPad::valuesChanged, this, &SettingsWidget::onDspPadChanged);
|
||||||
padsLayout->addWidget(m_padDsp);
|
padsLayout->addWidget(m_padDsp);
|
||||||
|
|
||||||
|
|
@ -225,7 +228,8 @@ void SettingsWidget::emitParams() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsWidget::onDspPadChanged(float x, float y) {
|
void SettingsWidget::onDspPadChanged(float x, float y) {
|
||||||
int power = 13 + (int)(x * 4.0f + 0.5f);
|
// Range: 2^6 (64) to 2^13 (8192)
|
||||||
|
int power = 6 + (int)(x * 7.0f + 0.5f);
|
||||||
m_fft = std::pow(2, power);
|
m_fft = std::pow(2, power);
|
||||||
m_hop = 64 + y * (8192 - 64);
|
m_hop = 64 + y * (8192 - 64);
|
||||||
emit dspParamsChanged(m_fft, m_hop);
|
emit dspParamsChanged(m_fft, m_hop);
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ private:
|
||||||
float m_contrast = 1.0f;
|
float m_contrast = 1.0f;
|
||||||
float m_brightness = 1.0f;
|
float m_brightness = 1.0f;
|
||||||
float m_entropy = 1.0f;
|
float m_entropy = 1.0f;
|
||||||
int m_fft = 32768;
|
int m_fft = 4096;
|
||||||
int m_hop = 1024;
|
int m_hop = 1024;
|
||||||
int m_bins = 26;
|
int m_bins = 26;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,30 @@ Processor::~Processor() {
|
||||||
if (m_out) fftwf_free(m_out);
|
if (m_out) fftwf_free(m_out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Processor::setSampleRate(int rate) {
|
||||||
|
m_sampleRate = rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Processor::setSmoothing(int historyLength) {
|
||||||
|
m_smoothingLength = std::max(1, historyLength);
|
||||||
|
while (m_history.size() > m_smoothingLength) {
|
||||||
|
m_history.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Processor::setExpander(float ratio, float thresholdDb) {
|
||||||
|
m_expandRatio = ratio;
|
||||||
|
m_expandThreshold = thresholdDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Processor::setHPF(float cutoffFreq) {
|
||||||
|
m_hpfCutoff = cutoffFreq;
|
||||||
|
}
|
||||||
|
|
||||||
void Processor::setNumBins(int n) {
|
void Processor::setNumBins(int n) {
|
||||||
m_customBins.clear();
|
m_customBins.clear();
|
||||||
m_freqsConst.clear();
|
m_freqsConst.clear();
|
||||||
m_history.clear(); // Clear history on bin change to avoid size mismatch
|
m_history.clear();
|
||||||
|
|
||||||
float minFreq = 40.0f;
|
float minFreq = 40.0f;
|
||||||
float maxFreq = 11000.0f;
|
float maxFreq = 11000.0f;
|
||||||
|
|
@ -54,7 +74,7 @@ void Processor::setFrameSize(int size) {
|
||||||
m_plan = fftwf_plan_dft_r2c_1d(m_frameSize, m_in, m_out, FFTW_ESTIMATE);
|
m_plan = fftwf_plan_dft_r2c_1d(m_frameSize, m_in, m_out, FFTW_ESTIMATE);
|
||||||
|
|
||||||
m_window.resize(m_frameSize);
|
m_window.resize(m_frameSize);
|
||||||
// Blackman-Harris window for excellent side-lobe suppression (reduces spectral leakage/noise)
|
// Blackman-Harris window
|
||||||
for (int i = 0; i < m_frameSize; ++i) {
|
for (int i = 0; i < m_frameSize; ++i) {
|
||||||
float a0 = 0.35875f;
|
float a0 = 0.35875f;
|
||||||
float a1 = 0.48829f;
|
float a1 = 0.48829f;
|
||||||
|
|
@ -114,24 +134,32 @@ Processor::Spectrum Processor::getSpectrum() {
|
||||||
float im = m_out[i][1];
|
float im = m_out[i][1];
|
||||||
float mag = 2.0f * std::sqrt(re*re + im*im) / m_frameSize;
|
float mag = 2.0f * std::sqrt(re*re + im*im) / m_frameSize;
|
||||||
|
|
||||||
|
float freq = i * (float)m_sampleRate / m_frameSize;
|
||||||
|
|
||||||
|
// HPF: Attenuate frequencies below cutoff
|
||||||
|
if (freq < m_hpfCutoff) {
|
||||||
|
mag *= 0.0001f; // -80dB attenuation
|
||||||
|
}
|
||||||
|
|
||||||
dbFull[i] = 20.0f * std::log10(std::max(mag, 1e-12f));
|
dbFull[i] = 20.0f * std::log10(std::max(mag, 1e-12f));
|
||||||
freqsFull[i] = i * (float)m_sampleRate / m_frameSize;
|
freqsFull[i] = freq;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Map to Custom Bins (Log Scale)
|
// 4. Map to Custom Bins (Log Scale)
|
||||||
std::vector<float> currentDb(m_freqsConst.size());
|
std::vector<float> currentDb(m_freqsConst.size());
|
||||||
for (size_t i = 0; i < m_freqsConst.size(); ++i) {
|
for (size_t i = 0; i < m_freqsConst.size(); ++i) {
|
||||||
float val = getInterpolatedDb(freqsFull, dbFull, m_freqsConst[i]);
|
float val = getInterpolatedDb(freqsFull, dbFull, m_freqsConst[i]);
|
||||||
|
|
||||||
|
// 4b. Apply Expander (Before Smoothing)
|
||||||
|
if (m_expandRatio != 1.0f) {
|
||||||
|
val = (val - m_expandThreshold) * m_expandRatio + m_expandThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
if (val < -100.0f) val = -100.0f;
|
if (val < -100.0f) val = -100.0f;
|
||||||
currentDb[i] = val;
|
currentDb[i] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Moving Average Filter
|
// 5. Moving Average Filter
|
||||||
// CRITICAL CHANGE: Reduced smoothing to 1 (effectively off) to allow
|
|
||||||
// the VisualizerWidget to detect sharp transients (Flux) accurately.
|
|
||||||
// The Visualizer will handle its own aesthetic smoothing.
|
|
||||||
m_smoothingLength = 1;
|
|
||||||
|
|
||||||
m_history.push_back(currentDb);
|
m_history.push_back(currentDb);
|
||||||
if (m_history.size() > m_smoothingLength) {
|
if (m_history.size() > m_smoothingLength) {
|
||||||
m_history.pop_front();
|
m_history.pop_front();
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,14 @@ public:
|
||||||
~Processor();
|
~Processor();
|
||||||
|
|
||||||
void setFrameSize(int size);
|
void setFrameSize(int size);
|
||||||
|
void setSampleRate(int rate);
|
||||||
void setNumBins(int n);
|
void setNumBins(int n);
|
||||||
|
|
||||||
|
// DSP Effects
|
||||||
|
void setSmoothing(int historyLength);
|
||||||
|
void setExpander(float ratio, float thresholdDb);
|
||||||
|
void setHPF(float cutoffFreq);
|
||||||
|
|
||||||
void pushData(const std::vector<float>& data);
|
void pushData(const std::vector<float>& data);
|
||||||
|
|
||||||
struct Spectrum {
|
struct Spectrum {
|
||||||
|
|
@ -39,7 +46,14 @@ private:
|
||||||
|
|
||||||
// Moving Average History
|
// Moving Average History
|
||||||
std::deque<std::vector<float>> m_history;
|
std::deque<std::vector<float>> m_history;
|
||||||
size_t m_smoothingLength = 3; // Number of frames to average
|
size_t m_smoothingLength = 3;
|
||||||
|
|
||||||
|
// Expander Settings
|
||||||
|
float m_expandRatio = 1.0f;
|
||||||
|
float m_expandThreshold = -60.0f;
|
||||||
|
|
||||||
|
// HPF Settings
|
||||||
|
float m_hpfCutoff = 0.0f;
|
||||||
|
|
||||||
float getInterpolatedDb(const std::vector<float>& freqs, const std::vector<float>& db, float targetFreq);
|
float getInterpolatedDb(const std::vector<float>& freqs, const std::vector<float>& db, float targetFreq);
|
||||||
};
|
};
|
||||||
|
|
@ -268,6 +268,87 @@ void VisualizerWidget::drawContent(QPainter& p, int w, int h) {
|
||||||
const auto& bins = m_channels[ch].bins;
|
const auto& bins = m_channels[ch].bins;
|
||||||
if (bins.empty()) continue;
|
if (bins.empty()) continue;
|
||||||
|
|
||||||
|
// 1. Calculate Raw Energy
|
||||||
|
std::vector<float> rawEnergy(bins.size());
|
||||||
|
for (size_t j = 0; j < bins.size(); ++j) {
|
||||||
|
rawEnergy[j] = std::clamp((bins[j].visualDb + 80.0f) / 80.0f, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Auto-Balance Highs vs Lows (Dynamic Normalization)
|
||||||
|
size_t splitIdx = bins.size() / 2;
|
||||||
|
float maxLow = 0.01f;
|
||||||
|
float maxHigh = 0.01f;
|
||||||
|
|
||||||
|
for (size_t j = 0; j < splitIdx; ++j) maxLow = std::max(maxLow, rawEnergy[j]);
|
||||||
|
for (size_t j = splitIdx; j < bins.size(); ++j) maxHigh = std::max(maxHigh, rawEnergy[j]);
|
||||||
|
|
||||||
|
float trebleBoost = maxLow / maxHigh;
|
||||||
|
trebleBoost = std::clamp(trebleBoost, 1.0f, 40.0f);
|
||||||
|
|
||||||
|
std::vector<float> vertexEnergy(bins.size());
|
||||||
|
float globalMax = 0.001f;
|
||||||
|
|
||||||
|
for (size_t j = 0; j < bins.size(); ++j) {
|
||||||
|
float val = rawEnergy[j];
|
||||||
|
|
||||||
|
if (j >= splitIdx) {
|
||||||
|
float t = (float)(j - splitIdx) / (bins.size() - splitIdx);
|
||||||
|
float boost = 1.0f + (trebleBoost - 1.0f) * t;
|
||||||
|
val *= boost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Soft-Knee Compression
|
||||||
|
float compressed = std::tanh(val);
|
||||||
|
vertexEnergy[j] = compressed;
|
||||||
|
if (compressed > globalMax) globalMax = compressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Global Normalization (Ensure peaks hit 1.0)
|
||||||
|
// This fixes the "faded highs" issue by ensuring the loudest part of the spectrum
|
||||||
|
// (even if it's just high hats) is fully opaque.
|
||||||
|
for (float& v : vertexEnergy) {
|
||||||
|
v = std::clamp(v / globalMax, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Calculate Segment Modifiers (Vote System)
|
||||||
|
std::vector<float> segmentModifiers(freqs.size() - 1, 0.0f);
|
||||||
|
|
||||||
|
for (size_t i = 1; i < vertexEnergy.size() - 1; ++i) {
|
||||||
|
float curr = vertexEnergy[i];
|
||||||
|
float prev = vertexEnergy[i-1];
|
||||||
|
float next = vertexEnergy[i+1];
|
||||||
|
|
||||||
|
// Is this vertex a local peak?
|
||||||
|
if (curr > prev && curr > next) {
|
||||||
|
bool leftDominant = (prev > next);
|
||||||
|
float sharpness = std::min(curr - prev, curr - next);
|
||||||
|
float intensity = std::clamp(sharpness * 4.0f, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
float brightBoost = 1.0f * intensity;
|
||||||
|
|
||||||
|
if (leftDominant) {
|
||||||
|
// Left Segment (i-1) gets Brightness
|
||||||
|
if (i > 0) segmentModifiers[i-1] += brightBoost;
|
||||||
|
|
||||||
|
// Right Side (Dark Side)
|
||||||
|
if (i < segmentModifiers.size()) segmentModifiers[i] -= 0.6f * intensity; // Dark
|
||||||
|
if (i + 1 < segmentModifiers.size()) segmentModifiers[i+1] -= 0.9f * intensity; // Darker
|
||||||
|
if (i + 2 < segmentModifiers.size()) segmentModifiers[i+2] -= 0.6f * intensity; // Dark
|
||||||
|
if (i + 3 < segmentModifiers.size()) segmentModifiers[i+3] -= 0.3f * intensity; // Fade
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Right Segment (i) gets Brightness
|
||||||
|
if (i < segmentModifiers.size()) segmentModifiers[i] += brightBoost;
|
||||||
|
|
||||||
|
// Left Side (Dark Side)
|
||||||
|
if (i > 0) segmentModifiers[i-1] -= 0.6f * intensity; // Dark
|
||||||
|
if (i >= 2) segmentModifiers[i-2] -= 0.9f * intensity; // Darker
|
||||||
|
if (i >= 3) segmentModifiers[i-3] -= 0.6f * intensity; // Dark
|
||||||
|
if (i >= 4) segmentModifiers[i-4] -= 0.3f * intensity; // Fade
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float xOffset = (ch == 1 && m_data.size() > 1) ? 1.005f : 1.0f;
|
float xOffset = (ch == 1 && m_data.size() > 1) ? 1.005f : 1.0f;
|
||||||
|
|
||||||
for (size_t i = 0; i < freqs.size() - 1; ++i) {
|
for (size_t i = 0; i < freqs.size() - 1; ++i) {
|
||||||
|
|
@ -288,30 +369,45 @@ void VisualizerWidget::drawContent(QPainter& p, int w, int h) {
|
||||||
binColor = QColor::fromHsvF(hue, 1.0f, 1.0f);
|
binColor = QColor::fromHsvF(hue, 1.0f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use visualDb (Physics processed)
|
// Base Brightness from Energy
|
||||||
float meanDb = (b.visualDb + bNext.visualDb) / 2.0f;
|
float avgEnergy = (vertexEnergy[i] + vertexEnergy[i+1]) / 2.0f;
|
||||||
float intensity = std::clamp((meanDb + 80.0f) / 80.0f, 0.0f, 1.0f);
|
float baseBrightness = std::pow(avgEnergy, 0.5f);
|
||||||
|
|
||||||
|
// Apply Shadow/Highlight Modifiers to Brightness
|
||||||
|
float modifier = segmentModifiers[i];
|
||||||
|
float multiplier = (modifier >= 0) ? (1.0f + modifier) : (1.0f / (1.0f - modifier * 2.0f));
|
||||||
|
|
||||||
|
float finalBrightness = std::clamp(baseBrightness * multiplier * m_brightness, 0.0f, 1.0f);
|
||||||
|
|
||||||
float h_val, s, v, a;
|
float h_val, s, v, a;
|
||||||
binColor.getHsvF(&h_val, &s, &v, &a);
|
binColor.getHsvF(&h_val, &s, &v, &a);
|
||||||
float brightness = (0.4f + 0.6f * intensity) * m_brightness;
|
v = std::clamp(v * finalBrightness, 0.0f, 1.0f);
|
||||||
v = std::clamp(v * brightness, 0.0f, 1.0f);
|
|
||||||
QColor dynamicBinColor = QColor::fromHsvF(h_val, s, v);
|
QColor dynamicBinColor = QColor::fromHsvF(h_val, s, v);
|
||||||
|
|
||||||
QColor fillColor, lineColor;
|
QColor fillColor, lineColor;
|
||||||
if (m_glass) {
|
if (m_glass) {
|
||||||
float uh, us, uv, ua;
|
float uh, us, uv, ua;
|
||||||
unifiedColor.getHsvF(&uh, &us, &uv, &ua);
|
unifiedColor.getHsvF(&uh, &us, &uv, &ua);
|
||||||
float uBrightness = (0.4f + 0.6f * intensity) * m_brightness;
|
fillColor = QColor::fromHsvF(uh, us, std::clamp(uv * finalBrightness, 0.0f, 1.0f));
|
||||||
fillColor = QColor::fromHsvF(uh, us, std::clamp(uv * uBrightness, 0.0f, 1.0f));
|
|
||||||
lineColor = dynamicBinColor;
|
lineColor = dynamicBinColor;
|
||||||
} else {
|
} else {
|
||||||
fillColor = dynamicBinColor;
|
fillColor = dynamicBinColor;
|
||||||
lineColor = dynamicBinColor;
|
lineColor = dynamicBinColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Alpha Calculation with Shadow Logic ---
|
||||||
|
// 1. Start with the boosted, normalized energy (avgEnergy)
|
||||||
|
// 2. Apply the shadow modifier (linear scalar for alpha)
|
||||||
|
// If modifier is negative (shadow), reduce alpha.
|
||||||
|
// If positive (peak), boost alpha slightly.
|
||||||
|
float alphaScalar = (modifier >= 0) ? (1.0f + modifier * 0.2f) : (1.0f + modifier);
|
||||||
|
|
||||||
|
float intensity = avgEnergy;
|
||||||
float alpha = 0.4f + (intensity - 0.5f) * m_contrast;
|
float alpha = 0.4f + (intensity - 0.5f) * m_contrast;
|
||||||
alpha = std::clamp(alpha, 0.0f, 1.0f);
|
|
||||||
|
// Apply the shadow scalar to the calculated alpha
|
||||||
|
alpha = std::clamp(alpha * alphaScalar, 0.0f, 1.0f);
|
||||||
|
|
||||||
fillColor.setAlphaF(alpha);
|
fillColor.setAlphaF(alpha);
|
||||||
lineColor.setAlphaF(0.9f);
|
lineColor.setAlphaF(0.9f);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue