43#include "../Core/AudioBuffer.h"
44#include "../Core/AudioSpec.h"
45#include "../Core/DspMath.h"
46#include "../Core/DryWetMixer.h"
47#include "../Core/Oversampling.h"
48#include "../Core/SmoothedValue.h"
49#include "../Core/StateBlob.h"
50#include "../Core/detail/ClipperShape.h"
103 prepared_.store(
false, std::memory_order_relaxed);
107 const int osFactor =
osFactor_.load(std::memory_order_relaxed);
133 prepared_.store(
true, std::memory_order_relaxed);
164 if (!
prepared_.load(std::memory_order_relaxed))
return;
165 if (buffer.getNumSamples() == 0 || buffer.getNumChannels() == 0)
return;
167 T mixVal =
mix_.load(std::memory_order_relaxed);
181 mixer_.mixWet(buffer, mixVal);
192 const int m = std::clamp(
static_cast<int>(mode), 0,
194 mode_.store(
static_cast<Mode>(m), std::memory_order_relaxed);
204 if (!std::isfinite(dB))
return;
205 ceilingDb_.store(std::clamp(dB, T(-60), T(0)), std::memory_order_relaxed);
215 if (!std::isfinite(dB))
return;
216 inputGainDb_.store(std::clamp(dB, T(0), T(48)), std::memory_order_relaxed);
240 if (!std::isfinite(amount))
return;
241 mix_.store(std::clamp(amount, T(0), T(1)), std::memory_order_relaxed);
258 if (!std::isfinite(ms))
return;
259 slewLimitMs_.store(std::max(ms, T(0)), std::memory_order_relaxed);
272 factor = std::bit_ceil(
static_cast<unsigned int>(std::max(1, factor)));
273 osFactor_.store(std::min(factor, 16), std::memory_order_relaxed);
276 [[nodiscard]]
Mode getMode() const noexcept {
return mode_.load(std::memory_order_relaxed); }
279 [[nodiscard]]
int getStages() const noexcept {
return stages_.load(std::memory_order_relaxed); }
280 [[nodiscard]] T
getMix() const noexcept {
return mix_.load(std::memory_order_relaxed); }
294 [[nodiscard]] std::vector<uint8_t>
getState()
const
297 w.
write(
"mode",
static_cast<int32_t
>(
mode_.load(std::memory_order_relaxed)));
298 w.
write(
"ceiling",
static_cast<float>(
ceilingDb_.load(std::memory_order_relaxed)));
299 w.
write(
"inputGain",
static_cast<float>(
inputGainDb_.load(std::memory_order_relaxed)));
300 w.
write(
"stages",
stages_.load(std::memory_order_relaxed));
301 w.
write(
"mix",
static_cast<float>(
mix_.load(std::memory_order_relaxed)));
302 w.
write(
"slewLimit",
static_cast<float>(
slewLimitMs_.load(std::memory_order_relaxed)));
303 w.
write(
"oversampling",
osFactor_.load(std::memory_order_relaxed));
328 static constexpr T
kPhi =
static_cast<T
>(1.6180339887498948482);
335 Mode modeVal =
mode_.load(std::memory_order_relaxed);
336 T ceilDb =
ceilingDb_.load(std::memory_order_relaxed);
337 T gainDb =
inputGainDb_.load(std::memory_order_relaxed);
338 int numStages =
stages_.load(std::memory_order_relaxed);
339 T slewMs =
slewLimitMs_.load(std::memory_order_relaxed);
345 const T slewScale = (slewMs > T(0))
346 ? T(1) / (slewMs * T(0.001)) /
static_cast<T
>(currentSampleRate)
352 case Mode::Hard: dispatchClipping<Mode::Hard>(buffer, numStages, slewScale);
break;
353 case Mode::Soft: dispatchClipping<Mode::Soft>(buffer, numStages, slewScale);
break;
354 case Mode::Analog: dispatchClipping<Mode::Analog>(buffer, numStages, slewScale);
break;
355 case Mode::GoldenRatio: dispatchClipping<Mode::GoldenRatio>(buffer, numStages, slewScale);
break;
356 default: dispatchClipping<Mode::Hard>(buffer, numStages, slewScale);
break;
361 [[nodiscard]]
static T
stageGainFor(T totalGainLin,
int numStages)
noexcept
363 return (numStages > 1) ? std::pow(totalGainLin, T(1) /
static_cast<T
>(numStages))
372 stages_.load(std::memory_order_relaxed)));
382 const int nCh = std::min(buffer.getNumChannels(),
kMaxChannels);
383 const int nS = buffer.getNumSamples();
390 for (
int start = 0; start < nS; start +=
kChunk)
392 const int n = std::min(
kChunk, nS - start);
395 for (
int j = 0; j < n; ++j)
401 const T ceilConst =
ceilingSm_.getCurrentValue();
403 for (
int ch = 0; ch < nCh; ++ch)
405 T* data = buffer.getChannel(ch) + start;
406 for (
int j = 0; j < n; ++j)
408 const T stageGain = gliding ?
gainRamp_[
static_cast<size_t>(j)] : gainConst;
409 const T ceiling = gliding ?
ceilRamp_[
static_cast<size_t>(j)] : ceilConst;
411 T totalGain = stageGain;
412 for (
int s = 1; s < numStages; ++s) totalGain *= stageGain;
413 const T driven = sample * totalGain;
416 for (
int s = 0; s < numStages; ++s)
419 sample = processSample<M>(sample, ceiling);
423 const T maxSlewDelta = ceiling * slewScale;
424 if (maxSlewDelta > T(0))
427 if (std::abs(delta) > maxSlewDelta)
428 sample =
slewPrev_[ch] + std::copysign(maxSlewDelta, delta);
433 peakIn = std::max(peakIn, std::abs(driven));
434 peakOut = std::max(peakOut, std::abs(sample));
441 if (peakIn > T(1e-6)) {
442 auto ratio = std::min(peakOut / peakIn, T(1));
456 return detail::clipperShape<detail::ClipperCurve::Hard>(sample, ceiling);
458 return detail::clipperShape<detail::ClipperCurve::Tanh>(sample, ceiling);
460 return detail::clipperShape<detail::ClipperCurve::Sine>(sample, ceiling);
462 return detail::clipperShape<detail::ClipperCurve::GoldenRatio>(sample, ceiling);
466 [[nodiscard]]
static T
dbToLinear(T dB)
noexcept {
return std::pow(T(10), dB / T(20)); }
469 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_
SmoothedValue< T > stageGainSm_
void snapParameters() noexcept
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.
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.
std::array< T, kChunk > gainRamp_
void setMode(Mode mode) noexcept
Sets the clipping algorithm.
T getCeiling() const noexcept
void dispatchClipping(AudioBufferView< T > &buffer, int numStages, T slewScale) noexcept
Per-mode processing loop (waveshaper inlined at compile time; the slew state and peak metering keep t...
T getMix() const noexcept
SmoothedValue< T > ceilingSm_
static constexpr double kParamRampMs
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).
std::array< T, kChunk > ceilRamp_
static constexpr int kChunk
Mode getMode() const noexcept
static T stageGainFor(T totalGainLin, int numStages) 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.
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.
Main namespace for the DSPark framework.
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.
constexpr bool isValid() const noexcept
Checks if the specification contains valid, processable parameters.
double sampleRate
Sample rate in Hz.