Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::signal::PhaserStagesT< SampleType > Class Template Reference

A cascaded first-order-allpass phaser. More...

#include <phaser_stages.hpp>

Public Types

using Wave = LfoWave
 The LFO shape enum, forwarded verbatim.

Public Member Functions

 PhaserStagesT ()
 A default instance is a Small Stone: 4 stages, 0.5 Hz triangle, 400 Hz centre, full depth, Color off, mix at the full-notch point, mono.
void prepare (double sample_rate)
 Sets the sample rate and returns the instance to a fresh, silent state.
void reset ()
 Clears every stage's integrator, the feedback memories, and both LFO phases, so a render from here is bit-identical every time.
void set_stage_count (int n)
 Active stage count.
int stage_count () const
void set_rate_hz (double hz)
 LFO rate in Hz. Forwarded to both channels' LFOs.
double rate_hz () const
void set_depth (float depth01)
 Sweep excursion, 0..1.
double depth () const
void set_center_hz (double fc)
 Allpass corner frequency at the LFO's midpoint.
double center_hz () const
void set_feedback (float fb)
 Feedback ("Color"), clamped to ±kFeedbackMax.
double feedback () const
void set_mix (float mix01)
 Dry/wet, 0..1. 0.5 is the full-notch point; 1.0 is a flat allpass.
double mix () const
void set_stereo_spread (float cycles01)
 Right-channel LFO lead in CYCLES, 0..0.5.
double stereo_spread () const
void set_wave (Wave wave)
 LFO shape, forwarded verbatim to both channels' LFOs.
Wave wave () const
void set_stagger_ratio (double ratio)
 Per-stage corner ratio; 1.0 (the default) means every stage shares one fc, which is the documented single-control-current topology.
double stagger_ratio () const
void set_seed (std::uint32_t seed)
 Seeds both LFOs identically, so the stochastic shapes stay a phase-shifted copy of each other rather than decorrelating.
double sweep_frequency_hz (int channel) const
 The channel's current swept corner frequency in Hz, after clamping — the quantity the notch laws take as fc.
void process (const SampleType *in_l, const SampleType *in_r, SampleType *out_l, SampleType *out_r, int n_frames)
 Stereo block process.
void process_mono (const SampleType *in, SampleType *out, int n_frames)
 Mono block process, driving channel 0 only.

Static Public Member Functions

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).
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) ).
static constexpr int notch_count (int stages)
 How many notches an N-stage cascade produces. N/2, for even N.
static constexpr double worst_case_gain ()
 The worst-case output/input magnitude over all frequencies, mixes and feedback settings — the number Forge's registry row cites.
static constexpr int latency_samples ()
 Zero. The dry path is a wire and the TPT stages add no buffering.

Static Public Attributes

static constexpr int kMinStages = 4
 Fewest stages.
static constexpr int kMaxStages = 12
 Most stages. The DP/4's documented stage count, used as the anchor.
static constexpr int kStageCountDefault = 4
 Compact four-stage phaser class: two notches.
static constexpr double kFeedbackMax = 0.9
 Feedback magnitude ceiling.
static constexpr double kColorOffFeedback = 0.0
 The two documented "Color" switch positions, as preset points.
static constexpr double kColorOnFeedback = 0.65
static constexpr double kSweepRangeRatio = 1.0
 fc(t) = center · (1 + depth · kSweepRangeRatio · lfo(t)).
static constexpr double kSweepFloorHz = 20.0
 Sweep floor in Hz.
static constexpr double kSweepCeilingRatio = 0.45
 Sweep ceiling as a fraction of the sample rate.
static constexpr double kStaggerDefault = 1.0
 Per-stage corner ratio: stage i runs at fc · kStagger^i.
static constexpr double kStaggerMin = 0.85
static constexpr double kStaggerMax = 1.15
static constexpr double kMixDefault = 0.5
 The full-notch mix point.
static constexpr double kRateDefaultHz = 0.5
 Default LFO rate.
static constexpr double kPi = 3.14159265358979323846
 π, at the precision the notch laws need.

Detailed Description

template<typename SampleType = float>
class pulp::signal::PhaserStagesT< SampleType >

A cascaded first-order-allpass phaser.

Stereo, 4–12 stages, bounded feedback, LFO sweep with quadrature stereo spread.

Member Typedef Documentation

◆ Wave

template<typename SampleType = float>
using pulp::signal::PhaserStagesT< SampleType >::Wave = LfoWave

The LFO shape enum, forwarded verbatim.

There is deliberately no phaser-local copy of it.

Constructor & Destructor Documentation

◆ PhaserStagesT()

template<typename SampleType = float>
pulp::signal::PhaserStagesT< SampleType >::PhaserStagesT ( )
inline

A default instance is a Small Stone: 4 stages, 0.5 Hz triangle, 400 Hz centre, full depth, Color off, mix at the full-notch point, mono.

Member Function Documentation

◆ center_hz()

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::center_hz ( ) const
inline

◆ depth()

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::depth ( ) const
inline

◆ feedback()

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::feedback ( ) const
inline

◆ latency_samples()

template<typename SampleType = float>
constexpr int pulp::signal::PhaserStagesT< SampleType >::latency_samples ( )
inlinestaticconstexpr

Zero. The dry path is a wire and the TPT stages add no buffering.

◆ mix()

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::mix ( ) const
inline

◆ notch_count()

template<typename SampleType = float>
constexpr int pulp::signal::PhaserStagesT< SampleType >::notch_count ( int stages)
inlinestaticconstexpr

How many notches an N-stage cascade produces. N/2, for even N.

◆ notch_frequency_analog_hz()

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::notch_frequency_analog_hz ( int k,
int stages,
double fc_hz )
inlinestatic

The cited analog-prototype notch law: f_k = fc · tan((2k−1)π/2N).

Exact for the continuous-time cascade, and the closed form that shows the notch pattern is a fixed ratio set around fc. It is an APPROXIMATION to what this code does — see the file doc block for where it breaks down, and prefer notch_frequency_hz for expectations.

◆ notch_frequency_hz()

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::notch_frequency_hz ( int k,
int stages,
double fc_hz,
double sample_rate )
inlinestatic

The exact notch law for the shipped TPT cascade: f_k = (fs/π)·arctan( tan(π·fc/fs) · tan((2k−1)π/2N) ).

Always strictly below Nyquist, for any fc and any k ≤ stages/2, because arctan cannot reach π/2.

◆ prepare()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::prepare ( double sample_rate)
inline

Sets the sample rate and returns the instance to a fresh, silent state.

Allocates nothing — every buffer here is a fixed-size member.

◆ process()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::process ( const SampleType * in_l,
const SampleType * in_r,
SampleType * out_l,
SampleType * out_r,
int n_frames )
inline

Stereo block process.

Safe in place: out_l/out_r may alias in_l/in_r, because each frame's input is read into a local before its output is written.

◆ process_mono()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::process_mono ( const SampleType * in,
SampleType * out,
int n_frames )
inline

Mono block process, driving channel 0 only.

The right channel's LFO still advances — so switching a running instance to stereo does not jump its phase — but its cascade is not run, so the mono path costs half of what the stereo one does.

◆ rate_hz()

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::rate_hz ( ) const
inline

◆ reset()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::reset ( )
inline

Clears every stage's integrator, the feedback memories, and both LFO phases, so a render from here is bit-identical every time.

◆ set_center_hz()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::set_center_hz ( double fc)
inline

Allpass corner frequency at the LFO's midpoint.

◆ set_depth()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::set_depth ( float depth01)
inline

Sweep excursion, 0..1.

1.0 swings fc across [0, 2·center] before the floor and ceiling clamps apply.

◆ set_feedback()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::set_feedback ( float fb)
inline

Feedback ("Color"), clamped to ±kFeedbackMax.

Positive adds a resonant peak — see the doc block on what it does NOT do — and moves the nulls off the open-loop law. Negative flattens the whole response and is a catalog-only extension with no hardware precedent. Both signs obey the same gain bound.

◆ set_mix()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::set_mix ( float mix01)
inline

Dry/wet, 0..1. 0.5 is the full-notch point; 1.0 is a flat allpass.

◆ set_rate_hz()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::set_rate_hz ( double hz)
inline

LFO rate in Hz. Forwarded to both channels' LFOs.

◆ set_seed()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::set_seed ( std::uint32_t seed)
inline

Seeds both LFOs identically, so the stochastic shapes stay a phase-shifted copy of each other rather than decorrelating.

A construction/preset choice, never automated (series law 2).

◆ set_stage_count()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::set_stage_count ( int n)
inline

Active stage count.

Clamped into [4, 12] and then rounded DOWN to the nearest even value — 5 → 4, 7 → 6, 13 → 12 — because odd counts put the last odd-π crossing exactly at Nyquist where it is not a notch. The result is readable through stage_count(); nothing is silently ignored.

◆ set_stagger_ratio()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::set_stagger_ratio ( double ratio)
inline

Per-stage corner ratio; 1.0 (the default) means every stage shares one fc, which is the documented single-control-current topology.

◆ set_stereo_spread()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::set_stereo_spread ( float cycles01)
inline

Right-channel LFO lead in CYCLES, 0..0.5.

0.25 is quadrature — the catalog's 90° default. 0 makes the two channels bit-identical.

◆ set_wave()

template<typename SampleType = float>
void pulp::signal::PhaserStagesT< SampleType >::set_wave ( Wave wave)
inline

LFO shape, forwarded verbatim to both channels' LFOs.

The catalog contract exposes triangle and sine. Direct C++ callers can pass the wider shared LfoWave enum; when they do, stereo spread remains literally a PHASE offset rather than a normalized-width macro. In particular, saw at 0.5 cycles is not an inversion (its discontinuity is phase-shifted), so its side/mid trajectory differs from triangle/sine.

◆ stage_count()

template<typename SampleType = float>
int pulp::signal::PhaserStagesT< SampleType >::stage_count ( ) const
inline

◆ stagger_ratio()

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::stagger_ratio ( ) const
inline

◆ stereo_spread()

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::stereo_spread ( ) const
inline

◆ sweep_frequency_hz()

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::sweep_frequency_hz ( int channel) const
inline

The channel's current swept corner frequency in Hz, after clamping — the quantity the notch laws take as fc.

Exposed because it is what a host draws as a sweep readout, and what the stereo-spread and waveform-shape tests measure directly instead of trying to recover it from audio.

◆ wave()

template<typename SampleType = float>
Wave pulp::signal::PhaserStagesT< SampleType >::wave ( ) const
inline

◆ worst_case_gain()

template<typename SampleType = float>
constexpr double pulp::signal::PhaserStagesT< SampleType >::worst_case_gain ( )
inlinestaticconstexpr

The worst-case output/input magnitude over all frequencies, mixes and feedback settings — the number Forge's registry row cites.

Equality, not an estimate: see the derivation in the file doc block.

Member Data Documentation

◆ kColorOffFeedback

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::kColorOffFeedback = 0.0
staticconstexpr

The two documented "Color" switch positions, as preset points.

◆ kColorOnFeedback

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::kColorOnFeedback = 0.65
staticconstexpr

◆ kFeedbackMax

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::kFeedbackMax = 0.9
staticconstexpr

Feedback magnitude ceiling.

The worst-case-gain proof is 1/(1 − kFeedbackMax); widening this without redoing that proof is a stability change, not a range change.

◆ kMaxStages

template<typename SampleType = float>
int pulp::signal::PhaserStagesT< SampleType >::kMaxStages = 12
staticconstexpr

Most stages. The DP/4's documented stage count, used as the anchor.

◆ kMinStages

template<typename SampleType = float>
int pulp::signal::PhaserStagesT< SampleType >::kMinStages = 4
staticconstexpr

Fewest stages.

Below 4 the cascade produces a single notch and the effect reads as a phase shift rather than a sweep.

◆ kMixDefault

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::kMixDefault = 0.5
staticconstexpr

The full-notch mix point.

◆ kPi

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::kPi = 3.14159265358979323846
staticconstexpr

π, at the precision the notch laws need.

◆ kRateDefaultHz

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::kRateDefaultHz = 0.5
staticconstexpr

Default LFO rate.

[design parameter] default 0.5 Hz, range 0.02 .. 10 Hz — the span a single vintage "Rate" pot covers, slow enough to read as a sweep and fast enough to reach the "jet" idiom.

◆ kStageCountDefault

template<typename SampleType = float>
int pulp::signal::PhaserStagesT< SampleType >::kStageCountDefault = 4
staticconstexpr

Compact four-stage phaser class: two notches.

◆ kStaggerDefault

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::kStaggerDefault = 1.0
staticconstexpr

Per-stage corner ratio: stage i runs at fc · kStagger^i.

[design parameter] default 1.0 (off), range 0.85 .. 1.15. OFF by default and flagged as an original catalog extension: no public source documents a hardware phaser staggering its stages, so switching it on leaves the vintage-behaviour claim behind.

◆ kStaggerMax

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::kStaggerMax = 1.15
staticconstexpr

◆ kStaggerMin

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::kStaggerMin = 0.85
staticconstexpr

◆ kSweepCeilingRatio

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::kSweepCeilingRatio = 0.45
staticconstexpr

Sweep ceiling as a fraction of the sample rate.

[design parameter] default 0.45, range 0.30 .. 0.49. The bilinear prewarp tan(π·fc/fs) diverges at Nyquist; 0.45 keeps the coefficient finite with margin.

◆ kSweepFloorHz

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::kSweepFloorHz = 20.0
staticconstexpr

Sweep floor in Hz.

[design parameter] default 20, range 5 .. 50. The mapping above reaches 0 Hz at depth 100 %, where tan(π·fc/fs) is 0, the stage degenerates to a sign flip, and every notch collapses onto DC. Any floor below the bottom of hearing removes that degeneracy without being audible as a limit.

◆ kSweepRangeRatio

template<typename SampleType = float>
double pulp::signal::PhaserStagesT< SampleType >::kSweepRangeRatio = 1.0
staticconstexpr

fc(t) = center · (1 + depth · kSweepRangeRatio · lfo(t)).

[design parameter] default 1.0, fixed — it is what makes depth 100 % mean "sweep from DC to twice centre", which the presets assume.


The documentation for this class was generated from the following file: