Three vibrato lineages that are not variations on one idea. More...
#include <pulp/signal/delay_line.hpp>#include <pulp/signal/envelope.hpp>#include <pulp/signal/interpolator.hpp>#include <pulp/signal/lfo.hpp>#include <pulp/signal/tpt_filter.hpp>#include <pulp/signal/units.hpp>#include <pulp/signal/vactrol.hpp>#include <algorithm>#include <array>#include <cmath>#include <cstddef>#include <cstdint>Go to the source code of this file.
Classes | |
| class | pulp::signal::DelayVibratoT< SampleType > |
| True pitch vibrato: a fractional delay whose tap position is modulated by a sine, which scales the frequency of everything passing through it equally. More... | |
| class | pulp::signal::PhaseVibratoT< SampleType > |
| Magnatone-style phase vibrato: N cascaded first-order allpass stages whose corner is driven straight from the LFO, summed back against the direct path. More... | |
| class | pulp::signal::UniVibeT< SampleType > |
| Univibe-style phase vibrato: four allpass stages at deliberately unequal corners, all swept together by one lamp-and-photocell control chain. More... | |
Namespaces | |
| namespace | pulp |
| namespace | pulp::signal |
Typedefs | |
| using | pulp::signal::DelayVibrato = DelayVibratoT<float> |
| using | pulp::signal::DelayVibrato64 = DelayVibratoT<double> |
| using | pulp::signal::PhaseVibrato = PhaseVibratoT<float> |
| using | pulp::signal::PhaseVibrato64 = PhaseVibratoT<double> |
| using | pulp::signal::UniVibe = UniVibeT<float> |
| using | pulp::signal::UniVibe64 = UniVibeT<double> |
Three vibrato lineages that are not variations on one idea.
"Vibrato" names two different signal-theoretic objects, and seventy years of guitar amplifiers calling both of them by the same word is the reason this header ships three classes instead of one with a mode switch.
The test suite measures the distinction rather than asserting it: the same instantaneous-frequency instrument reads the same cents at 200 Hz and 4 kHz through DelayVibratoT, and readings roughly fifty times apart through PhaseVibratoT.
The two phase engines then differ from each other in exactly one place, and it is the place the circuits differed:
Lineage sources are cited for topology and documented behaviour only: stage counts, the fact that the Univibe's phase capacitors are staggered rather than matched, the fact that the modulator is a lamp and photocells, the fact that a Chorus/Vibrato switch exists. No component values are reproduced; the corner frequencies here are design parameters chosen as a geometric spread and are labelled as such. (R.G. Keen, "The Technology of the Uni-Vibe", geofex.com, for the Univibe topology; Parker & D'Angelo, "A Digital Model of the Buchla Lowpass Gate", DAFx-13, by way of VactrolConditionerT, for the optical lag.)
No feedback anywhere. All three engines are feedforward. DelayVibratoT reads one tap with no regeneration; the two phase engines are allpass cascades with no resonance loop. Each allpass stage is unity-gain for steady-state sinusoids by the textbook property |H(e^jw)| = 1, so the sinusoidal bound on a direct/shifted crossfade is exactly 1. The sample gain is a different quantity and is larger — an allpass's impulse response changes sign, so a worst-case bounded input is amplified by the cascade's L1 norm. The suite measures both; see kSinusoidalGainBound and the worst-case-gain test.
Anti-aliasing policy: not applicable, by construction. No engine here contains a nonlinearity in the signal path. The allpass cascades are linear time-varying, and the only nonlinearity in the module — UniVibeT's c^kControlExponent — shapes a control signal, never a sample. The delay line's Lagrange read is an interpolation, not a harmonic generator. Nothing oversamples and nothing needs to.
RT contract: prepare() sizes DelayVibratoT's delay line for the WORST CASE across the declared parameter ranges and may allocate; it is the only member that does. set_*, process(), latency_samples(), and reset() allocate nothing, take no locks, and perform no I/O, and are safe per sample on the audio thread. Per-sample cost is dominated by one tan per active allpass stage (the corner moves every sample) and, for UniVibeT, one pow. All state is default-initialised to a valid fresh state.