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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "limiter_base.hpp"
|
||||||
|
#include "CircularBuffer.h"
|
||||||
|
|
||||||
|
class FastLimiter : public LimiterBase
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
// Lookahead buffer for clean limiting
|
||||||
|
CircularBuffer<float, uint8_t>* delayBufferLeft;
|
||||||
|
CircularBuffer<float, uint8_t>* delayBufferRight;
|
||||||
|
|
||||||
|
// Lookahead parameters
|
||||||
|
float lookaheadMs;
|
||||||
|
uint8_t lookaheadSamples;
|
||||||
|
|
||||||
|
// Envelope following
|
||||||
|
float envelope;
|
||||||
|
float peakHold;
|
||||||
|
uint8_t peakHoldCounter;
|
||||||
|
uint8_t peakHoldSamples;
|
||||||
|
|
||||||
|
// Internal gain smoothing
|
||||||
|
float currentGain;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FastLimiter(float sampleRate, float lookaheadMs = 5.0f);
|
||||||
|
~FastLimiter();
|
||||||
|
|
||||||
|
void setPeakHold(float peakHoldMs);
|
||||||
|
void process(float input[2], float thresholdDb = -3.0f, float ceilingDb = -0.1f) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float analyzeLevel(float left, float right);
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#include "limiter_base.hpp"
|
||||||
|
#include "basicmaths.h"
|
||||||
|
|
||||||
|
LimiterBase::LimiterBase(float sampleRate) : sampleRate(sampleRate), gainReduction(1.0f), makeupGain(1.0f)
|
||||||
|
{
|
||||||
|
setTiming(0.01f, 12.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LimiterBase::setTiming(float attackMs, float releaseMs)
|
||||||
|
{
|
||||||
|
// Convert milliseconds to coefficient for exponential decay
|
||||||
|
// Formula: coeff = exp(-1 / (time_in_samples))
|
||||||
|
attackCoeff = expf(-1.0f / (attackMs * 0.001f * sampleRate + 1e-6f));
|
||||||
|
releaseCoeff = expf(-1.0f / (releaseMs * 0.001f * sampleRate + 1e-6f));
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class LimiterBase
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
float sampleRate;
|
||||||
|
float gainReduction;
|
||||||
|
float attackCoeff;
|
||||||
|
float releaseCoeff;
|
||||||
|
float makeupGain;
|
||||||
|
|
||||||
|
public:
|
||||||
|
LimiterBase(float sampleRate);
|
||||||
|
virtual ~LimiterBase() = default;
|
||||||
|
virtual void setTiming(float attackMs, float releaseMs);
|
||||||
|
virtual void process(float input[2], float thresholdDb = -3.0f, float ceilingDb = -0.1f) = 0;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user