DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
Expander.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
39#include "../Core/DspMath.h"
40#include "../Core/AudioSpec.h"
41#include "../Core/AudioBuffer.h"
42#include "../Core/DenormalGuard.h"
43#include "../Core/RingBuffer.h"
44#include "../Core/StateBlob.h"
45
46#include <algorithm>
47#include <array>
48#include <atomic>
49#include <cmath>
50#include <cstddef>
51#include <cstdint>
52#include <numbers>
53#include <vector>
54
55namespace dspark {
56
63template <FloatType T>
65{
66public:
67 ~Expander() = default; // non-virtual: leaf class (no virtual dispatch)
68
69 enum class State { Closed, Open, Hold };
70
71 // -- Lifecycle -----------------------------------------------------------
72
80 void prepare(double sampleRate, int numChannels = 2) noexcept
81 {
82 if (!std::isfinite(sampleRate) || sampleRate <= 0.0) return;
83 sampleRate_ = sampleRate;
84 lookaheadChannels_ = std::clamp(numChannels, 1, MAX_CHANNELS);
85 maxLookaheadSamples_ = static_cast<int>(std::ceil(sampleRate * kMaxLookaheadMs / 1000.0));
86 for (int ch = 0; ch < lookaheadChannels_; ++ch)
87 lookahead_[static_cast<size_t>(ch)].prepare(maxLookaheadSamples_ + 1);
89 // Re-derive the hold counter for the (possibly new) rate: it used to
90 // be computed only inside setHold() with the rate of that moment, so
91 // the default 50 ms hold was silently ZERO until setHold was called,
92 // and a re-prepare at a new rate kept stale hold lengths.
93 setHold(holdMs_.load(std::memory_order_relaxed));
94 reset();
95 prepared_ = true;
96 }
97
102 void prepare(const AudioSpec& spec) noexcept
103 {
104 if (!spec.isValid()) return;
105 prepare(spec.sampleRate, spec.numChannels);
106 }
107
112 void processBlock(AudioBufferView<T> buffer) noexcept
113 {
114 if (!prepared_) return;
115 DenormalGuard guard;
116 cacheParams();
117
119 processBlockInternal<true, false>(buffer, nullptr);
120 else
121 processBlockInternal<false, false>(buffer, nullptr);
122 }
123
134 void processBlock(AudioBufferView<T> buffer, AudioBufferView<T> sidechain) noexcept
135 {
136 if (!prepared_) return;
137 DenormalGuard guard;
138 cacheParams();
139
140 if (sidechain.getNumChannels() <= 0 ||
141 sidechain.getNumSamples() < buffer.getNumSamples())
142 {
144 processBlockInternal<true, false>(buffer, nullptr);
145 else
146 processBlockInternal<false, false>(buffer, nullptr);
147 return;
148 }
149
151 processBlockInternal<true, true>(buffer, &sidechain);
152 else
153 processBlockInternal<false, true>(buffer, &sidechain);
154 }
155
156 // -- Parameters ----------------------------------------------------------
157 // All setters ignore non-finite values (NaN attack/release/ratio used to
158 // poison the gain smoother permanently through max()'s first-argument
159 // NaN pass-through).
160
162 void setThreshold(T dB) noexcept
163 {
164 if (!std::isfinite(dB)) return;
165 threshold_.store(dB, std::memory_order_relaxed);
166 }
167
169 void setRatio(T ratio) noexcept
170 {
171 if (!std::isfinite(ratio)) return;
172 ratio_.store(std::max(ratio, T(1)), std::memory_order_relaxed);
173 }
174
176 void setHysteresis(T dB) noexcept
177 {
178 if (!std::isfinite(dB)) return;
179 hysteresis_.store(std::max(dB, T(0)), std::memory_order_relaxed);
180 }
181
183 void setRange(T dB) noexcept
184 {
185 if (!std::isfinite(dB)) return;
186 const T r = std::min(dB, T(0));
187 rangeDb_.store(r, std::memory_order_relaxed);
188 rangeLinear_.store(decibelsToGain(r), std::memory_order_relaxed);
189 }
190
192 void setAttack(T ms) noexcept
193 {
194 if (!std::isfinite(ms)) return;
195 attackMs_.store(std::max(ms, T(0.01)), std::memory_order_relaxed);
197 }
198
200 void setHold(T ms) noexcept
201 {
202 if (!std::isfinite(ms)) return;
203 holdMs_.store(std::max(ms, T(0)), std::memory_order_relaxed);
204 if (sampleRate_ > 0.0)
205 {
206 // Capped in double before the int cast (a huge finite hold time
207 // would otherwise be a UB float-to-int conversion).
208 const double h = std::min(
209 sampleRate_ * static_cast<double>(holdMs_.load(std::memory_order_relaxed)) / 1000.0,
210 1.0e9);
211 holdSamples_.store(static_cast<int>(h), std::memory_order_relaxed);
212 }
213 }
214
216 void setRelease(T ms) noexcept
217 {
218 if (!std::isfinite(ms)) return;
219 releaseMs_.store(std::max(ms, T(0.01)), std::memory_order_relaxed);
221 }
222
234 void setSidechainHPF(bool enabled, double cutoffHz = 80.0) noexcept
235 {
236 scHpfEnabled_.store(enabled, std::memory_order_relaxed);
237 if (std::isfinite(cutoffHz) && cutoffHz > 0.0)
238 scHpfFreqHz_.store(static_cast<T>(cutoffHz), std::memory_order_relaxed);
240 }
241
253 void setLookahead(T ms) noexcept
254 {
255 if (!std::isfinite(ms)) return;
256 lookaheadMs_.store(std::clamp(ms, T(0), static_cast<T>(kMaxLookaheadMs)), std::memory_order_relaxed);
257 }
258
260 [[nodiscard]] T getLookahead() const noexcept { return lookaheadMs_.load(std::memory_order_relaxed); }
261
263 [[nodiscard]] int getLatency() const noexcept
264 {
265 return lookaheadSamplesFor(lookaheadMs_.load(std::memory_order_relaxed));
266 }
267
268 // -- Queries -------------------------------------------------------------
269
276 [[nodiscard]] State getGateState() const noexcept
277 {
278 return publishedState_.load(std::memory_order_relaxed);
279 }
280
284 [[nodiscard]] T getCurrentGainDb() const noexcept
285 {
286 return gainToDecibels(publishedGateGain_.load(std::memory_order_relaxed));
287 }
288
290 void reset() noexcept
291 {
293 gateGain_ = rangeLinear_.load(std::memory_order_relaxed);
294 envelope_ = T(0);
295 holdCounter_ = 0;
297
298 std::fill(scHpfState_.begin(), scHpfState_.end(), T(0));
299 std::fill(scHpfPrev_.begin(), scHpfPrev_.end(), T(0));
300 for (auto& line : lookahead_) line.reset();
301 }
302
303
305 [[nodiscard]] std::vector<uint8_t> getState() const
306 {
307 StateWriter w(stateId("XPND"), 1);
308 // static_cast<float>: the blob stores float, and StateWriter's
309 // overload set is ambiguous for a double argument.
310 w.write("threshold", static_cast<float>(threshold_.load(std::memory_order_relaxed)));
311 w.write("ratio", static_cast<float>(ratio_.load(std::memory_order_relaxed)));
312 w.write("hysteresis", static_cast<float>(hysteresis_.load(std::memory_order_relaxed)));
313 w.write("attack", static_cast<float>(attackMs_.load(std::memory_order_relaxed)));
314 w.write("hold", static_cast<float>(holdMs_.load(std::memory_order_relaxed)));
315 w.write("release", static_cast<float>(releaseMs_.load(std::memory_order_relaxed)));
316 w.write("rangeDb", static_cast<float>(rangeDb_.load(std::memory_order_relaxed)));
317 w.write("scHpf", scHpfEnabled_.load(std::memory_order_relaxed));
318 w.write("scHpfFreq", static_cast<float>(scHpfFreqHz_.load(std::memory_order_relaxed)));
319 w.write("lookahead", static_cast<float>(lookaheadMs_.load(std::memory_order_relaxed)));
320 return w.blob();
321 }
322
324 bool setState(const uint8_t* data, size_t size)
325 {
326 StateReader r(data, size);
327 if (!r.isValid() || r.processorId() != stateId("XPND")) return false;
328 setThreshold(static_cast<T>(r.read("threshold", -40.0f)));
329 setRatio(static_cast<T>(r.read("ratio", 4.0f)));
330 setHysteresis(static_cast<T>(r.read("hysteresis", 4.0f)));
331 setAttack(static_cast<T>(r.read("attack", 0.5f)));
332 setHold(static_cast<T>(r.read("hold", 50.0f)));
333 setRelease(static_cast<T>(r.read("release", 100.0f)));
334 setRange(static_cast<T>(r.read("rangeDb", -80.0f)));
335 setSidechainHPF(r.read("scHpf", false),
336 static_cast<double>(r.read("scHpfFreq", 80.0f)));
337 setLookahead(static_cast<T>(r.read("lookahead", 0.0f)));
338 return true;
339 }
340
341protected:
342 void cacheParams() noexcept
343 {
344 cachedThreshold_ = threshold_.load(std::memory_order_relaxed);
345 cachedRatio_ = ratio_.load(std::memory_order_relaxed);
346 cachedHysteresis_ = hysteresis_.load(std::memory_order_relaxed);
347 cachedRangeLinear_ = rangeLinear_.load(std::memory_order_relaxed);
348 cachedAttackCoeff_ = attackCoeff_.load(std::memory_order_relaxed);
349 cachedReleaseCoeff_ = releaseCoeff_.load(std::memory_order_relaxed);
350 cachedHoldSamples_ = holdSamples_.load(std::memory_order_relaxed);
351 cachedScHpfEnabled_ = scHpfEnabled_.load(std::memory_order_relaxed);
352 cachedDetAttCoeff_ = detAttCoeff_.load(std::memory_order_relaxed);
353 cachedDetRelCoeff_ = detRelCoeff_.load(std::memory_order_relaxed);
354 cachedScHpfCoeff_ = scHpfCoeff_.load(std::memory_order_relaxed);
355 cachedScHpfA0_ = scHpfA0_.load(std::memory_order_relaxed);
356 cachedLookahead_ = lookaheadSamplesFor(lookaheadMs_.load(std::memory_order_relaxed));
357 }
358
360 [[nodiscard]] int lookaheadSamplesFor(T ms) const noexcept
361 {
362 const double samples = std::round(static_cast<double>(ms) * sampleRate_ / 1000.0);
363 return std::clamp(static_cast<int>(std::min(samples, 1.0e9)), 0, maxLookaheadSamples_);
364 }
365
373 template <bool UseHPF, bool UseSc>
375 const AudioBufferView<T>* sidechain) noexcept
376 {
377 const int nCh = buffer.getNumChannels();
378 const int nS = buffer.getNumSamples();
379 // Detector channel count is capped by the per-channel HPF state
380 // arrays; the scalar gain below is applied to ALL audio channels.
381 const int detCh = UseSc ? std::min(sidechain->getNumChannels(), MAX_CHANNELS)
382 : std::min(nCh, MAX_CHANNELS);
383
384 for (int i = 0; i < nS; ++i)
385 {
386 T sc = T(0);
387
388 // 1. Calculate Sidechain Signal
389 for (int ch = 0; ch < detCh; ++ch)
390 {
391 T input = UseSc ? sidechain->getChannel(ch)[i]
392 : buffer.getChannel(ch)[i];
393 if constexpr (UseHPF)
394 {
395 // One-pole HPF on the raw bipolar signal per channel,
396 // normalized for unity gain at Nyquist (the unnormalized
397 // form boosted the detected HF slightly).
398 T output = cachedScHpfA0_ * (input - scHpfPrev_[ch]) + cachedScHpfCoeff_ * scHpfState_[ch];
399 scHpfPrev_[ch] = input;
400 scHpfState_[ch] = output;
401 input = output;
402 }
403 sc = std::max(sc, std::abs(input));
404 }
405
406 // 2. Smoothed-peak envelope detector (prevents LF intermodulation).
407 // Coefficients are derived from the sample rate in
408 // updateCoefficients() - fixed per-sample constants made the
409 // detector 4x faster at 192 kHz than at 44.1 kHz.
410 T envCoeff = sc > envelope_ ? cachedDetAttCoeff_ : cachedDetRelCoeff_;
411 envelope_ += envCoeff * (sc - envelope_);
412
413 // 3. Gain Calculation (Converting envelope to dB only once)
414 T levelDb = gainToDecibels(envelope_ + T(1e-9));
415
416 updateStateMachine(levelDb);
417 T gain = computeGain(levelDb);
418
419 // 4. Apply Gain (to the lookahead-delayed audio when enabled)
420 for (int ch = 0; ch < nCh; ++ch)
421 {
422 T& x = buffer.getChannel(ch)[i];
423 if (cachedLookahead_ > 0 && ch < lookaheadChannels_)
424 {
425 auto& line = lookahead_[static_cast<size_t>(ch)];
426 line.push(x);
427 x = line.read(cachedLookahead_);
428 }
429 x *= gain;
430 }
431 }
432
434 }
435
443 void publishMeters() noexcept
444 {
445 publishedState_.store(state_, std::memory_order_relaxed);
446 publishedGateGain_.store(gateGain_, std::memory_order_relaxed);
447 }
448
449 void updateStateMachine(T levelDb) noexcept
450 {
451 T openThresh = cachedThreshold_;
452 T closeThresh = cachedThreshold_ - cachedHysteresis_;
453
454 switch (state_)
455 {
456 case State::Closed:
457 if (levelDb > openThresh) state_ = State::Open;
458 break;
459 case State::Open:
460 if (levelDb < closeThresh)
461 {
464 }
465 break;
466 case State::Hold:
467 if (levelDb > openThresh)
469 else if (--holdCounter_ <= 0)
471 break;
472 }
473 }
474
475 [[nodiscard]] T computeGain(T levelDb) noexcept
476 {
477 T targetGain;
478
480 {
481 targetGain = T(1);
482 }
483 else
484 {
485 T underDb = cachedThreshold_ - levelDb;
486 // Downward-expander law: the transfer slope below threshold is the
487 // ratio R (out = thr + R*(in - thr)), so the attenuation applied is
488 // underDb*(R-1). As R -> inf this approaches full closure, i.e. the
489 // gate the class documents itself as a generalization of. (The prior
490 // (1 - 1/R) was the *compressor* law and made the expander ~R too
491 // gentle -- e.g. 5 dB instead of 10 dB of extra attenuation 10 dB
492 // below threshold at R=2, and it could never approach a gate.)
493 T reductionDb = underDb * (cachedRatio_ - T(1));
494
495 // Optimization note: replace decibelsToGain with fast_exp10 if available
496 T expandedGain = decibelsToGain(-reductionDb);
497 targetGain = std::max(expandedGain, cachedRangeLinear_);
498 }
499
500 // Apply smoothing to the linear gain
501 T coeff = (targetGain > gateGain_) ? cachedAttackCoeff_ : cachedReleaseCoeff_;
502 gateGain_ += coeff * (targetGain - gateGain_);
503
504 return gateGain_;
505 }
506
507 void updateCoefficients() noexcept
508 {
509 if (!(sampleRate_ > 0.0)) return;
510 T fs = static_cast<T>(sampleRate_);
511 attackCoeff_.store(T(1) - std::exp(T(-1) / (fs * attackMs_.load(std::memory_order_relaxed) / T(1000))),
512 std::memory_order_relaxed);
513 releaseCoeff_.store(T(1) - std::exp(T(-1) / (fs * releaseMs_.load(std::memory_order_relaxed) / T(1000))),
514 std::memory_order_relaxed);
515
516 // Detector ballistics (~0.5 ms attack, ~5 ms release), sample-rate aware.
517 detAttCoeff_.store(T(1) - std::exp(T(-1) / (fs * T(0.0005))), std::memory_order_relaxed);
518 detRelCoeff_.store(T(1) - std::exp(T(-1) / (fs * T(0.005))), std::memory_order_relaxed);
519
520 // Recompute the sidechain HPF for the (possibly new) sample rate.
521 // Design clamp [1, 0.45 * fs] keeps the one-pole stable; the atomic
522 // stores make this publication safe from any thread (the coefficient
523 // used to be a plain member written cross-thread).
524 const double scFreq = std::clamp(
525 static_cast<double>(scHpfFreqHz_.load(std::memory_order_relaxed)),
526 1.0, sampleRate_ * 0.45);
527 const T c = static_cast<T>(std::exp(-std::numbers::pi * 2.0 * scFreq / sampleRate_));
528 scHpfCoeff_.store(c, std::memory_order_relaxed);
529 scHpfA0_.store((T(1) + c) / T(2), std::memory_order_relaxed);
530 }
531
532 double sampleRate_ = 48000.0;
533 bool prepared_ = false;
534
535 std::atomic<T> threshold_ { T(-40) };
536 std::atomic<T> ratio_ { T(4) };
537 std::atomic<T> hysteresis_ { T(4) };
538 std::atomic<T> attackMs_ { T(0.5) };
539 std::atomic<T> holdMs_ { T(50) };
540 std::atomic<T> releaseMs_ { T(100) };
541 std::atomic<T> rangeDb_ { T(-80) };
542 std::atomic<T> rangeLinear_ { T(0.0001) };
543
544 std::atomic<bool> scHpfEnabled_ { false };
545 std::atomic<T> scHpfFreqHz_ { T(80) };
546 std::atomic<T> scHpfCoeff_ { T(0.995) };
547 std::atomic<T> scHpfA0_ { T(0.9975) };
548
549 // Per-channel sidechain HPF state (16 = detector channel cap).
550 static constexpr int MAX_CHANNELS = 16;
551 std::array<T, MAX_CHANNELS> scHpfState_{};
552 std::array<T, MAX_CHANNELS> scHpfPrev_{};
553
554 // Lookahead (audio delayed; detection runs on the undelayed signal).
555 static constexpr double kMaxLookaheadMs = 10.0;
556 std::atomic<T> lookaheadMs_ { T(0) };
560 std::array<RingBuffer<T>, MAX_CHANNELS> lookahead_ {};
561
562 std::atomic<T> attackCoeff_ { T(0) };
563 std::atomic<T> releaseCoeff_ { T(0) };
564 std::atomic<T> detAttCoeff_ { T(0.01) };
565 std::atomic<T> detRelCoeff_ { T(0.001) };
566 std::atomic<int> holdSamples_ { 0 };
567
568 // Cached per-block
570 T cachedRatio_ = T(4);
572 T cachedRangeLinear_ = T(0.0001);
576 T cachedDetRelCoeff_ = T(0.001);
577 T cachedScHpfCoeff_ = T(0.995);
578 T cachedScHpfA0_ = T(0.9975);
581
583 T gateGain_ = T(0);
584 // Cross-thread metering readouts of the two audio-thread words above.
585 std::atomic<State> publishedState_ { State::Closed };
586 std::atomic<T> publishedGateGain_ { T(0) };
587 T envelope_ = T(0);
589};
590
591} // namespace dspark
Non-owning view over audio channel data.
Definition AudioBuffer.h:50
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
Downward expander with ratio control, hysteresis, and sidechain.
Definition Expander.h:65
void publishMeters() noexcept
Publishes the metering words for cross-thread readout.
Definition Expander.h:443
void setHysteresis(T dB) noexcept
Sets the hysteresis gap in decibels to prevent chattering.
Definition Expander.h:176
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
Definition Expander.h:324
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
Definition Expander.h:305
void setRange(T dB) noexcept
Sets the maximum gain reduction limit in decibels (<= 0).
Definition Expander.h:183
std::atomic< T > hysteresis_
Definition Expander.h:537
void updateStateMachine(T levelDb) noexcept
Definition Expander.h:449
std::atomic< T > rangeDb_
Definition Expander.h:541
std::atomic< T > scHpfA0_
Definition Expander.h:547
std::atomic< T > holdMs_
Definition Expander.h:539
void reset() noexcept
Resets all internal DSP states to prevent clicks on playback start.
Definition Expander.h:290
void setRatio(T ratio) noexcept
Sets the expansion ratio (e.g., 4.0 for 4:1).
Definition Expander.h:169
void cacheParams() noexcept
Definition Expander.h:342
bool cachedScHpfEnabled_
Definition Expander.h:580
State getGateState() const noexcept
Definition Expander.h:276
std::atomic< State > publishedState_
Definition Expander.h:585
T computeGain(T levelDb) noexcept
Definition Expander.h:475
int maxLookaheadSamples_
Allocated delay (0 before prepare()).
Definition Expander.h:558
std::atomic< T > releaseCoeff_
Definition Expander.h:563
std::array< T, MAX_CHANNELS > scHpfPrev_
Definition Expander.h:552
std::array< T, MAX_CHANNELS > scHpfState_
Definition Expander.h:551
void setRelease(T ms) noexcept
Sets the release time in milliseconds.
Definition Expander.h:216
std::atomic< bool > scHpfEnabled_
Definition Expander.h:544
void setThreshold(T dB) noexcept
Sets the threshold in decibels.
Definition Expander.h:162
std::atomic< T > publishedGateGain_
Definition Expander.h:586
T getCurrentGainDb() const noexcept
Definition Expander.h:284
std::atomic< T > rangeLinear_
Definition Expander.h:542
std::atomic< T > attackCoeff_
Definition Expander.h:562
~Expander()=default
void setAttack(T ms) noexcept
Sets the attack time in milliseconds.
Definition Expander.h:192
void setHold(T ms) noexcept
Sets the hold time in milliseconds before release begins.
Definition Expander.h:200
void processBlockInternal(AudioBufferView< T > buffer, const AudioBufferView< T > *sidechain) noexcept
Internal processing loop templated on HPF/sidechain to avoid branching.
Definition Expander.h:374
void setLookahead(T ms) noexcept
Sets the lookahead: the audio is delayed so the gain moves ahead of it. 0 (default) is latency-free.
Definition Expander.h:253
int getLatency() const noexcept
Latency in samples (the lookahead), correct right after setLookahead().
Definition Expander.h:263
std::atomic< T > releaseMs_
Definition Expander.h:540
std::array< RingBuffer< T >, MAX_CHANNELS > lookahead_
Definition Expander.h:560
std::atomic< T > scHpfFreqHz_
Definition Expander.h:545
static constexpr double kMaxLookaheadMs
Definition Expander.h:555
std::atomic< T > detAttCoeff_
Definition Expander.h:564
std::atomic< T > detRelCoeff_
Definition Expander.h:565
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an audio block in place (internal detector key).
Definition Expander.h:112
int lookaheadChannels_
Channels with a prepared delay line.
Definition Expander.h:559
int lookaheadSamplesFor(T ms) const noexcept
Definition Expander.h:360
double sampleRate_
Definition Expander.h:532
std::atomic< T > scHpfCoeff_
Definition Expander.h:546
std::atomic< T > ratio_
Definition Expander.h:536
std::atomic< T > threshold_
Definition Expander.h:535
void prepare(const AudioSpec &spec) noexcept
Initializes the expander using an AudioSpec struct.
Definition Expander.h:102
void processBlock(AudioBufferView< T > buffer, AudioBufferView< T > sidechain) noexcept
Processes an audio block in place, keyed by an external sidechain.
Definition Expander.h:134
std::atomic< int > holdSamples_
Definition Expander.h:566
std::atomic< T > lookaheadMs_
Definition Expander.h:556
int cachedLookahead_
Audio-side samples (per block).
Definition Expander.h:557
void prepare(double sampleRate, int numChannels=2) noexcept
Initializes the expander with a specific sample rate.
Definition Expander.h:80
static constexpr int MAX_CHANNELS
Definition Expander.h:550
std::atomic< T > attackMs_
Definition Expander.h:538
T getLookahead() const noexcept
Returns the lookahead in milliseconds.
Definition Expander.h:260
void updateCoefficients() noexcept
Definition Expander.h:507
void setSidechainHPF(bool enabled, double cutoffHz=80.0) noexcept
Enables and configures the sidechain high-pass filter.
Definition Expander.h:234
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
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