42#include "../Core/AudioSpec.h"
43#include "../Core/DspMath.h"
89 if (!(sampleRate > 0.0) || !std::isfinite(sampleRate))
return;
90 maxPosStep_ =
static_cast<T
>(1.0 / (sampleRate * kMinGlideSeconds));
120 curve =
static_cast<Curve>(std::clamp(
static_cast<int>(curve), 0,
122 curve_.store(curve, std::memory_order_relaxed);
135 if (!std::isfinite(position))
return;
136 position_.store(std::clamp(position, T(0), T(1)), std::memory_order_relaxed);
145 return position_.load(std::memory_order_relaxed);
162 position = std::min(T(1), std::max(T(0), position));
163 fillGains(curve, &position, &gainA, &gainB, 1);
178 [[nodiscard]]
inline T
process(T a, T b)
noexcept
181 const T target = position_.load(std::memory_order_relaxed);
182 const Curve curve = curve_.load(std::memory_order_relaxed);
183 if (target != curPos_ || curve != lastCurve_)
185 curPos_ = maxPosStep_ > T(0) ?
moveTowards(curPos_, target, maxPosStep_) : target;
187 gainsFor(curve, curPos_, gainA_, gainB_);
190 return a * gainA_ + b * gainB_;
207 void process(
const T* inputA,
const T* inputB, T* output,
int numSamples)
noexcept
209 assert(inputA !=
nullptr && inputB !=
nullptr && output !=
nullptr);
210 assert(numSamples > 0);
211 if (numSamples <= 0)
return;
214 const T target = position_.load(std::memory_order_relaxed);
215 const Curve curve = curve_.load(std::memory_order_relaxed);
217 if (target == curPos_ && curve == lastCurve_)
222 for (
int i = 0; i < numSamples; ++i)
223 output[i] = inputA[i] * gA + inputB[i] * gB;
227 const T startPos = curPos_;
228 const Curve oldCurve = lastCurve_;
229 const bool prepared = maxPosStep_ > T(0);
230 const T step = prepared ? maxPosStep_
231 : std::abs(target - startPos) /
static_cast<T
>(numSamples);
232 const T invSamples = T(1) /
static_cast<T
>(numSamples);
234 T pos[kChunk], gA[kChunk], gB[kChunk], oldA[kChunk], oldB[kChunk];
235 for (
int start = 0; start < numSamples; start += kChunk)
237 const int n = std::min(kChunk, numSamples - start);
238 for (
int i = 0; i < n; ++i)
239 pos[i] =
moveTowards(startPos, target, step *
static_cast<T
>(start + i + 1));
240 fillGains(curve, pos, gA, gB, n);
242 if (curve != oldCurve)
244 fillGains(oldCurve, pos, oldA, oldB, n);
245 for (
int i = 0; i < n; ++i)
247 const T w =
static_cast<T
>(start + i + 1) * invSamples;
248 gA[i] = oldA[i] + w * (gA[i] - oldA[i]);
249 gB[i] = oldB[i] + w * (gB[i] - oldB[i]);
253 for (
int i = 0; i < n; ++i)
254 output[start + i] = inputA[start + i] * gA[i] + inputB[start + i] * gB[i];
258 curPos_ = prepared ?
moveTowards(startPos, target, step *
static_cast<T
>(numSamples))
261 gainsFor(curve, curPos_, gainA_, gainB_);
280 const T* positions, T* output,
int numSamples)
noexcept
282 assert(inputA && inputB && positions && output);
283 assert(numSamples > 0);
284 if (numSamples <= 0)
return;
286 const Curve curve = curve_.load(std::memory_order_relaxed);
288 T pos[kChunk], gA[kChunk], gB[kChunk];
289 for (
int start = 0; start < numSamples; start += kChunk)
291 const int n = std::min(kChunk, numSamples - start);
294 for (
int i = 0; i < n; ++i)
295 pos[i] = std::min(T(1), std::max(T(0), positions[start + i]));
296 fillGains(curve, pos, gA, gB, n);
297 for (
int i = 0; i < n; ++i)
298 output[start + i] = inputA[start + i] * gA[i] + inputB[start + i] * gB[i];
304 needsSettle_ =
false;
305 curPos_ = std::min(T(1), std::max(T(0), positions[numSamples - 1]));
307 gainsFor(curve, curPos_, gainA_, gainB_);
316 return publishedGainA_.load(std::memory_order_relaxed);
324 return publishedGainB_.load(std::memory_order_relaxed);
328 static constexpr int kChunk = 64;
329 static constexpr double kMinGlideSeconds = 0.02;
339 static inline void equalPowerGains(T t, T& gA, T& gB)
noexcept
341 const bool upper = t > T(0.5);
342 const T u = upper ? T(1) - t : t;
343 const T x = halfPi<T> * u;
345 const T s = x * (T(1) + x2 * (T(-1.0 / 6.0) + x2 * (T(1.0 / 120.0)
346 + x2 * (T(-1.0 / 5040.0) + x2 * T(1.0 / 362880.0)))));
347 const T c = T(1) + x2 * (T(-0.5) + x2 * (T(1.0 / 24.0) + x2 * (T(-1.0 / 720.0)
348 + x2 * (T(1.0 / 40320.0) + x2 * T(-1.0 / 3628800.0)))));
357 static void fillGains(
Curve curve,
const T* pos, T* gA, T* gB,
int n)
noexcept
362 for (
int i = 0; i < n; ++i)
363 equalPowerGains(pos[i], gA[i], gB[i]);
367 for (
int i = 0; i < n; ++i)
369 const T t = pos[i] * pos[i] * (T(3) - T(2) * pos[i]);
379 for (
int i = 0; i < n; ++i)
381 gA[i] = T(1) - pos[i];
389 void settle() noexcept
391 curPos_ = position_.load(std::memory_order_relaxed);
392 lastCurve_ = curve_.load(std::memory_order_relaxed);
393 gainsFor(lastCurve_, curPos_, gainA_, gainB_);
398 void settleIfFresh() noexcept
400 if (!needsSettle_)
return;
401 needsSettle_ =
false;
406 void publishGains() noexcept
408 publishedGainA_.store(gainA_, std::memory_order_relaxed);
409 publishedGainB_.store(gainB_, std::memory_order_relaxed);
414 std::atomic<T> position_ { T(0) };
423 T maxPosStep_ = T(0);
424 bool needsSettle_ =
true;
425 std::atomic<T> publishedGainA_ { T(1) };
426 std::atomic<T> publishedGainB_ { T(0) };
Artifact-free, SIMD-friendly crossfader for two audio signals.
void processAutomated(const T *inputA, const T *inputB, const T *positions, T *output, int numSamples) noexcept
Processes crossfading using a per-sample automation buffer.
void prepare(double sampleRate) noexcept
Enables the time-based glide (optional).
void setPosition(T position) noexcept
Sets the target crossfade blend position.
static void gainsFor(Curve curve, T position, T &gainA, T &gainB) noexcept
The gains a curve applies at a position.
Curve
Defines the amplitude response of the crossfade transition.
@ Linear
Linear interpolation. Constant amplitude, drops power at center.
@ EqualPower
Sine/cosine law. Constant power, no volume drop, smooth at both ends.
@ SCurve
Smoothstep interpolation. Slower progression at extremes.
T process(T a, T b) noexcept
Crossfades two individual samples.
T getGainB() const noexcept
Gets the current internal gain multiplier for signal B. Safe from any thread: loads a published atomi...
T getPosition() const noexcept
Retrieves the last requested position.
T getGainA() const noexcept
Gets the current internal gain multiplier for signal A. Safe from any thread: loads a published atomi...
void setCurve(Curve curve) noexcept
Sets the crossfade curve type. Thread-safe. Can be called from the GUI thread. A curve change is blen...
void reset() noexcept
Lands the applied position and curve on the requested ones, with no glide (for example at transport s...
void process(const T *inputA, const T *inputB, T *output, int numSamples) noexcept
Crossfades two audio buffers into an output buffer with automatic parameter smoothing.
void prepare(const AudioSpec &spec) noexcept
Enables the time-based glide from an audio spec (see prepare(double)).
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.
Describes the audio environment for a DSP processor.