39#include "../Core/DspMath.h"
40#include "../Core/AudioSpec.h"
41#include "../Core/AudioBuffer.h"
42#include "../Core/DenormalGuard.h"
43#include "../Core/RingBuffer.h"
44#include "../Core/StateBlob.h"
80 void prepare(
double sampleRate,
int numChannels = 2) noexcept
82 if (!std::isfinite(sampleRate) || sampleRate <= 0.0)
return;
104 if (!spec.isValid())
return;
105 prepare(spec.sampleRate, spec.numChannels);
119 processBlockInternal<true, false>(buffer,
nullptr);
121 processBlockInternal<false, false>(buffer,
nullptr);
140 if (sidechain.getNumChannels() <= 0 ||
141 sidechain.getNumSamples() < buffer.getNumSamples())
144 processBlockInternal<true, false>(buffer,
nullptr);
146 processBlockInternal<false, false>(buffer,
nullptr);
151 processBlockInternal<true, true>(buffer, &sidechain);
153 processBlockInternal<false, true>(buffer, &sidechain);
164 if (!std::isfinite(dB))
return;
165 threshold_.store(dB, std::memory_order_relaxed);
171 if (!std::isfinite(ratio))
return;
172 ratio_.store(std::max(ratio, T(1)), std::memory_order_relaxed);
178 if (!std::isfinite(dB))
return;
179 hysteresis_.store(std::max(dB, T(0)), std::memory_order_relaxed);
185 if (!std::isfinite(dB))
return;
186 const T r = std::min(dB, T(0));
187 rangeDb_.store(r, std::memory_order_relaxed);
194 if (!std::isfinite(ms))
return;
195 attackMs_.store(std::max(ms, T(0.01)), std::memory_order_relaxed);
202 if (!std::isfinite(ms))
return;
203 holdMs_.store(std::max(ms, T(0)), std::memory_order_relaxed);
208 const double h = std::min(
211 holdSamples_.store(
static_cast<int>(h), std::memory_order_relaxed);
218 if (!std::isfinite(ms))
return;
219 releaseMs_.store(std::max(ms, T(0.01)), std::memory_order_relaxed);
237 if (std::isfinite(cutoffHz) && cutoffHz > 0.0)
238 scHpfFreqHz_.store(
static_cast<T
>(cutoffHz), std::memory_order_relaxed);
255 if (!std::isfinite(ms))
return;
305 [[nodiscard]] std::vector<uint8_t>
getState()
const
310 w.
write(
"threshold",
static_cast<float>(
threshold_.load(std::memory_order_relaxed)));
311 w.
write(
"ratio",
static_cast<float>(
ratio_.load(std::memory_order_relaxed)));
312 w.
write(
"hysteresis",
static_cast<float>(
hysteresis_.load(std::memory_order_relaxed)));
313 w.
write(
"attack",
static_cast<float>(
attackMs_.load(std::memory_order_relaxed)));
314 w.
write(
"hold",
static_cast<float>(
holdMs_.load(std::memory_order_relaxed)));
315 w.
write(
"release",
static_cast<float>(
releaseMs_.load(std::memory_order_relaxed)));
316 w.
write(
"rangeDb",
static_cast<float>(
rangeDb_.load(std::memory_order_relaxed)));
318 w.
write(
"scHpfFreq",
static_cast<float>(
scHpfFreqHz_.load(std::memory_order_relaxed)));
319 w.
write(
"lookahead",
static_cast<float>(
lookaheadMs_.load(std::memory_order_relaxed)));
336 static_cast<double>(r.
read(
"scHpfFreq", 80.0f)));
362 const double samples = std::round(
static_cast<double>(ms) *
sampleRate_ / 1000.0);
373 template <
bool UseHPF,
bool UseSc>
377 const int nCh = buffer.getNumChannels();
378 const int nS = buffer.getNumSamples();
381 const int detCh = UseSc ? std::min(sidechain->getNumChannels(),
MAX_CHANNELS)
384 for (
int i = 0; i < nS; ++i)
389 for (
int ch = 0; ch < detCh; ++ch)
391 T input = UseSc ? sidechain->getChannel(ch)[i]
392 : buffer.getChannel(ch)[i];
393 if constexpr (UseHPF)
403 sc = std::max(sc, std::abs(input));
420 for (
int ch = 0; ch < nCh; ++ch)
422 T& x = buffer.getChannel(ch)[i];
425 auto& line =
lookahead_[
static_cast<size_t>(ch)];
460 if (levelDb < closeThresh)
467 if (levelDb > openThresh)
511 attackCoeff_.store(T(1) - std::exp(T(-1) / (fs *
attackMs_.load(std::memory_order_relaxed) / T(1000))),
512 std::memory_order_relaxed);
514 std::memory_order_relaxed);
517 detAttCoeff_.store(T(1) - std::exp(T(-1) / (fs * T(0.0005))), std::memory_order_relaxed);
518 detRelCoeff_.store(T(1) - std::exp(T(-1) / (fs * T(0.005))), std::memory_order_relaxed);
524 const double scFreq = std::clamp(
525 static_cast<double>(
scHpfFreqHz_.load(std::memory_order_relaxed)),
527 const T c =
static_cast<T
>(std::exp(-std::numbers::pi * 2.0 * scFreq /
sampleRate_));
529 scHpfA0_.store((T(1) + c) / T(2), std::memory_order_relaxed);
Non-owning view over audio channel data.
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
Downward expander with ratio control, hysteresis, and sidechain.
void publishMeters() noexcept
Publishes the metering words for cross-thread readout.
void setHysteresis(T dB) noexcept
Sets the hysteresis gap in decibels to prevent chattering.
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
void setRange(T dB) noexcept
Sets the maximum gain reduction limit in decibels (<= 0).
std::atomic< T > hysteresis_
void updateStateMachine(T levelDb) noexcept
std::atomic< T > rangeDb_
std::atomic< T > scHpfA0_
void reset() noexcept
Resets all internal DSP states to prevent clicks on playback start.
void setRatio(T ratio) noexcept
Sets the expansion ratio (e.g., 4.0 for 4:1).
void cacheParams() noexcept
State getGateState() const noexcept
std::atomic< State > publishedState_
T computeGain(T levelDb) noexcept
int maxLookaheadSamples_
Allocated delay (0 before prepare()).
std::atomic< T > releaseCoeff_
std::array< T, MAX_CHANNELS > scHpfPrev_
std::array< T, MAX_CHANNELS > scHpfState_
void setRelease(T ms) noexcept
Sets the release time in milliseconds.
std::atomic< bool > scHpfEnabled_
void setThreshold(T dB) noexcept
Sets the threshold in decibels.
std::atomic< T > publishedGateGain_
T getCurrentGainDb() const noexcept
std::atomic< T > rangeLinear_
std::atomic< T > attackCoeff_
void setAttack(T ms) noexcept
Sets the attack time in milliseconds.
void setHold(T ms) noexcept
Sets the hold time in milliseconds before release begins.
void processBlockInternal(AudioBufferView< T > buffer, const AudioBufferView< T > *sidechain) noexcept
Internal processing loop templated on HPF/sidechain to avoid branching.
void setLookahead(T ms) noexcept
Sets the lookahead: the audio is delayed so the gain moves ahead of it. 0 (default) is latency-free.
int getLatency() const noexcept
Latency in samples (the lookahead), correct right after setLookahead().
std::atomic< T > releaseMs_
std::array< RingBuffer< T >, MAX_CHANNELS > lookahead_
std::atomic< T > scHpfFreqHz_
static constexpr double kMaxLookaheadMs
std::atomic< T > detAttCoeff_
std::atomic< T > detRelCoeff_
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an audio block in place (internal detector key).
int lookaheadChannels_
Channels with a prepared delay line.
int lookaheadSamplesFor(T ms) const noexcept
std::atomic< T > scHpfCoeff_
std::atomic< T > threshold_
void prepare(const AudioSpec &spec) noexcept
Initializes the expander using an AudioSpec struct.
void processBlock(AudioBufferView< T > buffer, AudioBufferView< T > sidechain) noexcept
Processes an audio block in place, keyed by an external sidechain.
std::atomic< int > holdSamples_
std::atomic< T > lookaheadMs_
int cachedLookahead_
Audio-side samples (per block).
void prepare(double sampleRate, int numChannels=2) noexcept
Initializes the expander with a specific sample rate.
static constexpr int MAX_CHANNELS
std::atomic< T > attackMs_
T getLookahead() const noexcept
Returns the lookahead in milliseconds.
void updateCoefficients() noexcept
void setSidechainHPF(bool enabled, double cutoffHz=80.0) noexcept
Enables and configures the sidechain high-pass filter.
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.
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.