6#include "../Core/detail/BoxAverage.h"
55#include "../Core/DspMath.h"
56#include "../Core/AudioSpec.h"
57#include "../Core/AudioBuffer.h"
58#include "../Core/RingBuffer.h"
59#include "../Core/SmoothedValue.h"
60#include "../Core/DenormalGuard.h"
61#include "../Core/TruePeakDetector.h"
62#include "../Core/StateBlob.h"
74#ifndef DSPARK_NOINLINE
76 #define DSPARK_NOINLINE __declspec(noinline)
77 #elif defined(__GNUC__) || defined(__clang__)
78 #define DSPARK_NOINLINE __attribute__((noinline))
80 #define DSPARK_NOINLINE
116 void prepare(
double sampleRate,
int numChannels = 2,
double initialLookaheadMs = -1.0)
118 if (!(sampleRate > 0.0))
return;
133 const int maxLookaheadSamples =
static_cast<int>(std::min(maxLaExact, 1.0e7)) + 1;
140 dl.prepare(maxLookaheadSamples * 2 +
kChunk);
145 while (hist < maxLookaheadSamples + 4) hist <<= 1;
148 eHist_.assign(
static_cast<size_t>(hist), 1.0);
149 avgHist_.assign(
static_cast<size_t>(hist), 1.0);
150 rHist_.assign(
static_cast<size_t>(hist), 1.0);
151 sufMin_.assign(
static_cast<size_t>(hist) + 1, 1.0);
153 if (std::isfinite(initialLookaheadMs) && initialLookaheadMs > 0.0)
154 lookaheadMs_.store(std::clamp(
static_cast<T
>(initialLookaheadMs),
156 std::memory_order_relaxed);
165 const T ceilDb =
ceilingDb_.load(std::memory_order_relaxed);
208 const int nCh = std::min(buffer.getNumChannels(),
numChannels_);
209 const int nS = buffer.getNumSamples();
218 const T relMs = std::max(
releaseMs_.load(std::memory_order_relaxed), T(1));
222 for (
int ch = 0; ch < nCh; ++ch)
223 chData[ch] = buffer.getChannel(ch);
230 for (
int start = 0; start < nS; start +=
kChunk)
232 const int n = std::min(
kChunk, nS - start);
236 for (
int ch = 0; ch < nCh; ++ch)
238 const T* x = chData[ch] + start;
239 for (
int i = 0; i < n; ++i)
247 for (
int i = 0; i < n; ++i)
264 for (
int ch = 0; ch < nCh; ++ch)
266 T* x = chData[ch] + start;
268 line.pushBlock(x, n);
269 for (
int i = 0; i < n; ++i)
277 out = std::clamp(out, -ceiling, ceiling);
282 if (std::abs(out) > clipCeil)
313 if (channel < 0 || channel >=
numChannels_)
return input;
342 const T relMs = std::max(
releaseMs_.load(std::memory_order_relaxed), T(1));
353 out = std::clamp(out, -ceiling, ceiling);
358 if (std::abs(out) > clipCeil)
389 if (!std::isfinite(dB))
return;
390 ceilingDb_.store(dB, std::memory_order_relaxed);
402 if (!std::isfinite(ms))
return;
403 releaseMs_.store(std::max(T(1), ms), std::memory_order_relaxed);
418 if (!std::isfinite(ms))
return;
420 std::memory_order_relaxed);
457 if (!std::isfinite(ms))
return;
458 holdMs_.store(std::clamp(ms, T(0), T(50)), std::memory_order_relaxed);
462 [[nodiscard]] T
getHold() const noexcept {
return holdMs_.load(std::memory_order_relaxed); }
505 [[nodiscard]] std::vector<uint8_t>
getState()
const
508 w.
write(
"ceiling",
static_cast<float>(
ceilingDb_.load(std::memory_order_relaxed)));
509 w.
write(
"release",
static_cast<float>(
releaseMs_.load(std::memory_order_relaxed)));
510 w.
write(
"lookahead",
static_cast<float>(
lookaheadMs_.load(std::memory_order_relaxed)));
514 w.
write(
"hold",
static_cast<float>(
holdMs_.load(std::memory_order_relaxed)));
547 const double clampedMs = std::clamp(
static_cast<double>(ms), 0.5,
kMaxLookaheadMs);
548 const double exact =
sampleRate_ * clampedMs / 1000.0;
549 return std::max(1,
static_cast<int>(std::min(exact, 1.0e7)));
578 const int span = std::max(0, L - isp);
580 boxA_ = span / 2 + 1;
596 for (
int k = 0; k <
boxA_; ++k)
599 for (
int k = 0; k <
boxB_; ++k)
614 for (
int j =
holdLen_ - 1; j >= 0; --j)
616 m = std::min(m,
rHist_[
static_cast<size_t>((firstFrame + j) &
histMask_)]);
617 sufMin_[
static_cast<size_t>(j)] = m;
642 inline void advanceGain(T peak, T ceiling,
bool adaptive, T relMs)
noexcept
646 const double required = std::min(1.0,
static_cast<double>(ceiling / peak));
668 baseFactor = T(1) + std::min(durationMs / T(100), T(2));
670 coeff = 1.0 / (1.0 +
sampleRate_ *
static_cast<double>(relMs * baseFactor) / 1000.0);
705 const T ceilDb =
ceilingDb_.load(std::memory_order_relaxed);
712 const T relMs = std::max(
releaseMs_.load(std::memory_order_relaxed), T(1));
719 const T holdMs =
holdMs_.load(std::memory_order_relaxed);
742 T sign = (out >= T(0)) ? T(1) : T(-1);
743 T excess = std::abs(out) - clipCeil;
744 T blend = T(1) / (T(1) + excess * T(10));
745 out = sign * (clipCeil * blend + std::abs(out) * (T(1) - blend));
747 const T hardCeil = clipCeil * T(1.05);
748 if (std::abs(out) > hardCeil)
749 out = std::clamp(out, -hardCeil, hardCeil);
Non-owning view over audio channel data.
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
High-performance brickwall lookahead limiter.
void setLookahead(T ms) noexcept
Sets the lookahead time dynamically.
void prepare(double sampleRate, int numChannels=2, double initialLookaheadMs=-1.0)
Allocates memory and prepares the limiter for processing.
void setRelease(T ms) noexcept
Sets the base release time.
std::vector< double > rHist_
Required-gain history.
bool isAdaptiveReleaseEnabled() const noexcept
std::atomic< T > lookaheadMs_
T processSample(T input, int channel) noexcept
Processes a single sample of one channel.
T getGainReductionDb() const noexcept
Current gain reduction in dB (metering).
T lookaheadCurrent_
Smoothed read offset (glides on live changes).
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
void prepare(const AudioSpec &spec)
Prepares from AudioSpec (unified API).
int maxLimitSamples_
Adaptive-release duration cap (2 s).
void configureGainComputer() noexcept
void setSafetyClip(bool enabled) noexcept
Enables the post-limiter soft-knee safety clipper. RT-Safe.
void resetGainComputer() noexcept
std::atomic< bool > lookaheadDirty_
void advanceGain(T peak, T ceiling, bool adaptive, T relMs) noexcept
void syncParameters() noexcept
std::atomic< bool > truePeakEnabled_
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
int getLatency() const noexcept
Reports the processing latency (the lookahead) in samples.
int lookaheadSamplesFor(T ms) const noexcept
int blockPos_
Position inside the current hold block.
T pendingPeak_
Other channels' peaks for the next processSample() frame.
std::atomic< bool > safetyClipEnabled_
void reset() noexcept
Resets the internal state (delays, gain reduction). RT-Safe.
void updateReleaseCoefficient() noexcept
SmoothedValue< T > ceilingSmooth_
int lookaheadSamples_
Audio-side lookahead (consumed from lookaheadMs_).
T applySafetyClipper(T out, T clipCeil) const noexcept
int holdLeft_
Hold samples left before the release may act.
std::vector< double > eHist_
Released envelope history.
void applyLookaheadTarget() noexcept
void setTruePeak(bool enabled) noexcept
Enables 4x oversampled ISP true-peak detection. RT-Safe. The attack ramp shortens by the estimator's ...
std::vector< double > sufMin_
Suffix minima of the last complete hold block (+1 empty).
bool isSafetyClipEnabled() const noexcept
int holdSamples_
Hold length in samples.
std::vector< RingBuffer< T > > delayLines_
std::atomic< T > releaseMs_
void setAdaptiveRelease(bool enabled) noexcept
Enables program-dependent adaptive release. RT-Safe.
static constexpr int kChunk
static constexpr int kResumPeriod
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an AudioBufferView in-place.
double prefixMin_
Running minimum of the current hold block.
DSPARK_NOINLINE void restartHoldBlock(int64_t firstFrame) noexcept
T getLookahead() const noexcept
void updateHoldSamples() noexcept
std::atomic< bool > adaptiveRelease_
bool isTruePeakEnabled() const noexcept
static constexpr T kSafetyClipCeiling
-0.3 dBFS
std::atomic< T > publishedGain_
static constexpr double kMaxLookaheadMs
void setHold(T ms) noexcept
Sets how long the reduction is held after the last peak that asked for it, before the release starts....
T getHold() const noexcept
static constexpr int kMaxChannels
std::atomic< T > ceilingDb_
void setCeiling(T dB) noexcept
Sets the absolute output ceiling.
DSPARK_NOINLINE void resumBoxes() noexcept
std::vector< double > avgHist_
First moving-average output history.
T lastCeilingDb_
Change detector for the ceiling pow skip.
TruePeakDetector< T, kMaxChannels > truePeak_
Zero-allocation parameter smoother for real-time audio.
Tolerant reader: missing keys yield defaults, unknown keys are skipped.
float read(const char *key, float defaultValue) const
Reads a float, or defaultValue when the key is absent.
bool isValid() const noexcept
uint32_t processorId() const noexcept
Serializes key/value parameters into a versioned blob.
std::vector< uint8_t > blob() const
Finalizes and returns the blob.
void write(const char *key, float value)
Writes a float parameter.
Per-channel 4x-oversampled inter-sample peak estimator.
static constexpr int getTaps() noexcept
Input samples one estimate is computed from (taps per phase).
T processSample(T sample, int channel) noexcept
Feeds one sample and returns the local true-peak estimate.
void reset() noexcept
Clears all channel histories. Safe on the audio thread.
double advanceBoxAverage(double incoming, double outgoing, double &sum, double inverseLength) noexcept
Main namespace for the DSPark framework.
T decibelsToGain(T dB, T minusInfinityDb=T(-100)) noexcept
Converts a value in decibels to linear gain.
T gainToDecibels(T gain, T minusInfinityDb=T(-100)) noexcept
Converts a linear gain value to decibels.
constexpr uint32_t stateId(const char(&tag)[5]) noexcept
Builds a FOURCC processor id, e.g. dspark::stateId("COMP").
Describes the audio environment for a DSP processor.
int numChannels
Number of audio channels (e.g., 1 = mono, 2 = stereo).
double sampleRate
Sample rate in Hz.