33#include "../Core/AudioBuffer.h"
34#include "../Core/AudioSpec.h"
35#include "../Core/DenormalGuard.h"
36#include "../Core/DspMath.h"
37#include "../Core/Phasor.h"
38#include "../Core/StateBlob.h"
87 if (!spec.isValid())
return;
89 phasor_.prepare(spec.sampleRate);
91 const T initialFreq = frequency_.load(std::memory_order_relaxed);
92 const T initialMix = mix_.load(std::memory_order_relaxed);
94 phasor_.setFrequency(initialFreq);
95 currentFreq_ = initialFreq;
96 currentMix_ = initialMix;
97 mixMaxStep_ =
static_cast<T
>(1.0 / (spec.sampleRate * 0.02));
98 numChannels_ = spec.numChannels;
107 const int numCh = std::min(buffer.getNumChannels(), numChannels_);
108 const int numSamples = buffer.getNumSamples();
109 if (numSamples <= 0 || numCh <= 0)
return;
117 const T targetFreq = frequency_.load(std::memory_order_relaxed);
118 const T targetMix = mix_.load(std::memory_order_relaxed);
119 const T soarVal = soar_.load(std::memory_order_relaxed);
120 const auto modeVal = mode_.load(std::memory_order_relaxed);
127 const T invSamples = T(1) /
static_cast<T
>(numSamples);
128 const T freqStep = (targetFreq - currentFreq_) * invSamples;
129 const T mixStart = currentMix_;
132 constexpr int CHUNK_SIZE = 64;
133 std::array<T, CHUNK_SIZE> carrierChunk;
134 std::array<T, CHUNK_SIZE> mixChunk;
136 const T
twoPi = T(2) *
static_cast<T
>(std::numbers::pi);
138 for (
int start = 0; start < numSamples; start += CHUNK_SIZE)
140 const int chunkLen = std::min(CHUNK_SIZE, numSamples - start);
143 for (
int i = 0; i < chunkLen; ++i)
145 currentFreq_ += freqStep;
147 phasor_.setFrequency(currentFreq_);
148 T phase = phasor_.advance();
154 mixMaxStep_ *
static_cast<T
>(start + i + 1));
160 for (
int ch = 0; ch < numCh; ++ch)
162 T* data = buffer.getChannel(ch) + start;
164 for (
int i = 0; i < chunkLen; ++i)
166 const T dry = data[i];
167 const T carrier = carrierChunk[i];
169 const T absIn = std::abs(dry);
170 const T absCarrier = std::abs(carrier);
173 const T gm = std::sqrt(absIn * absCarrier + soarVal * absIn);
176 const T wet = gm * std::copysign(T(1), dry * carrier);
178 data[i] = dry + (wet - dry) * mixChunk[i];
184 for (
int ch = 0; ch < numCh; ++ch)
186 T* data = buffer.getChannel(ch) + start;
188 for (
int i = 0; i < chunkLen; ++i)
190 const T dry = data[i];
191 const T wet = dry * carrierChunk[i];
192 data[i] = dry + (wet - dry) * mixChunk[i];
201 currentFreq_ = targetFreq;
202 currentMix_ =
moveTowards(mixStart, targetMix, mixMaxStep_ *
static_cast<T
>(numSamples));
206 void reset() noexcept { phasor_.reset(); }
216 if (!std::isfinite(hz))
return;
217 frequency_.store(hz, std::memory_order_relaxed);
227 if (!std::isfinite(mix))
return;
228 mix_.store(std::clamp(mix, T(0), T(1)), std::memory_order_relaxed);
241 const int v = std::clamp(
static_cast<int>(m),
244 mode_.store(
static_cast<Mode>(v), std::memory_order_relaxed);
261 if (!std::isfinite(amount))
return;
262 soar_.store(std::max(amount, T(0)), std::memory_order_relaxed);
265 [[nodiscard]] T
getFrequency() const noexcept {
return frequency_.load(std::memory_order_relaxed); }
266 [[nodiscard]] T
getMix() const noexcept {
return mix_.load(std::memory_order_relaxed); }
267 [[nodiscard]]
Mode getMode() const noexcept {
return mode_.load(std::memory_order_relaxed); }
268 [[nodiscard]] T
getSoar() const noexcept {
return soar_.load(std::memory_order_relaxed); }
271 [[nodiscard]] std::vector<uint8_t>
getState()
const
276 w.
write(
"frequency",
static_cast<float>(frequency_.load(std::memory_order_relaxed)));
277 w.
write(
"mix",
static_cast<float>(mix_.load(std::memory_order_relaxed)));
278 w.
write(
"mode",
static_cast<int32_t
>(mode_.load(std::memory_order_relaxed)));
279 w.
write(
"soar",
static_cast<float>(soar_.load(std::memory_order_relaxed)));
296 int numChannels_ = 0;
299 std::atomic<T> frequency_ { T(440) };
300 std::atomic<T> mix_ { T(1) };
302 std::atomic<T> soar_ { T(0) };
305 T currentFreq_ { T(440) };
306 T currentMix_ { T(1) };
307 T mixMaxStep_ { T(1.0 / 960.0) };
Non-owning view over audio channel data.
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
Signal x carrier ring modulation with mix control and zero-latency smoothing.
Mode getMode() const noexcept
void reset() noexcept
Resets the internal phase of the carrier oscillator.
void processBlock(AudioBufferView< T > buffer) noexcept
Processes a block of audio in-place.
T getSoar() const noexcept
void setMix(T mix) noexcept
Sets the target dry/wet mix. Smoothed internally.
T getFrequency() const noexcept
Mode
Modulation mathematical mode.
@ GeometricMean
Geometric-mean mode: sqrt(|in|*|carrier|) * sign - richer, more musical.
@ Classic
Standard multiplication (sum & difference frequencies).
void prepare(const AudioSpec &spec) noexcept
Prepares the modulator for audio processing.
T getMix() const noexcept
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
void setSoar(T amount) noexcept
Sets the soar threshold for Geometric Mean mode.
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
void setMode(Mode m) noexcept
Sets the modulation mode.
void setFrequency(T hz) noexcept
Sets the target carrier frequency. Smoothed internally.
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 moveTowards(T from, T to, T maxDelta) noexcept
Moves a value toward a target by at most a given distance.
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").
constexpr T twoPi
2 * Pi (6.28318...).
Describes the audio environment for a DSP processor.