Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
phaser_stages.hpp
Go to the documentation of this file.
1#pragma once
2
286
288#include <pulp/signal/lfo.hpp>
290
291#include <algorithm>
292#include <array>
293#include <cmath>
294#include <cstdint>
295
296namespace pulp::signal {
297
300template <typename SampleType = float>
302public:
305 using Wave = LfoWave;
306
307 // ── Calibration constants (the table in the file doc block) ────────────
308
311 static constexpr int kMinStages = 4;
313 static constexpr int kMaxStages = 12;
315 static constexpr int kStageCountDefault = 4;
316
320 static constexpr double kFeedbackMax = 0.9;
322 static constexpr double kColorOffFeedback = 0.0;
323 static constexpr double kColorOnFeedback = 0.65;
324
328 static constexpr double kSweepRangeRatio = 1.0;
329
335 static constexpr double kSweepFloorHz = 20.0;
336
340 static constexpr double kSweepCeilingRatio = 0.45;
341
347 static constexpr double kStaggerDefault = 1.0;
348 static constexpr double kStaggerMin = 0.85;
349 static constexpr double kStaggerMax = 1.15;
350
352 static constexpr double kMixDefault = 0.5;
353
357 static constexpr double kRateDefaultHz = 0.5;
358
359 // ── Notch laws (shipped, so tests compute expectations from them) ──────
360
362 static constexpr double kPi = 3.14159265358979323846;
363
369 static double notch_frequency_analog_hz(int k, int stages, double fc_hz) {
370 return fc_hz * std::tan(odd_phase_fraction(k, stages));
371 }
372
377 static double notch_frequency_hz(int k, int stages, double fc_hz,
378 double sample_rate) {
379 const double prewarped = std::tan(kPi * fc_hz / sample_rate);
380 return sample_rate / kPi *
381 std::atan(prewarped * std::tan(odd_phase_fraction(k, stages)));
382 }
383
385 static constexpr int notch_count(int stages) { return stages / 2; }
386
390 static constexpr double worst_case_gain() {
391 return 1.0 / (1.0 - kFeedbackMax);
392 }
393
395 static constexpr int latency_samples() { return 0; }
396
397 // ── Lifecycle ─────────────────────────────────────────────────────────
398
403 set_wave(Wave::triangle);
405 prepare(44100.0);
406 }
407
410 void prepare(double sample_rate) {
411 if (sample_rate > 0.0) sample_rate_ = sample_rate;
412 for (auto& channel : stages_)
413 for (auto& stage : channel)
414 stage.prepare(static_cast<SampleType>(sample_rate_));
415 for (auto& lfo : lfo_) lfo.prepare(sample_rate_);
416 reset();
417 }
418
421 void reset() {
422 for (auto& channel : stages_)
423 for (auto& stage : channel) stage.reset();
424 feedback_state_.fill(SampleType{0});
425 for (auto& lfo : lfo_) lfo.reset();
426 for (int ch = 0; ch < kChannels; ++ch) sweep_hz_[ch] = center_hz_;
427 }
428
429 // ── Parameters ────────────────────────────────────────────────────────
430
435 void set_stage_count(int n) {
436 const int clamped = std::clamp(n, kMinStages, kMaxStages);
437 stage_count_ = clamped - (clamped % 2);
438 }
439 int stage_count() const { return stage_count_; }
440
442 void set_rate_hz(double hz) {
443 if (!std::isfinite(hz)) return;
444 for (auto& lfo : lfo_) lfo.set_rate_hz(hz);
445 }
446 double rate_hz() const { return lfo_[0].rate_hz(); }
447
450 void set_depth(float depth01) {
451 if (!std::isfinite(static_cast<double>(depth01))) return;
452 depth_ = std::clamp(static_cast<double>(depth01), 0.0, 1.0);
453 }
454 double depth() const { return depth_; }
455
457 void set_center_hz(double fc) {
458 if (!std::isfinite(fc)) return;
459 center_hz_ = clamp_hz(fc);
460 }
461 double center_hz() const { return center_hz_; }
462
468 void set_feedback(float fb) {
469 if (!std::isfinite(static_cast<double>(fb))) return;
470 feedback_ = std::clamp(static_cast<double>(fb), -kFeedbackMax,
472 }
473 double feedback() const { return feedback_; }
474
476 void set_mix(float mix01) {
477 if (!std::isfinite(static_cast<double>(mix01))) return;
478 mix_ = std::clamp(static_cast<double>(mix01), 0.0, 1.0);
479 }
480 double mix() const { return mix_; }
481
484 void set_stereo_spread(float cycles01) {
485 if (!std::isfinite(static_cast<double>(cycles01))) return;
486 spread_ = std::clamp(static_cast<double>(cycles01), 0.0, 0.5);
487 lfo_[1].set_stereo_offset(spread_);
488 }
489 double stereo_spread() const { return spread_; }
490
499 for (auto& lfo : lfo_) lfo.set_wave(wave);
500 }
501 Wave wave() const { return lfo_[0].wave(); }
502
506 if (!std::isfinite(ratio)) return;
507 stagger_ = std::clamp(ratio, kStaggerMin, kStaggerMax);
508 double p = 1.0;
509 for (int i = 0; i < kMaxStages; ++i) {
510 stagger_pow_[static_cast<std::size_t>(i)] = p;
511 p *= stagger_;
512 }
513 }
514 double stagger_ratio() const { return stagger_; }
515
519 void set_seed(std::uint32_t seed) {
520 for (auto& lfo : lfo_) lfo.set_seed(seed);
521 }
522
528 double sweep_frequency_hz(int channel) const {
529 return sweep_hz_[static_cast<std::size_t>(
530 std::clamp(channel, 0, kChannels - 1))];
531 }
532
533 // ── Processing ────────────────────────────────────────────────────────
534
538 void process(const SampleType* in_l, const SampleType* in_r,
539 SampleType* out_l, SampleType* out_r, int n_frames) {
540 const double dry = 1.0 - mix_;
541 for (int i = 0; i < n_frames; ++i) {
542 const SampleType xl = in_l[i];
543 const SampleType xr = in_r[i];
544 if (!std::isfinite(static_cast<double>(xl)) ||
545 !std::isfinite(static_cast<double>(xr))) {
546 reset();
547 out_l[i] = out_r[i] = SampleType{0};
548 continue;
549 }
550 const double wl = run_cascade(0, advance_sweep(0), xl);
551 const double wr = run_cascade(1, advance_sweep(1), xr);
552 out_l[i] = static_cast<SampleType>(dry * static_cast<double>(xl) +
553 mix_ * wl);
554 out_r[i] = static_cast<SampleType>(dry * static_cast<double>(xr) +
555 mix_ * wr);
556 }
557 }
558
563 void process_mono(const SampleType* in, SampleType* out, int n_frames) {
564 const double dry = 1.0 - mix_;
565 for (int i = 0; i < n_frames; ++i) {
566 const SampleType x = in[i];
567 if (!std::isfinite(static_cast<double>(x))) {
568 reset();
569 out[i] = SampleType{0};
570 continue;
571 }
572 const double w = run_cascade(0, advance_sweep(0), x);
573 advance_sweep(1);
574 out[i] = static_cast<SampleType>(dry * static_cast<double>(x) +
575 mix_ * w);
576 }
577 }
578
579private:
580 static constexpr int kChannels = 2;
581
584 static double odd_phase_fraction(int k, int stages) {
585 return static_cast<double>(2 * k - 1) * kPi /
586 (2.0 * static_cast<double>(stages));
587 }
588
589 double clamp_hz(double hz) const {
590 return std::clamp(hz, kSweepFloorHz, kSweepCeilingRatio * sample_rate_);
591 }
592
598 double advance_sweep(int ch) {
599 const auto c = static_cast<std::size_t>(ch);
600 const double lfo = static_cast<double>(lfo_[c].next());
601 const double fc =
602 clamp_hz(center_hz_ * (1.0 + depth_ * kSweepRangeRatio * lfo));
603 sweep_hz_[c] = fc;
604 return fc;
605 }
606
609 double run_cascade(int ch, double fc, SampleType x) {
610 const auto c = static_cast<std::size_t>(ch);
611 auto& stages = stages_[c];
612 SampleType v = static_cast<SampleType>(
613 static_cast<double>(x) +
614 feedback_ * static_cast<double>(feedback_state_[c]));
615 for (int i = 0; i < stage_count_; ++i) {
616 const auto s = static_cast<std::size_t>(i);
617 stages[s].set_cutoff(
618 static_cast<SampleType>(clamp_hz(fc * stagger_pow_[s])));
619 v = stages[s].process_allpass(v);
620 }
621 feedback_state_[c] = snap_to_zero(v);
622 return static_cast<double>(v);
623 }
624
625 double sample_rate_ = 44100.0;
626 double center_hz_ = 400.0;
627 double depth_ = 1.0;
628 double feedback_ = kColorOffFeedback;
629 double mix_ = kMixDefault;
630 double spread_ = 0.0;
631 double stagger_ = kStaggerDefault;
632 int stage_count_ = kStageCountDefault;
633
634 std::array<double, kMaxStages> stagger_pow_{};
635 std::array<double, kChannels> sweep_hz_{};
636 std::array<SampleType, kChannels> feedback_state_{};
637 std::array<std::array<TptFilterT<SampleType>, kMaxStages>, kChannels>
638 stages_{};
644 std::array<EffectLfoT<double>, kChannels> lfo_{};
645};
646
649
650} // namespace pulp::signal
A cascaded first-order-allpass phaser.
Definition phaser_stages.hpp:301
double center_hz() const
Definition phaser_stages.hpp:461
static constexpr int notch_count(int stages)
How many notches an N-stage cascade produces. N/2, for even N.
Definition phaser_stages.hpp:385
static constexpr double kSweepRangeRatio
Definition phaser_stages.hpp:328
static double notch_frequency_analog_hz(int k, int stages, double fc_hz)
The cited analog-prototype notch law: f_k = fc · tan((2k−1)π/2N).
Definition phaser_stages.hpp:369
PhaserStagesT()
A default instance is a Small Stone: 4 stages, 0.5 Hz triangle, 400 Hz centre, full depth,...
Definition phaser_stages.hpp:401
double stagger_ratio() const
Definition phaser_stages.hpp:514
static constexpr double kColorOnFeedback
Definition phaser_stages.hpp:323
int stage_count() const
Definition phaser_stages.hpp:439
static constexpr int kStageCountDefault
Definition phaser_stages.hpp:315
static constexpr double kColorOffFeedback
Definition phaser_stages.hpp:322
static constexpr double kPi
Definition phaser_stages.hpp:362
static constexpr double kSweepCeilingRatio
Definition phaser_stages.hpp:340
Wave wave() const
Definition phaser_stages.hpp:501
void set_stage_count(int n)
Active stage count.
Definition phaser_stages.hpp:435
static constexpr int latency_samples()
Zero. The dry path is a wire and the TPT stages add no buffering.
Definition phaser_stages.hpp:395
double mix() const
Definition phaser_stages.hpp:480
static constexpr double kStaggerDefault
Definition phaser_stages.hpp:347
double rate_hz() const
Definition phaser_stages.hpp:446
void set_wave(Wave wave)
LFO shape, forwarded verbatim to both channels' LFOs.
Definition phaser_stages.hpp:498
double sweep_frequency_hz(int channel) const
The channel's current swept corner frequency in Hz, after clamping — the quantity the notch laws take...
Definition phaser_stages.hpp:528
static constexpr double kSweepFloorHz
Definition phaser_stages.hpp:335
static constexpr double kRateDefaultHz
Definition phaser_stages.hpp:357
static constexpr double kStaggerMin
Definition phaser_stages.hpp:348
static constexpr double kMixDefault
Definition phaser_stages.hpp:352
void set_depth(float depth01)
Sweep excursion, 0..1.
Definition phaser_stages.hpp:450
void set_mix(float mix01)
Dry/wet, 0..1. 0.5 is the full-notch point; 1.0 is a flat allpass.
Definition phaser_stages.hpp:476
static constexpr int kMaxStages
Definition phaser_stages.hpp:313
static constexpr double worst_case_gain()
The worst-case output/input magnitude over all frequencies, mixes and feedback settings — the number ...
Definition phaser_stages.hpp:390
static constexpr double kFeedbackMax
Definition phaser_stages.hpp:320
void set_stagger_ratio(double ratio)
Per-stage corner ratio; 1.0 (the default) means every stage shares one fc, which is the documented si...
Definition phaser_stages.hpp:505
double stereo_spread() const
Definition phaser_stages.hpp:489
void process_mono(const SampleType *in, SampleType *out, int n_frames)
Mono block process, driving channel 0 only.
Definition phaser_stages.hpp:563
static double notch_frequency_hz(int k, int stages, double fc_hz, double sample_rate)
The exact notch law for the shipped TPT cascade: f_k = (fs/π)·arctan( tan(π·fc/fs) · tan((2k−1)π/2N) ...
Definition phaser_stages.hpp:377
void set_seed(std::uint32_t seed)
Seeds both LFOs identically, so the stochastic shapes stay a phase-shifted copy of each other rather ...
Definition phaser_stages.hpp:519
void process(const SampleType *in_l, const SampleType *in_r, SampleType *out_l, SampleType *out_r, int n_frames)
Stereo block process.
Definition phaser_stages.hpp:538
void reset()
Clears every stage's integrator, the feedback memories, and both LFO phases, so a render from here is...
Definition phaser_stages.hpp:421
double depth() const
Definition phaser_stages.hpp:454
void set_stereo_spread(float cycles01)
Right-channel LFO lead in CYCLES, 0..0.5.
Definition phaser_stages.hpp:484
void prepare(double sample_rate)
Sets the sample rate and returns the instance to a fresh, silent state.
Definition phaser_stages.hpp:410
void set_feedback(float fb)
Feedback ("Color"), clamped to ±kFeedbackMax.
Definition phaser_stages.hpp:468
static constexpr double kStaggerMax
Definition phaser_stages.hpp:349
double feedback() const
Definition phaser_stages.hpp:473
static constexpr int kMinStages
Definition phaser_stages.hpp:311
LfoWave Wave
The LFO shape enum, forwarded verbatim.
Definition phaser_stages.hpp:305
void set_rate_hz(double hz)
LFO rate in Hz. Forwarded to both channels' LFOs.
Definition phaser_stages.hpp:442
void set_center_hz(double fc)
Allpass corner frequency at the LFO's midpoint.
Definition phaser_stages.hpp:457
The house low-frequency oscillator: seven waveforms plus a continuous shape morph,...
Definition additive_bank.hpp:218
PhaserStagesT< double > PhaserStages64
Definition phaser_stages.hpp:648
@ c
Definition spectrum_trace.hpp:35
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
PhaserStagesT< float > PhaserStages
Definition phaser_stages.hpp:647
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
@ 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
@ s
Definition input_events.hpp:41
@ x
Definition input_events.hpp:41
@ v
Definition input_events.hpp:41
First-order topology-preserving transform (TPT) filter.