31 static_assert(std::is_floating_point_v<SampleType>);
59 if (required >
static_cast<double>(std::numeric_limits<int>::max() - 1))
61 const int maximum_delay_samples =
static_cast<int>(required);
65#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
68 replacement_left.
prepare(maximum_delay_samples);
69 replacement_right.
prepare(maximum_delay_samples);
70#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
71 }
catch (
const std::bad_alloc&) {
73 }
catch (
const std::length_error&) {
78 left_history_ = std::move(replacement_left);
79 right_history_ = std::move(replacement_right);
95 return maximum_delay_ms_;
100 const auto samples =
static_cast<std::size_t
>(left_history_.max_delay() + 1);
101 return 2u * samples *
sizeof(SampleType);
105 active_tap_count_ = std::min(count,
kMaxTaps);
106 update_feedback_weights();
109 return active_tap_count_;
112 [[nodiscard]]
bool set_tap(std::size_t index, Tap
tap)
noexcept {
113 if (index >=
kMaxTaps || !std::isfinite(
tap.delay_ms) ||
114 !std::isfinite(
tap.level) || !std::isfinite(
tap.pan) ||
115 !std::isfinite(
tap.stereo_width) || !std::isfinite(
tap.feedback_weight))
118 tap.delay_ms = std::clamp(
tap.delay_ms, minimum_delay_ms(), maximum_delay_ms_);
122 tap.level = std::clamp(
tap.level, -1.0, 1.0);
123 tap.pan = std::clamp(
tap.pan, -1.0, 1.0);
124 tap.stereo_width = std::clamp(
tap.stereo_width, 0.0, 1.0);
125 tap.feedback_weight = std::clamp(
tap.feedback_weight, -1.0, 1.0);
129 update_feedback_weights();
133 Tap
tap(std::size_t index)
const noexcept {
134 return index <
kMaxTaps ? taps_[index] : Tap{};
138 if (std::isfinite(gain))
142 return feedback_gain_;
148 if (std::isfinite(amount01))
149 cross_feedback_ = std::clamp(amount01, 0.0, 1.0);
152 return cross_feedback_;
156 return std::abs(feedback_gain_);
164 if (!prepared_ || active_tap_count_ == 0)
166 bool has_feedback_route =
false;
167 bool has_audible_route =
false;
168 double latest_delay = 0.0;
169 for (std::size_t
i = 0;
i < active_tap_count_; ++
i) {
170 has_feedback_route |= derived_[
i].normalized_feedback_weight != 0.0;
171 if (taps_[
i].level != 0.0) {
172 has_audible_route =
true;
173 latest_delay = std::max(latest_delay, derived_[
i].delay_samples);
176 if (!has_audible_route)
178 if (feedback_gain_ != 0.0 && has_feedback_route)
180 return static_cast<int>(std::ceil(latest_delay));
184 left_history_.discard_history();
185 right_history_.discard_history();
188 void process_sample(SampleType input_left, SampleType input_right, SampleType& output_left,
189 SampleType& output_right)
noexcept {
190 output_left = SampleType{};
191 output_right = SampleType{};
194 if (!std::isfinite(
static_cast<double>(input_left)) ||
195 !std::isfinite(
static_cast<double>(input_right))) {
200 long double wet_left = 0.0L;
201 long double wet_right = 0.0L;
202 long double feedback_left = 0.0L;
203 long double feedback_right = 0.0L;
205 for (std::size_t
i = 0;
i < active_tap_count_; ++
i) {
206 const auto&
tap = taps_[
i];
207 const auto& derived = derived_[
i];
208 const auto history_delay =
static_cast<SampleType
>(derived.delay_samples - 1.0);
209 const SampleType delayed_left = left_history_.read(history_delay);
210 const SampleType delayed_right = right_history_.read(history_delay);
212 wet_left +=
static_cast<long double>(
tap.level) *
213 (derived.left_to_left * delayed_left +
214 derived.right_to_left * delayed_right);
215 wet_right +=
static_cast<long double>(
tap.level) *
216 (derived.left_to_right * delayed_left +
217 derived.right_to_right * delayed_right);
218 feedback_left += derived.normalized_feedback_weight * delayed_left;
219 feedback_right += derived.normalized_feedback_weight * delayed_right;
222 const long double self = 1.0L -
static_cast<long double>(cross_feedback_);
223 const long double cross =
static_cast<long double>(cross_feedback_);
224 const long double gain =
static_cast<long double>(feedback_gain_);
225 const long double write_left =
226 static_cast<long double>(input_left) + gain * (self * feedback_left + cross * feedback_right);
227 const long double write_right =
228 static_cast<long double>(input_right) + gain * (self * feedback_right + cross * feedback_left);
229 const long double limit =
static_cast<long double>(std::numeric_limits<SampleType>::max());
231 if (!std::isfinite(wet_left) || !std::isfinite(wet_right) ||
232 !std::isfinite(write_left) || !std::isfinite(write_right) ||
233 std::abs(wet_left) > limit || std::abs(wet_right) > limit ||
234 std::abs(write_left) > limit || std::abs(write_right) > limit) {
239 output_left =
snap_to_zero(
static_cast<SampleType
>(wet_left));
240 output_right =
snap_to_zero(
static_cast<SampleType
>(wet_right));
241 left_history_.push(
snap_to_zero(
static_cast<SampleType
>(write_left)));
242 right_history_.push(
snap_to_zero(
static_cast<SampleType
>(write_right)));
245 void process_block(
const SampleType* input_left,
const SampleType* input_right,
246 SampleType* output_left, SampleType* output_right,
247 std::size_t sample_count)
noexcept {
248 if (input_left ==
nullptr || input_right ==
nullptr || output_left ==
nullptr ||
249 output_right ==
nullptr)
251 for (std::size_t
i = 0;
i < sample_count; ++
i) {
252 const SampleType
left = input_left[
i];
253 const SampleType
right = input_right[
i];
260 double delay_samples = 12000.0;
261 double left_to_left = 1.0;
262 double left_to_right = 0.0;
263 double right_to_left = 0.0;
264 double right_to_right = 1.0;
265 double normalized_feedback_weight = 1.0;
268 double minimum_delay_ms() const noexcept {
269 return 1000.0 / sample_rate_;
272 void update_tap(std::size_t index)
noexcept {
273 auto&
tap = taps_[index];
274 auto& derived = derived_[index];
279 const double reconstructed =
tap.
delay_ms * sample_rate_ / 1000.0;
280 const double nearest_integer = std::round(reconstructed);
281 const double integer_tolerance =
282 8.0 * std::numeric_limits<double>::epsilon() *
283 std::max(1.0, std::abs(reconstructed));
284 const double canonical =
285 std::abs(reconstructed - nearest_integer) <= integer_tolerance
288 derived.delay_samples = std::max(1.0, canonical);
292 const auto gains = [](
double position) {
293 const double angle = (position + 1.0) * std::numbers::pi / 4.0;
294 return std::array<double, 2>{std::cos(angle), std::sin(angle)};
296 const auto left_gains = gains(left_position);
297 const auto right_gains = gains(right_position);
298 derived.left_to_left = left_gains[0];
299 derived.left_to_right = left_gains[1];
300 derived.right_to_left = right_gains[0];
301 derived.right_to_right = right_gains[1];
304 void update_feedback_weights() noexcept {
306 for (std::size_t
i = 0;
i < active_tap_count_; ++
i)
307 sum += std::abs(taps_[
i].feedback_weight);
308 const double denominator = std::max(1.0, sum);
310 derived_[
i].normalized_feedback_weight =
311 i < active_tap_count_ ? taps_[
i].feedback_weight / denominator : 0.0;
314 void update_all_taps() noexcept {
317 update_feedback_weights();
320 double sample_rate_ = 48000.0;
321 double maximum_delay_ms_ = 2000.0;
322 double feedback_gain_ = 0.0;
323 double cross_feedback_ = 1.0;
324 std::size_t active_tap_count_ = 1;
325 bool prepared_ =
false;
326 std::array<Tap, kMaxTaps> taps_{};
327 std::array<DerivedTap, kMaxTaps> derived_{};
328 DelayLineT<SampleType> left_history_{};
329 DelayLineT<SampleType> right_history_{};
A wet-only stereo multitap delay with normalized tap feedback.
Definition cross_feedback_multitap_delay.hpp:29
static constexpr double kMaximumDelayMs
Definition cross_feedback_multitap_delay.hpp:36
double cross_feedback() const noexcept
Definition cross_feedback_multitap_delay.hpp:151
double sample_rate() const noexcept
Definition cross_feedback_multitap_delay.hpp:91
void reset() noexcept
Definition cross_feedback_multitap_delay.hpp:183
std::size_t retained_bytes() const noexcept
Definition cross_feedback_multitap_delay.hpp:97
bool prepare(double sample_rate, double maximum_delay_ms=2000.0)
Definition cross_feedback_multitap_delay.hpp:51
static constexpr double kMinimumSampleRate
Definition cross_feedback_multitap_delay.hpp:34
std::size_t active_tap_count() const noexcept
Definition cross_feedback_multitap_delay.hpp:108
void set_feedback_gain(double gain) noexcept
Definition cross_feedback_multitap_delay.hpp:137
static constexpr double kMaximumFeedbackGain
Definition cross_feedback_multitap_delay.hpp:37
bool prepared() const noexcept
Definition cross_feedback_multitap_delay.hpp:88
void process_sample(SampleType input_left, SampleType input_right, SampleType &output_left, SampleType &output_right) noexcept
Definition cross_feedback_multitap_delay.hpp:188
bool set_tap(std::size_t index, Tap tap) noexcept
Definition cross_feedback_multitap_delay.hpp:112
CrossFeedbackMultitapDelayT() noexcept
Definition cross_feedback_multitap_delay.hpp:47
double worst_case_feedback_gain() const noexcept
Definition cross_feedback_multitap_delay.hpp:155
Tap tap(std::size_t index) const noexcept
Definition cross_feedback_multitap_delay.hpp:133
static constexpr double kMaximumSampleRate
Definition cross_feedback_multitap_delay.hpp:35
static constexpr int latency_samples() noexcept
Definition cross_feedback_multitap_delay.hpp:159
void set_active_tap_count(std::size_t count) noexcept
Definition cross_feedback_multitap_delay.hpp:104
static constexpr std::size_t kMaxTaps
Definition cross_feedback_multitap_delay.hpp:33
int tail_samples() const noexcept
Definition cross_feedback_multitap_delay.hpp:163
double maximum_delay_ms() const noexcept
Definition cross_feedback_multitap_delay.hpp:94
void set_cross_feedback(double amount01) noexcept
Zero is independent same-channel feedback; one is pure cross-channel feedback.
Definition cross_feedback_multitap_delay.hpp:147
double feedback_gain() const noexcept
Definition cross_feedback_multitap_delay.hpp:141
void process_block(const SampleType *input_left, const SampleType *input_right, SampleType *output_left, SampleType *output_right, std::size_t sample_count) noexcept
Definition cross_feedback_multitap_delay.hpp:245
Definition delay_line.hpp:13
void prepare(int max_delay_samples)
Definition delay_line.hpp:16
Definition additive_bank.hpp:218
CrossFeedbackMultitapDelayT< float > CrossFeedbackMultitapDelay
Definition cross_feedback_multitap_delay.hpp:332
@ right
Definition multi_channel_meter.hpp:67
@ left
Definition multi_channel_meter.hpp:66
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
@ i
Definition formant_filter_bank.hpp:20
CrossFeedbackMultitapDelayT< double > CrossFeedbackMultitapDelay64
Definition cross_feedback_multitap_delay.hpp:333
float sum(const float *data, std::size_t count) noexcept
Definition cross_feedback_multitap_delay.hpp:39
double stereo_width
Definition cross_feedback_multitap_delay.hpp:43
double pan
Definition cross_feedback_multitap_delay.hpp:42
double feedback_weight
Definition cross_feedback_multitap_delay.hpp:44
double delay_ms
Definition cross_feedback_multitap_delay.hpp:40
double level
Definition cross_feedback_multitap_delay.hpp:41