DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
Crossfade.h
Go to the documentation of this file.
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
26#include "../Core/DspMath.h"
27
28#include <algorithm>
29#include <atomic>
30#include <cassert>
31#include <cmath>
32
33namespace dspark {
34
44template <FloatType T>
45class Crossfade final
46{
47public:
49 enum class Curve
50 {
51 Linear,
53 SCurve
54 };
55
56 Crossfade() = default;
57 ~Crossfade() = default;
58
65 void setCurve(Curve curve) noexcept
66 {
67 curve = static_cast<Curve>(std::clamp(static_cast<int>(curve), 0,
68 static_cast<int>(Curve::SCurve)));
69 curve_.store(curve, std::memory_order_relaxed);
70 }
71
81 void setPosition(T position) noexcept
82 {
83 if (!std::isfinite(position)) return;
84 position_.store(std::clamp(position, T(0), T(1)), std::memory_order_relaxed);
85 }
86
91 [[nodiscard]] T getPosition() const noexcept
92 {
93 return position_.load(std::memory_order_relaxed);
94 }
95
107 [[nodiscard]] inline T process(T a, T b) noexcept
108 {
109 refreshGainsFromAtomics();
110 return a * gainA_ + b * gainB_;
111 }
112
125 void process(const T* inputA, const T* inputB, T* output, int numSamples) noexcept
126 {
127 assert(inputA != nullptr && inputB != nullptr && output != nullptr);
128 assert(numSamples > 0);
129
130 const T oldGainA = gainA_;
131 const T oldGainB = gainB_;
132
133 refreshGainsFromAtomics();
134
135 if (oldGainA == gainA_ && oldGainB == gainB_)
136 {
137 // Hot-path: No parameter change. Ideal for SIMD autovectorization.
138 const T gA = gainA_;
139 const T gB = gainB_;
140 for (int i = 0; i < numSamples; ++i)
141 output[i] = inputA[i] * gA + inputB[i] * gB;
142 }
143 else
144 {
145 // Parameter change detected: Apply linear block smoothing (De-zippering)
146 const T invSamples = T(1) / static_cast<T>(numSamples);
147 const T stepA = (gainA_ - oldGainA) * invSamples;
148 const T stepB = (gainB_ - oldGainB) * invSamples;
149
150 T currentA = oldGainA;
151 T currentB = oldGainB;
152
153 for (int i = 0; i < numSamples; ++i)
154 {
155 currentA += stepA;
156 currentB += stepB;
157 output[i] = inputA[i] * currentA + inputB[i] * currentB;
158 }
159 }
160 }
161
174 void processAutomated(const T* inputA, const T* inputB,
175 const T* positions, T* output, int numSamples) noexcept
176 {
177 assert(inputA && inputB && positions && output);
178 assert(numSamples > 0);
179
180 Curve curv = curve_.load(std::memory_order_relaxed);
181 T gA = gainA_, gB = gainB_;
182
183 for (int i = 0; i < numSamples; ++i)
184 {
185 // min/max with this argument order also resolves a NaN in the
186 // automation buffer to 0 (100% A) instead of poisoning the output.
187 T pos = std::min(T(1), std::max(T(0), positions[i]));
188 computeGains(curv, pos, gA, gB);
189 output[i] = inputA[i] * gA + inputB[i] * gB;
190 }
191
192 // Update internal state to match the end of the automation block.
193 // NOTE: We DO NOT write back to the atomic position_ to avoid Data Races
194 // with the GUI thread. We only update the audio-thread local cache.
195 lastPos_ = std::min(T(1), std::max(T(0), positions[numSamples - 1]));
196 lastCurve_ = curv;
197 gainA_ = gA;
198 gainB_ = gB;
199 }
200
202 [[nodiscard]] T getGainA() const noexcept { return gainA_; }
203
205 [[nodiscard]] T getGainB() const noexcept { return gainB_; }
206
207private:
211 static inline void computeGains(Curve curve, T pos, T& gA, T& gB) noexcept
212 {
213 switch (curve)
214 {
215 case Curve::Linear:
216 gA = T(1) - pos;
217 gB = pos;
218 break;
219
221 // SIMD Optimization: Replaced std::cos/sin with hardware-accelerated std::sqrt.
222 // sqrt(1-x)^2 + sqrt(x)^2 = (1-x) + x = 1.0 (Constant Power).
223 // Avoids heavy trigonometric penalties while maintaining identical energetic response.
224 gA = std::sqrt(T(1) - pos);
225 gB = std::sqrt(pos);
226 break;
227
228 case Curve::SCurve:
229 {
230 T t = pos * pos * (T(3) - T(2) * pos);
231 gA = T(1) - t;
232 gB = t;
233 break;
234 }
235
236 default:
237 // Unreachable through the clamped setter; keeps the out
238 // parameters defined if the enum ever grows without a case.
239 gA = T(1) - pos;
240 gB = pos;
241 break;
242 }
243 }
244
248 inline void refreshGainsFromAtomics() noexcept
249 {
250 T pos = position_.load(std::memory_order_relaxed);
251 Curve curv = curve_.load(std::memory_order_relaxed);
252
253 if (pos != lastPos_ || curv != lastCurve_)
254 {
255 computeGains(curv, pos, gainA_, gainB_);
256 lastPos_ = pos;
257 lastCurve_ = curv;
258 }
259 }
260
261 // Communication: GUI -> Audio Thread
262 std::atomic<Curve> curve_ { Curve::EqualPower };
263 std::atomic<T> position_ { T(0) };
264
265 // Audio Thread Local State (Cache)
266 T gainA_ = T(1);
267 T gainB_ = T(0);
268 T lastPos_ = T(-1); // Sentinel to force initial compute
269 Curve lastCurve_ = Curve::EqualPower;
270};
271
272} // namespace dspark
Artifact-free, SIMD-friendly crossfader for two audio signals.
Definition Crossfade.h:46
void processAutomated(const T *inputA, const T *inputB, const T *positions, T *output, int numSamples) noexcept
Processes crossfading using a per-sample automation buffer.
Definition Crossfade.h:174
void setPosition(T position) noexcept
Sets the target crossfade blend position.
Definition Crossfade.h:81
Curve
Defines the amplitude response of the crossfade transition.
Definition Crossfade.h:50
@ Linear
Linear interpolation. Constant amplitude, drops power at center.
@ EqualPower
Equal power interpolation (hardware SQRT). Constant power, no volume drop.
@ SCurve
Smoothstep interpolation. Slower progression at extremes.
T process(T a, T b) noexcept
Crossfades between two individual samples.
Definition Crossfade.h:107
Crossfade()=default
T getGainB() const noexcept
Gets the current internal gain multiplier for signal B.
Definition Crossfade.h:205
T getPosition() const noexcept
Retrieves the last requested position.
Definition Crossfade.h:91
T getGainA() const noexcept
Gets the current internal gain multiplier for signal A.
Definition Crossfade.h:202
void setCurve(Curve curve) noexcept
Sets the crossfade curve type. Thread-safe. Can be called from the GUI thread.
Definition Crossfade.h:65
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.
Definition Crossfade.h:125
~Crossfade()=default
Main namespace for the DSPark framework.