DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
Gain.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
19#include "../Core/DspMath.h"
20#include "../Core/SmoothedValue.h"
21#include "../Core/AudioSpec.h"
22#include "../Core/AudioBuffer.h"
23#include "../Core/SimdOps.h"
24#include "../Core/StateBlob.h"
25
26#include <algorithm>
27#include <atomic>
28#include <cmath>
29#include <cstdint>
30#include <vector>
31
32namespace dspark {
33
46template <FloatType T>
47class Gain
48{
49public:
51 {
52 gainSmooth_.reset(T(1));
53 }
54
55 ~Gain() = default;
56
63 void prepare(double sampleRate, double rampTimeMs = 10.0)
64 {
65 if (!(sampleRate > 0.0) || !std::isfinite(sampleRate) || !std::isfinite(rampTimeMs)) return;
66 sampleRate_ = sampleRate;
67 rampTimeMs_.store(std::max(0.0, rampTimeMs), std::memory_order_relaxed);
68
69 gainSmooth_.prepare(sampleRate, rampTimeMs_.load(std::memory_order_relaxed));
71 }
72
76 void prepare(const AudioSpec& spec)
77 {
78 prepare(spec.sampleRate, rampTimeMs_.load(std::memory_order_relaxed));
79 }
80
85 void setGainDb(T dB) noexcept
86 {
87 if (!std::isfinite(dB)) return;
88 targetGainLinear_.store(decibelsToGain(dB), std::memory_order_relaxed);
89 }
90
97 void setGainLinear(T linear) noexcept
98 {
99 if (!std::isfinite(linear)) return;
100 targetGainLinear_.store(std::max(T(0), linear), std::memory_order_relaxed);
101 }
102
104 [[nodiscard]] T getGainDb() const noexcept
105 {
106 return gainToDecibels(targetGainLinear_.load(std::memory_order_relaxed));
107 }
108
110 [[nodiscard]] T getGainLinear() const noexcept
111 {
112 return targetGainLinear_.load(std::memory_order_relaxed);
113 }
114
121 [[nodiscard]] T getCurrentGain() const noexcept
122 {
123 return publishedGain_.load(std::memory_order_relaxed);
124 }
125
129 void setMuted(bool muted) noexcept
130 {
131 muted_.store(muted, std::memory_order_relaxed);
132 }
133
134 [[nodiscard]] bool isMuted() const noexcept { return muted_.load(std::memory_order_relaxed); }
135
140 void setInverted(bool inverted) noexcept
141 {
142 inverted_.store(inverted, std::memory_order_relaxed);
143 }
144
145 [[nodiscard]] bool isInverted() const noexcept { return inverted_.load(std::memory_order_relaxed); }
146
151 void setRampTime(double rampTimeMs) noexcept
152 {
153 if (!std::isfinite(rampTimeMs)) return;
154 rampTimeMs_.store(std::max(0.0, rampTimeMs), std::memory_order_relaxed);
155 rampTimeChanged_.store(true, std::memory_order_release);
156 }
157
163 void processBlock(AudioBufferView<T> buffer) noexcept
164 {
166
167 const int nCh = buffer.getNumChannels();
168 const int nS = buffer.getNumSamples();
169
170 if (nCh == 1)
171 {
172 process(buffer.getChannel(0), nS);
173 }
174 else
175 {
176 T* channels[16];
177 int useCh = nCh < 16 ? nCh : 16;
178 for (int c = 0; c < useCh; ++c)
179 channels[c] = buffer.getChannel(c);
180 process(channels, useCh, nS);
181 }
182 }
183
187 void process(T* data, int numSamples) noexcept
188 {
190 int i = 0;
191
192 // Per-sample ramp (branchless in the hot path)
193 for (; i < numSamples && gainSmooth_.isSmoothing(); ++i)
194 {
195 data[i] *= gainSmooth_.getNextValue();
196 }
197
198 // SIMD bulk path
199 if (i < numSamples)
200 {
201 simd::applyGain(data + i, gainSmooth_.getCurrentValue(), numSamples - i);
202 }
203
204 publishGain();
205 }
206
210 void process(T** channelData, int numChannels, int numSamples) noexcept
211 {
213 int i = 0;
214
215 for (; i < numSamples && gainSmooth_.isSmoothing(); ++i)
216 {
217 const T g = gainSmooth_.getNextValue();
218 for (int ch = 0; ch < numChannels; ++ch)
219 channelData[ch][i] *= g;
220 }
221
222 if (i < numSamples)
223 {
224 const T g = gainSmooth_.getCurrentValue();
225 const int remaining = numSamples - i;
226 for (int ch = 0; ch < numChannels; ++ch)
227 simd::applyGain(channelData[ch] + i, g, remaining);
228 }
229
230 publishGain();
231 }
232
236 void process(const T* input, T* output, int numSamples) noexcept
237 {
239 int i = 0;
240
241 for (; i < numSamples && gainSmooth_.isSmoothing(); ++i)
242 {
243 output[i] = input[i] * gainSmooth_.getNextValue();
244 }
245
246 if (i < numSamples)
247 {
248 const T g = gainSmooth_.getCurrentValue();
249 const int remaining = numSamples - i;
250
251 // Allow out-of-place SIMD by copying then applying in-place SIMD gain
252 std::copy_n(input + i, remaining, output + i);
253 simd::applyGain(output + i, g, remaining);
254 }
255
256 publishGain();
257 }
258
260 void skipRamp() noexcept
261 {
263 }
264
266 void reset() noexcept
267 {
269 }
270
271
273 [[nodiscard]] std::vector<uint8_t> getState() const
274 {
275 StateWriter w(stateId("GAIN"), 1);
276 w.write("gainLinear", static_cast<float>(targetGainLinear_.load(std::memory_order_relaxed)));
277 w.write("rampMs", static_cast<float>(rampTimeMs_.load(std::memory_order_relaxed)));
278 w.write("muted", muted_.load(std::memory_order_relaxed));
279 w.write("inverted", inverted_.load(std::memory_order_relaxed));
280 return w.blob();
281 }
282
284 bool setState(const uint8_t* data, size_t size)
285 {
286 StateReader r(data, size);
287 if (!r.isValid() || r.processorId() != stateId("GAIN")) return false;
288 setGainLinear(static_cast<T>(r.read("gainLinear", 1.0f)));
289 setRampTime(static_cast<double>(r.read("rampMs", 10.0f)));
290 setMuted(r.read("muted", false));
291 setInverted(r.read("inverted", false));
292 return true;
293 }
294
295protected:
303 inline void publishGain() noexcept
304 {
305 publishedGain_.store(gainSmooth_.getCurrentValue(), std::memory_order_relaxed);
306 }
307
312 inline void updateInternalState() noexcept
313 {
314 // exchange (not load+store: a publication landing between the two
315 // would be lost) with acquire, pairing with setRampTime's release.
316 if (rampTimeChanged_.exchange(false, std::memory_order_acquire))
317 gainSmooth_.setRampTime(sampleRate_, rampTimeMs_.load(std::memory_order_relaxed));
318
319 const T baseTarget = muted_.load(std::memory_order_relaxed) ? T(0)
320 : targetGainLinear_.load(std::memory_order_relaxed);
321
322 const T finalTarget = inverted_.load(std::memory_order_relaxed) ? -baseTarget : baseTarget;
323
324 if (finalTarget != currentTarget_)
325 {
326 gainSmooth_.setTargetValue(finalTarget);
327 currentTarget_ = finalTarget;
328 }
329 }
330
332 void forceSynchronize() noexcept
333 {
334 rampTimeChanged_.store(false, std::memory_order_relaxed);
335
336 const T baseTarget = muted_.load(std::memory_order_relaxed) ? T(0)
337 : targetGainLinear_.load(std::memory_order_relaxed);
338 const T finalTarget = inverted_.load(std::memory_order_relaxed) ? -baseTarget : baseTarget;
339
340 currentTarget_ = finalTarget;
342 publishedGain_.store(gainSmooth_.getCurrentValue(), std::memory_order_relaxed);
343 }
344
345 double sampleRate_ = 48000.0;
346
347 // Audio Thread State
348 T currentTarget_ { T(1) };
350
351 // Atomic Communication (UI -> Audio Thread)
352 std::atomic<T> publishedGain_ { T(1) };
353 std::atomic<T> targetGainLinear_ { T(1) };
354 std::atomic<double> rampTimeMs_ { 10.0 };
355 std::atomic<bool> rampTimeChanged_ { false };
356 std::atomic<bool> muted_ { false };
357 std::atomic<bool> inverted_ { false };
358};
359
360} // namespace dspark
Non-owning view over audio channel data.
Definition AudioBuffer.h:50
Professional click-free gain processor.
Definition Gain.h:48
void setGainLinear(T linear) noexcept
Sets the target gain as a linear multiplier. (Thread-safe, callable from UI).
Definition Gain.h:97
T currentTarget_
Definition Gain.h:348
void process(T *data, int numSamples) noexcept
Processes an interleaved or single-channel buffer in-place.
Definition Gain.h:187
double sampleRate_
Definition Gain.h:345
void prepare(double sampleRate, double rampTimeMs=10.0)
Prepares the gain processor for playback.
Definition Gain.h:63
bool isMuted() const noexcept
Definition Gain.h:134
std::atomic< bool > rampTimeChanged_
Definition Gain.h:355
std::atomic< bool > muted_
Definition Gain.h:356
std::atomic< T > targetGainLinear_
Definition Gain.h:353
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
Definition Gain.h:284
void setInverted(bool inverted) noexcept
Enables smooth phase inversion. (Thread-safe, callable from UI). Reverses polarity by ramping smoothl...
Definition Gain.h:140
void process(T **channelData, int numChannels, int numSamples) noexcept
Processes separate channel buffers in-place.
Definition Gain.h:210
void reset() noexcept
Resets gain internal state to match targets immediately.
Definition Gain.h:266
void setMuted(bool muted) noexcept
Enables or disables mute smoothly. (Thread-safe, callable from UI).
Definition Gain.h:129
void forceSynchronize() noexcept
Forces instantaneous synchronization of state without ramping.
Definition Gain.h:332
void updateInternalState() noexcept
Synchronizes atomic UI variables with audio thread state. Must be called at the start of any audio pr...
Definition Gain.h:312
T getGainLinear() const noexcept
Returns current target linear gain.
Definition Gain.h:110
std::atomic< double > rampTimeMs_
Definition Gain.h:354
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
Definition Gain.h:273
T getCurrentGain() const noexcept
Returns the current internal smoothed value.
Definition Gain.h:121
void process(const T *input, T *output, int numSamples) noexcept
Processes input to output (not in-place).
Definition Gain.h:236
~Gain()=default
void setRampTime(double rampTimeMs) noexcept
Sets the smoothing ramp time. (Thread-safe). Non-finite values are ignored; negatives clamp to 0.
Definition Gain.h:151
void setGainDb(T dB) noexcept
Sets the target gain in decibels. (Thread-safe, callable from UI).
Definition Gain.h:85
void prepare(const AudioSpec &spec)
Prepares from AudioSpec, preserving existing ramp time.
Definition Gain.h:76
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an AudioBufferView in-place. (Audio Thread only).
Definition Gain.h:163
bool isInverted() const noexcept
Definition Gain.h:145
void publishGain() noexcept
Publishes the smoother's value for cross-thread metering.
Definition Gain.h:303
SmoothedValue< T > gainSmooth_
Definition Gain.h:349
std::atomic< bool > inverted_
Definition Gain.h:357
void skipRamp() noexcept
Skips smoothing - immediately sets current gain to target.
Definition Gain.h:260
T getGainDb() const noexcept
Returns current target gain in dB.
Definition Gain.h:104
std::atomic< T > publishedGain_
Cross-thread metering readout.
Definition Gain.h:352
Zero-allocation parameter smoother for real-time audio.
Tolerant reader: missing keys yield defaults, unknown keys are skipped.
Definition StateBlob.h:161
float read(const char *key, float defaultValue) const
Reads a float, or defaultValue when the key is absent.
Definition StateBlob.h:204
bool isValid() const noexcept
Definition StateBlob.h:199
uint32_t processorId() const noexcept
Definition StateBlob.h:200
Serializes key/value parameters into a versioned blob.
Definition StateBlob.h:53
std::vector< uint8_t > blob() const
Finalizes and returns the blob.
Definition StateBlob.h:105
void write(const char *key, float value)
Writes a float parameter.
Definition StateBlob.h:71
void applyGain(float *DSPARK_RESTRICT data, float gain, int count) noexcept
Multiplies all samples in a buffer by a gain factor.
Definition SimdOps.h:186
Main namespace for the DSPark framework.
T decibelsToGain(T dB, T minusInfinityDb=T(-100)) noexcept
Converts a value in decibels to linear gain.
Definition DspMath.h:74
T gainToDecibels(T gain, T minusInfinityDb=T(-100)) noexcept
Converts a linear gain value to decibels.
Definition DspMath.h:89
constexpr uint32_t stateId(const char(&tag)[5]) noexcept
Builds a FOURCC processor id, e.g. dspark::stateId("COMP").
Definition StateBlob.h:651
Describes the audio environment for a DSP processor.
Definition AudioSpec.h:37
double sampleRate
Sample rate in Hz.
Definition AudioSpec.h:45