79 T initialRate = rate_.load(std::memory_order_relaxed);
80 currentRate_ = initialRate;
81 lfo_.prepare(sampleRate_);
82 lfo_.setFrequency(initialRate);
84 depthSmoother_.
reset(sampleRate_, kDepthRampMs,
85 static_cast<float>(depth_.load(std::memory_order_relaxed)));
88 activeShape_ = shape_.load(std::memory_order_relaxed);
89 offsetR_ = stereo_.load(std::memory_order_relaxed) ? T(0.5) : T(0);
90 fadeLength_ = std::max(1,
static_cast<int>(sampleRate_ * (kFadeMs / 1000.0)));
100 const int numCh = std::min(buffer.getNumChannels(), numChannels_);
101 const int numSamples = buffer.getNumSamples();
103 if (numCh == 0 || numSamples == 0)
109 updateInternalState();
115 if (fadeRemaining_ > 0)
117 start = std::min(numSamples, fadeRemaining_);
118 processFade(buffer, numCh, start);
120 if (start == numSamples)
return;
123 switch (activeShape_)
125 case Shape::Sine: processBlockShape<Shape::Sine>(buffer, numCh, start, numSamples);
break;
126 case Shape::Triangle: processBlockShape<Shape::Triangle>(buffer, numCh, start, numSamples);
break;
127 case Shape::Square: processBlockShape<Shape::Square>(buffer, numCh, start, numSamples);
break;
148 if (!std::isfinite(hz))
return;
149 rate_.store(std::max(T(0), hz), std::memory_order_relaxed);
159 if (!std::isfinite(depth))
return;
160 depth_.store(std::clamp(depth, T(0), T(1)), std::memory_order_relaxed);
169 const int s = std::clamp(
static_cast<int>(shape), 0,
171 shape_.store(
static_cast<Shape>(s), std::memory_order_relaxed);
178 void setStereo(
bool enabled)
noexcept { stereo_.store(enabled, std::memory_order_relaxed); }
180 [[nodiscard]] T
getRate() const noexcept {
return rate_.load(std::memory_order_relaxed); }
181 [[nodiscard]] T
getDepth() const noexcept {
return depth_.load(std::memory_order_relaxed); }
182 [[nodiscard]]
Shape getShape() const noexcept {
return shape_.load(std::memory_order_relaxed); }
183 [[nodiscard]]
bool isStereo() const noexcept {
return stereo_.load(std::memory_order_relaxed); }
187 [[nodiscard]] std::vector<uint8_t>
getState()
const
190 w.
write(
"rate", rate_.load(std::memory_order_relaxed));
191 w.
write(
"depth", depth_.load(std::memory_order_relaxed));
192 w.
write(
"shape",
static_cast<int32_t
>(shape_.load(std::memory_order_relaxed)));
193 w.
write(
"stereo", stereo_.load(std::memory_order_relaxed));
210 static constexpr float kDepthRampMs = 5.0f;
211 static constexpr double kFadeMs = 5.0;
212 static constexpr T kSquareSlewTime = T(0.02);
214 double sampleRate_ = 44100.0;
215 int numChannels_ = 2;
217 std::atomic<T> rate_{ T(4) };
218 std::atomic<T> depth_{ T(0.5) };
220 std::atomic<bool> stereo_{
false };
223 T currentRate_ = T(4);
227 T fadeOffsetR_ = T(0);
229 int fadeRemaining_ = 0;
231 Smoothers::LinearSmoother depthSmoother_;
237 inline void updateInternalState() noexcept
239 T targetRate = rate_.load(std::memory_order_relaxed);
240 if (targetRate != currentRate_)
242 currentRate_ = targetRate;
243 lfo_.setFrequency(currentRate_);
246 const Shape shape = shape_.load(std::memory_order_relaxed);
247 const T offset = stereo_.load(std::memory_order_relaxed) ? T(0.5) : T(0);
248 if (shape != activeShape_ || offset != offsetR_)
251 fadeShape_ = activeShape_;
252 fadeOffsetR_ = offsetR_;
253 activeShape_ = shape;
255 fadeRemaining_ = fadeLength_;
258 depthSmoother_.setTargetValue(
static_cast<float>(depth_.load(std::memory_order_relaxed)));
262 [[nodiscard]]
static inline T wrapPhase(T phase)
noexcept
264 return phase >= T(1) ? phase - T(1) : phase;
268 [[nodiscard]]
inline T evalShape(
Shape shape, T phase)
const noexcept
273 case Shape::Square:
return computeShape<Shape::Square>(phase);
275 default:
return computeShape<Shape::Sine>(phase);
284 void processFade(AudioBufferView<T>& buffer,
int numCh,
int n)
noexcept
286 T*
const channelL = buffer.getChannel(0);
287 T*
const channelR = (numCh > 1) ? buffer.getChannel(1) :
nullptr;
288 const T invLength = T(1) /
static_cast<T
>(fadeLength_);
290 for (
int i = 0; i < n; ++i)
292 const T depthVal =
static_cast<T
>(depthSmoother_.getNextValue());
293 const T phaseL = lfo_.advance();
294 const T w =
static_cast<T
>(fadeLength_ - fadeRemaining_ + 1) * invLength;
297 const T oldL = evalShape(fadeShape_, phaseL);
298 const T modL = oldL + w * (evalShape(activeShape_, phaseL) - oldL);
299 const T gainL = T(1) - depthVal * (T(1) - modL) * T(0.5);
300 channelL[i] *= gainL;
302 if (channelR !=
nullptr)
304 const T oldR = evalShape(fadeShape_, wrapPhase(phaseL + fadeOffsetR_));
305 const T modR = oldR + w * (evalShape(activeShape_, wrapPhase(phaseL + offsetR_)) - oldR);
306 channelR[i] *= T(1) - depthVal * (T(1) - modR) * T(0.5);
309 for (
int ch = 2; ch < numCh; ++ch)
310 buffer.getChannel(ch)[i] *= gainL;
318 [[nodiscard]]
inline T computeShape(T phase)
const noexcept
323 return fastSin(phase * twoPi<T>);
328 if (t < T(1))
return t;
329 if (t < T(3))
return T(2) - t;
335 if (phase < kSquareSlewTime)
return T(-1) + (phase / kSquareSlewTime) * T(2);
336 if (phase < T(0.5))
return T(1);
337 if (phase < T(0.5) + kSquareSlewTime)
return T(1) - ((phase - T(0.5)) / kSquareSlewTime) * T(2);
346 void processBlockShape(AudioBufferView<T>& buffer,
int numCh,
int start,
int end)
noexcept
348 T*
const channelL = buffer.getChannel(0);
349 T*
const channelR = (numCh > 1) ? buffer.getChannel(1) :
nullptr;
350 const bool stereo = offsetR_ != T(0);
352 for (
int i = start; i < end; ++i)
354 T depthVal =
static_cast<T
>(depthSmoother_.getNextValue());
355 T phaseL = lfo_.advance();
357 T modL = computeShape<S>(phaseL);
358 T gainL = T(1) - depthVal * (T(1) - modL) * T(0.5);
360 channelL[i] *= gainL;
362 if (channelR !=
nullptr)
366 T modR = computeShape<S>(wrapPhase(phaseL + offsetR_));
367 T gainR = T(1) - depthVal * (T(1) - modR) * T(0.5);
368 channelR[i] *= gainR;
372 channelR[i] *= gainL;
377 for (
int ch = 2; ch < numCh; ++ch)
379 buffer.getChannel(ch)[i] *= gainL;