getting very close to a real release. of course, i'll have to deal with the bugs first. so instead, here's more visual effects!
This commit is contained in:
parent
f662dcb989
commit
26ccd55d8c
|
|
@ -17,15 +17,15 @@ option(BUILD_IOS "Build for iOS" OFF)
|
|||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Multimedia OpenGLWidgets)
|
||||
|
||||
# --- FFTW3 Configuration ---
|
||||
# --- FFTW3 Configuration (Double Precision) ---
|
||||
|
||||
if(APPLE AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64" AND NOT BUILD_IOS)
|
||||
message(STATUS "Detected Apple Silicon Desktop. Using Homebrew FFTW3F.")
|
||||
find_library(FFTW3_LIB NAMES fftw3f libfftw3f PATHS /opt/homebrew/lib NO_DEFAULT_PATH)
|
||||
message(STATUS "Detected Apple Silicon Desktop. Using Homebrew FFTW3 (Double).")
|
||||
find_library(FFTW3_LIB NAMES fftw3 libfftw3 PATHS /opt/homebrew/lib NO_DEFAULT_PATH)
|
||||
find_path(FFTW3_INCLUDE_DIR fftw3.h PATHS /opt/homebrew/include NO_DEFAULT_PATH)
|
||||
|
||||
if(NOT FFTW3_LIB OR NOT FFTW3_INCLUDE_DIR)
|
||||
message(FATAL_ERROR "FFTW3F not found in /opt/homebrew. Please run: brew install fftw")
|
||||
message(FATAL_ERROR "FFTW3 not found in /opt/homebrew. Please run: brew install fftw")
|
||||
endif()
|
||||
|
||||
add_library(fftw3 STATIC IMPORTED)
|
||||
|
|
@ -35,9 +35,9 @@ if(APPLE AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64" AND NOT BUILD_IOS)
|
|||
)
|
||||
|
||||
else()
|
||||
message(STATUS "Building FFTW3 from source...")
|
||||
message(STATUS "Building FFTW3 from source (Double Precision)...")
|
||||
|
||||
set(ENABLE_FLOAT ON CACHE BOOL "Build single precision" FORCE)
|
||||
set(ENABLE_FLOAT OFF CACHE BOOL "Build double precision" FORCE)
|
||||
set(ENABLE_SSE OFF CACHE BOOL "Disable SSE" FORCE)
|
||||
set(ENABLE_SSE2 OFF CACHE BOOL "Disable SSE2" FORCE)
|
||||
set(ENABLE_AVX OFF CACHE BOOL "Disable AVX" FORCE)
|
||||
|
|
@ -68,7 +68,6 @@ else()
|
|||
endif()
|
||||
|
||||
# --- Loop Tempo Estimator ---
|
||||
# Disable tests and vamp plugin for the library to speed up build and reduce deps
|
||||
set(BUILD_TESTS OFF CACHE BOOL "Build tests" FORCE)
|
||||
set(BUILD_VAMP_PLUGIN OFF CACHE BOOL "Build Vamp plugin" FORCE)
|
||||
add_subdirectory(libraries/loop-tempo-estimator)
|
||||
|
|
@ -83,7 +82,6 @@ set(IOS_ASSETS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/ios/Assets.xcassets")
|
|||
set(IOS_CONTENTS_JSON "${IOS_ASSETS_PATH}/Contents.json")
|
||||
set(ANDROID_RES_PATH "${CMAKE_CURRENT_SOURCE_DIR}/android/res/mipmap-mdpi/ic_launcher.png")
|
||||
|
||||
# Find ImageMagick 'magick' executable
|
||||
find_program(MAGICK_EXECUTABLE NAMES magick)
|
||||
if(NOT MAGICK_EXECUTABLE)
|
||||
message(WARNING "ImageMagick 'magick' not found. Icons will not be generated.")
|
||||
|
|
@ -115,6 +113,8 @@ set(PROJECT_SOURCES
|
|||
src/CommonWidgets.cpp
|
||||
src/PlayerControls.cpp
|
||||
src/MainWindow.cpp
|
||||
src/complex_block.cpp
|
||||
src/trig_interpolation.cpp
|
||||
)
|
||||
|
||||
if(EXISTS "${ICON_SOURCE}")
|
||||
|
|
@ -129,6 +129,8 @@ set(PROJECT_HEADERS
|
|||
src/CommonWidgets.h
|
||||
src/PlayerControls.h
|
||||
src/MainWindow.h
|
||||
src/complex_block.h
|
||||
src/trig_interpolation.h
|
||||
)
|
||||
|
||||
qt_add_executable(YrCrystals MANUAL_FINALIZATION ${PROJECT_SOURCES} ${PROJECT_HEADERS})
|
||||
|
|
@ -144,8 +146,8 @@ endif()
|
|||
|
||||
# --- Linking ---
|
||||
|
||||
if(TARGET fftw3f)
|
||||
set(FFTW_TARGET fftw3f)
|
||||
if(TARGET fftw3)
|
||||
set(FFTW_TARGET fftw3)
|
||||
target_include_directories(YrCrystals PRIVATE
|
||||
"${fftw3_source_SOURCE_DIR}/api"
|
||||
"${fftw3_source_BINARY_DIR}"
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ AudioEngine::AudioEngine(QObject* parent) : QObject(parent) {
|
|||
// Configure Main: Expander + HPF + Moderate Smoothing
|
||||
for(auto p : m_processors) {
|
||||
p->setExpander(1.5f, -50.0f);
|
||||
p->setHPF(80.0f); // Mid 2nd Order HPF (80Hz)
|
||||
p->setHPF(80.0f);
|
||||
p->setSmoothing(3);
|
||||
}
|
||||
|
||||
|
|
@ -63,10 +63,22 @@ AudioEngine::AudioEngine(QObject* parent) : QObject(parent) {
|
|||
// Configure Transient: Aggressive expansion, light smoothing
|
||||
for(auto p : m_transientProcessors) {
|
||||
p->setExpander(2.5f, -40.0f);
|
||||
p->setHPF(100.0f); // Clean up transients
|
||||
p->setHPF(100.0f);
|
||||
p->setSmoothing(2);
|
||||
}
|
||||
|
||||
// Deep Processors (Tertiary, High Res)
|
||||
// Initial size will be set in setDspParams, default to 2x frameSize
|
||||
m_deepProcessors.push_back(new Processor(m_frameSize * 2, m_sampleRate));
|
||||
m_deepProcessors.push_back(new Processor(m_frameSize * 2, m_sampleRate));
|
||||
|
||||
// Configure Deep: Low expander, no HPF (catch sub-bass), heavy smoothing
|
||||
for(auto p : m_deepProcessors) {
|
||||
p->setExpander(1.2f, -60.0f);
|
||||
p->setHPF(0.0f); // Allow full sub-bass
|
||||
p->setSmoothing(5);
|
||||
}
|
||||
|
||||
m_processTimer = new QTimer(this);
|
||||
m_processTimer->setInterval(16);
|
||||
connect(m_processTimer, &QTimer::timeout, this, &AudioEngine::onProcessTimer);
|
||||
|
|
@ -76,18 +88,33 @@ AudioEngine::~AudioEngine() {
|
|||
stop();
|
||||
for(auto p : m_processors) delete p;
|
||||
for(auto p : m_transientProcessors) delete p;
|
||||
for(auto p : m_deepProcessors) delete p;
|
||||
if (m_fileSource) delete m_fileSource;
|
||||
}
|
||||
|
||||
void AudioEngine::setNumBins(int n) {
|
||||
for(auto p : m_processors) p->setNumBins(n);
|
||||
for(auto p : m_transientProcessors) p->setNumBins(n);
|
||||
for(auto p : m_deepProcessors) p->setNumBins(n);
|
||||
}
|
||||
|
||||
void AudioEngine::setSmoothingParams(int granularity, int detail, float strength) {
|
||||
for(auto p : m_processors) p->setCepstralParams(granularity, detail, strength);
|
||||
// Transient: Less smoothing to keep punch
|
||||
for(auto p : m_transientProcessors) p->setCepstralParams(granularity, detail, strength * 0.3f);
|
||||
// Deep: More smoothing for stability
|
||||
for(auto p : m_deepProcessors) p->setCepstralParams(granularity, detail, strength * 1.2f);
|
||||
}
|
||||
|
||||
void AudioEngine::loadTrack(const QString& filePath) {
|
||||
stop();
|
||||
|
||||
{
|
||||
QMutexLocker locker(&m_dataMutex);
|
||||
m_pcmData.clear();
|
||||
m_complexData.clear();
|
||||
m_buffer.close();
|
||||
}
|
||||
|
||||
m_sampleRate = 48000;
|
||||
|
||||
|
|
@ -145,12 +172,15 @@ void AudioEngine::onBufferReady() {
|
|||
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);
|
||||
for(auto p : m_deepProcessors) p->setSampleRate(m_sampleRate);
|
||||
}
|
||||
|
||||
const int frames = static_cast<int>(buffer.frameCount());
|
||||
const int channels = buffer.format().channelCount();
|
||||
auto sampleType = buffer.format().sampleFormat();
|
||||
|
||||
QMutexLocker locker(&m_dataMutex);
|
||||
|
||||
if (sampleType == QAudioFormat::Int16) {
|
||||
const int16_t* src = buffer.constData<int16_t>();
|
||||
if (!src) return;
|
||||
|
|
@ -194,6 +224,8 @@ void AudioEngine::onBufferReady() {
|
|||
}
|
||||
|
||||
void AudioEngine::onFinished() {
|
||||
QMutexLocker locker(&m_dataMutex);
|
||||
|
||||
if (m_pcmData.isEmpty()) {
|
||||
emit trackLoaded(false);
|
||||
return;
|
||||
|
|
@ -219,6 +251,26 @@ void AudioEngine::onFinished() {
|
|||
emit analysisReady(0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Block Hilbert Transform (Offline Processing) ---
|
||||
if (totalFrames > 0) {
|
||||
std::vector<double> inputL(totalFrames);
|
||||
std::vector<double> inputR(totalFrames);
|
||||
|
||||
for (size_t i = 0; i < totalFrames; ++i) {
|
||||
inputL[i] = static_cast<double>(rawFloats[i * 2]);
|
||||
inputR[i] = static_cast<double>(rawFloats[i * 2 + 1]);
|
||||
}
|
||||
|
||||
BlockHilbert blockHilbert;
|
||||
auto analyticPair = blockHilbert.hilbertTransform(inputL, inputR);
|
||||
|
||||
m_complexData.resize(totalFloats);
|
||||
for (size_t i = 0; i < totalFrames; ++i) {
|
||||
m_complexData[i * 2] = analyticPair.first[i];
|
||||
m_complexData[i * 2 + 1] = analyticPair.second[i];
|
||||
}
|
||||
}
|
||||
// ----------------------------
|
||||
|
||||
m_buffer.setData(m_pcmData);
|
||||
|
|
@ -284,6 +336,7 @@ void AudioEngine::stop() {
|
|||
}
|
||||
|
||||
void AudioEngine::seek(float position) {
|
||||
QMutexLocker locker(&m_dataMutex);
|
||||
if (m_pcmData.isEmpty()) return;
|
||||
qint64 pos = position * m_pcmData.size();
|
||||
pos -= pos % 8;
|
||||
|
|
@ -300,25 +353,34 @@ void AudioEngine::setDspParams(int frameSize, int hopSize) {
|
|||
// Transient: 1/4 size (Minimum 64)
|
||||
int transSize = std::max(64, frameSize / 4);
|
||||
for(auto p : m_transientProcessors) p->setFrameSize(transSize);
|
||||
|
||||
// Deep: 2x or 4x size
|
||||
int deepSize;
|
||||
if (frameSize < 2048) {
|
||||
deepSize = frameSize * 4;
|
||||
} else {
|
||||
deepSize = frameSize * 2;
|
||||
}
|
||||
for(auto p : m_deepProcessors) p->setFrameSize(deepSize);
|
||||
}
|
||||
|
||||
void AudioEngine::onProcessTimer() {
|
||||
if (!m_buffer.isOpen()) return;
|
||||
|
||||
QMutexLocker locker(&m_dataMutex);
|
||||
|
||||
qint64 currentPos = m_buffer.pos();
|
||||
emit positionChanged((float)currentPos / m_pcmData.size());
|
||||
|
||||
const float* samples = reinterpret_cast<const float*>(m_pcmData.constData());
|
||||
qint64 sampleIdx = currentPos / sizeof(float);
|
||||
qint64 totalSamples = m_pcmData.size() / sizeof(float);
|
||||
|
||||
if (sampleIdx + m_frameSize * 2 >= totalSamples) return;
|
||||
if (sampleIdx + m_frameSize * 2 >= m_complexData.size()) return;
|
||||
|
||||
// Prepare data for Main Processors
|
||||
std::vector<float> ch0(m_frameSize), ch1(m_frameSize);
|
||||
// Prepare data for Main Processors (Complex Double)
|
||||
std::vector<std::complex<double>> ch0(m_frameSize), ch1(m_frameSize);
|
||||
for (int i = 0; i < m_frameSize; ++i) {
|
||||
ch0[i] = samples[sampleIdx + i*2];
|
||||
ch1[i] = samples[sampleIdx + i*2 + 1];
|
||||
ch0[i] = m_complexData[sampleIdx + i*2];
|
||||
ch1[i] = m_complexData[sampleIdx + i*2 + 1];
|
||||
}
|
||||
|
||||
m_processors[0]->pushData(ch0);
|
||||
|
|
@ -326,7 +388,7 @@ void AudioEngine::onProcessTimer() {
|
|||
|
||||
// Prepare data for Transient Processors (Smaller window)
|
||||
int transSize = std::max(64, m_frameSize / 4);
|
||||
std::vector<float> tCh0(transSize), tCh1(transSize);
|
||||
std::vector<std::complex<double>> tCh0(transSize), tCh1(transSize);
|
||||
int offset = m_frameSize - transSize;
|
||||
for (int i = 0; i < transSize; ++i) {
|
||||
tCh0[i] = ch0[offset + i];
|
||||
|
|
@ -336,6 +398,21 @@ void AudioEngine::onProcessTimer() {
|
|||
m_transientProcessors[0]->pushData(tCh0);
|
||||
m_transientProcessors[1]->pushData(tCh1);
|
||||
|
||||
// Prepare data for Deep Processors (Larger window)
|
||||
// We need to grab more data from m_complexData if available
|
||||
// Deep size is dynamic, check first processor
|
||||
// Note: Processor::pushData handles buffering, so we just push the current m_frameSize chunk
|
||||
// and the processor will append it to its internal history.
|
||||
// However, for best results with a larger FFT, we should ideally provide the full window if possible,
|
||||
// but since we are streaming, pushing the hop (m_frameSize) is the standard overlap-add approach.
|
||||
// Wait, m_frameSize here acts as the "hop" for the larger processors if we just push it.
|
||||
// Processor::pushData shifts by data.size().
|
||||
// So if we push m_frameSize samples, the Deep processor (size e.g. 8192) will shift by 4096 and append 4096.
|
||||
// This results in 50% overlap if DeepSize = 2 * FrameSize. Perfect.
|
||||
|
||||
m_deepProcessors[0]->pushData(ch0);
|
||||
m_deepProcessors[1]->pushData(ch1);
|
||||
|
||||
std::vector<FrameData> results;
|
||||
|
||||
// Final Compressor Settings
|
||||
|
|
@ -345,14 +422,16 @@ void AudioEngine::onProcessTimer() {
|
|||
for (size_t i = 0; i < m_processors.size(); ++i) {
|
||||
auto specMain = m_processors[i]->getSpectrum();
|
||||
auto specTrans = m_transientProcessors[i]->getSpectrum();
|
||||
auto specDeep = m_deepProcessors[i]->getSpectrum();
|
||||
|
||||
// Capture Primary DB (Steady State) for Crystal Pattern
|
||||
std::vector<float> primaryDb = specMain.db;
|
||||
|
||||
// Mix: Overlay the expanded transient peaks onto the main spectrum
|
||||
if (specMain.db.size() == specTrans.db.size()) {
|
||||
// Mix: Overlay Main + Transient + Deep
|
||||
if (specMain.db.size() == specTrans.db.size() && specMain.db.size() == specDeep.db.size()) {
|
||||
for(size_t b = 0; b < specMain.db.size(); ++b) {
|
||||
float val = std::max(specMain.db[b], specTrans.db[b]);
|
||||
// Max of all three
|
||||
float val = std::max({specMain.db[b], specTrans.db[b], specDeep.db[b]});
|
||||
|
||||
// Final Compressor (Hard Knee)
|
||||
if (val > compThreshold) {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@
|
|||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
#include <QTimer>
|
||||
#include <QMutex>
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
#include "Processor.h"
|
||||
#include "complex_block.h"
|
||||
|
||||
class AudioEngine : public QObject {
|
||||
Q_OBJECT
|
||||
|
|
@ -18,7 +21,7 @@ public:
|
|||
|
||||
struct FrameData {
|
||||
std::vector<float> freqs;
|
||||
std::vector<float> db; // Mixed (Primary + Transient) -> For Bar Height
|
||||
std::vector<float> db; // Mixed (Primary + Transient + Deep)
|
||||
std::vector<float> primaryDb; // Primary Only -> For Crystal Pattern
|
||||
};
|
||||
|
||||
|
|
@ -31,6 +34,9 @@ public slots:
|
|||
void setDspParams(int frameSize, int hopSize);
|
||||
void setNumBins(int n);
|
||||
|
||||
// Cepstral/Smoothing Controls
|
||||
void setSmoothingParams(int granularity, int detail, float strength);
|
||||
|
||||
signals:
|
||||
void playbackFinished();
|
||||
void positionChanged(float pos);
|
||||
|
|
@ -47,7 +53,11 @@ private slots:
|
|||
private:
|
||||
QAudioSink* m_sink = nullptr;
|
||||
QBuffer m_buffer;
|
||||
QByteArray m_pcmData;
|
||||
QByteArray m_pcmData; // Raw PCM for playback (Real)
|
||||
mutable QMutex m_dataMutex; // Protects m_pcmData and m_complexData
|
||||
|
||||
// Complex Analytical Stream (Pre-calculated) - Double Precision
|
||||
std::vector<std::complex<double>> m_complexData;
|
||||
|
||||
QAudioDecoder* m_decoder = nullptr;
|
||||
QFile* m_fileSource = nullptr;
|
||||
|
|
@ -55,6 +65,7 @@ private:
|
|||
|
||||
std::vector<Processor*> m_processors; // Main (Steady)
|
||||
std::vector<Processor*> m_transientProcessors; // Secondary (Fast/Transient)
|
||||
std::vector<Processor*> m_deepProcessors; // Tertiary (Deep/Bass)
|
||||
|
||||
int m_frameSize = 4096;
|
||||
int m_hopSize = 1024;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,12 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
|||
connect(m_engine, &AudioEngine::spectrumReady, m_playerPage->visualizer(), &VisualizerWidget::updateData);
|
||||
connect(m_engine, &AudioEngine::analysisReady, this, &MainWindow::onAnalysisReady);
|
||||
|
||||
// Connect new smoothing params from Settings to Engine
|
||||
connect(m_playerPage->settings(), &SettingsWidget::paramsChanged, this, [this](bool, bool, bool, bool, bool, bool, float, float, float, int granularity, int detail, float strength){
|
||||
QMetaObject::invokeMethod(m_engine, "setSmoothingParams", Qt::QueuedConnection,
|
||||
Q_ARG(int, granularity), Q_ARG(int, detail), Q_ARG(float, strength));
|
||||
});
|
||||
|
||||
audioThread->start();
|
||||
}
|
||||
|
||||
|
|
@ -82,8 +88,13 @@ void MainWindow::initUi() {
|
|||
connect(set, &SettingsWidget::dspParamsChanged, this, &MainWindow::onDspChanged);
|
||||
connect(set, &SettingsWidget::binsChanged, this, &MainWindow::onBinsChanged);
|
||||
|
||||
// Connect BPM Scale change to update logic
|
||||
connect(set, &SettingsWidget::bpmScaleChanged, this, &MainWindow::updateSmoothing);
|
||||
|
||||
connect(set, &SettingsWidget::paramsChanged, this, &MainWindow::saveSettings);
|
||||
connect(set, &SettingsWidget::binsChanged, this, &MainWindow::saveSettings);
|
||||
// Also save when BPM scale changes
|
||||
connect(set, &SettingsWidget::bpmScaleChanged, this, &MainWindow::saveSettings);
|
||||
|
||||
connect(m_playerPage, &PlayerPage::toggleFullScreen, this, &MainWindow::onToggleFullScreen);
|
||||
|
||||
|
|
@ -241,9 +252,14 @@ void MainWindow::loadSettings() {
|
|||
bool mirrored = root["mirrored"].toBool(false);
|
||||
int bins = root["bins"].toInt(26);
|
||||
float brightness = root["brightness"].toDouble(1.0);
|
||||
float entropy = root["entropy"].toDouble(1.0);
|
||||
|
||||
m_playerPage->settings()->setParams(glass, focus, trails, albumColors, shadow, mirrored, bins, brightness, entropy);
|
||||
// New Smoothing Params
|
||||
int granularity = root["granularity"].toInt(33);
|
||||
int detail = root["detail"].toInt(50);
|
||||
float strength = root["strength"].toDouble(0.0);
|
||||
int bpmScaleIndex = root["bpmScaleIndex"].toInt(2); // Default 1/4
|
||||
|
||||
m_playerPage->settings()->setParams(glass, focus, trails, albumColors, shadow, mirrored, bins, brightness, granularity, detail, strength, bpmScaleIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -261,7 +277,12 @@ void MainWindow::saveSettings() {
|
|||
root["mirrored"] = s->isMirrored();
|
||||
root["bins"] = s->getBins();
|
||||
root["brightness"] = s->getBrightness();
|
||||
root["entropy"] = s->getEntropy();
|
||||
|
||||
// New Smoothing Params
|
||||
root["granularity"] = s->getGranularity();
|
||||
root["detail"] = s->getDetail();
|
||||
root["strength"] = s->getStrength();
|
||||
root["bpmScaleIndex"] = s->getBpmScaleIndex();
|
||||
|
||||
QFile f(QDir(m_settingsDir).filePath(".yrcrystals.json"));
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
|
|
@ -299,32 +320,35 @@ void MainWindow::onTrackLoaded(bool success) {
|
|||
}
|
||||
|
||||
void MainWindow::onAnalysisReady(float bpm, float confidence) {
|
||||
// Feedback Mechanism:
|
||||
// Adjust Entropy based on BPM.
|
||||
// High BPM (Fast/Punchy) -> Lower Entropy Slider (0.5 - 0.8) to allow transients.
|
||||
// Low BPM (Slow/Ambient) -> Higher Entropy Slider (1.0 - 1.5) to smooth noise.
|
||||
// Default (No BPM) -> 1.0
|
||||
|
||||
float targetEntropy = 1.0f;
|
||||
|
||||
if (bpm > 0.0f) {
|
||||
// Map 60..180 BPM to 1.5..0.5 Entropy
|
||||
// Formula: 1.5 - ((bpm - 60) / 120)
|
||||
// Clamped between 0.5 and 1.5
|
||||
float normalized = (bpm - 60.0f) / 120.0f;
|
||||
targetEntropy = 1.5f - normalized;
|
||||
targetEntropy = std::clamp(targetEntropy, 0.5f, 1.5f);
|
||||
qDebug() << "Feedback: BPM" << bpm << "-> Setting Entropy to" << targetEntropy;
|
||||
} else {
|
||||
qDebug() << "Feedback: No BPM -> Default Entropy 1.0";
|
||||
m_lastBpm = bpm;
|
||||
updateSmoothing();
|
||||
}
|
||||
|
||||
// Update Settings Widget (which updates Visualizer)
|
||||
void MainWindow::updateSmoothing() {
|
||||
if (m_lastBpm <= 0.0f) return;
|
||||
|
||||
float scale = m_playerPage->settings()->getBpmScale();
|
||||
float effectiveBpm = m_lastBpm * scale;
|
||||
|
||||
// Feedback Mechanism:
|
||||
// Adjust Smoothing Strength based on effective BPM.
|
||||
// High BPM (Fast/Punchy) -> Lower Strength (More Raw).
|
||||
// Low BPM (Slow/Ambient) -> Higher Strength (Smoother).
|
||||
|
||||
float targetStrength = 0.0f;
|
||||
|
||||
// Map 60..180 BPM to 0.8..0.0 Strength
|
||||
float normalized = std::clamp((effectiveBpm - 60.0f) / 120.0f, 0.0f, 1.0f);
|
||||
targetStrength = 0.8f * (1.0f - normalized);
|
||||
|
||||
qDebug() << "Feedback: BPM" << m_lastBpm << "Scale" << scale << "Effective" << effectiveBpm << "-> Strength" << targetStrength;
|
||||
|
||||
// Update Settings Widget (which updates Visualizer/Engine)
|
||||
SettingsWidget* s = m_playerPage->settings();
|
||||
s->setParams(
|
||||
s->isGlass(), s->isFocus(), s->isTrails(), s->isAlbumColors(),
|
||||
s->isShadow(), s->isMirrored(), s->getBins(), s->getBrightness(),
|
||||
targetEntropy
|
||||
s->getGranularity(), s->getDetail(), targetStrength, s->getBpmScaleIndex()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ private slots:
|
|||
void onTrackLoaded(bool success);
|
||||
void onTrackDoubleClicked(QListWidgetItem* item);
|
||||
void onAnalysisReady(float bpm, float confidence);
|
||||
void updateSmoothing(); // New slot for BPM feedback logic
|
||||
void play();
|
||||
void pause();
|
||||
void nextTrack();
|
||||
|
|
@ -59,4 +60,6 @@ private:
|
|||
enum class PendingAction { None, File, Folder };
|
||||
PendingAction m_pendingAction = PendingAction::None;
|
||||
QString m_settingsDir;
|
||||
|
||||
float m_lastBpm = 0.0f; // Store last detected BPM
|
||||
};
|
||||
|
|
@ -76,7 +76,7 @@ SettingsWidget::SettingsWidget(QWidget* parent) : QWidget(parent) {
|
|||
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(15, 15, 15, 15);
|
||||
layout->setSpacing(15);
|
||||
layout->setSpacing(10);
|
||||
|
||||
QHBoxLayout* header = new QHBoxLayout();
|
||||
QLabel* title = new QLabel("Settings", this);
|
||||
|
|
@ -102,73 +102,71 @@ SettingsWidget::SettingsWidget(QWidget* parent) : QWidget(parent) {
|
|||
return cb;
|
||||
};
|
||||
|
||||
// Defaults: Only Glass checked
|
||||
// Updated Defaults based on user request
|
||||
m_checkGlass = createCheck("Glass", true, 0, 0);
|
||||
m_checkFocus = createCheck("Focus", false, 0, 1);
|
||||
m_checkTrails = createCheck("Trails", false, 1, 0);
|
||||
m_checkFocus = createCheck("Focus", true, 0, 1);
|
||||
m_checkTrails = createCheck("Trails", true, 1, 0);
|
||||
m_checkAlbumColors = createCheck("Album Colors", false, 1, 1);
|
||||
m_checkShadow = createCheck("Shadow", false, 2, 0);
|
||||
m_checkMirrored = createCheck("Mirrored", false, 2, 1);
|
||||
m_checkMirrored = createCheck("Mirrored", true, 2, 1);
|
||||
layout->addLayout(grid);
|
||||
|
||||
// Bins Slider
|
||||
QHBoxLayout* binsLayout = new QHBoxLayout();
|
||||
m_lblBins = new QLabel("Bins: 26", this);
|
||||
m_lblBins->setStyleSheet("color: white; font-weight: bold; border: none; background: transparent; min-width: 80px;");
|
||||
// Helper for sliders
|
||||
auto addSlider = [&](const QString& label, int min, int max, int val, QSlider*& slider, QLabel*& lbl) {
|
||||
QHBoxLayout* h = new QHBoxLayout();
|
||||
lbl = new QLabel(label, this);
|
||||
lbl->setStyleSheet("color: white; font-weight: bold; border: none; background: transparent; min-width: 80px;");
|
||||
slider = new QSlider(Qt::Horizontal, this);
|
||||
slider->setRange(min, max);
|
||||
slider->setValue(val);
|
||||
slider->setStyleSheet("QSlider::handle:horizontal { background: #aaa; width: 24px; margin: -10px 0; border-radius: 12px; } QSlider::groove:horizontal { background: #444; height: 4px; }");
|
||||
h->addWidget(lbl);
|
||||
h->addWidget(slider);
|
||||
layout->addLayout(h);
|
||||
};
|
||||
|
||||
m_sliderBins = new QSlider(Qt::Horizontal, this);
|
||||
m_sliderBins->setRange(10, 64);
|
||||
m_sliderBins->setValue(26);
|
||||
m_sliderBins->setStyleSheet("QSlider::handle:horizontal { background: #aaa; width: 24px; margin: -10px 0; border-radius: 12px; } QSlider::groove:horizontal { background: #444; height: 4px; }");
|
||||
// Updated Slider Defaults
|
||||
addSlider("Bins: 21", 10, 64, 21, m_sliderBins, m_lblBins);
|
||||
connect(m_sliderBins, &QSlider::valueChanged, this, &SettingsWidget::onBinsChanged);
|
||||
|
||||
binsLayout->addWidget(m_lblBins);
|
||||
binsLayout->addWidget(m_sliderBins);
|
||||
layout->addLayout(binsLayout);
|
||||
|
||||
// Brightness Slider
|
||||
QHBoxLayout* brightLayout = new QHBoxLayout();
|
||||
m_lblBrightness = new QLabel("Bright: 100%", this);
|
||||
m_lblBrightness->setStyleSheet("color: white; font-weight: bold; border: none; background: transparent; min-width: 80px;");
|
||||
|
||||
m_sliderBrightness = new QSlider(Qt::Horizontal, this);
|
||||
m_sliderBrightness->setRange(10, 200); // 10% to 200%
|
||||
m_sliderBrightness->setValue(100);
|
||||
m_sliderBrightness->setStyleSheet("QSlider::handle:horizontal { background: #aaa; width: 24px; margin: -10px 0; border-radius: 12px; } QSlider::groove:horizontal { background: #444; height: 4px; }");
|
||||
addSlider("Bright: 66%", 10, 200, 66, m_sliderBrightness, m_lblBrightness);
|
||||
connect(m_sliderBrightness, &QSlider::valueChanged, this, &SettingsWidget::onBrightnessChanged);
|
||||
|
||||
brightLayout->addWidget(m_lblBrightness);
|
||||
brightLayout->addWidget(m_sliderBrightness);
|
||||
layout->addLayout(brightLayout);
|
||||
addSlider("Granularity", 0, 100, 95, m_sliderGranularity, m_lblGranularity);
|
||||
connect(m_sliderGranularity, &QSlider::valueChanged, this, &SettingsWidget::onSmoothingChanged);
|
||||
|
||||
// Entropy Slider
|
||||
QHBoxLayout* entropyLayout = new QHBoxLayout();
|
||||
m_lblEntropy = new QLabel("Entropy: 1.0", this);
|
||||
m_lblEntropy->setStyleSheet("color: white; font-weight: bold; border: none; background: transparent; min-width: 80px;");
|
||||
addSlider("Detail", 0, 100, 5, m_sliderDetail, m_lblDetail);
|
||||
connect(m_sliderDetail, &QSlider::valueChanged, this, &SettingsWidget::onSmoothingChanged);
|
||||
|
||||
m_sliderEntropy = new QSlider(Qt::Horizontal, this);
|
||||
m_sliderEntropy->setRange(0, 300); // 0.0 to 3.0
|
||||
m_sliderEntropy->setValue(100);
|
||||
m_sliderEntropy->setStyleSheet("QSlider::handle:horizontal { background: #aaa; width: 24px; margin: -10px 0; border-radius: 12px; } QSlider::groove:horizontal { background: #444; height: 4px; }");
|
||||
connect(m_sliderEntropy, &QSlider::valueChanged, this, &SettingsWidget::onEntropyChanged);
|
||||
addSlider("Strength", 0, 100, 95, m_sliderStrength, m_lblStrength);
|
||||
connect(m_sliderStrength, &QSlider::valueChanged, this, &SettingsWidget::onSmoothingChanged);
|
||||
|
||||
entropyLayout->addWidget(m_lblEntropy);
|
||||
entropyLayout->addWidget(m_sliderEntropy);
|
||||
layout->addLayout(entropyLayout);
|
||||
// BPM Scale Selector
|
||||
QHBoxLayout* bpmLayout = new QHBoxLayout();
|
||||
QLabel* lblBpm = new QLabel("BPM Scale:", this);
|
||||
lblBpm->setStyleSheet("color: white; font-weight: bold; border: none; background: transparent; min-width: 80px;");
|
||||
|
||||
m_comboBpmScale = new QComboBox(this);
|
||||
m_comboBpmScale->addItems({"1/1", "1/2", "1/4 (Default)", "1/8", "1/16"});
|
||||
m_comboBpmScale->setCurrentIndex(4); // Default to 1/16
|
||||
m_comboBpmScale->setStyleSheet("QComboBox { background: #444; color: white; border: 1px solid #666; border-radius: 4px; padding: 4px; } QComboBox::drop-down { border: none; }");
|
||||
connect(m_comboBpmScale, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SettingsWidget::onBpmScaleChanged);
|
||||
|
||||
bpmLayout->addWidget(lblBpm);
|
||||
bpmLayout->addWidget(m_comboBpmScale);
|
||||
layout->addLayout(bpmLayout);
|
||||
|
||||
QHBoxLayout* padsLayout = new QHBoxLayout();
|
||||
|
||||
m_padDsp = new XYPad("DSP", this);
|
||||
m_padDsp->setFormatter([](float x, float y) {
|
||||
// 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);
|
||||
return QString("FFT: %1\nHop: %2").arg(fft).arg(hop);
|
||||
});
|
||||
|
||||
// Default to ~4096 FFT (x approx 0.857) and reasonable hop
|
||||
m_padDsp->setValues(0.857f, 0.118f);
|
||||
// Default to FFT 8192 (x=1.0), Hop 64 (y=0.0)
|
||||
m_padDsp->setValues(1.0f, 0.0f);
|
||||
connect(m_padDsp, &XYPad::valuesChanged, this, &SettingsWidget::onDspPadChanged);
|
||||
padsLayout->addWidget(m_padDsp);
|
||||
|
||||
|
|
@ -178,14 +176,15 @@ SettingsWidget::SettingsWidget(QWidget* parent) : QWidget(parent) {
|
|||
float cont = 0.1f + y * 2.9f;
|
||||
return QString("Hue: %1\nCont: %2").arg(hue, 0, 'f', 2).arg(cont, 0, 'f', 2);
|
||||
});
|
||||
m_padColor->setValues(0.45f, (1.0f - 0.1f) / 2.9f);
|
||||
// Default to Hue 0.35 (x=0.175), Cont 0.10 (y=0.0)
|
||||
m_padColor->setValues(0.175f, 0.0f);
|
||||
connect(m_padColor, &XYPad::valuesChanged, this, &SettingsWidget::onColorPadChanged);
|
||||
padsLayout->addWidget(m_padColor);
|
||||
|
||||
layout->addLayout(padsLayout);
|
||||
}
|
||||
|
||||
void SettingsWidget::setParams(bool glass, bool focus, bool trails, bool albumColors, bool shadow, bool mirrored, int bins, float brightness, float entropy) {
|
||||
void SettingsWidget::setParams(bool glass, bool focus, bool trails, bool albumColors, bool shadow, bool mirrored, int bins, float brightness, int granularity, int detail, float strength, int bpmScaleIndex) {
|
||||
bool oldState = blockSignals(true);
|
||||
m_checkGlass->setChecked(glass);
|
||||
m_checkFocus->setChecked(focus);
|
||||
|
|
@ -197,14 +196,21 @@ void SettingsWidget::setParams(bool glass, bool focus, bool trails, bool albumCo
|
|||
m_lblBins->setText(QString("Bins: %1").arg(bins));
|
||||
|
||||
m_brightness = brightness;
|
||||
int brightVal = static_cast<int>(brightness * 100.0f);
|
||||
m_sliderBrightness->setValue(brightVal);
|
||||
m_lblBrightness->setText(QString("Bright: %1%").arg(brightVal));
|
||||
m_sliderBrightness->setValue(static_cast<int>(brightness * 100.0f));
|
||||
m_lblBrightness->setText(QString("Bright: %1%").arg(static_cast<int>(brightness * 100.0f)));
|
||||
|
||||
m_entropy = entropy;
|
||||
int entVal = static_cast<int>(entropy * 100.0f);
|
||||
m_sliderEntropy->setValue(entVal);
|
||||
m_lblEntropy->setText(QString("Entropy: %1").arg(entropy, 0, 'f', 1));
|
||||
m_granularity = granularity;
|
||||
m_sliderGranularity->setValue(granularity);
|
||||
|
||||
m_detail = detail;
|
||||
m_sliderDetail->setValue(detail);
|
||||
|
||||
m_strength = strength;
|
||||
m_sliderStrength->setValue(static_cast<int>(strength * 100.0f));
|
||||
|
||||
if (bpmScaleIndex >= 0 && bpmScaleIndex < m_comboBpmScale->count()) {
|
||||
m_comboBpmScale->setCurrentIndex(bpmScaleIndex);
|
||||
}
|
||||
|
||||
blockSignals(oldState);
|
||||
|
||||
|
|
@ -223,12 +229,32 @@ void SettingsWidget::emitParams() {
|
|||
m_hue,
|
||||
m_contrast,
|
||||
m_brightness,
|
||||
m_entropy
|
||||
m_granularity,
|
||||
m_detail,
|
||||
m_strength
|
||||
);
|
||||
}
|
||||
|
||||
float SettingsWidget::getBpmScale() const {
|
||||
switch(m_comboBpmScale->currentIndex()) {
|
||||
case 0: return 0.25f; // 1/1
|
||||
case 1: return 0.5f; // 1/2
|
||||
case 2: return 1.0f; // 1/4 (Default)
|
||||
case 3: return 2.0f; // 1/8
|
||||
case 4: return 4.0f; // 1/16
|
||||
default: return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
int SettingsWidget::getBpmScaleIndex() const {
|
||||
return m_comboBpmScale->currentIndex();
|
||||
}
|
||||
|
||||
void SettingsWidget::onBpmScaleChanged(int index) {
|
||||
emit bpmScaleChanged(getBpmScale());
|
||||
}
|
||||
|
||||
void SettingsWidget::onDspPadChanged(float x, float y) {
|
||||
// Range: 2^6 (64) to 2^13 (8192)
|
||||
int power = 6 + (int)(x * 7.0f + 0.5f);
|
||||
m_fft = std::pow(2, power);
|
||||
m_hop = 64 + y * (8192 - 64);
|
||||
|
|
@ -253,9 +279,10 @@ void SettingsWidget::onBrightnessChanged(int val) {
|
|||
emitParams();
|
||||
}
|
||||
|
||||
void SettingsWidget::onEntropyChanged(int val) {
|
||||
m_entropy = val / 100.0f;
|
||||
m_lblEntropy->setText(QString("Entropy: %1").arg(m_entropy, 0, 'f', 1));
|
||||
void SettingsWidget::onSmoothingChanged(int val) {
|
||||
m_granularity = m_sliderGranularity->value();
|
||||
m_detail = m_sliderDetail->value();
|
||||
m_strength = m_sliderStrength->value() / 100.0f;
|
||||
emitParams();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
#include <QComboBox>
|
||||
#include "VisualizerWidget.h"
|
||||
#include "CommonWidgets.h"
|
||||
|
||||
|
|
@ -46,22 +47,33 @@ public:
|
|||
bool isMirrored() const { return m_checkMirrored->isChecked(); }
|
||||
int getBins() const { return m_sliderBins->value(); }
|
||||
float getBrightness() const { return m_brightness; }
|
||||
float getEntropy() const { return m_entropy; }
|
||||
|
||||
void setParams(bool glass, bool focus, bool trails, bool albumColors, bool shadow, bool mirrored, int bins, float brightness, float entropy);
|
||||
int getGranularity() const { return m_sliderGranularity->value(); }
|
||||
int getDetail() const { return m_sliderDetail->value(); }
|
||||
float getStrength() const { return m_strength; }
|
||||
|
||||
// Returns the multiplier (e.g., 1.0 for 1/4, 0.5 for 1/2)
|
||||
float getBpmScale() const;
|
||||
int getBpmScaleIndex() const;
|
||||
|
||||
void setParams(bool glass, bool focus, bool trails, bool albumColors, bool shadow, bool mirrored, int bins, float brightness, int granularity, int detail, float strength, int bpmScaleIndex);
|
||||
|
||||
signals:
|
||||
void paramsChanged(bool glass, bool focus, bool trails, bool albumColors, bool shadow, bool mirrored, float hue, float contrast, float brightness, float entropy);
|
||||
void paramsChanged(bool glass, bool focus, bool trails, bool albumColors, bool shadow, bool mirrored, float hue, float contrast, float brightness, int granularity, int detail, float strength);
|
||||
void dspParamsChanged(int fft, int hop);
|
||||
void binsChanged(int n);
|
||||
void bpmScaleChanged(float scale);
|
||||
void closeClicked();
|
||||
|
||||
private slots:
|
||||
void emitParams();
|
||||
void onDspPadChanged(float x, float y);
|
||||
void onColorPadChanged(float x, float y);
|
||||
void onBinsChanged(int val);
|
||||
void onBrightnessChanged(int val);
|
||||
void onEntropyChanged(int val);
|
||||
void onSmoothingChanged(int val);
|
||||
void onBpmScaleChanged(int index);
|
||||
|
||||
private:
|
||||
QCheckBox* m_checkGlass;
|
||||
QCheckBox* m_checkFocus;
|
||||
|
|
@ -75,12 +87,24 @@ private:
|
|||
QLabel* m_lblBins;
|
||||
QSlider* m_sliderBrightness;
|
||||
QLabel* m_lblBrightness;
|
||||
QSlider* m_sliderEntropy;
|
||||
QLabel* m_lblEntropy;
|
||||
|
||||
QSlider* m_sliderGranularity;
|
||||
QLabel* m_lblGranularity;
|
||||
QSlider* m_sliderDetail;
|
||||
QLabel* m_lblDetail;
|
||||
QSlider* m_sliderStrength;
|
||||
QLabel* m_lblStrength;
|
||||
|
||||
QComboBox* m_comboBpmScale;
|
||||
|
||||
float m_hue = 0.9f;
|
||||
float m_contrast = 1.0f;
|
||||
float m_brightness = 1.0f;
|
||||
float m_entropy = 1.0f;
|
||||
|
||||
int m_granularity = 33;
|
||||
int m_detail = 50;
|
||||
float m_strength = 0.0f;
|
||||
|
||||
int m_fft = 4096;
|
||||
int m_hop = 1024;
|
||||
int m_bins = 26;
|
||||
|
|
|
|||
|
|
@ -4,21 +4,28 @@
|
|||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <numeric>
|
||||
|
||||
const double PI = 3.14159265358979323846;
|
||||
|
||||
Processor::Processor(int frameSize, int sampleRate)
|
||||
: m_frameSize(0), m_sampleRate(sampleRate),
|
||||
m_in(nullptr), m_out(nullptr), m_plan(nullptr)
|
||||
m_in(nullptr), m_out(nullptr), m_plan(nullptr),
|
||||
m_cep_in(nullptr), m_cep_out(nullptr), m_cep_plan_fwd(nullptr), m_cep_plan_inv(nullptr)
|
||||
{
|
||||
setFrameSize(frameSize);
|
||||
setNumBins(26);
|
||||
}
|
||||
|
||||
Processor::~Processor() {
|
||||
if (m_plan) fftwf_destroy_plan(m_plan);
|
||||
if (m_in) fftwf_free(m_in);
|
||||
if (m_out) fftwf_free(m_out);
|
||||
if (m_plan) fftw_destroy_plan(m_plan);
|
||||
if (m_in) fftw_free(m_in);
|
||||
if (m_out) fftw_free(m_out);
|
||||
|
||||
if (m_cep_plan_fwd) fftw_destroy_plan(m_cep_plan_fwd);
|
||||
if (m_cep_plan_inv) fftw_destroy_plan(m_cep_plan_inv);
|
||||
if (m_cep_in) fftw_free(m_cep_in);
|
||||
if (m_cep_out) fftw_free(m_cep_out);
|
||||
}
|
||||
|
||||
void Processor::setSampleRate(int rate) {
|
||||
|
|
@ -41,55 +48,73 @@ void Processor::setHPF(float cutoffFreq) {
|
|||
m_hpfCutoff = cutoffFreq;
|
||||
}
|
||||
|
||||
void Processor::setCepstralParams(int granularity, int detail, float strength) {
|
||||
m_granularity = granularity;
|
||||
m_detail = detail;
|
||||
m_cepstralStrength = strength;
|
||||
}
|
||||
|
||||
void Processor::setNumBins(int n) {
|
||||
m_customBins.clear();
|
||||
m_freqsConst.clear();
|
||||
m_history.clear();
|
||||
|
||||
float minFreq = 40.0f;
|
||||
float maxFreq = 11000.0f;
|
||||
double minFreq = 40.0;
|
||||
double maxFreq = 11000.0;
|
||||
|
||||
for (int i = 0; i <= n; ++i) {
|
||||
float f = minFreq * std::pow(maxFreq / minFreq, (float)i / n);
|
||||
double f = minFreq * std::pow(maxFreq / minFreq, (double)i / n);
|
||||
m_customBins.push_back(f);
|
||||
}
|
||||
|
||||
m_freqsConst.push_back(10.0f);
|
||||
m_freqsConst.push_back(10.0);
|
||||
for (size_t i = 0; i < m_customBins.size() - 1; ++i) {
|
||||
m_freqsConst.push_back((m_customBins[i] + m_customBins[i+1]) / 2.0f);
|
||||
m_freqsConst.push_back((m_customBins[i] + m_customBins[i+1]) / 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
void Processor::setFrameSize(int size) {
|
||||
if (m_frameSize == size) return;
|
||||
|
||||
if (m_plan) fftwf_destroy_plan(m_plan);
|
||||
if (m_in) fftwf_free(m_in);
|
||||
if (m_out) fftwf_free(m_out);
|
||||
if (m_plan) fftw_destroy_plan(m_plan);
|
||||
if (m_in) fftw_free(m_in);
|
||||
if (m_out) fftw_free(m_out);
|
||||
|
||||
if (m_cep_plan_fwd) fftw_destroy_plan(m_cep_plan_fwd);
|
||||
if (m_cep_plan_inv) fftw_destroy_plan(m_cep_plan_inv);
|
||||
if (m_cep_in) fftw_free(m_cep_in);
|
||||
if (m_cep_out) fftw_free(m_cep_out);
|
||||
|
||||
m_frameSize = size;
|
||||
|
||||
m_in = (float*)fftwf_malloc(sizeof(float) * m_frameSize);
|
||||
m_out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * (m_frameSize / 2 + 1));
|
||||
m_plan = fftwf_plan_dft_r2c_1d(m_frameSize, m_in, m_out, FFTW_ESTIMATE);
|
||||
// Main FFT (Complex -> Complex)
|
||||
m_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * m_frameSize);
|
||||
m_out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * m_frameSize);
|
||||
m_plan = fftw_plan_dft_1d(m_frameSize, m_in, m_out, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
|
||||
// Cepstral FFTs (Complex -> Complex)
|
||||
m_cep_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * m_frameSize);
|
||||
m_cep_out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * m_frameSize);
|
||||
m_cep_plan_fwd = fftw_plan_dft_1d(m_frameSize, m_cep_in, m_cep_out, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
m_cep_plan_inv = fftw_plan_dft_1d(m_frameSize, m_cep_in, m_cep_out, FFTW_BACKWARD, FFTW_ESTIMATE);
|
||||
|
||||
m_window.resize(m_frameSize);
|
||||
// Blackman-Harris window
|
||||
for (int i = 0; i < m_frameSize; ++i) {
|
||||
float a0 = 0.35875f;
|
||||
float a1 = 0.48829f;
|
||||
float a2 = 0.14128f;
|
||||
float a3 = 0.01168f;
|
||||
m_window[i] = a0 - a1 * std::cos(2.0f * PI * i / (m_frameSize - 1))
|
||||
+ a2 * std::cos(4.0f * PI * i / (m_frameSize - 1))
|
||||
- a3 * std::cos(6.0f * PI * i / (m_frameSize - 1));
|
||||
double a0 = 0.35875;
|
||||
double a1 = 0.48829;
|
||||
double a2 = 0.14128;
|
||||
double a3 = 0.01168;
|
||||
m_window[i] = a0 - a1 * std::cos(2.0 * PI * i / (m_frameSize - 1))
|
||||
+ a2 * std::cos(4.0 * PI * i / (m_frameSize - 1))
|
||||
- a3 * std::cos(6.0 * PI * i / (m_frameSize - 1));
|
||||
}
|
||||
|
||||
m_buffer.assign(m_frameSize, 0.0f);
|
||||
m_buffer.assign(m_frameSize, {0.0, 0.0});
|
||||
m_history.clear();
|
||||
}
|
||||
|
||||
void Processor::pushData(const std::vector<float>& data) {
|
||||
void Processor::pushData(const std::vector<std::complex<double>>& data) {
|
||||
if (data.size() == m_frameSize) {
|
||||
std::copy(data.begin(), data.end(), m_buffer.begin());
|
||||
} else if (data.size() < m_frameSize) {
|
||||
|
|
@ -98,7 +123,7 @@ void Processor::pushData(const std::vector<float>& data) {
|
|||
}
|
||||
}
|
||||
|
||||
float Processor::getInterpolatedDb(const std::vector<float>& freqs, const std::vector<float>& db, float targetFreq) {
|
||||
double Processor::getInterpolatedDb(const std::vector<double>& freqs, const std::vector<double>& db, double targetFreq) {
|
||||
auto it = std::lower_bound(freqs.begin(), freqs.end(), targetFreq);
|
||||
if (it == freqs.begin()) return db[0];
|
||||
if (it == freqs.end()) return db.back();
|
||||
|
|
@ -106,61 +131,188 @@ float Processor::getInterpolatedDb(const std::vector<float>& freqs, const std::v
|
|||
size_t idxUpper = std::distance(freqs.begin(), it);
|
||||
size_t idxLower = idxUpper - 1;
|
||||
|
||||
float f0 = freqs[idxLower];
|
||||
float f1 = freqs[idxUpper];
|
||||
float d0 = db[idxLower];
|
||||
float d1 = db[idxUpper];
|
||||
double f0 = freqs[idxLower];
|
||||
double f1 = freqs[idxUpper];
|
||||
double d0 = db[idxLower];
|
||||
double d1 = db[idxUpper];
|
||||
|
||||
float t = (targetFreq - f0) / (f1 - f0);
|
||||
double t = (targetFreq - f0) / (f1 - f0);
|
||||
return d0 + t * (d1 - d0);
|
||||
}
|
||||
|
||||
// Implementation of the Trig Interpolation "Idealize Curve" logic
|
||||
std::vector<double> Processor::idealizeCurve(const std::vector<double>& magSpectrum) {
|
||||
size_t n = magSpectrum.size();
|
||||
std::vector<double> current_curve = magSpectrum;
|
||||
|
||||
// Map slider values to algorithm parameters
|
||||
int num_bins = 4 + static_cast<int>(m_granularity / 100.0 * 60.0);
|
||||
int iterations = 1 + static_cast<int>(m_detail / 100.0 * 4.0);
|
||||
|
||||
for (int iter = 0; iter < iterations; ++iter) {
|
||||
std::vector<double> next_curve(n, 0.0);
|
||||
std::vector<bool> is_point_set(n, false);
|
||||
|
||||
// 1. Binning
|
||||
std::vector<size_t> bin_boundaries;
|
||||
double log_n = std::log((double)n);
|
||||
for (int i = 0; i <= num_bins; ++i) {
|
||||
double ratio = static_cast<double>(i) / num_bins;
|
||||
size_t boundary = static_cast<size_t>(std::exp(ratio * log_n));
|
||||
boundary = std::max((size_t)1, std::min(n - 1, boundary));
|
||||
bin_boundaries.push_back(boundary);
|
||||
}
|
||||
bin_boundaries[0] = 0;
|
||||
|
||||
// 2. Find extrema and process within bins
|
||||
for (int i = 0; i < num_bins; ++i) {
|
||||
size_t bin_start = bin_boundaries[i];
|
||||
size_t bin_end = bin_boundaries[i+1];
|
||||
if (bin_start >= bin_end -1) continue;
|
||||
|
||||
std::vector<std::pair<size_t, double>> extrema;
|
||||
for (size_t j = bin_start + 1; j < bin_end; ++j) {
|
||||
if ((current_curve[j] > current_curve[j-1] && current_curve[j] > current_curve[j+1]) ||
|
||||
(current_curve[j] < current_curve[j-1] && current_curve[j] < current_curve[j+1])) {
|
||||
extrema.push_back({j, current_curve[j]});
|
||||
}
|
||||
}
|
||||
if (extrema.empty()) continue;
|
||||
|
||||
// 3. Weaving/Smoothing via cosine bell interpolation
|
||||
for (size_t j = bin_start; j <= bin_end; ++j) {
|
||||
double total_weight = 0.0;
|
||||
double weighted_sum = 0.0;
|
||||
for (const auto& extremum : extrema) {
|
||||
double dist = static_cast<double>(j) - extremum.first;
|
||||
double lobe_width = (bin_end - bin_start) / 2.0;
|
||||
if (std::abs(dist) < lobe_width) {
|
||||
double weight = (std::cos(dist / lobe_width * PI) + 1.0) / 2.0; // Hann window
|
||||
weighted_sum += extremum.second * weight;
|
||||
total_weight += weight;
|
||||
}
|
||||
}
|
||||
if (total_weight > 0) {
|
||||
next_curve[j] = weighted_sum / total_weight;
|
||||
is_point_set[j] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Gap Filling (linear interpolation)
|
||||
size_t last_set_point = 0;
|
||||
if(is_point_set[0]) last_set_point = 0;
|
||||
else {
|
||||
for(size_t i=0; i<n; ++i) if(is_point_set[i]) { last_set_point = i; break; }
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < n; ++i) {
|
||||
if (is_point_set[i]) {
|
||||
if (i > last_set_point + 1) { // Gap detected
|
||||
double start_val = next_curve[last_set_point];
|
||||
double end_val = next_curve[i];
|
||||
for (size_t j = last_set_point + 1; j < i; ++j) {
|
||||
double progress = static_cast<double>(j - last_set_point) / (i - last_set_point);
|
||||
next_curve[j] = start_val + (end_val - start_val) * progress;
|
||||
}
|
||||
}
|
||||
last_set_point = i;
|
||||
}
|
||||
}
|
||||
current_curve = next_curve;
|
||||
}
|
||||
return current_curve;
|
||||
}
|
||||
|
||||
Processor::Spectrum Processor::getSpectrum() {
|
||||
// 1. Windowing
|
||||
// 1. Windowing (Complex)
|
||||
for (int i = 0; i < m_frameSize; ++i) {
|
||||
m_in[i] = m_buffer[i] * m_window[i];
|
||||
m_in[i][0] = m_buffer[i].real() * m_window[i];
|
||||
m_in[i][1] = m_buffer[i].imag() * m_window[i];
|
||||
}
|
||||
|
||||
// 2. FFT
|
||||
fftwf_execute(m_plan);
|
||||
fftw_execute(m_plan);
|
||||
|
||||
// 3. Compute Magnitude (dB)
|
||||
// 3. Compute Magnitude
|
||||
int bins = m_frameSize / 2 + 1;
|
||||
std::vector<float> freqsFull(bins);
|
||||
std::vector<float> dbFull(bins);
|
||||
std::vector<double> freqsFull(bins);
|
||||
std::vector<double> magFull(bins);
|
||||
|
||||
for (int i = 0; i < bins; ++i) {
|
||||
float re = m_out[i][0];
|
||||
float im = m_out[i][1];
|
||||
float mag = 2.0f * std::sqrt(re*re + im*im) / m_frameSize;
|
||||
double re = m_out[i][0];
|
||||
double im = m_out[i][1];
|
||||
double mag = 2.0 * std::sqrt(re*re + im*im) / m_frameSize;
|
||||
|
||||
float freq = i * (float)m_sampleRate / m_frameSize;
|
||||
double freq = i * (double)m_sampleRate / m_frameSize;
|
||||
|
||||
// HPF: 2nd Order Butterworth Curve
|
||||
// Gain = 1 / sqrt(1 + (fc/f)^4)
|
||||
// HPF
|
||||
if (m_hpfCutoff > 0.0f && freq > 0.0f) {
|
||||
float ratio = m_hpfCutoff / freq;
|
||||
float gain = 1.0f / std::sqrt(1.0f + (ratio * ratio * ratio * ratio));
|
||||
double ratio = m_hpfCutoff / freq;
|
||||
double gain = 1.0 / std::sqrt(1.0 + (ratio * ratio * ratio * ratio));
|
||||
mag *= gain;
|
||||
} else if (freq == 0.0f) {
|
||||
mag = 0.0f;
|
||||
} else if (freq == 0.0) {
|
||||
mag = 0.0;
|
||||
}
|
||||
|
||||
dbFull[i] = 20.0f * std::log10(std::max(mag, 1e-12f));
|
||||
magFull[i] = mag;
|
||||
freqsFull[i] = freq;
|
||||
}
|
||||
|
||||
// 4. Map to Custom Bins (Log Scale)
|
||||
std::vector<float> currentDb(m_freqsConst.size());
|
||||
// --- Cepstral Smoothing / Trig Interpolation ---
|
||||
if (m_cepstralStrength > 0.0f) {
|
||||
// 1. Log Magnitude
|
||||
for(int i=0; i<m_frameSize; ++i) {
|
||||
// Mirror for symmetry to get real cepstrum
|
||||
int idx = (i <= m_frameSize/2) ? i : (m_frameSize - i);
|
||||
double val = std::max(1e-9, magFull[idx]);
|
||||
m_cep_in[i][0] = std::log(val);
|
||||
m_cep_in[i][1] = 0.0;
|
||||
}
|
||||
|
||||
// 2. IFFT -> Cepstrum
|
||||
fftw_execute(m_cep_plan_inv); // Result in m_cep_out (scaled by N)
|
||||
|
||||
// 3. Hilbert on Cepstrum (Analytic Cepstrum)
|
||||
double scale = 1.0 / m_frameSize;
|
||||
m_cep_in[0][0] = m_cep_out[0][0] * scale;
|
||||
m_cep_in[0][1] = m_cep_out[0][1] * scale;
|
||||
|
||||
for(int i=1; i<m_frameSize; ++i) {
|
||||
if (i < m_frameSize/2) {
|
||||
// Positive quefrencies * 2
|
||||
m_cep_in[i][0] = m_cep_out[i][0] * scale * 2.0;
|
||||
m_cep_in[i][1] = m_cep_out[i][1] * scale * 2.0;
|
||||
} else {
|
||||
// Negative quefrencies = 0
|
||||
m_cep_in[i][0] = 0.0;
|
||||
m_cep_in[i][1] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Idealize Curve (Smoothing)
|
||||
std::vector<double> envelope = idealizeCurve(magFull);
|
||||
|
||||
// Apply Strength (Mix)
|
||||
for(size_t i=0; i<magFull.size(); ++i) {
|
||||
magFull[i] = magFull[i] * (1.0 - m_cepstralStrength) + envelope[i] * m_cepstralStrength;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Map to Custom Bins (Log Scale) & Convert to dB
|
||||
std::vector<double> currentDb(m_freqsConst.size());
|
||||
for (size_t i = 0; i < m_freqsConst.size(); ++i) {
|
||||
float val = getInterpolatedDb(freqsFull, dbFull, m_freqsConst[i]);
|
||||
double val = getInterpolatedDb(freqsFull, magFull, m_freqsConst[i]);
|
||||
|
||||
// Convert to dB
|
||||
val = 20.0 * std::log10(std::max(val, 1e-12));
|
||||
|
||||
// 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.0) val = -100.0;
|
||||
currentDb[i] = val;
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +326,7 @@ Processor::Spectrum Processor::getSpectrum() {
|
|||
if (!m_history.empty()) {
|
||||
for (const auto& vec : m_history) {
|
||||
for (size_t i = 0; i < vec.size(); ++i) {
|
||||
averagedDb[i] += vec[i];
|
||||
averagedDb[i] += static_cast<float>(vec[i]);
|
||||
}
|
||||
}
|
||||
float factor = 1.0f / m_history.size();
|
||||
|
|
@ -183,5 +335,9 @@ Processor::Spectrum Processor::getSpectrum() {
|
|||
}
|
||||
}
|
||||
|
||||
return {m_freqsConst, averagedDb};
|
||||
// Convert freqs to float for return
|
||||
std::vector<float> freqsRet(m_freqsConst.size());
|
||||
for(size_t i=0; i<m_freqsConst.size(); ++i) freqsRet[i] = static_cast<float>(m_freqsConst[i]);
|
||||
|
||||
return {freqsRet, averagedDb};
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <complex>
|
||||
#include <fftw3.h>
|
||||
|
||||
class Processor {
|
||||
|
|
@ -19,7 +20,11 @@ public:
|
|||
void setExpander(float ratio, float thresholdDb);
|
||||
void setHPF(float cutoffFreq);
|
||||
|
||||
void pushData(const std::vector<float>& data);
|
||||
// Cepstral Smoothing (Trig Interpolation)
|
||||
void setCepstralParams(int granularity, int detail, float strength);
|
||||
|
||||
// Input is now double precision complex
|
||||
void pushData(const std::vector<std::complex<double>>& data);
|
||||
|
||||
struct Spectrum {
|
||||
std::vector<float> freqs;
|
||||
|
|
@ -32,20 +37,27 @@ private:
|
|||
int m_frameSize;
|
||||
int m_sampleRate;
|
||||
|
||||
float* m_in;
|
||||
fftwf_complex* m_out;
|
||||
fftwf_plan m_plan;
|
||||
std::vector<float> m_window;
|
||||
// FFTW Resources for Main Spectrum (Double Precision)
|
||||
fftw_complex* m_in;
|
||||
fftw_complex* m_out;
|
||||
fftw_plan m_plan;
|
||||
std::vector<double> m_window;
|
||||
|
||||
// FFTW Resources for Cepstral Smoothing (Double Precision)
|
||||
fftw_complex* m_cep_in;
|
||||
fftw_complex* m_cep_out;
|
||||
fftw_plan m_cep_plan_fwd;
|
||||
fftw_plan m_cep_plan_inv;
|
||||
|
||||
// Buffer for the current audio frame
|
||||
std::vector<float> m_buffer;
|
||||
std::vector<std::complex<double>> m_buffer;
|
||||
|
||||
// Mapping & Smoothing
|
||||
std::vector<float> m_customBins;
|
||||
std::vector<float> m_freqsConst;
|
||||
std::vector<double> m_customBins;
|
||||
std::vector<double> m_freqsConst;
|
||||
|
||||
// Moving Average History
|
||||
std::deque<std::vector<float>> m_history;
|
||||
std::deque<std::vector<double>> m_history;
|
||||
size_t m_smoothingLength = 3;
|
||||
|
||||
// Expander Settings
|
||||
|
|
@ -55,5 +67,13 @@ private:
|
|||
// HPF Settings
|
||||
float m_hpfCutoff = 0.0f;
|
||||
|
||||
float getInterpolatedDb(const std::vector<float>& freqs, const std::vector<float>& db, float targetFreq);
|
||||
// Cepstral Settings
|
||||
int m_granularity = 33;
|
||||
int m_detail = 50;
|
||||
float m_cepstralStrength = 0.0f;
|
||||
|
||||
double getInterpolatedDb(const std::vector<double>& freqs, const std::vector<double>& db, double targetFreq);
|
||||
|
||||
// Internal helper for the Trig Interpolation logic
|
||||
std::vector<double> idealizeCurve(const std::vector<double>& magSpectrum);
|
||||
};
|
||||
|
|
@ -33,7 +33,7 @@ void VisualizerWidget::setNumBins(int n) {
|
|||
m_channels.clear();
|
||||
}
|
||||
|
||||
void VisualizerWidget::setParams(bool glass, bool focus, bool trails, bool albumColors, bool shadow, bool mirrored, float hue, float contrast, float brightness, float entropy) {
|
||||
void VisualizerWidget::setParams(bool glass, bool focus, bool trails, bool albumColors, bool shadow, bool mirrored, float hue, float contrast, float brightness) {
|
||||
m_glass = glass;
|
||||
m_focus = focus;
|
||||
m_trailsEnabled = trails;
|
||||
|
|
@ -43,7 +43,6 @@ void VisualizerWidget::setParams(bool glass, bool focus, bool trails, bool album
|
|||
m_hueFactor = hue;
|
||||
m_contrast = contrast;
|
||||
m_brightness = brightness;
|
||||
m_entropyStrength = entropy;
|
||||
update();
|
||||
}
|
||||
|
||||
|
|
@ -73,16 +72,6 @@ QColor VisualizerWidget::applyModifiers(QColor c) {
|
|||
return QColor::fromHsvF(c.hsvHueF(), s, v);
|
||||
}
|
||||
|
||||
float VisualizerWidget::calculateEntropy(const std::deque<float>& history) {
|
||||
if (history.size() < 2) return 0.0f;
|
||||
float sum = std::accumulate(history.begin(), history.end(), 0.0f);
|
||||
float mean = sum / history.size();
|
||||
float sqSum = 0.0f;
|
||||
for (float v : history) sqSum += (v - mean) * (v - mean);
|
||||
// Normalize: 10dB std dev is considered "Max Chaos"
|
||||
return std::clamp(std::sqrt(sqSum / history.size()) / 10.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
void VisualizerWidget::updateData(const std::vector<AudioEngine::FrameData>& data) {
|
||||
if (QApplication::applicationState() != Qt::ApplicationActive || !isVisible()) return;
|
||||
m_data = data;
|
||||
|
|
@ -102,39 +91,29 @@ void VisualizerWidget::updateData(const std::vector<AudioEngine::FrameData>& dat
|
|||
float rawVal = db[i];
|
||||
float primaryVal = (i < primaryDb.size()) ? primaryDb[i] : rawVal;
|
||||
|
||||
// 1. Update History & Calculate Entropy
|
||||
bin.history.push_back(rawVal);
|
||||
if (bin.history.size() > 15) bin.history.pop_front();
|
||||
// 1. Calculate Responsiveness (Simplified Physics)
|
||||
float responsiveness = 0.2f;
|
||||
|
||||
float entropy = calculateEntropy(bin.history);
|
||||
float order = 1.0f - entropy;
|
||||
|
||||
// 2. Calculate Responsiveness (The Physics Core)
|
||||
float p = 3.0f * m_entropyStrength;
|
||||
float responsiveness = 0.02f + (0.98f * std::pow(order, p));
|
||||
|
||||
// 3. Update Visual Bar Height (Mixed Signal)
|
||||
// 2. Update Visual Bar Height (Mixed Signal)
|
||||
bin.visualDb = (bin.visualDb * (1.0f - responsiveness)) + (rawVal * responsiveness);
|
||||
|
||||
// 4. Update Primary Visual DB (Steady Signal for Pattern)
|
||||
// Use a fixed, slower responsiveness for the pattern to keep it stable
|
||||
// 3. Update Primary Visual DB (Steady Signal for Pattern)
|
||||
float patternResp = 0.1f;
|
||||
bin.primaryVisualDb = (bin.primaryVisualDb * (1.0f - patternResp)) + (primaryVal * patternResp);
|
||||
|
||||
// 5. Trail Physics
|
||||
// 4. Trail Physics
|
||||
bin.trailLife = std::max(bin.trailLife - 0.02f, 0.0f);
|
||||
|
||||
float flux = rawVal - bin.lastRawDb;
|
||||
bin.lastRawDb = rawVal;
|
||||
|
||||
if (flux > 0) {
|
||||
float impactMultiplier = 1.0f + (3.0f * order * m_entropyStrength);
|
||||
float jumpTarget = bin.visualDb + (flux * impactMultiplier);
|
||||
float jumpTarget = bin.visualDb + (flux * 1.5f);
|
||||
|
||||
if (jumpTarget > bin.trailDb) {
|
||||
bin.trailDb = jumpTarget;
|
||||
bin.trailLife = 1.0f;
|
||||
bin.trailThickness = 1.0f + (order * 4.0f);
|
||||
bin.trailThickness = 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -314,7 +293,7 @@ void VisualizerWidget::drawContent(QPainter& p, int w, int h) {
|
|||
v = std::clamp(v / globalMax, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
// 4. Calculate Segment Modifiers (Procedural Pattern with Competition)
|
||||
// 4. Calculate Segment Modifiers (Procedural Pattern)
|
||||
std::vector<float> brightMods(freqs.size() - 1, 0.0f);
|
||||
std::vector<float> alphaMods(freqs.size() - 1, 0.0f);
|
||||
|
||||
|
|
@ -328,18 +307,8 @@ void VisualizerWidget::drawContent(QPainter& p, int w, int h) {
|
|||
bool leftDominant = (prev > next);
|
||||
float sharpness = std::min(curr - prev, curr - next);
|
||||
|
||||
// COMPETITION LOGIC (Controlled by Entropy)
|
||||
// m_entropyStrength (0.0 to 3.0)
|
||||
// Low Entropy = Smoother, Less Aggressive
|
||||
// High Entropy = Sharper, More Faceted
|
||||
|
||||
float entropyFactor = std::max(0.1f, m_entropyStrength);
|
||||
|
||||
// Aggressive Contrast: Boost low sharpness
|
||||
float peakIntensity = std::clamp(std::pow(sharpness * 10.0f * entropyFactor, 0.3f), 0.0f, 1.0f);
|
||||
|
||||
// Dynamic Decay:
|
||||
float decayBase = 0.65f - std::clamp(sharpness * 3.0f * entropyFactor, 0.0f, 0.35f);
|
||||
float peakIntensity = std::clamp(std::pow(sharpness * 10.0f, 0.3f), 0.0f, 1.0f);
|
||||
float decayBase = 0.65f - std::clamp(sharpness * 3.0f, 0.0f, 0.35f);
|
||||
|
||||
auto applyPattern = [&](int dist, bool isBrightSide, int direction) {
|
||||
int segIdx = (direction == -1) ? (i - dist) : (i + dist - 1);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class VisualizerWidget : public QWidget {
|
|||
public:
|
||||
VisualizerWidget(QWidget* parent = nullptr);
|
||||
void updateData(const std::vector<AudioEngine::FrameData>& data);
|
||||
void setParams(bool glass, bool focus, bool trails, bool albumColors, bool shadow, bool mirrored, float hue, float contrast, float brightness, float entropy);
|
||||
void setParams(bool glass, bool focus, bool trails, bool albumColors, bool shadow, bool mirrored, float hue, float contrast, float brightness);
|
||||
void setAlbumPalette(const std::vector<QColor>& palette);
|
||||
void setNumBins(int n);
|
||||
|
||||
|
|
@ -28,7 +28,6 @@ private:
|
|||
void drawContent(QPainter& p, int w, int h);
|
||||
|
||||
struct BinState {
|
||||
std::deque<float> history; // For entropy calculation
|
||||
float visualDb = -100.0f; // Mixed (Height)
|
||||
float primaryVisualDb = -100.0f; // Primary (Pattern)
|
||||
float lastRawDb = -100.0f; // To calculate flux
|
||||
|
|
@ -57,9 +56,7 @@ private:
|
|||
float m_hueFactor = 0.9f;
|
||||
float m_contrast = 1.0f;
|
||||
float m_brightness = 1.0f;
|
||||
float m_entropyStrength = 1.0f;
|
||||
|
||||
float getX(float freq);
|
||||
QColor applyModifiers(QColor c);
|
||||
float calculateEntropy(const std::deque<float>& history);
|
||||
};
|
||||
Loading…
Reference in New Issue