Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
phaser.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <algorithm>
6#include <cmath>
7#include <array>
8
9namespace pulp::signal {
10
11// Phaser effect using cascaded allpass filters with LFO modulation.
12// RT contract: configuration setters, process, and reset use fixed member
13// storage and allocate no memory.
14template <typename SampleType = float>
15class PhaserT {
16public:
17 void set_sample_rate(SampleType sr) { sample_rate_ = sr; }
18 void set_rate(SampleType hz) { rate_ = hz; } // LFO rate
19 void set_depth(SampleType d) { depth_ = d; } // Modulation depth (0-1)
20 void set_feedback(SampleType fb) {
21 feedback_ = std::clamp(fb, SampleType{-0.95f}, SampleType{0.95f});
22 }
23 void set_mix(SampleType m) { mix_ = m; }
24 void set_stages(int n) { stages_ = std::clamp(n, 2, max_stages); }
25
26 SampleType process(SampleType input) {
27 // LFO
28 SampleType lfo = std::sin(SampleType{2.0f} * pi * phase_) *
29 SampleType{0.5f} + SampleType{0.5f};
30 phase_ += rate_ / sample_rate_;
31 if (phase_ >= SampleType{1.0f}) phase_ -= SampleType{1.0f};
32
33 // Map LFO to frequency range (200Hz - 5000Hz)
34 SampleType min_freq = SampleType{200.0f};
35 SampleType max_freq = SampleType{5000.0f};
36 SampleType freq = min_freq + lfo * depth_ * (max_freq - min_freq);
37
38 // Allpass coefficient
39 SampleType w0 = SampleType{2.0f} * pi * freq / sample_rate_;
40 SampleType coeff = (SampleType{1.0f} - std::tan(w0 * SampleType{0.5f})) /
41 (SampleType{1.0f} + std::tan(w0 * SampleType{0.5f}));
42
43 // Process through allpass chain
44 SampleType x = input + feedback_state_ * feedback_;
45 for (int i = 0; i < stages_; ++i) {
46 SampleType y = coeff * (x - ap_state_[i * 2 + 1]) + ap_state_[i * 2];
47 ap_state_[i * 2] = x;
48 ap_state_[i * 2 + 1] = y;
49 x = y;
50 }
51 // Snap the feedback state so the allpass recirculation flushes denormal
52 // tails into silence with no FTZ guard. No-op above 1e-15.
53 feedback_state_ = snap_to_zero(x);
54
55 return input * (SampleType{1.0f} - mix_) + x * mix_;
56 }
57
58 void process(SampleType* buffer, int num_samples) {
59 for (int i = 0; i < num_samples; ++i)
60 buffer[i] = process(buffer[i]);
61 }
62
63 void reset() {
64 ap_state_.fill(0);
65 feedback_state_ = 0;
66 phase_ = 0;
67 }
68
69private:
70 static constexpr SampleType pi = SampleType{3.14159265358979323846f};
71 static constexpr int max_stages = 8;
72
73 SampleType sample_rate_ = SampleType{44100.0f};
74 SampleType rate_ = SampleType{0.5f};
75 SampleType depth_ = SampleType{0.7f};
76 SampleType feedback_ = SampleType{0.5f};
77 SampleType mix_ = SampleType{0.5f};
78 int stages_ = 4;
79 SampleType phase_ = 0;
80 SampleType feedback_state_ = 0;
81 std::array<SampleType, max_stages * 2> ap_state_{};
82};
83
86
87} // namespace pulp::signal
Definition phaser.hpp:15
void set_depth(SampleType d)
Definition phaser.hpp:19
void process(SampleType *buffer, int num_samples)
Definition phaser.hpp:58
SampleType process(SampleType input)
Definition phaser.hpp:26
void set_feedback(SampleType fb)
Definition phaser.hpp:20
void reset()
Definition phaser.hpp:63
void set_mix(SampleType m)
Definition phaser.hpp:23
void set_stages(int n)
Definition phaser.hpp:24
void set_sample_rate(SampleType sr)
Definition phaser.hpp:17
void set_rate(SampleType hz)
Definition phaser.hpp:18
Definition additive_bank.hpp:218
@ buffer
A borrowed static buffer with a traversable playhead.
Definition granular.hpp:110
PhaserT< double > Phaser64
Definition phaser.hpp:85
PhaserT< float > Phaser
Definition phaser.hpp:84
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