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"
124 const int nS = buffer.getNumSamples();
131 for (
int ch = 0; ch < buffer.getNumChannels(); ++ch)
133 T* d = buffer.getChannel(ch);
134 for (
int i = 0; i < nS; ++i)
135 if (!std::isfinite(d[i])) d[i] = T(0);
139 T targetRate =
rate_.load(std::memory_order_relaxed);
140 T targetDepth =
depthMs_.load(std::memory_order_relaxed);
142 T fbVal =
feedback_.load(std::memory_order_relaxed);
143 T mixVal =
mix_.load(std::memory_order_relaxed);
144 int nVoices =
numVoices_.load(std::memory_order_relaxed);
145 bool autoD =
autoDepth_.load(std::memory_order_relaxed);
155 const auto wf =
lfoWaveform_.load(std::memory_order_relaxed);
158 lfos_[ch][v].setWaveform(wf);
162 T currentSpread =
stereoSpread_.load(std::memory_order_relaxed);
163 if (std::abs(currentSpread -
lastSpread_) > T(0.001))
170 T targetCenterSamples = targetCenter *
static_cast<T
>(
spec_.
sampleRate) / T(1000);
171 T targetDepthSamples = targetDepth *
static_cast<T
>(
spec_.
sampleRate) / T(1000);
173 if (autoD && targetRate > T(0.01))
174 targetDepthSamples /= std::sqrt(targetRate);
178 targetDepthSamples = std::min(targetDepthSamples,
179 std::max(T(0), targetCenterSamples - T(2)));
182 for (
int ch = 0; ch < nCh; ++ch)
183 for (
int v = 0; v < nVoices; ++v)
184 lfos_[ch][v].setFrequency(targetRate);
186 T voiceNorm = T(1) / std::sqrt(
static_cast<T
>(nVoices));
192 T smoothCoeff = std::min(T(1), T(240) /
static_cast<T
>(
spec_.
sampleRate));
195 for (
int ch = 0; ch < nCh; ++ch)
197 auto* channelData = buffer.getChannel(ch);
204 for (
int i = 0; i < nS; ++i)
207 centerSamples += (targetCenterSamples - centerSamples) * smoothCoeff;
208 depthSamples += (targetDepthSamples - depthSamples) * smoothCoeff;
210 T input = channelData[i];
216 ring.push(input + feedbackSig);
221 for (
int v = 0; v < nVoices; ++v)
223 T lfoVal =
lfos_[ch][v].getNextSample();
224 T rawDelay = centerSamples + lfoVal * depthSamples;
229 wetRaw += ring.readInterpolated(delay);
232 fbState_[ch] = wetRaw /
static_cast<T
>(nVoices);
233 channelData[i] = wetRaw * voiceNorm;
244 mixer_.mixWet(buffer, mixVal);
269 if (!std::isfinite(hz))
return;
270 rate_.store(std::clamp(hz, T(0.01), T(20)), std::memory_order_relaxed);
279 if (!std::isfinite(ms))
return;
280 depthMs_.store(std::clamp(ms, T(0), T(20)), std::memory_order_relaxed);
290 if (!std::isfinite(dryWet))
return;
291 mix_.store(std::clamp(dryWet, T(0), T(1)), std::memory_order_relaxed);
311 if (!std::isfinite(amount))
return;
312 feedback_.store(std::clamp(amount, T(-0.99), T(0.99)), std::memory_order_relaxed);
321 if (!std::isfinite(ms))
return;
322 centerDelayMs_.store(std::clamp(ms, T(0.1), T(30)), std::memory_order_relaxed);
331 if (!std::isfinite(amount))
return;
332 stereoSpread_.store(std::clamp(amount, T(0), T(1)), std::memory_order_relaxed);
351 const int w = std::clamp(
static_cast<int>(wf), 0,
354 std::memory_order_relaxed);
360 [[nodiscard]] std::vector<uint8_t>
getState()
const
363 w.
write(
"rate",
rate_.load(std::memory_order_relaxed));
364 w.
write(
"depthMs",
depthMs_.load(std::memory_order_relaxed));
365 w.
write(
"mix",
mix_.load(std::memory_order_relaxed));
372 static_cast<int32_t
>(
lfoWaveform_.load(std::memory_order_relaxed)));
390 r.
read(
"waveform", 0)));
401 int nv =
numVoices_.load(std::memory_order_relaxed);
407 T chOffset = (ch > 0 && spreadVal > T(0))
408 ?
static_cast<T
>(ch) * spreadVal / (T(2) *
static_cast<T
>(
kMaxChannels))
413 T basePhase =
static_cast<T
>(v) /
static_cast<T
>(nv);
414 T finalPhase = basePhase + chOffset;
415 finalPhase -= std::floor(finalPhase);
416 lfos_[ch][v].setPhase(finalPhase);
427 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.