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 numChannels_ = spec.numChannels;
106 const int numCh = std::min(buffer.getNumChannels(), numChannels_);
107 const int numSamples = buffer.getNumSamples();
108 if (numSamples <= 0 || numCh <= 0)
return;
116 const T targetFreq = frequency_.load(std::memory_order_relaxed);
117 const T targetMix = mix_.load(std::memory_order_relaxed);
118 const T soarVal = soar_.load(std::memory_order_relaxed);
119 const auto modeVal = mode_.load(std::memory_order_relaxed);
122 const T invSamples = T(1) /
static_cast<T
>(numSamples);
123 const T freqStep = (targetFreq - currentFreq_) * invSamples;
124 const T mixStep = (targetMix - currentMix_) * invSamples;
127 constexpr int CHUNK_SIZE = 64;
128 std::array<T, CHUNK_SIZE> carrierChunk;
129 std::array<T, CHUNK_SIZE> mixChunk;
131 const T
twoPi = T(2) *
static_cast<T
>(std::numbers::pi);
133 for (
int start = 0; start < numSamples; start += CHUNK_SIZE)
135 const int chunkLen = std::min(CHUNK_SIZE, numSamples - start);
138 for (
int i = 0; i < chunkLen; ++i)
140 currentFreq_ += freqStep;
141 currentMix_ += mixStep;
143 phasor_.setFrequency(currentFreq_);
144 T phase = phasor_.advance();
149 mixChunk[i] = currentMix_;
155 for (
int ch = 0; ch < numCh; ++ch)
157 T* data = buffer.getChannel(ch) + start;
159 for (
int i = 0; i < chunkLen; ++i)
161 const T dry = data[i];
162 const T carrier = carrierChunk[i];
164 const T absIn = std::abs(dry);
165 const T absCarrier = std::abs(carrier);
168 const T gm = std::sqrt(absIn * absCarrier + soarVal * absIn);
171 const T wet = gm * std::copysign(T(1), dry * carrier);
173 data[i] = dry + (wet - dry) * mixChunk[i];
179 for (
int ch = 0; ch < numCh; ++ch)
181 T* data = buffer.getChannel(ch) + start;
183 for (
int i = 0; i < chunkLen; ++i)
185 const T dry = data[i];
186 const T wet = dry * carrierChunk[i];
187 data[i] = dry + (wet - dry) * mixChunk[i];
196 currentFreq_ = targetFreq;
197 currentMix_ = targetMix;
201 void reset() noexcept { phasor_.reset(); }
211 if (!std::isfinite(hz))
return;
212 frequency_.store(hz, std::memory_order_relaxed);
222 if (!std::isfinite(mix))
return;
223 mix_.store(std::clamp(mix, T(0), T(1)), std::memory_order_relaxed);
236 const int v = std::clamp(
static_cast<int>(m),
239 mode_.store(
static_cast<Mode>(v), std::memory_order_relaxed);
256 if (!std::isfinite(amount))
return;
257 soar_.store(std::max(amount, T(0)), std::memory_order_relaxed);
260 [[nodiscard]] T
getFrequency() const noexcept {
return frequency_.load(std::memory_order_relaxed); }
261 [[nodiscard]] T
getMix() const noexcept {
return mix_.load(std::memory_order_relaxed); }
262 [[nodiscard]]
Mode getMode() const noexcept {
return mode_.load(std::memory_order_relaxed); }
263 [[nodiscard]] T
getSoar() const noexcept {
return soar_.load(std::memory_order_relaxed); }
266 [[nodiscard]] std::vector<uint8_t>
getState()
const
271 w.
write(
"frequency",
static_cast<float>(frequency_.load(std::memory_order_relaxed)));
272 w.
write(
"mix",
static_cast<float>(mix_.load(std::memory_order_relaxed)));
273 w.
write(
"mode",
static_cast<int32_t
>(mode_.load(std::memory_order_relaxed)));
274 w.
write(
"soar",
static_cast<float>(soar_.load(std::memory_order_relaxed)));
291 int numChannels_ = 0;
294 std::atomic<T> frequency_ { T(440) };
295 std::atomic<T> mix_ { T(1) };
297 std::atomic<T> soar_ { T(0) };
300 T currentFreq_ { T(440) };
301 T currentMix_ { T(1) };
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 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.