Download Source/Plugin/AudioAnalyser.h from RAM2118/pluginbridge: direct link, hf CLI and curl.
- Browser
- Download file 8.66 kB
-
https://huggingface.co/RAM2118/pluginbridge/resolve/main/Source/Plugin/AudioAnalyser.h
- Command line
-
hf download hf://RAM2118/pluginbridge/Source/Plugin/AudioAnalyser.h
-
curl -L -o AudioAnalyser.h https://huggingface.co/RAM2118/pluginbridge/resolve/main/Source/Plugin/AudioAnalyser.h
8.66 kB
| // Real-time audio analyser — runs in processBlock, results read from MCP thread. | |
| // All inter-thread communication via std::atomic — no locks in audio path. | |
| class AudioAnalyser | |
| { | |
| public: | |
| AudioAnalyser() = default; | |
| void prepareToPlay(double sampleRate, int blockSize) | |
| { | |
| currentSampleRate = sampleRate; | |
| currentBlockSize = blockSize; | |
| // FFT setup (1024-point) | |
| fftBuffer.fill(0.0f); | |
| fftWritePos = 0; | |
| // LUFS — simplified momentary loudness (400ms window) | |
| lufsWindowSamples = (int)(sampleRate * 0.4); | |
| lufsAccumulator = 0.0; | |
| lufsSampleCount = 0; | |
| // Reset all outputs | |
| momentaryLUFS.store(-100.0f); | |
| truePeak.store(-100.0f); | |
| stereoWidth.store(0.0f); | |
| spectralCentroid.store(0.0f); | |
| isSilent.store(true); | |
| for (auto& b : bands) | |
| b.store(0.0f); | |
| } | |
| // Called from audio thread — MUST be lock-free | |
| void processBlock(const juce::AudioBuffer<float>& buffer) | |
| { | |
| const int numSamples = buffer.getNumSamples(); | |
| const int numChannels = buffer.getNumChannels(); | |
| if (numSamples == 0 || numChannels == 0) return; | |
| const float* left = buffer.getReadPointer(0); | |
| const float* right = numChannels > 1 ? buffer.getReadPointer(1) : left; | |
| // --- True Peak --- | |
| float peak = 0.0f; | |
| for (int i = 0; i < numSamples; ++i) | |
| { | |
| float absL = std::fabs(left[i]); | |
| float absR = std::fabs(right[i]); | |
| peak = std::max(peak, std::max(absL, absR)); | |
| } | |
| float peakDb = peak > 0.0f ? 20.0f * std::log10(peak) : -100.0f; | |
| truePeak.store(peakDb); | |
| // --- Silence Gate --- | |
| if (peakDb < -60.0f) | |
| { | |
| isSilent.store(true); | |
| return; | |
| } | |
| isSilent.store(false); | |
| // --- LUFS (simplified momentary — sum of squares over 400ms) --- | |
| for (int i = 0; i < numSamples; ++i) | |
| { | |
| float mono = (left[i] + right[i]) * 0.5f; | |
| lufsAccumulator += (double)(mono * mono); | |
| lufsSampleCount++; | |
| if (lufsSampleCount >= lufsWindowSamples) | |
| { | |
| double meanSquare = lufsAccumulator / (double)lufsSampleCount; | |
| float lufs = (meanSquare > 0.0) ? (float)(-0.691 + 10.0 * std::log10(meanSquare)) : -100.0f; | |
| momentaryLUFS.store(lufs); | |
| lufsAccumulator = 0.0; | |
| lufsSampleCount = 0; | |
| } | |
| } | |
| // --- Stereo Width (correlation-based) --- | |
| float sumMid = 0.0f, sumSide = 0.0f; | |
| for (int i = 0; i < numSamples; ++i) | |
| { | |
| float mid = (left[i] + right[i]) * 0.5f; | |
| float side = (left[i] - right[i]) * 0.5f; | |
| sumMid += mid * mid; | |
| sumSide += side * side; | |
| } | |
| float totalEnergy = sumMid + sumSide; | |
| float width = (totalEnergy > 1e-10f) ? sumSide / totalEnergy : 0.0f; | |
| // Smooth | |
| float prevWidth = stereoWidth.load(); | |
| stereoWidth.store(prevWidth * 0.9f + width * 0.1f); | |
| // --- FFT accumulator (ring buffer → run FFT when full) --- | |
| for (int i = 0; i < numSamples; ++i) | |
| { | |
| float mono = (left[i] + right[i]) * 0.5f; | |
| fftBuffer[fftWritePos] = mono; | |
| fftWritePos++; | |
| if (fftWritePos >= kFFTSize) | |
| { | |
| runFFT(); | |
| fftWritePos = 0; | |
| } | |
| } | |
| } | |
| // Called from HTTP/MCP thread — thread-safe read of atomic values | |
| std::string getCompactAnalysis() const | |
| { | |
| if (isSilent.load()) | |
| return "silent"; | |
| char buf[256]; | |
| float lufs = momentaryLUFS.load(); | |
| float tp = truePeak.load(); | |
| float width = stereoWidth.load(); | |
| float centroid = spectralCentroid.load(); | |
| // Band deviations from flat (0 = flat reference) | |
| // Only report bands that deviate > ±2dB | |
| std::string bandStr; | |
| static const char* bandNames[] = {"sub", "bass", "low", "mid", "hi", "pres", "brill"}; | |
| for (int i = 0; i < 7; ++i) | |
| { | |
| float db = bands[i].load(); | |
| if (db > 2.0f) | |
| bandStr += std::string(bandNames[i]) + ":+" + std::to_string((int)db) + " "; | |
| else if (db < -2.0f) | |
| bandStr += std::string(bandNames[i]) + ":" + std::to_string((int)db) + " "; | |
| } | |
| if (bandStr.empty()) | |
| bandStr = "flat "; | |
| // Brightness descriptor from centroid | |
| const char* brightnessDesc = "balanced"; | |
| if (centroid > 4000.0f) brightnessDesc = "bright"; | |
| else if (centroid < 1500.0f) brightnessDesc = "dark"; | |
| snprintf(buf, sizeof(buf), "%.1f LUFS | TP:%.1f | %s| W:%.2f | %s", | |
| lufs, tp, bandStr.c_str(), width, brightnessDesc); | |
| return std::string(buf); | |
| } | |
| private: | |
| static constexpr int kFFTOrder = 10; | |
| static constexpr int kFFTSize = 1 << kFFTOrder; // 1024 | |
| void runFFT() | |
| { | |
| // Apply window | |
| std::array<float, kFFTSize> windowed; | |
| for (int i = 0; i < kFFTSize; ++i) | |
| windowed[i] = fftBuffer[i] * hanningWindow(i, kFFTSize); | |
| // JUCE FFT requires 2x buffer (real + imaginary interleaved) | |
| std::array<float, kFFTSize * 2> fftData{}; | |
| for (int i = 0; i < kFFTSize; ++i) | |
| fftData[i] = windowed[i]; | |
| // fft is a stored member — no heap allocation in audio thread | |
| fft.performFrequencyOnlyForwardTransform(fftData.data()); | |
| // Now fftData[0..kFFTSize/2] contains magnitudes | |
| int halfSize = kFFTSize / 2; | |
| float binWidth = (float)currentSampleRate / (float)kFFTSize; | |
| // --- 7 Band Energy --- | |
| // sub: 20-60, bass: 60-250, low: 250-500, mid: 500-2k, hi: 2k-6k, pres: 6k-12k, brill: 12k-20k | |
| static const float bandEdges[] = {20, 60, 250, 500, 2000, 6000, 12000, 20000}; | |
| float bandEnergy[7] = {}; | |
| int bandCount[7] = {}; | |
| for (int bin = 1; bin < halfSize; ++bin) | |
| { | |
| float freq = bin * binWidth; | |
| float mag = fftData[bin]; | |
| for (int b = 0; b < 7; ++b) | |
| { | |
| if (freq >= bandEdges[b] && freq < bandEdges[b + 1]) | |
| { | |
| bandEnergy[b] += mag * mag; | |
| bandCount[b]++; | |
| break; | |
| } | |
| } | |
| } | |
| // Convert to dB relative to average | |
| float totalEnergy = 0.0f; | |
| for (int b = 0; b < 7; ++b) | |
| totalEnergy += bandEnergy[b]; | |
| float avgEnergy = totalEnergy / 7.0f; | |
| for (int b = 0; b < 7; ++b) | |
| { | |
| float bandDb = 0.0f; | |
| if (avgEnergy > 1e-10f && bandEnergy[b] > 1e-10f) | |
| bandDb = 10.0f * std::log10(bandEnergy[b] / avgEnergy); | |
| bands[b].store(bandDb); | |
| } | |
| // --- Spectral Centroid --- | |
| float weightedSum = 0.0f; | |
| float magnitudeSum = 0.0f; | |
| for (int bin = 1; bin < halfSize; ++bin) | |
| { | |
| float freq = bin * binWidth; | |
| float mag = fftData[bin]; | |
| weightedSum += freq * mag; | |
| magnitudeSum += mag; | |
| } | |
| float centroid = (magnitudeSum > 1e-10f) ? weightedSum / magnitudeSum : 0.0f; | |
| // Smooth | |
| float prevCentroid = spectralCentroid.load(); | |
| spectralCentroid.store(prevCentroid * 0.8f + centroid * 0.2f); | |
| } | |
| static float hanningWindow(int i, int size) | |
| { | |
| return 0.5f * (1.0f - std::cos(2.0f * 3.14159265358979f * i / (float)(size - 1))); | |
| } | |
| // State | |
| double currentSampleRate = 44100.0; | |
| int currentBlockSize = 512; | |
| // FFT — constructed once, reused per block (no allocation in audio thread) | |
| juce::dsp::FFT fft{kFFTOrder}; | |
| // FFT ring buffer | |
| std::array<float, kFFTSize> fftBuffer{}; | |
| int fftWritePos = 0; | |
| // LUFS accumulator | |
| int lufsWindowSamples = 17640; // 400ms at 44.1kHz | |
| double lufsAccumulator = 0.0; | |
| int lufsSampleCount = 0; | |
| // Thread-safe outputs (written in audio thread, read from HTTP thread) | |
| std::atomic<float> momentaryLUFS{-100.0f}; | |
| std::atomic<float> truePeak{-100.0f}; | |
| std::atomic<float> stereoWidth{0.0f}; | |
| std::atomic<float> spectralCentroid{0.0f}; | |
| std::atomic<bool> isSilent{true}; | |
| std::array<std::atomic<float>, 7> bands{}; // sub, bass, low, mid, hi, pres, brill | |
| }; | |