temp-cortex limiter dump

This commit is contained in:
2026-08-01 15:09:30 -04:00
parent ae9019465e
commit 3ad30942a7
4 changed files with 204 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
#include "fast_limiter.hpp"
#include "basicmaths.h"
FastLimiter::FastLimiter(float sampleRate, float lookaheadMs)
: LimiterBase(sampleRate),
lookaheadMs(lookaheadMs),
envelope(0.0f),
peakHold(0.0f),
peakHoldCounter(0),
currentGain(1.0f)
{
lookaheadSamples = (uint8_t)(lookaheadMs * 0.001f * sampleRate);
if (lookaheadSamples < 2) lookaheadSamples = 2; // Minimum 2 samples
// Allocate delay line buffers
float* delayDataLeft = new float[lookaheadSamples];
float* delayDataRight = new float[lookaheadSamples];
// Initialize buffers with zeros
for (uint8_t i = 0; i < lookaheadSamples; i++) {
delayDataLeft[i] = 0.0f;
delayDataRight[i] = 0.0f;
}
// Set up circular buffers
delayBufferLeft = new CircularBuffer<float, uint8_t>(delayDataLeft, lookaheadSamples);
delayBufferRight = new CircularBuffer<float, uint8_t>(delayDataRight, lookaheadSamples);
// 1ms peak hold by default
setPeakHold(1.0f);
}
FastLimiter::~FastLimiter()
{
if (delayBufferLeft) {
delete[] delayBufferLeft->getData(); // Delete the underlying data first
delete delayBufferLeft;
delayBufferLeft = nullptr;
}
if (delayBufferRight) {
delete[] delayBufferRight->getData(); // Delete the underlying data first
delete delayBufferRight;
delayBufferRight = nullptr;
}
}
void FastLimiter::setPeakHold(float peakHoldMs)
{
peakHoldSamples = (uint8_t)(peakHoldMs * 0.001f * sampleRate);
if (peakHoldSamples > 255) peakHoldSamples = 255; // Cap at uint8_t max
}
float FastLimiter::analyzeLevel(float left, float right)
{
// Get peak level from both channels
float leftAbs = (left < 0) ? -left : left;
float rightAbs = (right < 0) ? -right : right;
float peakLevel = (leftAbs > rightAbs) ? leftAbs : rightAbs;
// Peak hold logic - maintain peaks for a short duration
// This helps catch transients that might be missed
if (peakLevel > peakHold) {
peakHold = peakLevel;
peakHoldCounter = peakHoldSamples;
} else if (peakHoldCounter > 0) {
peakHoldCounter--;
peakLevel = peakHold; // Use held peak
} else {
peakHold = peakLevel;
}
return peakLevel;
}
void FastLimiter::process(float input[2], float thresholdDb, float ceilingDb)
{
// Step 1: Write input to delay buffers
delayBufferLeft->write(input[0]);
delayBufferRight->write(input[1]);
// Step 2: Analyze the current input level (lookahead analysis)
float currentLevel = analyzeLevel(input[0], input[1]);
// Step 3: Envelope following for level detection
if (currentLevel > envelope) {
// Fast attack for rising levels
envelope = currentLevel + attackCoeff * (envelope - currentLevel);
} else {
// Slower release for falling levels
envelope = currentLevel + releaseCoeff * (envelope - currentLevel);
}
// Step 4: Convert dB values to linear
float thresholdLinear = powf(10.0f, thresholdDb / 20.0f);
float ceilingLinear = powf(10.0f, ceilingDb / 20.0f);
// Step 5: Calculate required gain reduction
float targetGain = 1.0f;
if (envelope > thresholdLinear) {
// Calculate how much we need to reduce gain
float overAmount = envelope / thresholdLinear;
targetGain = 1.0f / overAmount;
// Ensure we don't exceed ceiling
float potentialOutput = envelope * targetGain;
if (potentialOutput > ceilingLinear) {
targetGain = ceilingLinear / envelope;
}
}
// Step 6: Smooth gain changes (this is critical for clean limiting)
if (targetGain < currentGain) {
// Fast attack when reducing gain (limiting kicks in)
currentGain = targetGain + attackCoeff * (currentGain - targetGain);
} else {
// Slower release when increasing gain (limiter backing off)
currentGain = targetGain + releaseCoeff * (currentGain - targetGain);
}
// Step 7: Calculate makeup gain
makeupGain = ceilingLinear / thresholdLinear;
// Step 8: Read delayed samples and apply processing
if (!delayBufferLeft->isEmpty() && !delayBufferRight->isEmpty()) {
float delayedLeft = delayBufferLeft->read();
float delayedRight = delayBufferRight->read();
// Apply gain reduction and makeup gain to delayed signal
float finalGain = currentGain * makeupGain;
input[0] = delayedLeft * finalGain;
input[1] = delayedRight * finalGain;
} else {
// Buffers not full yet, output silence to avoid pops
input[0] = 0.0f;
input[1] = 0.0f;
}
}