51 if (!std::isfinite(delay_samples) || delay_samples < 1.0 || delay_samples > 2.0)
53 return {.delay_samples = delay_samples,
54 .feedback = (1.0 - delay_samples) / (1.0 + delay_samples),
59 if (!
design.valid || !std::isfinite(omega))
60 return std::numeric_limits<double>::quiet_NaN();
66 double omega)
noexcept {
67 if (!
design.valid || !std::isfinite(omega))
68 return std::numeric_limits<double>::quiet_NaN();
70 const auto denominator = 1.0 +
a *
a + 2.0 *
a * std::cos(omega);
71 if (!(denominator > 0.0) || !std::isfinite(denominator))
72 return std::numeric_limits<double>::quiet_NaN();
73 return (1.0 -
a *
a) / denominator;
77 std::array<double, 6> weights{};
78 if (!std::isfinite(fraction) || fraction < 0.0 || fraction > 1.0) {
79 weights.fill(std::numeric_limits<double>::quiet_NaN());
82 constexpr std::array<double, 6> nodes{-2.0, -1.0, 0.0, 1.0, 2.0, 3.0};
83 for (std::size_t
i = 0;
i < nodes.size(); ++
i) {
85 for (std::size_t j = 0; j < nodes.size(); ++j) {
87 weight *= (fraction - nodes[j]) / (nodes[
i] - nodes[j]);
94template <
typename SampleType>
95SampleType
lagrange5(
double fraction, SampleType ym2, SampleType ym1, SampleType y0, SampleType y1,
96 SampleType y2, SampleType y3)
noexcept {
97 static_assert(std::is_floating_point_v<SampleType>);
99 const std::array<SampleType, 6> samples{ym2, ym1, y0, y1, y2, y3};
101 for (std::size_t
i = 0;
i < samples.size(); ++
i)
102 output +=
static_cast<SampleType
>(weights[
i]) * samples[
i];
116 if (!std::isfinite(delay_samples) || delay_samples < 0.0)
118 const auto nearest = std::round(delay_samples);
119 const auto tolerance =
120 8.0 * std::numeric_limits<double>::epsilon() * std::max(1.0, std::abs(delay_samples));
121 const auto canonical = std::abs(delay_samples -
nearest) <= tolerance ?
nearest : delay_samples;
124 constexpr auto size_digits = std::numeric_limits<std::size_t>::digits;
125 constexpr auto double_digits = std::numeric_limits<double>::digits;
126 const auto size_limit =
static_cast<double>(std::numeric_limits<std::size_t>::max());
127 if constexpr (size_digits <= double_digits) {
128 if (canonical > size_limit)
133 if (canonical >= size_limit)
136 const auto integer_value = std::floor(canonical);
137 return {.integer =
static_cast<std::size_t
>(integer_value),
138 .fraction = canonical - integer_value,
139 .canonical = canonical,
149 [[nodiscard]]
explicit operator bool() const noexcept {
162 static_assert(std::is_floating_point_v<SampleType>);
170 move_from(std::move(other));
175 move_from(std::move(other));
180 constexpr auto additional_history = 3u;
186 std::vector<SampleType> replacement;
187 if (storage_size > replacement.max_size()) {
190#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
193 replacement.assign(storage_size, SampleType{});
194#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
195 }
catch (
const std::bad_alloc&) {
197 }
catch (
const std::length_error&) {
202 buffer_.swap(replacement);
211 std::fill(buffer_.begin(), buffer_.end(), SampleType{});
228 return prepared_ ? maximum_delay_samples_ : 0
u;
231 return buffer_.size();
234 return buffer_.size() *
sizeof(SampleType);
237 return prepared_ ? maximum_delay_samples_ + 3u : 0
u;
240 [[nodiscard]]
static constexpr std::size_t
257 write_ = (write_ + 1u) % buffer_.size();
258 valid_ = std::min(valid_ + 1u, buffer_.size());
273 if (!split.valid || split.canonical <
static_cast<double>(minimum) ||
274 split.canonical >
static_cast<double>(maximum_delay_samples_))
277 long double output = 0.0L;
280 static_cast<long double>(split.fraction), tap_delay(split.integer - 1u),
281 tap_delay(split.integer), tap_delay(split.integer + 1u),
282 tap_delay(split.integer + 2u)));
285 split.fraction, tap_delay(split.integer - 2u), tap_delay(split.integer - 1u),
286 tap_delay(split.integer), tap_delay(split.integer + 1u),
287 tap_delay(split.integer + 2u), tap_delay(split.integer + 3u));
290 const auto limit =
static_cast<long double>(std::numeric_limits<SampleType>::max());
291 if (!std::isfinite(output) || output > limit || output < -limit)
308 buffer_ = std::move(other.buffer_);
309 write_ = std::exchange(other.write_, 0
u);
310 valid_ = std::exchange(other.valid_, 0
u);
311 maximum_delay_samples_ = std::exchange(other.maximum_delay_samples_, 0
u);
312 prepared_ = std::exchange(other.prepared_,
false);
315 other.buffer_.clear();
318 [[nodiscard]]
long double tap_delay(std::size_t
delay)
const noexcept {
321 const auto index = (write_ + buffer_.size() -
delay) % buffer_.size();
322 return static_cast<long double>(buffer_[index]);
325 std::vector<SampleType> buffer_{};
326 std::size_t write_ = 0;
327 std::size_t valid_ = 0;
328 std::size_t maximum_delay_samples_ = 0;
329 bool prepared_ =
false;
337 [[nodiscard]]
explicit operator bool() const noexcept {
351 static_assert(std::is_floating_point_v<SampleType>);
361 std::size_t integer_interval_start) {
363 integer_interval_start);
382 std::vector<SampleType> replacement;
383 if (storage_size > replacement.max_size()) {
386#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
389 replacement.assign(storage_size, SampleType{});
390#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
391 }
catch (
const std::bad_alloc&) {
393 }
catch (
const std::length_error&) {
398 buffer_.swap(replacement);
409 std::fill(buffer_.begin(), buffer_.end(), SampleType{});
416 [[nodiscard]] std::optional<FractionalDelayMethod>
method() const noexcept {
417 return prepared_ ? std::optional{method_} : std::nullopt;
420 return prepared_ ? maximum_delay_samples_ : 0
u;
428 return thiran_integer_interval_start_;
431 return buffer_.size();
442 [[nodiscard]]
static constexpr std::size_t
449 [[nodiscard]]
static constexpr std::size_t
461 double delay_samples)
noexcept {
464 if (!std::isfinite(input)) {
470 const auto outside_general_range =
472 split.canonical >
static_cast<double>(maximum_delay_samples_);
473 const auto outside_thiran_interval =
475 (!split.valid || split.integer != thiran_integer_interval_start_);
476 if (outside_general_range || outside_thiran_interval) {
482 long double output = 0.0L;
484 output = read_thiran1(split.fraction);
486 output = read_lagrange3(split.integer, split.fraction);
488 output = read_lagrange5(split.integer, split.fraction);
490 const auto limit =
static_cast<long double>(std::numeric_limits<SampleType>::max());
491 if (!std::isfinite(output) || output > limit || output < -limit) {
492 replace_latest_with_zero();
493 reset_recursive_state();
501 double delay_samples)
noexcept {
504 if (input ==
nullptr || output ==
nullptr)
508 return process_block(input, output, frames,
509 [delay_samples](std::size_t)
noexcept {
return delay_samples; });
513 const double* delay_samples,
514 std::size_t frames)
noexcept {
517 if (input ==
nullptr || output ==
nullptr || delay_samples ==
nullptr)
521 return process_block(input, output, frames,
522 [delay_samples](std::size_t
i)
noexcept {
return delay_samples[
i]; });
526 template <
typename DelayAt>
528 std::size_t frames, DelayAt&& delay_at)
noexcept {
530 for (std::size_t
i = 0;
i < frames; ++
i) {
531 const auto current_input = input[
i];
532 const auto current =
process(current_input, delay_at(
i));
533 output[
i] = current.sample;
537 result.
status = current.status;
544 void reset_state() noexcept {
547 reset_recursive_state();
550 void reset_recursive_state() noexcept {
551 thiran_input_ = 0.0L;
552 thiran_output_ = 0.0L;
555 void push(SampleType sample)
noexcept {
556 buffer_[write_] = sample;
557 write_ = (write_ + 1u) % buffer_.size();
558 valid_ = std::min(valid_ + 1u, buffer_.size());
561 void inject_zero() noexcept {
563 reset_recursive_state();
566 void replace_latest_with_zero() noexcept {
567 const auto index = (write_ + buffer_.size() - 1u) % buffer_.size();
568 buffer_[
index] = SampleType{};
571 [[nodiscard]]
long double tap(std::size_t
delay)
const noexcept {
574 const auto index = (write_ + buffer_.size() - 1u -
delay) % buffer_.size();
575 return static_cast<long double>(buffer_[
index]);
578 [[nodiscard]]
long double read_thiran1(
double fraction)
noexcept {
579 const auto source = tap(thiran_integer_interval_start_ - 1u);
581 const auto a =
static_cast<long double>(design.feedback);
582 const auto output =
a * source + thiran_input_ -
a * thiran_output_;
583 thiran_input_ = source;
584 thiran_output_ = output;
588 [[nodiscard]]
long double read_lagrange3(std::size_t integer,
double fraction)
const noexcept {
590 static_cast<long double>(fraction), tap(integer - 1u), tap(integer), tap(integer + 1u),
594 [[nodiscard]]
long double read_lagrange5(std::size_t integer,
double fraction)
const noexcept {
596 tap(integer + 1u), tap(integer + 2u), tap(integer + 3u));
599 std::vector<SampleType> buffer_{};
600 std::size_t write_ = 0;
601 std::size_t valid_ = 0;
602 std::size_t maximum_delay_samples_ = 0;
603 std::size_t thiran_integer_interval_start_ = 1;
605 long double thiran_input_ = 0.0L;
606 long double thiran_output_ = 0.0L;
607 bool prepared_ =
false;
Prepared history shared by any number of stateless Lagrange read heads.
Definition fractional_delay.hpp:161
FractionalDelaySampleResult< SampleType > read_at(double delay_samples, FractionalDelayMethod method) const noexcept
Reads one stateless head from the current history snapshot.
Definition fractional_delay.hpp:265
FractionalDelayHistoryT()=default
FractionalDelayHistoryT(const FractionalDelayHistoryT &)=delete
FractionalDelayHistoryT(FractionalDelayHistoryT &&other) noexcept
Definition fractional_delay.hpp:169
bool prepare(std::size_t maximum_delay_samples)
Definition fractional_delay.hpp:179
std::size_t retained_samples() const noexcept
Definition fractional_delay.hpp:230
std::size_t retained_bytes() const noexcept
Definition fractional_delay.hpp:233
FractionalDelaySampleResult< SampleType > read_lagrange5_at(double delay_samples) const noexcept
Definition fractional_delay.hpp:302
void reset() noexcept
Definition fractional_delay.hpp:210
FractionalDelayStatus push(SampleType sample) noexcept
Advances history by one sample.
Definition fractional_delay.hpp:251
FractionalDelayHistoryT & operator=(FractionalDelayHistoryT &&other) noexcept
Definition fractional_delay.hpp:173
void discard_history() noexcept
Forgets all prior samples in constant time.
Definition fractional_delay.hpp:219
FractionalDelayHistoryT & operator=(const FractionalDelayHistoryT &)=delete
bool prepared() const noexcept
Definition fractional_delay.hpp:224
std::size_t required_older_lookback() const noexcept
Definition fractional_delay.hpp:236
std::size_t maximum_delay_samples() const noexcept
Definition fractional_delay.hpp:227
FractionalDelaySampleResult< SampleType > read_lagrange3_at(double delay_samples) const noexcept
Definition fractional_delay.hpp:297
static constexpr std::size_t minimum_delay_samples(FractionalDelayMethod method) noexcept
Definition fractional_delay.hpp:241
A prepared mono fractional-delay line.
Definition fractional_delay.hpp:350
static constexpr std::size_t minimum_delay_samples(FractionalDelayMethod method) noexcept
Definition fractional_delay.hpp:443
std::size_t retained_samples() const noexcept
Definition fractional_delay.hpp:430
bool prepared() const noexcept
Definition fractional_delay.hpp:413
FractionalDelaySampleResult< SampleType > process(SampleType input, double delay_samples) noexcept
Definition fractional_delay.hpp:460
bool prepare_thiran1(std::size_t maximum_delay_samples, std::size_t integer_interval_start)
Prepares Thiran-1 for delays in [integer_interval_start, integer_interval_start + 1).
Definition fractional_delay.hpp:360
std::optional< std::size_t > thiran_integer_interval_start() const noexcept
Definition fractional_delay.hpp:425
std::optional< FractionalDelayMethod > method() const noexcept
Definition fractional_delay.hpp:416
std::size_t required_older_lookback() const noexcept
Definition fractional_delay.hpp:433
std::size_t maximum_delay_samples() const noexcept
Definition fractional_delay.hpp:419
bool prepare(std::size_t maximum_delay_samples, FractionalDelayMethod method)
Definition fractional_delay.hpp:354
FractionalDelayBlockResult process(const SampleType *input, SampleType *output, std::size_t frames, double delay_samples) noexcept
Definition fractional_delay.hpp:499
static constexpr std::size_t older_stencil_lookback(FractionalDelayMethod method) noexcept
Definition fractional_delay.hpp:450
FractionalDelayBlockResult process(const SampleType *input, SampleType *output, const double *delay_samples, std::size_t frames) noexcept
Definition fractional_delay.hpp:512
void reset() noexcept
Definition fractional_delay.hpp:408
static constexpr std::size_t processing_latency_samples() noexcept
Processing itself adds no host-compensated latency.
Definition fractional_delay.hpp:438
std::size_t minimum_delay_samples() const noexcept
Definition fractional_delay.hpp:422
High-quality sample interpolation for delay lines and resampling.
Definition design_adherence.hpp:32
@ index
Definition gpu_pass.hpp:140
FractionalDelaySplit split_fractional_delay(double delay_samples) noexcept
Definition fractional_delay.hpp:115
Definition additive_bank.hpp:218
double thiran1_group_delay_samples(const Thiran1Coefficients &design, double omega) noexcept
Exact group delay of the first-order allpass at angular frequency omega.
Definition fractional_delay.hpp:65
FractionalDelayHistoryT< float > FractionalDelayHistory
Definition fractional_delay.hpp:612
SampleType lagrange5(double fraction, SampleType ym2, SampleType ym1, SampleType y0, SampleType y1, SampleType y2, SampleType y3) noexcept
Definition fractional_delay.hpp:95
FractionalDelayHistoryT< double > FractionalDelayHistory64
Definition fractional_delay.hpp:613
FractionalDelayStatus
Definition fractional_delay.hpp:33
@ ok
Definition fractional_delay.hpp:34
@ non_finite_input
Definition fractional_delay.hpp:37
@ output_out_of_range
Definition fractional_delay.hpp:39
@ invalid_delay
Definition fractional_delay.hpp:38
@ not_prepared
Definition fractional_delay.hpp:35
@ invalid_argument
Definition fractional_delay.hpp:36
@ delay
Definition envelope.hpp:57
FractionalDelayLineT< double > FractionalDelayLine64
Definition fractional_delay.hpp:611
@ non_finite_input
Definition comb_filter.hpp:36
@ output_out_of_range
Definition comb_filter.hpp:37
@ not_prepared
Definition comb_filter.hpp:33
@ invalid_argument
Definition comb_filter.hpp:35
std::array< double, 6 > lagrange5_weights(double fraction) noexcept
Definition fractional_delay.hpp:76
double thiran1_magnitude(const Thiran1Coefficients &design, double omega) noexcept
Definition fractional_delay.hpp:58
@ a
Definition formant_filter_bank.hpp:20
@ u
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
FractionalDelayLineT< float > FractionalDelayLine
Definition fractional_delay.hpp:610
@ nearest
Snap to the nearest in-scale degree in either direction.
Definition harmony_engine.hpp:175
FractionalDelayMethod
Definition fractional_delay.hpp:22
@ lagrange3
Definition fractional_delay.hpp:24
@ lagrange5
Definition fractional_delay.hpp:25
@ thiran1
Definition fractional_delay.hpp:23
@ ok
Definition breakpoint_envelope.hpp:36
Thiran1Coefficients design_thiran1(double delay_samples) noexcept
Designs H(z) = (a + z^-1) / (1 + a z^-1), where a = (1 - D) / (1 + D).
Definition fractional_delay.hpp:50
constexpr bool is_valid_fractional_delay_method(FractionalDelayMethod method) noexcept
Definition fractional_delay.hpp:28
Definition fractional_delay.hpp:332
FractionalDelayStatus status
Definition fractional_delay.hpp:333
std::size_t processed_frames
Definition fractional_delay.hpp:334
std::size_t fault_count
Definition fractional_delay.hpp:335
Definition fractional_delay.hpp:145
FractionalDelayStatus status
Definition fractional_delay.hpp:147
SampleType sample
Definition fractional_delay.hpp:146
static SampleType lagrange(SampleType frac, SampleType ym1, SampleType y0, SampleType y1, SampleType y2)
4-point Lagrange interpolation.
Definition interpolator.hpp:60
Definition fractional_delay.hpp:42
double feedback
Definition fractional_delay.hpp:44
bool valid
Definition fractional_delay.hpp:45
double delay_samples
Definition fractional_delay.hpp:43
Definition fractional_delay.hpp:108
double canonical
Definition fractional_delay.hpp:111
std::size_t integer
Definition fractional_delay.hpp:109
bool valid
Definition fractional_delay.hpp:112
double fraction
Definition fractional_delay.hpp:110