34#include "../Core/RingBuffer.h"
35#include "../Core/Oscillator.h"
36#include "../Core/DryWetMixer.h"
37#include "../Core/DspMath.h"
38#include "../Core/AudioSpec.h"
39#include "../Core/AudioBuffer.h"
40#include "../Core/StateBlob.h"
122 const int nS = buffer.getNumSamples();
129 for (
int ch = 0; ch < buffer.getNumChannels(); ++ch)
131 T* d = buffer.getChannel(ch);
132 for (
int i = 0; i < nS; ++i)
133 if (!std::isfinite(d[i])) d[i] = T(0);
137 T targetRate =
rate_.load(std::memory_order_relaxed);
138 T targetDepth =
depthMs_.load(std::memory_order_relaxed);
140 T fbVal =
feedback_.load(std::memory_order_relaxed);
141 T mixVal =
mix_.load(std::memory_order_relaxed);
142 int nVoices =
numVoices_.load(std::memory_order_relaxed);
143 bool autoD =
autoDepth_.load(std::memory_order_relaxed);
153 const auto wf =
lfoWaveform_.load(std::memory_order_relaxed);
156 lfos_[ch][v].setWaveform(wf);
160 T currentSpread =
stereoSpread_.load(std::memory_order_relaxed);
161 if (std::abs(currentSpread -
lastSpread_) > T(0.001))
168 T targetCenterSamples = targetCenter *
static_cast<T
>(
spec_.
sampleRate) / T(1000);
169 T targetDepthSamples = targetDepth *
static_cast<T
>(
spec_.
sampleRate) / T(1000);
171 if (autoD && targetRate > T(0.01))
172 targetDepthSamples /= std::sqrt(targetRate);
176 targetDepthSamples = std::min(targetDepthSamples,
177 std::max(T(0), targetCenterSamples - T(2)));
180 for (
int ch = 0; ch < nCh; ++ch)
181 for (
int v = 0; v < nVoices; ++v)
182 lfos_[ch][v].setFrequency(targetRate);
184 T voiceNorm = T(1) / std::sqrt(
static_cast<T
>(nVoices));
190 T smoothCoeff = std::min(T(1), T(240) /
static_cast<T
>(
spec_.
sampleRate));
193 for (
int ch = 0; ch < nCh; ++ch)
195 auto* channelData = buffer.getChannel(ch);
202 for (
int i = 0; i < nS; ++i)
205 centerSamples += (targetCenterSamples - centerSamples) * smoothCoeff;
206 depthSamples += (targetDepthSamples - depthSamples) * smoothCoeff;
208 T input = channelData[i];
214 ring.push(input + feedbackSig);
219 for (
int v = 0; v < nVoices; ++v)
221 T lfoVal =
lfos_[ch][v].getNextSample();
222 T rawDelay = centerSamples + lfoVal * depthSamples;
227 wetRaw += ring.readInterpolated(delay);
230 fbState_[ch] = wetRaw /
static_cast<T
>(nVoices);
231 channelData[i] = wetRaw * voiceNorm;
242 mixer_.mixWet(buffer, mixVal);
267 if (!std::isfinite(hz))
return;
268 rate_.store(std::clamp(hz, T(0.01), T(20)), std::memory_order_relaxed);
277 if (!std::isfinite(ms))
return;
278 depthMs_.store(std::clamp(ms, T(0), T(20)), std::memory_order_relaxed);
288 if (!std::isfinite(dryWet))
return;
289 mix_.store(std::clamp(dryWet, T(0), T(1)), std::memory_order_relaxed);
309 if (!std::isfinite(amount))
return;
310 feedback_.store(std::clamp(amount, T(-0.99), T(0.99)), std::memory_order_relaxed);
319 if (!std::isfinite(ms))
return;
320 centerDelayMs_.store(std::clamp(ms, T(0.1), T(30)), std::memory_order_relaxed);
329 if (!std::isfinite(amount))
return;
330 stereoSpread_.store(std::clamp(amount, T(0), T(1)), std::memory_order_relaxed);
349 const int w = std::clamp(
static_cast<int>(wf), 0,
352 std::memory_order_relaxed);
358 [[nodiscard]] std::vector<uint8_t>
getState()
const
361 w.
write(
"rate",
rate_.load(std::memory_order_relaxed));
362 w.
write(
"depthMs",
depthMs_.load(std::memory_order_relaxed));
363 w.
write(
"mix",
mix_.load(std::memory_order_relaxed));
370 static_cast<int32_t
>(
lfoWaveform_.load(std::memory_order_relaxed)));
388 r.
read(
"waveform", 0)));
399 int nv =
numVoices_.load(std::memory_order_relaxed);
405 T chOffset = (ch > 0 && spreadVal > T(0))
406 ?
static_cast<T
>(ch) * spreadVal / (T(2) *
static_cast<T
>(
kMaxChannels))
411 T basePhase =
static_cast<T
>(v) /
static_cast<T
>(nv);
412 T finalPhase = basePhase + chOffset;
413 finalPhase -= std::floor(finalPhase);
414 lfos_[ch][v].setPhase(finalPhase);
425 std::atomic<T>
mix_ { T(0.5) };
Non-owning view over audio channel data.
Multi-voice chorus/flanger with true stereo spread and smooth parameter handling.
void setModWaveform(typename Oscillator< T >::Waveform wf) noexcept
Sets the oscillator waveform for all voices.
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
std::atomic< bool > autoDepth_
void setMix(T dryWet) noexcept
Sets the wet/dry mix ratio.
std::atomic< typename Oscillator< T >::Waveform > lfoWaveform_
std::atomic< int > numVoices_
std::atomic< T > depthMs_
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an audio block in-place.
std::array< RingBuffer< T >, kMaxChannels > delayLines_
void setStereoSpread(T amount) noexcept
Sets the stereo spread amount.
std::atomic< bool > waveformDirty_
void reset() noexcept
Resets delay lines and LFO phases.
void setVoices(int count) noexcept
Sets the number of active voices per channel.
void setCenterDelay(T ms) noexcept
Sets the base delay time.
std::array< T, kMaxChannels > fbState_
std::array< std::array< Oscillator< T >, kMaxVoices >, kMaxChannels > lfos_
static constexpr int kMaxVoices
std::atomic< T > stereoSpread_
void setFeedback(T amount) noexcept
Sets the feedback gain.
void prepare(const AudioSpec &spec)
Prepares the chorus for processing, allocating delay lines.
std::atomic< T > centerDelayMs_
void setDepthMs(T ms) noexcept
Sets the LFO modulation depth in milliseconds.
std::atomic< bool > lfoPhaseDirty_
static constexpr int kMaxChannels
void setRate(T hz) noexcept
Sets the LFO modulation rate.
void updateLfoPhases() noexcept
Recalculates LFO phases for all channels and voices based on stereo spread.
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
void setAutoDepth(bool enabled) noexcept
Couples depth to rate automatically to prevent pitch artifacts at high speeds.
std::atomic< T > feedback_
Pre-allocated, SIMD-friendly dry/wet blender for real-time audio.
Band-limited oscillator featuring PolyBLEP anti-aliasing and analog-modeled integration.
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 fastTanh(T x) noexcept
Fast tanh approximation using Pade rational function.
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.
int numChannels
Number of audio channels (e.g., 1 = mono, 2 = stereo).
double sampleRate
Sample rate in Hz.