ambivalence/Source/DSP/AcousticMetrics.h
2026-08-15 15:36:28 +02:00

104 lines
No EOL
3.6 KiB
C++

#pragma once
#include "DSPConstants.h"
#include <array>
#include <atomic>
#include <vector> // <- 1 row added
namespace FDNReverb {
// -----------------------------------------------------------------------------
// AcousticMetrics class
// -----------------------------------------------------------------------------
// Computes real-time acoustic metrics (D50, C50, C80, EDT).
//
// Principle:
// Accumulate the squared energy of the input signal in a ring buffer,
// then compare it against the energy from 50 ms / 80 ms ago,
// and compute the D50 / C50 / C80 values.
//
// Sample-rate support:
// times (ms) are converted to sample counts,
// so 44.1 kHz through 192 kHz are supported automatically.
//
// CPU:
// O(1) per-sample computation (energy accumulation)
// CPU overhead: below ~0.5%
// -----------------------------------------------------------------------------
class AcousticMetrics {
public:
AcousticMetrics() = default;
// -- initialize --
// sampleRate: sample rate (Hz)
// analysisWindowMs: analysis window (ms). 2000 ms (2 s)
void prepare(double sampleRate, float analysisWindowMs = 2000.0f);
// -- per-sample state update --
// sample: current Wet signal sample (mono)
void processSample(float sample) noexcept;
// -- value getters --
// ranges:
// D50: 0.0 ~ 1.0 ( 0.3~0.9)
// C50: -10 ~ +30 dB
// C80: -10 ~ +30 dB
// EDT: 0.0 ~ 5.0 (s)
float getD50() const noexcept { return d50.load(std::memory_order_relaxed); }
float getC50() const noexcept { return c50.load(std::memory_order_relaxed); }
float getC80() const noexcept { return c80.load(std::memory_order_relaxed); }
float getEDT() const noexcept { return edt.load(std::memory_order_relaxed); }
// --- added: expose energy history for drawing ---
// get the instantaneous energy (squared) at a time offset in the past
float getEnergyAtTimeOffset(float secondsAgo) const noexcept;
// input activity detection (energy over the last 50 ms)
bool isActive() const noexcept;
// -- reset --
void reset() noexcept;
private:
// -- compute --
void updateMetrics() noexcept;
// -- parameter --
double sampleRate{ 48000.0 };
float analysisWindowMs{ 2000.0f };
// 50ms / 80ms sample count ( sample rate depends on )
int samples50ms{ 2400 }; // @ 48kHz
int samples80ms{ 3840 }; // @ 48kHz
int analysisWindowSamples{ 96000 }; // 2000ms @ 48kHz
// -- buffer --
// energy history ( squared value )
std::vector<float> energyHistory;
int historyWritePos{ 0 };
// -- cumulative energy value --
// 50ms cumulative energy ( time )
double recent50msEnergy{ 0.0 };
// 80ms cumulative energy ( time )
double recent80msEnergy{ 0.0 };
// entire cumulative energy
double totalEnergy{ 0.0 };
// EDT : energy decay tracking
float energyPeak{ 0.0f };
int energyPeakPos{ 0 };
// -- output value (atomic for thread safety) --
std::atomic<float> d50{ 0.0f };
std::atomic<float> c50{ 0.0f };
std::atomic<float> c80{ 0.0f };
std::atomic<float> edt{ 0.0f };
// -- update --
// sample compute ,
// sample interval update
int updateCounter{ 0 };
static constexpr int kUpdateInterval = 1024; // about 21ms @ 48kHz
};
} // namespace FDNReverb