temp-cortex limiter dump
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user