25#include "../Core/AudioBuffer.h"
26#include "../Core/AudioSpec.h"
27#include "../Core/DenormalGuard.h"
28#include "../Core/DspMath.h"
29#include "../Core/Phasor.h"
30#include "../Core/Smoothers.h"
31#include "../Core/StateBlob.h"
77 T initialRate = rate_.load(std::memory_order_relaxed);
78 currentRate_ = initialRate;
80 for (
int ch = 0; ch < kMaxChannels; ++ch)
82 phasors_[ch].prepare(sampleRate_);
83 phasors_[ch].setFrequency(initialRate);
86 depthSmoother_.
reset(sampleRate_, kDepthRampMs,
87 static_cast<float>(depth_.load(std::memory_order_relaxed)));
89 isStereoActive_ = stereo_.load(std::memory_order_relaxed);
90 if (isStereoActive_ && numChannels_ >= 2)
91 phasors_[1].setPhase(T(0.5));
100 const int numCh = std::min(buffer.getNumChannels(), numChannels_);
101 const int numSamples = buffer.getNumSamples();
103 if (numCh == 0 || numSamples == 0)
109 updateInternalState();
111 Shape currentShape = shape_.load(std::memory_order_relaxed);
114 switch (currentShape)
116 case Shape::Sine: processBlockShape<Shape::Sine>(buffer, numCh, numSamples);
break;
117 case Shape::Triangle: processBlockShape<Shape::Triangle>(buffer, numCh, numSamples);
break;
118 case Shape::Square: processBlockShape<Shape::Square>(buffer, numCh, numSamples);
break;
131 phasors_[1].setPhase(T(0.5));
141 if (!std::isfinite(hz))
return;
142 rate_.store(std::max(T(0), hz), std::memory_order_relaxed);
152 if (!std::isfinite(depth))
return;
153 depth_.store(std::clamp(depth, T(0), T(1)), std::memory_order_relaxed);
162 const int s = std::clamp(
static_cast<int>(shape), 0,
164 shape_.store(
static_cast<Shape>(s), std::memory_order_relaxed);
171 void setStereo(
bool enabled)
noexcept { stereo_.store(enabled, std::memory_order_relaxed); }
173 [[nodiscard]] T
getRate() const noexcept {
return rate_.load(std::memory_order_relaxed); }
174 [[nodiscard]] T
getDepth() const noexcept {
return depth_.load(std::memory_order_relaxed); }
175 [[nodiscard]]
Shape getShape() const noexcept {
return shape_.load(std::memory_order_relaxed); }
176 [[nodiscard]]
bool isStereo() const noexcept {
return stereo_.load(std::memory_order_relaxed); }
180 [[nodiscard]] std::vector<uint8_t>
getState()
const
183 w.
write(
"rate", rate_.load(std::memory_order_relaxed));
184 w.
write(
"depth", depth_.load(std::memory_order_relaxed));
185 w.
write(
"shape",
static_cast<int32_t
>(shape_.load(std::memory_order_relaxed)));
186 w.
write(
"stereo", stereo_.load(std::memory_order_relaxed));
203 static constexpr int kMaxChannels = 2;
204 static constexpr float kDepthRampMs = 5.0f;
205 static constexpr T kSquareSlewTime = T(0.02);
207 double sampleRate_ = 44100.0;
208 int numChannels_ = 2;
210 std::atomic<T> rate_{ T(4) };
211 std::atomic<T> depth_{ T(0.5) };
213 std::atomic<bool> stereo_{
false };
216 T currentRate_ = T(4);
217 bool isStereoActive_ =
false;
219 Smoothers::LinearSmoother depthSmoother_;
220 Phasor<T> phasors_[kMaxChannels]{};
225 inline void updateInternalState() noexcept
227 T targetRate = rate_.load(std::memory_order_relaxed);
228 if (targetRate != currentRate_)
230 currentRate_ = targetRate;
231 phasors_[0].setFrequency(currentRate_);
232 phasors_[1].setFrequency(currentRate_);
235 bool targetStereo = stereo_.load(std::memory_order_relaxed);
236 if (targetStereo != isStereoActive_)
238 isStereoActive_ = targetStereo;
239 if (isStereoActive_) {
241 T newPhase = std::fmod(phasors_[0].getPhase() + T(0.5), T(1));
242 phasors_[1].setPhase(newPhase);
244 phasors_[1].setPhase(phasors_[0].getPhase());
248 depthSmoother_.setTargetValue(
static_cast<float>(depth_.load(std::memory_order_relaxed)));
255 [[nodiscard]]
inline T computeShape(T phase)
const noexcept
260 return fastSin(phase * twoPi<T>);
265 if (t < T(1))
return t;
266 if (t < T(3))
return T(2) - t;
272 if (phase < kSquareSlewTime)
return T(-1) + (phase / kSquareSlewTime) * T(2);
273 if (phase < T(0.5))
return T(1);
274 if (phase < T(0.5) + kSquareSlewTime)
return T(1) - ((phase - T(0.5)) / kSquareSlewTime) * T(2);
283 void processBlockShape(AudioBufferView<T>& buffer,
int numCh,
int numSamples)
noexcept
285 T*
const channelL = buffer.getChannel(0);
286 T*
const channelR = (numCh > 1) ? buffer.getChannel(1) :
nullptr;
288 for (
int i = 0; i < numSamples; ++i)
290 T depthVal =
static_cast<T
>(depthSmoother_.getNextValue());
291 T phaseL = phasors_[0].advance();
293 T modL = computeShape<S>(phaseL);
294 T gainL = T(1) - depthVal * (T(1) - modL) * T(0.5);
296 channelL[i] *= gainL;
298 if (channelR !=
nullptr)
302 T phaseR = phasors_[1].advance();
303 T modR = computeShape<S>(phaseR);
304 T gainR = T(1) - depthVal * (T(1) - modR) * T(0.5);
305 channelR[i] *= gainR;
310 (void)phasors_[1].advance();
311 channelR[i] *= gainL;
316 for (
int ch = 2; ch < numCh; ++ch)
318 buffer.getChannel(ch)[i] *= gainL;
Non-owning view over audio channel data.
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
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.
LFO-driven amplitude modulation with stereo auto-pan option.
T getRate() const noexcept
bool isStereo() const noexcept
void prepare(const AudioSpec &spec)
Prepares the tremolo processor and allocates internal states.
void setDepth(T depth) noexcept
Sets the modulation depth. Thread-safe.
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
void setShape(Shape shape) noexcept
Sets the LFO waveform shape. Thread-safe.
@ Triangle
Linear modulation, sharper peaks.
@ Sine
Classic smooth tremolo (opto-isolator style)
@ Square
Slew-rate limited gating effect (analog-style, click-free)
Shape getShape() const noexcept
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an audio block in-place with SIMD-friendly dispatch.
T getDepth() const noexcept
void setRate(T hz) noexcept
Sets the LFO rate. Thread-safe (can be called from UI thread).
void reset() noexcept
Hard-resets the internal LFO phase to zero.
void setStereo(bool enabled) noexcept
Enables auto-pan by offsetting the right channel LFO phase by 180 degrees.
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").
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.
void reset(double sampleRate, float rampTimeMilliseconds, float initialValue=0.0f) noexcept