35#include "../Core/DspMath.h"
36#include "../Core/AudioSpec.h"
37#include "../Core/AudioBuffer.h"
38#include "../Core/DenormalGuard.h"
39#include "../Core/StateBlob.h"
75 if (!std::isfinite(sampleRate) || sampleRate <= 0.0)
return;
93 if (!spec.isValid())
return;
108 processBlockInternal<true, false>(buffer,
nullptr);
110 processBlockInternal<false, false>(buffer,
nullptr);
129 if (sidechain.getNumChannels() <= 0 ||
130 sidechain.getNumSamples() < buffer.getNumSamples())
133 processBlockInternal<true, false>(buffer,
nullptr);
135 processBlockInternal<false, false>(buffer,
nullptr);
140 processBlockInternal<true, true>(buffer, &sidechain);
142 processBlockInternal<false, true>(buffer, &sidechain);
153 if (!std::isfinite(dB))
return;
154 threshold_.store(dB, std::memory_order_relaxed);
160 if (!std::isfinite(ratio))
return;
161 ratio_.store(std::max(ratio, T(1)), std::memory_order_relaxed);
167 if (!std::isfinite(dB))
return;
168 hysteresis_.store(std::max(dB, T(0)), std::memory_order_relaxed);
174 if (!std::isfinite(dB))
return;
175 const T r = std::min(dB, T(0));
176 rangeDb_.store(r, std::memory_order_relaxed);
183 if (!std::isfinite(ms))
return;
184 attackMs_.store(std::max(ms, T(0.01)), std::memory_order_relaxed);
191 if (!std::isfinite(ms))
return;
192 holdMs_.store(std::max(ms, T(0)), std::memory_order_relaxed);
197 const double h = std::min(
200 holdSamples_.store(
static_cast<int>(h), std::memory_order_relaxed);
207 if (!std::isfinite(ms))
return;
208 releaseMs_.store(std::max(ms, T(0.01)), std::memory_order_relaxed);
226 if (std::isfinite(cutoffHz) && cutoffHz > 0.0)
227 scHpfFreqHz_.store(
static_cast<T
>(cutoffHz), std::memory_order_relaxed);
257 [[nodiscard]] std::vector<uint8_t>
getState()
const
262 w.
write(
"threshold",
static_cast<float>(
threshold_.load(std::memory_order_relaxed)));
263 w.
write(
"ratio",
static_cast<float>(
ratio_.load(std::memory_order_relaxed)));
264 w.
write(
"hysteresis",
static_cast<float>(
hysteresis_.load(std::memory_order_relaxed)));
265 w.
write(
"attack",
static_cast<float>(
attackMs_.load(std::memory_order_relaxed)));
266 w.
write(
"hold",
static_cast<float>(
holdMs_.load(std::memory_order_relaxed)));
267 w.
write(
"release",
static_cast<float>(
releaseMs_.load(std::memory_order_relaxed)));
268 w.
write(
"rangeDb",
static_cast<float>(
rangeDb_.load(std::memory_order_relaxed)));
270 w.
write(
"scHpfFreq",
static_cast<float>(
scHpfFreqHz_.load(std::memory_order_relaxed)));
287 static_cast<double>(r.
read(
"scHpfFreq", 80.0f)));
315 template <
bool UseHPF,
bool UseSc>
319 const int nCh = buffer.getNumChannels();
320 const int nS = buffer.getNumSamples();
323 const int detCh = UseSc ? std::min(sidechain->getNumChannels(),
MAX_CHANNELS)
326 for (
int i = 0; i < nS; ++i)
331 for (
int ch = 0; ch < detCh; ++ch)
333 T input = UseSc ? sidechain->getChannel(ch)[i]
334 : buffer.getChannel(ch)[i];
335 if constexpr (UseHPF)
345 sc = std::max(sc, std::abs(input));
362 for (
int ch = 0; ch < nCh; ++ch)
364 buffer.getChannel(ch)[i] *= gain;
380 if (levelDb < closeThresh)
387 if (levelDb > openThresh)
431 attackCoeff_.store(T(1) - std::exp(T(-1) / (fs *
attackMs_.load(std::memory_order_relaxed) / T(1000))),
432 std::memory_order_relaxed);
434 std::memory_order_relaxed);
437 detAttCoeff_.store(T(1) - std::exp(T(-1) / (fs * T(0.0005))), std::memory_order_relaxed);
438 detRelCoeff_.store(T(1) - std::exp(T(-1) / (fs * T(0.005))), std::memory_order_relaxed);
444 const double scFreq = std::clamp(
445 static_cast<double>(
scHpfFreqHz_.load(std::memory_order_relaxed)),
447 const T c =
static_cast<T
>(std::exp(-std::numbers::pi * 2.0 * scFreq /
sampleRate_));
449 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 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
T computeGain(T levelDb) noexcept
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.
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.
std::atomic< T > releaseMs_
std::atomic< T > scHpfFreqHz_
std::atomic< T > detAttCoeff_
std::atomic< T > detRelCoeff_
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an audio block in place (internal detector key).
std::atomic< T > scHpfCoeff_
void prepare(double sampleRate) noexcept
Initializes the expander with a specific sample rate.
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_
static constexpr int MAX_CHANNELS
std::atomic< T > attackMs_
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.