DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
SampleAndHold.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
24#include "DspMath.h"
25
26#include <algorithm>
27#include <cmath>
28#include <limits>
29
30namespace dspark {
31
38template <FloatType T>
40{
41public:
43 enum class Mode
44 {
45 Counter,
46 Trigger
47 };
48
53 void setMode(Mode mode) noexcept { mode_ = mode; }
54
65 void setHoldSamples(int numSamples) noexcept
66 {
67 setHoldPeriod(static_cast<double>(numSamples));
68 }
69
83 void setHoldPeriod(double samples) noexcept
84 {
85 constexpr double maxPeriod = static_cast<double>(std::numeric_limits<int>::max());
86 holdPeriod_ = (samples >= 1.0) ? std::min(samples, maxPeriod) : 1.0; // NaN -> 1
87 // An integer period drops any sub-sample offset left by a fractional
88 // one, so it captures on the sample grid (period 1 is transparent).
89 if (holdPeriod_ == std::floor(holdPeriod_))
90 phase_ = std::floor(phase_);
91 }
92
94 [[nodiscard]] double getHoldPeriod() const noexcept { return holdPeriod_; }
95
106 void setHoldRate(double targetRate, double actualRate) noexcept
107 {
108 if (!(targetRate > 0.0) || !(actualRate > 0.0))
109 {
110 setHoldPeriod(1.0);
111 return;
112 }
113 setHoldPeriod(actualRate / targetRate);
114 }
115
128 [[nodiscard]] T process(T input, bool trigger = false) noexcept
129 {
130 if (mode_ == Mode::Counter)
131 advanceCounter(input);
132 else if (trigger) // Mode::Trigger
133 heldValue_ = input;
134 previous_ = input;
135 return heldValue_;
136 }
137
148 void processBlock(T* data, int numSamples) noexcept
149 {
150 if (mode_ == Mode::Counter)
151 {
152 // Branch hoisted out of the hot loop
153 for (int i = 0; i < numSamples; ++i)
154 {
155 const T input = data[i];
156 advanceCounter(input);
157 previous_ = input;
158 data[i] = heldValue_;
159 }
160 }
161 else if (numSamples > 0)
162 {
163 // In trigger mode with no triggers provided, it holds indefinitely.
164 previous_ = data[numSamples - 1];
165 for (int i = 0; i < numSamples; ++i)
166 {
167 data[i] = heldValue_;
168 }
169 }
170 }
171
184 void processBlock(T* data, const bool* triggers, int numSamples) noexcept
185 {
186 if (mode_ == Mode::Trigger && triggers != nullptr)
187 {
188 // Branch hoisted out of the hot loop
189 for (int i = 0; i < numSamples; ++i)
190 {
191 if (triggers[i])
192 {
193 heldValue_ = data[i];
194 }
195 previous_ = data[i];
196 data[i] = heldValue_;
197 }
198 }
199 else
200 {
201 processBlock(data, numSamples);
202 }
203 }
204
209 [[nodiscard]] T getHeldValue() const noexcept { return heldValue_; }
210
216 void reset(T initialValue = T(0)) noexcept
217 {
218 heldValue_ = initialValue;
219 previous_ = initialValue;
220 phase_ = 0.0; // Ensures initialValue is output before the next capture
221 }
222
223private:
227 inline void advanceCounter(T input) noexcept
228 {
229 phase_ += 1.0;
230 if (phase_ >= holdPeriod_)
231 {
232 phase_ -= holdPeriod_;
233 // The period shrank below the time already elapsed: capture now.
234 if (phase_ >= 1.0) phase_ = 0.0;
235 heldValue_ = (phase_ > 0.0) ? input - static_cast<T>(phase_) * (input - previous_)
236 : input;
237 }
238 }
239
240 Mode mode_ = Mode::Counter;
241 double holdPeriod_ = 1.0;
242 double phase_ = 0.0;
243 T heldValue_ = T(0);
244 T previous_ = T(0);
245};
246
247} // namespace dspark
Holds a sample value for N samples or until externally triggered.
Mode
Defines the capture behavior of the processor.
@ Counter
Holds for a fixed number of samples automatically (Decimation).
@ Trigger
Holds until an external trigger captures a new value (level-sensitive).
void setHoldSamples(int numSamples) noexcept
Sets the hold duration for Counter mode in whole samples.
double getHoldPeriod() const noexcept
Returns the Counter-mode hold period in samples.
void setMode(Mode mode) noexcept
Sets the operating mode.
void setHoldPeriod(double samples) noexcept
Sets a fractional hold period for Counter mode, in samples.
void processBlock(T *data, const bool *triggers, int numSamples) noexcept
Processes a block of samples in-place using an external trigger buffer.
T getHeldValue() const noexcept
Retrieves the current held value without advancing the state.
T process(T input, bool trigger=false) noexcept
Processes a single sample.
void setHoldRate(double targetRate, double actualRate) noexcept
Sets the hold period from a target effective sample rate.
void reset(T initialValue=T(0)) noexcept
Resets the processor state and sets an initial output value.
void processBlock(T *data, int numSamples) noexcept
Processes a block of samples in-place (Counter mode optimized).
Main namespace for the DSPark framework.