40#include "../Core/AudioBuffer.h"
41#include "../Core/AudioSpec.h"
42#include "../Core/DspMath.h"
43#include "../Core/DryWetMixer.h"
44#include "../Core/Oversampling.h"
45#include "../Core/StateBlob.h"
97 prepared_.store(
false, std::memory_order_relaxed);
101 const int osFactor =
osFactor_.load(std::memory_order_relaxed);
118 prepared_.store(
true, std::memory_order_relaxed);
148 if (!
prepared_.load(std::memory_order_relaxed))
return;
149 if (buffer.getNumSamples() == 0 || buffer.getNumChannels() == 0)
return;
151 T mixVal =
mix_.load(std::memory_order_relaxed);
165 mixer_.mixWet(buffer, mixVal);
176 const int m = std::clamp(
static_cast<int>(mode), 0,
178 mode_.store(
static_cast<Mode>(m), std::memory_order_relaxed);
188 if (!std::isfinite(dB))
return;
189 ceilingDb_.store(std::clamp(dB, T(-60), T(0)), std::memory_order_relaxed);
199 if (!std::isfinite(dB))
return;
200 inputGainDb_.store(std::clamp(dB, T(0), T(48)), std::memory_order_relaxed);
224 if (!std::isfinite(amount))
return;
225 mix_.store(std::clamp(amount, T(0), T(1)), std::memory_order_relaxed);
242 if (!std::isfinite(ms))
return;
243 slewLimitMs_.store(std::max(ms, T(0)), std::memory_order_relaxed);
256 factor = std::bit_ceil(
static_cast<unsigned int>(std::max(1, factor)));
257 osFactor_.store(std::min(factor, 16), std::memory_order_relaxed);
260 [[nodiscard]]
Mode getMode() const noexcept {
return mode_.load(std::memory_order_relaxed); }
263 [[nodiscard]]
int getStages() const noexcept {
return stages_.load(std::memory_order_relaxed); }
264 [[nodiscard]] T
getMix() const noexcept {
return mix_.load(std::memory_order_relaxed); }
278 [[nodiscard]] std::vector<uint8_t>
getState()
const
281 w.
write(
"mode",
static_cast<int32_t
>(
mode_.load(std::memory_order_relaxed)));
282 w.
write(
"ceiling",
static_cast<float>(
ceilingDb_.load(std::memory_order_relaxed)));
283 w.
write(
"inputGain",
static_cast<float>(
inputGainDb_.load(std::memory_order_relaxed)));
284 w.
write(
"stages",
stages_.load(std::memory_order_relaxed));
285 w.
write(
"mix",
static_cast<float>(
mix_.load(std::memory_order_relaxed)));
286 w.
write(
"slewLimit",
static_cast<float>(
slewLimitMs_.load(std::memory_order_relaxed)));
287 w.
write(
"oversampling",
osFactor_.load(std::memory_order_relaxed));
312 static constexpr T
kPhi =
static_cast<T
>(1.6180339887498948482);
319 Mode modeVal =
mode_.load(std::memory_order_relaxed);
320 T ceilDb =
ceilingDb_.load(std::memory_order_relaxed);
321 T gainDb =
inputGainDb_.load(std::memory_order_relaxed);
322 int numStages =
stages_.load(std::memory_order_relaxed);
323 T slewMs =
slewLimitMs_.load(std::memory_order_relaxed);
329 T stageGain = (numStages > 1)
330 ? std::pow(totalGainLin, T(1) /
static_cast<T
>(numStages))
334 T maxSlewDelta = T(0);
336 maxSlewDelta = (ceiling / (slewMs * T(0.001))) /
static_cast<T
>(currentSampleRate);
342 case Mode::Hard: dispatchClipping<Mode::Hard>(buffer, totalGainLin, stageGain, numStages, ceiling, maxSlewDelta);
break;
343 case Mode::Soft: dispatchClipping<Mode::Soft>(buffer, totalGainLin, stageGain, numStages, ceiling, maxSlewDelta);
break;
344 case Mode::Analog: dispatchClipping<Mode::Analog>(buffer, totalGainLin, stageGain, numStages, ceiling, maxSlewDelta);
break;
345 case Mode::GoldenRatio: dispatchClipping<Mode::GoldenRatio>(buffer, totalGainLin, stageGain, numStages, ceiling, maxSlewDelta);
break;
346 default: dispatchClipping<Mode::Hard>(buffer, totalGainLin, stageGain, numStages, ceiling, maxSlewDelta);
break;
357 const int nCh = std::min(buffer.getNumChannels(),
kMaxChannels);
358 const int nS = buffer.getNumSamples();
363 for (
int ch = 0; ch < nCh; ++ch)
365 T* data = buffer.getChannel(ch);
366 for (
int i = 0; i < nS; ++i)
369 T driven = sample * totalGainLin;
372 for (
int s = 0; s < numStages; ++s)
375 sample = processSample<M>(sample, ceiling);
379 if (maxSlewDelta > T(0))
382 if (std::abs(delta) > maxSlewDelta)
383 sample =
slewPrev_[ch] + std::copysign(maxSlewDelta, delta);
388 peakIn = std::max(peakIn, std::abs(driven));
389 peakOut = std::max(peakOut, std::abs(sample));
395 if (peakIn > T(1e-6)) {
396 auto ratio = std::min(peakOut / peakIn, T(1));
411 return std::clamp(sample, -ceiling, ceiling);
415 return ceiling * std::tanh(sample / ceiling);
424 constexpr T
halfPi =
static_cast<T
>(std::numbers::pi * 0.5);
430 T threshold = ceiling /
kPhi;
431 T absSample = std::abs(sample);
433 if (absSample <= threshold)
437 T sign = std::copysign(T(1), sample);
438 T excess = absSample - threshold;
439 T range = ceiling - threshold;
441 return sign * (threshold + (range * excess) / (excess + range));
446 [[nodiscard]]
static T
dbToLinear(T dB)
noexcept {
return std::pow(T(10), dB / T(20)); }
449 return linear > T(1e-5) ? T(20) * std::log10(linear) : minusInfinityDb;
Non-owning view over audio channel data.
Real-time audio clipper with analog modeling and anti-aliasing features.
std::unique_ptr< Oversampling< T > > oversampler_
std::atomic< T > inputGainDb_
std::atomic< int > osFactor_
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob. Oversampling factor applies on the next prepare() as usual.
std::atomic< T > gainReductionDb_
T slewPrev_[kMaxChannels]
std::atomic< T > ceilingDb_
static T processSample(T sample, T ceiling) noexcept
Compile-time resolution of the waveshaping math.
void setSlewLimit(T ms) noexcept
Enables slew limiting to soften clipping edges.
void setInputGain(T dB) noexcept
Sets the input drive/gain before clipping.
void processInternal(AudioBufferView< T > &buffer, double currentSampleRate) noexcept
Core DSP routing. Resolves atomics and branches to the per-mode template.
int getLatency() const noexcept
Returns latency in samples introduced by oversampling filters.
void dispatchClipping(AudioBufferView< T > &buffer, T totalGainLin, T stageGain, int numStages, T ceiling, T maxSlewDelta) noexcept
Per-mode processing loop (waveshaper inlined at compile time; the slew state and peak metering keep t...
static constexpr T kPhi
Mathematical Golden Ratio used for the GoldenRatio soft-knee transition.
static constexpr int kMaxStages
void prepare(const AudioSpec &spec)
Prepares the clipper for processing, allocating any necessary internal buffers.
Mode
Defines the harmonic waveshaping algorithm used for clipping.
@ Hard
Brickwall digital clipping. High odd harmonics.
@ Analog
Sine-based soft clipping. Transformer-like saturation.
@ Soft
Tanh soft clipping. Even/odd blend, tape-like.
@ GoldenRatio
Mathematical soft-knee using phi. Extremely transparent until heavy drive.
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an audio buffer in-place through the clipping algorithm.
void setStages(int count) noexcept
Sets the number of cascaded clipping stages.
void setMode(Mode mode) noexcept
Sets the clipping algorithm.
T getCeiling() const noexcept
T getMix() const noexcept
void setMix(T amount) noexcept
Sets the dry/wet ratio of the processor.
void reset() noexcept
Resets the internal state of the clipper.
void setOversampling(int factor) noexcept
Sets the oversampling multiplier to mitigate aliasing.
T getGainReductionDb() const noexcept
Retrieves the maximum gain reduction applied during the last block (for UI metering).
Mode getMode() const noexcept
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
static T gainToDecibels(T linear, T minusInfinityDb) noexcept
static constexpr int kMaxChannels
int getStages() const noexcept
void setCeiling(T dB) noexcept
Sets the absolute maximum output level.
std::atomic< bool > prepared_
T getInputGain() const noexcept
T getSlewLimit() const noexcept
int getOversampling() const noexcept
Returns the published oversampling factor (applied on the next prepare()).
std::atomic< Mode > mode_
static T dbToLinear(T dB) noexcept
std::atomic< T > slewLimitMs_
std::atomic< int > stages_
Pre-allocated, SIMD-friendly dry/wet blender for real-time audio.
Power-of-two oversampling processor with polyphase anti-aliasing.
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 fastSin(T x) noexcept
Fast sine approximation (degree-9 odd minimax polynomial).
constexpr uint32_t stateId(const char(&tag)[5]) noexcept
Builds a FOURCC processor id, e.g. dspark::stateId("COMP").
constexpr T halfPi
Pi / 2 (1.57079...). Quarter period; sin/cos phase offset.
Describes the audio environment for a DSP processor.
constexpr bool isValid() const noexcept
Checks if the specification contains valid, processable parameters.
double sampleRate
Sample rate in Hz.