DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
Gain.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
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(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
118 [[nodiscard]] T getCurrentGain() const noexcept
119 {
120 return gainSmooth_.getCurrentValue();
121 }
122
126 void setMuted(bool muted) noexcept
127 {
128 muted_.store(muted, std::memory_order_relaxed);
129 }
130
131 [[nodiscard]] bool isMuted() const noexcept { return muted_.load(std::memory_order_relaxed); }
132
137 void setInverted(bool inverted) noexcept
138 {
139 inverted_.store(inverted, std::memory_order_relaxed);
140 }
141
142 [[nodiscard]] bool isInverted() const noexcept { return inverted_.load(std::memory_order_relaxed); }
143
148 void setRampTime(double rampTimeMs) noexcept
149 {
150 if (!std::isfinite(rampTimeMs)) return;
151 rampTimeMs_.store(std::max(0.0, rampTimeMs), std::memory_order_relaxed);
152 rampTimeChanged_.store(true, std::memory_order_release);
153 }
154
160 void processBlock(AudioBufferView<T> buffer) noexcept
161 {
163
164 const int nCh = buffer.getNumChannels();
165 const int nS = buffer.getNumSamples();
166
167 if (nCh == 1)
168 {
169 process(buffer.getChannel(0), nS);
170 }
171 else
172 {
173 T* channels[16];
174 int useCh = nCh < 16 ? nCh : 16;
175 for (int c = 0; c < useCh; ++c)
176 channels[c] = buffer.getChannel(c);
177 process(channels, useCh, nS);
178 }
179 }
180
184 void process(T* data, int numSamples) noexcept
185 {
187 int i = 0;
188
189 // Per-sample ramp (branchless in the hot path)
190 for (; i < numSamples && gainSmooth_.isSmoothing(); ++i)
191 {
192 data[i] *= gainSmooth_.getNextValue();
193 }
194
195 // SIMD bulk path
196 if (i < numSamples)
197 {
198 simd::applyGain(data + i, gainSmooth_.getCurrentValue(), numSamples - i);
199 }
200 }
201
205 void process(T** channelData, int numChannels, int numSamples) noexcept
206 {
208 int i = 0;
209
210 for (; i < numSamples && gainSmooth_.isSmoothing(); ++i)
211 {
212 const T g = gainSmooth_.getNextValue();
213 for (int ch = 0; ch < numChannels; ++ch)
214 channelData[ch][i] *= g;
215 }
216
217 if (i < numSamples)
218 {
219 const T g = gainSmooth_.getCurrentValue();
220 const int remaining = numSamples - i;
221 for (int ch = 0; ch < numChannels; ++ch)
222 simd::applyGain(channelData[ch] + i, g, remaining);
223 }
224 }
225
229 void process(const T* input, T* output, int numSamples) noexcept
230 {
232 int i = 0;
233
234 for (; i < numSamples && gainSmooth_.isSmoothing(); ++i)
235 {
236 output[i] = input[i] * gainSmooth_.getNextValue();
237 }
238
239 if (i < numSamples)
240 {
241 const T g = gainSmooth_.getCurrentValue();
242 const int remaining = numSamples - i;
243
244 // Allow out-of-place SIMD by copying then applying in-place SIMD gain
245 std::copy_n(input + i, remaining, output + i);
246 simd::applyGain(output + i, g, remaining);
247 }
248 }
249
251 void skipRamp() noexcept
252 {
254 }
255
257 void reset() noexcept
258 {
260 }
261
262
264 [[nodiscard]] std::vector<uint8_t> getState() const
265 {
266 StateWriter w(stateId("GAIN"), 1);
267 w.write("gainLinear", static_cast<float>(targetGainLinear_.load(std::memory_order_relaxed)));
268 w.write("rampMs", static_cast<float>(rampTimeMs_.load(std::memory_order_relaxed)));
269 w.write("muted", muted_.load(std::memory_order_relaxed));
270 w.write("inverted", inverted_.load(std::memory_order_relaxed));
271 return w.blob();
272 }
273
275 bool setState(const uint8_t* data, size_t size)
276 {
277 StateReader r(data, size);
278 if (!r.isValid() || r.processorId() != stateId("GAIN")) return false;
279 setGainLinear(static_cast<T>(r.read("gainLinear", 1.0f)));
280 setRampTime(static_cast<double>(r.read("rampMs", 10.0f)));
281 setMuted(r.read("muted", false));
282 setInverted(r.read("inverted", false));
283 return true;
284 }
285
286protected:
291 inline void updateInternalState() noexcept
292 {
293 // exchange (not load+store: a publication landing between the two
294 // would be lost) with acquire, pairing with setRampTime's release.
295 if (rampTimeChanged_.exchange(false, std::memory_order_acquire))
296 gainSmooth_.setRampTime(sampleRate_, rampTimeMs_.load(std::memory_order_relaxed));
297
298 const T baseTarget = muted_.load(std::memory_order_relaxed) ? T(0)
299 : targetGainLinear_.load(std::memory_order_relaxed);
300
301 const T finalTarget = inverted_.load(std::memory_order_relaxed) ? -baseTarget : baseTarget;
302
303 if (finalTarget != currentTarget_)
304 {
305 gainSmooth_.setTargetValue(finalTarget);
306 currentTarget_ = finalTarget;
307 }
308 }
309
311 void forceSynchronize() noexcept
312 {
313 rampTimeChanged_.store(false, std::memory_order_relaxed);
314
315 const T baseTarget = muted_.load(std::memory_order_relaxed) ? T(0)
316 : targetGainLinear_.load(std::memory_order_relaxed);
317 const T finalTarget = inverted_.load(std::memory_order_relaxed) ? -baseTarget : baseTarget;
318
319 currentTarget_ = finalTarget;
321 }
322
323 double sampleRate_ = 48000.0;
324
325 // Audio Thread State
326 T currentTarget_ { T(1) };
328
329 // Atomic Communication (UI -> Audio Thread)
330 std::atomic<T> targetGainLinear_ { T(1) };
331 std::atomic<double> rampTimeMs_ { 10.0 };
332 std::atomic<bool> rampTimeChanged_ { false };
333 std::atomic<bool> muted_ { false };
334 std::atomic<bool> inverted_ { false };
335};
336
337} // 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:326
void process(T *data, int numSamples) noexcept
Processes an interleaved or single-channel buffer in-place.
Definition Gain.h:184
double sampleRate_
Definition Gain.h:323
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:131
std::atomic< bool > rampTimeChanged_
Definition Gain.h:332
std::atomic< bool > muted_
Definition Gain.h:333
std::atomic< T > targetGainLinear_
Definition Gain.h:330
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
Definition Gain.h:275
void setInverted(bool inverted) noexcept
Enables smooth phase inversion. (Thread-safe, callable from UI). Reverses polarity by ramping smoothl...
Definition Gain.h:137
void process(T **channelData, int numChannels, int numSamples) noexcept
Processes separate channel buffers in-place.
Definition Gain.h:205
void reset() noexcept
Resets gain internal state to match targets immediately.
Definition Gain.h:257
void setMuted(bool muted) noexcept
Enables or disables mute smoothly. (Thread-safe, callable from UI).
Definition Gain.h:126
void forceSynchronize() noexcept
Forces instantaneous synchronization of state without ramping.
Definition Gain.h:311
void updateInternalState() noexcept
Synchronizes atomic UI variables with audio thread state. Must be called at the start of any audio pr...
Definition Gain.h:291
T getGainLinear() const noexcept
Returns current target linear gain.
Definition Gain.h:110
std::atomic< double > rampTimeMs_
Definition Gain.h:331
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
Definition Gain.h:264
T getCurrentGain() const noexcept
Returns the current internal smoothed value.
Definition Gain.h:118
void process(const T *input, T *output, int numSamples) noexcept
Processes input to output (not in-place).
Definition Gain.h:229
~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:148
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:160
bool isInverted() const noexcept
Definition Gain.h:142
SmoothedValue< T > gainSmooth_
Definition Gain.h:327
std::atomic< bool > inverted_
Definition Gain.h:334
void skipRamp() noexcept
Skips smoothing - immediately sets current gain to target.
Definition Gain.h:251
T getGainDb() const noexcept
Returns current target gain in dB.
Definition Gain.h:104
Zero-allocation parameter smoother for real-time audio.
Tolerant reader: missing keys yield defaults, unknown keys are skipped.
Definition StateBlob.h:160
float read(const char *key, float defaultValue) const
Reads a float, or defaultValue when the key is absent.
Definition StateBlob.h:203
bool isValid() const noexcept
Definition StateBlob.h:198
uint32_t processorId() const noexcept
Definition StateBlob.h:199
Serializes key/value parameters into a versioned blob.
Definition StateBlob.h:52
std::vector< uint8_t > blob() const
Finalizes and returns the blob.
Definition StateBlob.h:104
void write(const char *key, float value)
Writes a float parameter.
Definition StateBlob.h:70
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:65
T gainToDecibels(T gain, T minusInfinityDb=T(-100)) noexcept
Converts a linear gain value to decibels.
Definition DspMath.h:78
constexpr uint32_t stateId(const char(&tag)[5]) noexcept
Builds a FOURCC processor id, e.g. dspark::stateId("COMP").
Definition StateBlob.h:639
Describes the audio environment for a DSP processor.
Definition AudioSpec.h:35
double sampleRate
Sample rate in Hz.
Definition AudioSpec.h:43