DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
SampleAndHold.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
23#include "DspMath.h"
24
25#include <cmath>
26#include <limits>
27
28namespace dspark {
29
36template <FloatType T>
38{
39public:
41 enum class Mode
42 {
43 Counter,
44 Trigger
45 };
46
51 void setMode(Mode mode) noexcept { mode_ = mode; }
52
63 void setHoldSamples(int numSamples) noexcept
64 {
65 holdPeriod_ = numSamples > 0 ? numSamples : 1;
66 }
67
78 void setHoldRate(double targetRate, double actualRate) noexcept
79 {
80 if (!(targetRate > 0.0) || !(actualRate > 0.0))
81 {
83 return;
84 }
85 // Clamp in double before the integer conversion: a huge ratio would
86 // overflow the int cast (undefined behaviour).
87 const double ratio = actualRate / targetRate;
88 constexpr double maxPeriod = static_cast<double>(std::numeric_limits<int>::max());
89 setHoldSamples(ratio >= maxPeriod ? std::numeric_limits<int>::max()
90 : static_cast<int>(std::llround(ratio)));
91 }
92
105 [[nodiscard]] T process(T input, bool trigger = false) noexcept
106 {
107 if (mode_ == Mode::Counter)
108 {
109 ++counter_;
110 if (counter_ >= holdPeriod_)
111 {
112 heldValue_ = input;
113 counter_ = 0;
114 }
115 }
116 else // Mode::Trigger
117 {
118 if (trigger)
119 {
120 heldValue_ = input;
121 }
122 }
123 return heldValue_;
124 }
125
136 void processBlock(T* data, int numSamples) noexcept
137 {
138 if (mode_ == Mode::Counter)
139 {
140 // Branch hoisted out of the hot loop
141 for (int i = 0; i < numSamples; ++i)
142 {
143 ++counter_;
144 if (counter_ >= holdPeriod_)
145 {
146 heldValue_ = data[i];
147 counter_ = 0;
148 }
149 data[i] = heldValue_;
150 }
151 }
152 else
153 {
154 // In trigger mode with no triggers provided, it holds indefinitely.
155 for (int i = 0; i < numSamples; ++i)
156 {
157 data[i] = heldValue_;
158 }
159 }
160 }
161
174 void processBlock(T* data, const bool* triggers, int numSamples) noexcept
175 {
176 if (mode_ == Mode::Trigger && triggers != nullptr)
177 {
178 // Branch hoisted out of the hot loop
179 for (int i = 0; i < numSamples; ++i)
180 {
181 if (triggers[i])
182 {
183 heldValue_ = data[i];
184 }
185 data[i] = heldValue_;
186 }
187 }
188 else
189 {
190 processBlock(data, numSamples);
191 }
192 }
193
198 [[nodiscard]] T getHeldValue() const noexcept { return heldValue_; }
199
205 void reset(T initialValue = T(0)) noexcept
206 {
207 heldValue_ = initialValue;
208 counter_ = 0; // Ensures initialValue is output before the next capture
209 }
210
211private:
212 Mode mode_ = Mode::Counter;
213 int holdPeriod_ = 1;
214 int counter_ = 0;
215 T heldValue_ = T(0);
216};
217
218} // namespace dspark
Core mathematical utilities for digital signal processing.
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 samples.
void setMode(Mode mode) noexcept
Sets the operating mode.
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 based on 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.