Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
tempo_delay_wrapper.hpp
Go to the documentation of this file.
1#pragma once
2
5
8
9#include <algorithm>
10#include <cmath>
11#include <cstddef>
12#include <cstdint>
13#include <limits>
14
15namespace pulp::signal {
16
22
37
40 std::size_t processed_frames = 0;
41 std::size_t fault_count = 0;
42
43 constexpr explicit operator bool() const noexcept {
45 }
46};
47
62 public:
63 static constexpr double kMaximumFeedback = 0.98;
64 static constexpr std::size_t kMinimumCapacitySamples = 3;
65
66 TempoDelayWrapper() = default;
69
71 double sample_rate, std::size_t maximum_delay_samples,
73 std::uint32_t transition_samples = 64) {
74 if (!std::isfinite(sample_rate) || !(sample_rate > 0.0) ||
79 if (!is_supported(interpolation))
81
84 if (!left.prepare(maximum_delay_samples) || !right.prepare(maximum_delay_samples))
86
87 left_history_ = std::move(left);
88 right_history_ = std::move(right);
89 sample_rate_ = sample_rate;
90 maximum_delay_samples_ = maximum_delay_samples;
91 interpolation_ = interpolation;
92 transition_samples_ = transition_samples;
93 current_left_delay_samples_ = 2.0;
94 current_right_delay_samples_ = 2.0;
95 target_left_delay_samples_ = 2.0;
96 target_right_delay_samples_ = 2.0;
97 transition_remaining_ = 0;
98 initialized_ = false;
99 feedback_ = 0.0;
100 crossfeed_ = 0.0;
101 prepared_ = true;
103 }
104
105 [[nodiscard]] TempoDelayWrapperError set_delay_samples(double samples) noexcept {
106 if (!prepared_)
108 if (!std::isfinite(samples))
110 const auto minimum = static_cast<double>(minimum_delay_samples(interpolation_));
111 if (samples < minimum)
113 if (samples > static_cast<double>(maximum_delay_samples_))
115 adopt_target(samples, samples);
117 }
118
119 [[nodiscard]] TempoDelayWrapperError set_delay_samples(double left_samples,
120 double right_samples) noexcept {
121 if (!prepared_)
123 const auto left_status = validate_delay(left_samples);
124 const auto right_status = validate_delay(right_samples);
125 if (left_status != TempoDelayWrapperError::none)
126 return left_status;
127 if (right_status != TempoDelayWrapperError::none)
128 return right_status;
129 adopt_target(left_samples, right_samples);
131 }
132
134 double beats_per_minute) noexcept {
135 if (!prepared_)
137 const auto result = tempo_delay_samples(division, beats_per_minute, sample_rate_);
138 if (!result) {
139 if (result.error == TempoDelayError::invalid_tempo)
141 if (result.error == TempoDelayError::invalid_division)
143 if (result.error == TempoDelayError::out_of_range)
146 }
147 return set_delay_samples(result.samples);
148 }
149
150 [[nodiscard]] TempoDelayWrapperError set_feedback(double feedback) noexcept {
153 feedback_ = feedback;
155 }
156
157 [[nodiscard]] TempoDelayWrapperError set_crossfeed(double crossfeed) noexcept {
158 if (!std::isfinite(crossfeed) || crossfeed < 0.0 || crossfeed > 1.0)
160 crossfeed_ = crossfeed;
162 }
163
164 [[nodiscard]] TempoDelayWrapperProcessResult process(float* left, float* right,
165 std::size_t frame_count) noexcept {
167 if (!prepared_) {
169 return result;
170 }
171 if (frame_count == 0)
172 return result;
173 if (left == nullptr || right == nullptr) {
175 return result;
176 }
177
178 for (std::size_t index = 0; index < frame_count; ++index) {
179 const auto left_read = read(left_history_, current_left_delay_samples_);
180 const auto right_read = read(right_history_, current_right_delay_samples_);
181 if (!left_read || !right_read) {
182 result.error = left_read.status == FractionalDelayStatus::invalid_argument ||
183 right_read.status == FractionalDelayStatus::invalid_argument
186 return result;
187 }
188
189 float wet_left = left_read.sample;
190 float wet_right = right_read.sample;
191 if (transition_remaining_ != 0) {
192 const auto next_left = read(left_history_, target_left_delay_samples_);
193 const auto next_right = read(right_history_, target_right_delay_samples_);
194 if (!next_left || !next_right) {
196 return result;
197 }
198 const auto elapsed = transition_samples_ - transition_remaining_ + 1U;
199 const auto amount =
200 transition_samples_ == 0
201 ? 1.0f
202 : static_cast<float>(elapsed) / static_cast<float>(transition_samples_);
203 wet_left += amount * (next_left.sample - wet_left);
204 wet_right += amount * (next_right.sample - wet_right);
205 --transition_remaining_;
206 if (transition_remaining_ == 0) {
207 current_left_delay_samples_ = target_left_delay_samples_;
208 current_right_delay_samples_ = target_right_delay_samples_;
209 }
210 }
211
212 const float input_left = left[index];
213 const float input_right = right[index];
214 if (!std::isfinite(input_left) || !std::isfinite(input_right)) {
215 ++result.fault_count;
218 }
219 const float safe_left = std::isfinite(input_left) ? input_left : 0.0f;
220 const float safe_right = std::isfinite(input_right) ? input_right : 0.0f;
221 const float feedback_left =
222 feedback_ * ((1.0f - crossfeed_) * wet_left + crossfeed_ * wet_right);
223 const float feedback_right =
224 feedback_ * ((1.0f - crossfeed_) * wet_right + crossfeed_ * wet_left);
225 left[index] = wet_left;
226 right[index] = wet_right;
227 const auto left_status = left_history_.push(safe_left + feedback_left);
228 const auto right_status = right_history_.push(safe_right + feedback_right);
229 if (left_status != FractionalDelayStatus::ok ||
230 right_status != FractionalDelayStatus::ok)
231 ++result.fault_count;
232 ++result.processed_frames;
233 }
234 return result;
235 }
236
237 void reset() noexcept {
238 if (!prepared_)
239 return;
240 left_history_.reset();
241 right_history_.reset();
242 current_left_delay_samples_ = initialized_ ? target_left_delay_samples_ : 2.0;
243 current_right_delay_samples_ = initialized_ ? target_right_delay_samples_ : 2.0;
244 transition_remaining_ = 0;
245 }
246
247 [[nodiscard]] bool prepared() const noexcept {
248 return prepared_;
249 }
250 [[nodiscard]] bool initialized() const noexcept {
251 return initialized_;
252 }
253 [[nodiscard]] double sample_rate() const noexcept {
254 return prepared_ ? sample_rate_ : 0.0;
255 }
256 [[nodiscard]] std::size_t maximum_delay_samples() const noexcept {
257 return prepared_ ? maximum_delay_samples_ : 0;
258 }
259 [[nodiscard]] double current_delay_samples() const noexcept {
260 return prepared_ ? current_left_delay_samples_ : 0.0;
261 }
262 [[nodiscard]] double target_delay_samples() const noexcept {
263 return prepared_ ? target_left_delay_samples_ : 0.0;
264 }
265 [[nodiscard]] double current_right_delay_samples() const noexcept {
266 return prepared_ ? current_right_delay_samples_ : 0.0;
267 }
268 [[nodiscard]] double target_right_delay_samples() const noexcept {
269 return prepared_ ? target_right_delay_samples_ : 0.0;
270 }
271 [[nodiscard]] bool transition_active() const noexcept {
272 return transition_remaining_ != 0;
273 }
274 [[nodiscard]] double feedback() const noexcept {
275 return feedback_;
276 }
277 [[nodiscard]] double crossfeed() const noexcept {
278 return crossfeed_;
279 }
280 [[nodiscard]] TempoDelayWrapperInterpolation interpolation() const noexcept {
281 return interpolation_;
282 }
283 [[nodiscard]] static constexpr int latency_samples() noexcept {
284 return 0;
285 }
286 [[nodiscard]] int tail_samples() const noexcept {
287 return feedback_ > 0.0 ? -1
288 : static_cast<int>(std::max(target_left_delay_samples_,
289 target_right_delay_samples_));
290 }
291
292 private:
293 static constexpr bool is_supported(TempoDelayWrapperInterpolation interpolation) noexcept {
296 }
297
298 static constexpr std::size_t
299 minimum_delay_samples(TempoDelayWrapperInterpolation interpolation) noexcept {
301 }
302
303 [[nodiscard]] FractionalDelaySampleResult<float>
304 read(const FractionalDelayHistoryT<float>& history, double delay) const noexcept {
305 return interpolation_ == TempoDelayWrapperInterpolation::lagrange5
306 ? history.read_lagrange5_at(delay)
307 : history.read_lagrange3_at(delay);
308 }
309
310 [[nodiscard]] TempoDelayWrapperError validate_delay(double samples) const noexcept {
311 if (!std::isfinite(samples))
313 if (samples < static_cast<double>(minimum_delay_samples(interpolation_)))
315 if (samples > static_cast<double>(maximum_delay_samples_))
318 }
319
320 void adopt_target(double left_samples, double right_samples) noexcept {
321 if (!initialized_) {
322 initialized_ = true;
323 current_left_delay_samples_ = left_samples;
324 current_right_delay_samples_ = right_samples;
325 target_left_delay_samples_ = left_samples;
326 target_right_delay_samples_ = right_samples;
327 transition_remaining_ = 0;
328 return;
329 }
330 if (left_samples == target_left_delay_samples_ &&
331 right_samples == target_right_delay_samples_)
332 return;
333 target_left_delay_samples_ = left_samples;
334 target_right_delay_samples_ = right_samples;
335 if (transition_samples_ == 0) {
336 current_left_delay_samples_ = left_samples;
337 current_right_delay_samples_ = right_samples;
338 transition_remaining_ = 0;
339 return;
340 }
341 transition_remaining_ = transition_samples_;
342 }
343
344 FractionalDelayHistoryT<float> left_history_;
345 FractionalDelayHistoryT<float> right_history_;
346 double sample_rate_ = 0.0;
347 std::size_t maximum_delay_samples_ = 0;
348 double current_left_delay_samples_ = 2.0;
349 double current_right_delay_samples_ = 2.0;
350 double target_left_delay_samples_ = 2.0;
351 double target_right_delay_samples_ = 2.0;
352 double feedback_ = 0.0;
353 double crossfeed_ = 0.0;
354 std::uint32_t transition_samples_ = 64;
355 std::uint32_t transition_remaining_ = 0;
357 bool prepared_ = false;
358 bool initialized_ = false;
359};
360
361} // namespace pulp::signal
Prepared history shared by any number of stateless Lagrange read heads.
Definition fractional_delay.hpp:161
TempoDelayWrapperError set_delay_samples(double left_samples, double right_samples) noexcept
Definition tempo_delay_wrapper.hpp:119
double target_right_delay_samples() const noexcept
Definition tempo_delay_wrapper.hpp:268
int tail_samples() const noexcept
Definition tempo_delay_wrapper.hpp:286
TempoDelayWrapperError set_delay_samples(double samples) noexcept
Definition tempo_delay_wrapper.hpp:105
double current_delay_samples() const noexcept
Definition tempo_delay_wrapper.hpp:259
TempoDelayWrapper & operator=(const TempoDelayWrapper &)=delete
static constexpr double kMaximumFeedback
Definition tempo_delay_wrapper.hpp:63
double sample_rate() const noexcept
Definition tempo_delay_wrapper.hpp:253
void reset() noexcept
Definition tempo_delay_wrapper.hpp:237
TempoDelayWrapperError set_tempo(timebase::BeatDivision division, double beats_per_minute) noexcept
Definition tempo_delay_wrapper.hpp:133
bool prepared() const noexcept
Definition tempo_delay_wrapper.hpp:247
static constexpr int latency_samples() noexcept
Definition tempo_delay_wrapper.hpp:283
bool initialized() const noexcept
Definition tempo_delay_wrapper.hpp:250
TempoDelayWrapperInterpolation interpolation() const noexcept
Definition tempo_delay_wrapper.hpp:280
TempoDelayWrapperError prepare(double sample_rate, std::size_t maximum_delay_samples, TempoDelayWrapperInterpolation interpolation=TempoDelayWrapperInterpolation::lagrange3, std::uint32_t transition_samples=64)
Definition tempo_delay_wrapper.hpp:70
std::size_t maximum_delay_samples() const noexcept
Definition tempo_delay_wrapper.hpp:256
double target_delay_samples() const noexcept
Definition tempo_delay_wrapper.hpp:262
TempoDelayWrapperError set_crossfeed(double crossfeed) noexcept
Definition tempo_delay_wrapper.hpp:157
static constexpr std::size_t kMinimumCapacitySamples
Definition tempo_delay_wrapper.hpp:64
TempoDelayWrapperError set_feedback(double feedback) noexcept
Definition tempo_delay_wrapper.hpp:150
TempoDelayWrapperProcessResult process(float *left, float *right, std::size_t frame_count) noexcept
Definition tempo_delay_wrapper.hpp:164
TempoDelayWrapper(const TempoDelayWrapper &)=delete
bool transition_active() const noexcept
Definition tempo_delay_wrapper.hpp:271
double current_right_delay_samples() const noexcept
Definition tempo_delay_wrapper.hpp:265
double feedback() const noexcept
Definition tempo_delay_wrapper.hpp:274
double crossfeed() const noexcept
Definition tempo_delay_wrapper.hpp:277
Prepared fractional-delay storage with Thiran-1 and Lagrange-3/5 reads.
Definition additive_bank.hpp:218
TempoDelayWrapperInterpolation
Definition tempo_delay_wrapper.hpp:17
@ lagrange3
Definition tempo_delay_wrapper.hpp:18
@ lagrange5
Definition tempo_delay_wrapper.hpp:19
@ right
Definition multi_channel_meter.hpp:67
@ left
Definition multi_channel_meter.hpp:66
SampleType lagrange5(double fraction, SampleType ym2, SampleType ym1, SampleType y0, SampleType y1, SampleType y2, SampleType y3) noexcept
Definition fractional_delay.hpp:95
@ ok
Definition fractional_delay.hpp:34
@ invalid_delay
Definition fractional_delay.hpp:38
@ invalid_argument
Definition fractional_delay.hpp:36
@ delay
Definition envelope.hpp:57
TempoDelayWrapperError
Definition tempo_delay_wrapper.hpp:23
@ none
Definition tempo_delay_wrapper.hpp:24
@ invalid_sample_rate
Definition tempo_delay_wrapper.hpp:26
@ invalid_feedback
Definition tempo_delay_wrapper.hpp:33
@ out_of_range
Definition tempo_delay_wrapper.hpp:32
@ non_finite_input
Definition tempo_delay_wrapper.hpp:35
@ invalid_crossfeed
Definition tempo_delay_wrapper.hpp:34
@ invalid_tempo
Definition tempo_delay_wrapper.hpp:30
@ unsupported_interpolation
Definition tempo_delay_wrapper.hpp:28
@ invalid_delay
Definition tempo_delay_wrapper.hpp:29
@ invalid_capacity
Definition tempo_delay_wrapper.hpp:27
@ invalid_division
Definition tempo_delay_wrapper.hpp:31
@ not_prepared
Definition tempo_delay_wrapper.hpp:25
@ non_finite_input
Definition comb_filter.hpp:36
@ not_prepared
Definition comb_filter.hpp:33
@ invalid_sample_rate
Definition auto_ducked_send.hpp:18
constexpr double kMaximumTempoDelaySampleRate
Definition tempo_delay.hpp:41
@ out_of_range
Definition tempo_delay.hpp:24
@ invalid_tempo
Definition tempo_delay.hpp:21
@ invalid_division
Definition tempo_delay.hpp:20
@ lagrange3
Definition fractional_delay.hpp:24
@ thiran1
Definition fractional_delay.hpp:23
@ none
Definition dither.hpp:17
@ invalid_capacity
Definition graphic_eq.hpp:22
TempoDelayResult tempo_delay_samples(timebase::BeatDivision division, double beats_per_minute, double sample_rate) noexcept
Convert an exact persisted musical division to a fractional delay length.
Definition tempo_delay.hpp:50
@ out_of_range
Definition formant_filter_bank.hpp:27
BeatDivision
Definition beat_division.hpp:15
Definition tempo_delay_wrapper.hpp:38
TempoDelayWrapperError error
Definition tempo_delay_wrapper.hpp:39
std::size_t fault_count
Definition tempo_delay_wrapper.hpp:41
std::size_t processed_frames
Definition tempo_delay_wrapper.hpp:40
Musical-division conversion and fixed-state delay-time transitions.