159template <
typename SampleType =
float>
252 sample_rate_ = sample_rate > 0.0 ? sample_rate : sample_rate_;
257 modulated_capacity_ =
264 const auto pole =
static_cast<double>(std::exp(-kTwoPi *
kDcBlockHz / sample_rate_));
265 for (
auto& ch : channels_) {
266 ch.line.prepare(modulated_capacity_);
267 ch.fixed.assign(fixed_capacity_, 0.0);
269 ch.blocker.set_pole(pole);
270 ch.bbd.prepare(sample_rate_);
274 ch.shifter.prepare(sample_rate_);
275 ch.shifter.set_feedback(0.0);
276 ch.shifter.set_mix(1.0);
277 ch.shifter.set_shift_hz(barberpole_hz_);
284 for (
auto& lfo : lfos_) lfo.prepare(sample_rate_);
285 mode_switch_samples_ =
288 apply_lfo_settings();
295 for (
auto& ch : channels_) {
297 std::fill(ch.fixed.begin(), ch.fixed.end(), 0.0);
305 ch.delay_smoother.set_immediate(delay_samples_for(mode_, 0.0));
307 for (
auto& lfo : lfos_) lfo.reset();
308 mode_switch_remaining_ = 0;
309 previous_mode_ = mode_;
315 for (
auto& ch : channels_) {
316 ch.line.discard_history();
321 ch.shifter.discard_history();
324 ch.delay_smoother.set_immediate(delay_samples_for(mode_, 0.0));
326 for (
auto& lfo : lfos_) lfo.reset();
327 mode_switch_remaining_ = 0;
328 previous_mode_ = mode_;
337 if (
mode == mode_)
return;
338 previous_mode_ = mode_;
340 mode_switch_remaining_ = mode_switch_samples_;
352 if (!std::isfinite(hz))
return;
354 apply_lfo_settings();
359 apply_lfo_settings();
370 if (!std::isfinite(cycles01))
return;
371 stereo_spread_ = std::clamp(cycles01, 0.0, 0.5);
372 apply_lfo_settings();
379 if (!std::isfinite(ms))
return;
385 if (!std::isfinite(ms))
return;
391 if (!std::isfinite(ms))
return;
400 if (!std::isfinite(fb))
return;
409 if (!std::isfinite(wet01))
return;
410 mix_ = std::clamp(wet01, 0.0, 1.0);
412 double mix()
const {
return mix_; }
416 if (!std::isfinite(hz))
return;
418 for (
auto& ch : channels_) ch.shifter.set_shift_hz(barberpole_hz_);
424 void process(
const SampleType* in, SampleType* out,
int n) {
425 for (
int i = 0;
i < n; ++
i) {
426 if (!std::isfinite(
static_cast<double>(in[
i]))) {
428 out[
i] = SampleType{0};
431 const Sweep sweep = advance_sweep();
432 out[
i] =
static_cast<SampleType
>(
433 run_channel(channels_[0],
static_cast<double>(in[
i]), sweep));
438 SampleType* out_left, SampleType* out_right,
int n) {
439 for (
int i = 0;
i < n; ++
i) {
440 if (!std::isfinite(
static_cast<double>(in_left[
i])) ||
441 !std::isfinite(
static_cast<double>(in_right[
i]))) {
443 out_left[
i] = out_right[
i] = SampleType{0};
446 const Sweep sweep = advance_sweep();
447 out_left[
i] =
static_cast<SampleType
>(
448 run_channel(channels_[0],
static_cast<double>(in_left[
i]), sweep));
449 out_right[
i] =
static_cast<SampleType
>(
450 run_channel(channels_[1],
static_cast<double>(in_right[
i]), sweep, 1));
491 return channels_[
static_cast<std::size_t
>(channel)].delay_ms;
504 const double d = delay_ms * 0.001;
505 return polarity == Polarity::positive ? (2.0 * k + 1.0) / (2.0 * d)
506 :
static_cast<double>(k) / d;
524 static void mix_gains(
double wet01,
double& dry_gain,
double& wet_gain) {
525 const double theta = std::clamp(wet01, 0.0, 1.0) * 0.5 * kPi;
526 dry_gain = std::cos(theta);
527 wet_gain = std::sin(theta);
531 static constexpr double kPi = 3.14159265358979323846;
532 static constexpr double kTwoPi = 6.283185307179586476925286766559;
537 double lfo[2] = {0.0, 0.0};
538 double wet_scale = 1.0;
539 double old_dry = 0.0;
540 double new_dry = 1.0;
541 bool switching =
false;
542 bool past_midpoint =
true;
543 bool snap_delay =
false;
547 chardelay::FractionalDelayLine line{};
548 std::vector<double> fixed{};
549 std::size_t fixed_write = 0;
550 std::size_t fixed_valid = 0;
551 DcBlocker<double> blocker{};
552 chardelay::BbdChannel bbd{};
553 SsbFrequencyShifterT<double> shifter{};
554 SmoothedValue<double> delay_smoother{};
555 double feedback = 0.0;
556 double delay_ms = 0.0;
559 Sweep advance_sweep() {
567 s.lfo[0] =
static_cast<double>(lfos_[0].next());
568 s.lfo[1] =
static_cast<double>(lfos_[1].next());
570 const bool was_past = past_midpoint();
571 if (mode_switch_remaining_ > 0) --mode_switch_remaining_;
572 s.switching = mode_switch_remaining_ > 0;
573 s.past_midpoint = past_midpoint();
574 s.snap_delay = !was_past &&
s.past_midpoint;
578 1.0 -
static_cast<double>(mode_switch_remaining_) /
579 static_cast<double>(mode_switch_samples_);
581 s.old_dry,
s.new_dry);
584 s.wet_scale = std::abs(std::cos(kPi *
progress));
589 bool past_midpoint()
const {
590 return mode_switch_remaining_ <= 0 || mode_switch_remaining_ * 2 <= mode_switch_samples_;
594 double delay_samples_for(
Mode mode,
double lfo)
const {
598 ms = center_ms_ + effective_depth_for(
mode) * lfo;
600 case Mode::through_zero:
610 case Mode::barberpole:
617 double effective_depth_for(
Mode mode)
const {
621 case Mode::through_zero:
622 return std::min(depth_ms_, offset_ms_);
623 case Mode::barberpole:
640 double run_channel(Channel& ch,
double input,
const Sweep& sweep,
int index = 0) {
641 const Mode active = sweep.past_midpoint ? mode_ : previous_mode_;
645 const double target = delay_samples_for(active, sweep.lfo[index]);
646 if (sweep.snap_delay) ch.delay_smoother.set_immediate(target);
647 ch.delay_smoother.set_target(target);
648 const double delay = std::clamp(ch.delay_smoother.next(), 1.0, ch.line.max_delay());
655 const double dry_direct = input;
656 const double dry_fixed = read_fixed(ch);
657 push_fixed(ch, input);
660 ? sweep.old_dry * dry_for(previous_mode_, dry_direct, dry_fixed) +
661 sweep.new_dry * dry_for(mode_, dry_direct, dry_fixed)
662 : dry_for(active, dry_direct, dry_fixed);
673 const double sign = polarity_ == Polarity::negative ? -1.0 : 1.0;
676 if (engine_ == Engine::bbd) {
683 const double recirculated =
snap_to_zero(ch.blocker.process(ch.feedback));
685 wet = ch.bbd.process(input + feedback_ * sign * recirculated) * sweep.wet_scale;
700 wet = ch.line.read(std::max(1.0,
delay - 1.0)) * sweep.wet_scale;
706 if (active == Mode::barberpole) wet = ch.shifter.process(wet);
713 const double recirculated =
snap_to_zero(ch.blocker.process(wet));
714 ch.line.push(input + feedback_ * sign * recirculated);
718 double dry_gain = 0.0, wet_gain = 0.0;
721 return snap_to_zero(dry_gain * dry + wet_gain * sign * wet);
728 double read_fixed(
const Channel& ch)
const {
730 const std::size_t capacity = ch.fixed.size();
731 if (capacity == 0)
return 0.0;
732 if (ch.fixed_valid < capacity &&
delay >= ch.fixed_valid)
return 0.0;
733 const std::size_t
index = (ch.fixed_write + capacity - (
delay % capacity)) % capacity;
734 return ch.fixed[
index];
737 void push_fixed(Channel& ch,
double x) {
738 if (ch.fixed.empty())
return;
739 ch.fixed[ch.fixed_write] =
x;
740 ch.fixed_write = (ch.fixed_write + 1u) % ch.fixed.size();
741 ch.fixed_valid = std::min(ch.fixed_valid + 1u, ch.fixed.size());
744 void apply_lfo_settings() {
745 for (std::size_t
i = 0;
i < lfos_.size(); ++
i) {
746 lfos_[
i].set_rate_hz(rate_hz_);
747 lfos_[
i].set_wave(wave_);
748 lfos_[
i].set_phase_offset(
i == 0 ? 0.0 : stereo_spread_);
752 double sample_rate_ = 44100.0;
753 std::size_t modulated_capacity_ = 8;
754 std::size_t fixed_capacity_ = 8;
756 Mode mode_ = Mode::classic;
757 Mode previous_mode_ = Mode::classic;
758 Polarity polarity_ = Polarity::positive;
759 Engine engine_ = Engine::clean;
762 double rate_hz_ = 0.5;
763 double depth_ms_ = 1.5;
764 double center_ms_ = 3.0;
765 double offset_ms_ = 4.0;
766 double feedback_ = 0.6;
768 double stereo_spread_ = 0.25;
769 double barberpole_hz_ = 3.0;
771 int mode_switch_samples_ = 2;
772 int mode_switch_remaining_ = 0;
774 std::array<Channel, 2> channels_{};
775 std::array<EffectLfoT<double>, 2> lfos_{};
The flanger.
Definition flanger.hpp:160
void set_mode(Mode mode)
Takes effect through a kModeSwitchMs transition rather than instantly.
Definition flanger.hpp:336
double offset_ms() const
Definition flanger.hpp:394
static constexpr double kControlSmoothMs
Definition flanger.hpp:228
Polarity polarity() const
Definition flanger.hpp:346
void prepare(double sample_rate)
Sizes both delay buffers for the widest sweep any mode can ask for and prepares the composed engines.
Definition flanger.hpp:251
static constexpr double kCenterMaxMs
Definition flanger.hpp:201
int latency_samples() const noexcept
Zero in classic and barberpole modes — the dry path is a direct passthrough and the mix is a gain,...
Definition flanger.hpp:467
void set_offset_ms(double ms)
Definition flanger.hpp:390
static constexpr double kFbClamp
Definition flanger.hpp:172
static constexpr int kInterpGuardSamples
Definition flanger.hpp:222
static constexpr double kMinClassicDelayMs
Definition flanger.hpp:217
void process_stereo(const SampleType *in_left, const SampleType *in_right, SampleType *out_left, SampleType *out_right, int n)
Definition flanger.hpp:437
void set_waveform(Wave wave)
Definition flanger.hpp:357
static constexpr double kBbdCharacter
Definition flanger.hpp:245
void set_delay_engine(Engine engine)
Definition flanger.hpp:348
Mode mode() const
Definition flanger.hpp:343
void set_mix(double wet01)
Equal-power dry/wet, in the range [0, 1].
Definition flanger.hpp:408
double feedback() const
Definition flanger.hpp:403
Engine delay_engine() const
Definition flanger.hpp:349
void reset()
Rewinds every buffer, filter, oscillator and generator.
Definition flanger.hpp:294
static double notch_spacing_hz(double delay_ms)
Spacing between adjacent notches — the reciprocal of the delay, for either polarity.
Definition flanger.hpp:511
void set_feedback(double fb)
Resonance, in the range [-kFbClamp, +kFbClamp].
Definition flanger.hpp:399
void set_stereo_spread(double cycles01)
Stereo spread in CYCLES of the sweep, in the range [0, 0.5].
Definition flanger.hpp:369
static constexpr double kCenterMinMs
Definition flanger.hpp:200
static constexpr double kModeSwitchMs
Definition flanger.hpp:233
void set_rate_hz(double hz)
Definition flanger.hpp:351
static constexpr double kOffsetMaxMs
Definition flanger.hpp:206
void set_barberpole_shift_hz(double hz)
Barberpole shift in Hz. Positive climbs, negative descends.
Definition flanger.hpp:415
LfoWave Wave
Definition flanger.hpp:165
double instantaneous_delay_ms(int channel=0) const
The delay the modulated line was last read at, in ms.
Definition flanger.hpp:490
void set_depth_ms(double ms)
Modulation excursion.
Definition flanger.hpp:378
double center_delay_ms() const
Definition flanger.hpp:388
void process(const SampleType *in, SampleType *out, int n)
Definition flanger.hpp:424
double mix() const
Definition flanger.hpp:412
void set_center_delay_ms(double ms)
Definition flanger.hpp:384
int fixed_delay_samples() const noexcept
The fixed dry delay in samples, exact and integer.
Definition flanger.hpp:496
void discard_history() noexcept
Constant-time audio fault recovery.
Definition flanger.hpp:314
double effective_depth_ms() const
The excursion actually in force, after the mode's clamp.
Definition flanger.hpp:484
static constexpr double kRateMaxHz
Definition flanger.hpp:210
double barberpole_shift_hz() const
Definition flanger.hpp:420
double depth_ms() const
Definition flanger.hpp:382
static constexpr double kRateMinHz
Definition flanger.hpp:209
static constexpr double kDepthMinMs
Definition flanger.hpp:194
static double notch_hz(int k, double delay_ms, Polarity polarity)
Notch frequencies of the comb at a given delay, from Δf = 1/D.
Definition flanger.hpp:503
void set_polarity(Polarity polarity)
Definition flanger.hpp:345
static constexpr double kOffsetMinMs
Definition flanger.hpp:205
FlangerMode Mode
Definition flanger.hpp:162
static void mix_gains(double wet01, double &dry_gain, double &wet_gain)
The dry and wet weights the shipped equal-power law produces at a given mix.
Definition flanger.hpp:524
static constexpr double kBarberpoleShiftMaxHz
Definition flanger.hpp:238
FlangerPolarity Polarity
Definition flanger.hpp:163
FlangerDelayEngine Engine
Definition flanger.hpp:164
static constexpr double worst_case_gain()
The bound the Forge registry cites (series law 8): the steady-state envelope of a recursive comb at t...
Definition flanger.hpp:517
static constexpr double kDepthMaxMs
Definition flanger.hpp:195
static constexpr double kLoopElementGainBound
Definition flanger.hpp:185
static constexpr double kDcBlockHz
Definition flanger.hpp:191
The one shared old→new crossfade gain law for the SIGNAL side.
The single-sideband (Bode/Moog) frequency shifter: a Hilbert phase-difference network,...
The house low-frequency oscillator: seven waveforms plus a continuous shape morph,...
@ index
Definition gpu_pass.hpp:140
T samples_to_ms(T samples, T sample_rate)
Samples → milliseconds at sample_rate Hz.
Definition units.hpp:153
T ms_to_samples(T ms, T sample_rate)
Milliseconds → samples at sample_rate Hz.
Definition units.hpp:147
Definition additive_bank.hpp:218
FlangerMode
Which of the three sweep topologies the engine runs.
Definition flanger.hpp:140
@ through_zero
Swept wet crossing a fixed-delay dry. Latency = offset.
Definition flanger.hpp:142
@ barberpole
Fixed delay, frequency-shifted wet. Latency 0.
Definition flanger.hpp:143
@ classic
One modulated delay against an undelayed dry. Latency 0.
Definition flanger.hpp:141
@ delay
Definition envelope.hpp:57
FlangerDelayEngine
Which delay engine fills the modulated slot.
Definition flanger.hpp:153
@ clean
Lagrange-interpolated line. Linear, transparent, the default.
Definition flanger.hpp:154
@ bbd
The bucket-brigade character model: dark, companded, alive.
Definition flanger.hpp:155
FlangerT< double > Flanger64
Definition flanger.hpp:779
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
@ EqualPower
(cos(u·π/2), sin(u·π/2)) — power sum == 1.
Definition crossfade.hpp:43
constexpr T crossfade_smoothstep(T t) noexcept
Smoothstep progress shaping: cubic 3u²−2u³ with zero slope at both ends, clamped to [0,...
Definition crossfade.hpp:50
@ i
Definition formant_filter_bank.hpp:20
LfoWave
The effect-wave vocabulary, shared by these functions and by EffectLfoT (lfo.hpp),...
Definition lfo_sampling.hpp:53
@ sine
Definition lfo_sampling.hpp:54
@ direct
set_shift_semitones. Detents and the pedal law are bypassed.
Definition pitch_shifter.hpp:194
void crossfade_gains(T u, CrossfadeGainLaw law, T &old_gain, T &new_gain) noexcept
Old/new blend gains for an ALREADY-SHAPED progress u (the caller applies any smoothstep/clamp first).
Definition crossfade.hpp:61
FlangerPolarity
The sign of the wet contribution, in the mix and in the feedback tap alike.
Definition flanger.hpp:147
@ negative
y = dry − wet. Notches at k/D, including one at DC.
Definition flanger.hpp:149
@ positive
y = dry + wet. Notches at (2k+1)/(2D).
Definition flanger.hpp:148
FlangerT< float > Flanger
Definition flanger.hpp:778
@ progress
Definition finite_stretch_builder.hpp:30
@ s
Definition input_events.hpp:41
@ x
Definition input_events.hpp:41
@ fixed
Definition design_ir.hpp:61
The real-unit vocabulary shared by every DSP block in the catalog.