Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
vibrato.hpp File Reference

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>

Detailed Description

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.

  • True pitch vibrato (DelayVibratoT) modulates the read position of a delay line. The rate of change of that position IS a frequency-scaling factor, so it applies to every partial passing through equally: a 200 Hz tone and a 4 kHz tone move by the same number of cents at the same instant. This is real frequency modulation, and it is the only one of the three that shifts pitch at all. (Dattorro, "Effect Design, Part 2: Delay-Line Modulation and Chorus", JAES 45(10):764-788, 1997.)
  • Phase vibrato (PhaseVibratoT, UniVibeT) modulates the corner frequency of a cascade of allpass stages. An allpass's phase depends on frequency relative to its corner, so sweeping the corner produces an apparent pitch wobble that is strong near the swept corner and almost absent far from it — frequency-DEPENDENT, and accompanied by moving notches once the shifted path is summed with the direct one. That frequency dependence is not an approximation error; it is the sound.

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:

  • Magnatone's varistor is a voltage-controlled resistor, so it tracks its control voltage immediately. PhaseVibratoT drives its corner straight from the LFO.
  • The Univibe's modulator is an incandescent lamp facing four photocells. A photoresistor's conductance rises fast and decays slowly, so the sweep arrives late and leaves later. UniVibeT routes the LFO through VactrolConditionerT (vactrol.hpp) — the same asymmetric one-pole the Buchla lowpass gate uses, with its own constants — and that lag, plus four deliberately unequal corner frequencies, is the whole character.

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.