Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
vibrato.hpp
Go to the documentation of this file.
1#pragma once
2
77
81#include <pulp/signal/lfo.hpp>
83#include <pulp/signal/units.hpp>
85
86#include <algorithm>
87#include <array>
88#include <cmath>
89#include <cstddef>
90#include <cstdint>
91
92namespace pulp::signal {
93
105template <typename SampleType = float>
107public:
109 static constexpr double kDefaultRateHz = 5.5;
110 static constexpr double kMinRateHz = 0.1;
111 static constexpr double kMaxRateHz = 20.0;
112
117 static constexpr double kDefaultDepthCents = 20.0;
118 static constexpr double kMaxDepthCents = 100.0;
119
123 static constexpr double kBaseDelayHeadroom = 2.0;
124
129 static constexpr double kMinBaseDelaySamples = 2.0;
130
133 static constexpr int kInterpolatorMarginSamples = 4;
134
137 static constexpr double kMaxLifecycleMs = 5000.0;
138
144 static constexpr double kInterpolatorPeakGain = 1.25;
145
147 lfo_.set_wave(LfoWave::sine);
148 lifecycle_.set_curve(0.0);
149 lifecycle_.set_attack_ms(0.0);
150 lifecycle_.set_hold_ms(0.0);
151 lifecycle_.set_decay_ms(0.0);
152 lifecycle_.set_sustain(1.0);
153 update();
154 }
155
161 void prepare(double sample_rate) {
162 sample_rate_ = sample_rate > 0.0 ? sample_rate : sample_rate_;
163 lfo_.prepare(sample_rate_);
164 lifecycle_.prepare(sample_rate_);
165 line_.prepare(worst_case_read_index(sample_rate_));
166 update();
167 arm();
168 }
169
170 void set_rate_hz(double hz) {
171 if (!std::isfinite(hz)) return;
172 rate_hz_ = std::clamp(hz, kMinRateHz, kMaxRateHz);
173 lfo_.set_rate_hz(rate_hz_);
174 update();
175 }
176
177 double rate_hz() const { return rate_hz_; }
178
183 void set_depth_cents(double cents) {
184 if (!std::isfinite(cents)) return;
185 depth_cents_ = std::clamp(cents, 0.0, kMaxDepthCents);
186 update();
187 }
188
189 double depth_cents() const { return depth_cents_; }
190
194 void set_delay_ms(double ms) {
195 if (!std::isfinite(ms)) return;
196 delay_ms_ = std::clamp(ms, 0.0, kMaxLifecycleMs);
197 lifecycle_.set_delay_ms(delay_ms_);
198 arm();
199 }
200
202 void set_fade_in_ms(double ms) {
203 if (!std::isfinite(ms)) return;
204 fade_in_ms_ = std::clamp(ms, 0.0, kMaxLifecycleMs);
205 lifecycle_.set_attack_ms(fade_in_ms_);
206 arm();
207 }
208
210 double base_delay_samples() const { return base_delay_samples_; }
211
213 double modulation_amplitude_samples() const { return amplitude_samples_; }
214
217 double depth_envelope() const { return lifecycle_active_ ? depth_env_ : 1.0; }
218
222 std::size_t latency_samples() const {
223 return static_cast<std::size_t>(
224 std::ceil(base_delay_samples_ + amplitude_samples_));
225 }
226
227 void reset() {
228 line_.reset();
229 lfo_.reset();
230 depth_env_ = 0.0;
231 arm();
232 }
233
234 void discard_history() noexcept {
235 line_.discard_history();
236 lfo_.reset();
237 depth_env_ = 0.0;
238 arm();
239 }
240
241 SampleType process(SampleType input) {
242 if (!std::isfinite(static_cast<double>(input))) {
244 return SampleType{0};
245 }
246 const double env = lifecycle_active_ ? advance_lifecycle() : 1.0;
247 const double modulation = static_cast<double>(lfo_.next());
248 const double delay = base_delay_samples_ + amplitude_samples_ * env * modulation;
249
250 line_.push(input);
251
252 const int index = static_cast<int>(std::floor(delay));
253 const double frac = delay - static_cast<double>(index);
254 // Weights in double even when the samples are float: the fractional
255 // position is a double, and rounding it to float would put a
256 // quantisation staircase directly on the modulation the effect exists
257 // to produce.
258 const double interpolated = Interpolator::lagrange(
259 frac,
260 static_cast<double>(line_.read(index - 1)),
261 static_cast<double>(line_.read(index)),
262 static_cast<double>(line_.read(index + 1)),
263 static_cast<double>(line_.read(index + 2)));
264 return static_cast<SampleType>(interpolated);
265 }
266
267private:
268 static constexpr double kTwoPi = 6.283185307179586476925286766559;
269
271 static double amplitude_seconds(double cents, double rate_hz) {
272 return (units::cents_to_ratio(cents) - 1.0) / (kTwoPi * rate_hz);
273 }
274
277 static int worst_case_read_index(double sample_rate) {
278 const double amplitude = amplitude_seconds(kMaxDepthCents, kMinRateHz) * sample_rate;
279 const double base = std::max(kBaseDelayHeadroom * amplitude, kMinBaseDelaySamples);
280 return static_cast<int>(std::ceil(base + amplitude)) + kInterpolatorMarginSamples;
281 }
282
283 void update() {
284 amplitude_samples_ = amplitude_seconds(depth_cents_, rate_hz_) * sample_rate_;
285 base_delay_samples_ =
286 std::max(kBaseDelayHeadroom * amplitude_samples_, kMinBaseDelaySamples);
287 }
288
289 void arm() {
290 lifecycle_active_ = delay_ms_ > 0.0 || fade_in_ms_ > 0.0;
291 depth_env_ = 0.0;
292 if (lifecycle_active_) lifecycle_.gate_on();
293 }
294
295 double advance_lifecycle() {
296 depth_env_ = static_cast<double>(lifecycle_.next());
297 return depth_env_;
298 }
299
300 double sample_rate_ = 44100.0;
301 double rate_hz_ = kDefaultRateHz;
302 double depth_cents_ = kDefaultDepthCents;
303 double delay_ms_ = 0.0;
304 double fade_in_ms_ = 0.0;
305
306 double amplitude_samples_ = 0.0;
307 double base_delay_samples_ = kMinBaseDelaySamples;
308 double depth_env_ = 0.0;
309 bool lifecycle_active_ = false;
310
311 DelayLineT<SampleType> line_{};
312 EffectLfoT<double> lfo_{};
313 DahdsrT<double> lifecycle_{};
314};
315
318
331template <typename SampleType = float>
333public:
337 static constexpr double kDefaultRateHz = 0.8;
338 static constexpr double kMinRateHz = 0.05;
339 static constexpr double kMaxRateHz = 10.0;
340
343 static constexpr double kDefaultDepth = 0.6;
344
347 static constexpr double kSweepOctaves = 1.5;
348
351 static constexpr double kDefaultCenterHz = 500.0;
352 static constexpr double kMinCenterHz = 200.0;
353 static constexpr double kMaxCenterHz = 2000.0;
354
357 static constexpr int kDefaultStageCount = 2;
358 static constexpr int kMaxStages = 4;
359
361 static constexpr double kDefaultMix = 0.5;
362
368 static constexpr double kSinusoidalGainBound = 1.0;
369
371 lfo_.set_wave(LfoWave::sine);
372 lfo_.set_rate_hz(rate_hz_);
373 }
374
375 void prepare(double sample_rate) {
376 sample_rate_ = sample_rate > 0.0 ? sample_rate : sample_rate_;
377 lfo_.prepare(sample_rate_);
378 for (auto& stage : stages_) stage.prepare(static_cast<SampleType>(sample_rate_));
379 }
380
381 void set_rate_hz(double hz) {
382 if (!std::isfinite(hz)) return;
383 rate_hz_ = std::clamp(hz, kMinRateHz, kMaxRateHz);
384 lfo_.set_rate_hz(rate_hz_);
385 }
386
387 double rate_hz() const { return rate_hz_; }
388
389 void set_depth(double depth) {
390 if (!std::isfinite(depth)) return;
391 depth_ = std::clamp(depth, 0.0, 1.0);
392 }
393 double depth() const { return depth_; }
394
395 void set_center_hz(double hz) {
396 if (!std::isfinite(hz)) return;
397 center_hz_ = std::clamp(hz, kMinCenterHz, kMaxCenterHz);
398 }
399 double center_hz() const { return center_hz_; }
400
403 void set_stage_count(int count) {
404 const int clamped = std::clamp(count, 1, kMaxStages);
405 for (int i = stage_count_; i < clamped; ++i) stages_[static_cast<std::size_t>(i)].reset();
406 stage_count_ = clamped;
407 }
408
409 int stage_count() const { return stage_count_; }
410
411 void set_mix(double mix) {
412 if (!std::isfinite(mix)) return;
413 mix_ = std::clamp(mix, 0.0, 1.0);
414 }
415 double mix() const { return mix_; }
416
419 double corner_hz() const { return corner_hz_; }
420
422 std::size_t latency_samples() const { return 0; }
423
424 void reset() {
425 lfo_.reset();
426 for (auto& stage : stages_) stage.reset();
427 corner_hz_ = center_hz_;
428 }
429
430 SampleType process(SampleType input) {
431 if (!std::isfinite(static_cast<double>(input))) {
432 reset();
433 return SampleType{0};
434 }
435 const double modulation = static_cast<double>(lfo_.next());
436 corner_hz_ = center_hz_ * std::exp2(depth_ * kSweepOctaves * modulation);
437
438 double shifted = static_cast<double>(input);
439 for (int i = 0; i < stage_count_; ++i) {
440 auto& stage = stages_[static_cast<std::size_t>(i)];
441 // The corner moves every sample, so the coefficient is recomputed
442 // every sample. Sharing one `tan` across stages would need a
443 // coefficient setter the shared TPT filter does not expose; paying
444 // it per stage keeps this composed rather than forked.
445 stage.set_cutoff(static_cast<SampleType>(corner_hz_));
446 shifted = static_cast<double>(
447 stage.process_allpass(static_cast<SampleType>(shifted)));
448 }
449
450 const double direct = static_cast<double>(input);
451 return static_cast<SampleType>(direct + mix_ * (shifted - direct));
452 }
453
454private:
455 double sample_rate_ = 44100.0;
456 double rate_hz_ = kDefaultRateHz;
457 double depth_ = kDefaultDepth;
458 double center_hz_ = kDefaultCenterHz;
459 double mix_ = kDefaultMix;
460 int stage_count_ = kDefaultStageCount;
461 double corner_hz_ = kDefaultCenterHz;
462
463 EffectLfoT<double> lfo_{};
464 std::array<TptFilterT<SampleType>, kMaxStages> stages_{};
465};
466
469
489template <typename SampleType = float>
490class UniVibeT {
491public:
493 enum class Mode : std::uint8_t { vibrato, chorus };
494
495 static constexpr int kStageCount = 4;
496
501 static constexpr std::array<double, kStageCount> kStageBaseHz{200.0, 430.0, 900.0, 1900.0};
502
504 static constexpr double kDefaultRateHz = 3.0;
505 static constexpr double kMinRateHz = 0.3;
506 static constexpr double kMaxRateHz = 8.0;
507
509 static constexpr double kDefaultDepth = 0.7;
510
512 static constexpr double kSweepOctaves = 1.0;
513
517 static constexpr double kVactrolRiseMs = 0.8;
518 static constexpr double kVactrolFallMs = 35.0;
519
522 static constexpr double kControlExponent = 1.2;
523
526 static constexpr double kVibratoMix = 0.9;
527
530 static constexpr double kChorusBlend = 0.5;
531
533 static constexpr double kSinusoidalGainBound = 1.0;
534
536 lfo_.set_wave(LfoWave::sine);
537 lfo_.set_rate_hz(rate_hz_);
538 vactrol_.set_rise_ms(kVactrolRiseMs);
539 vactrol_.set_fall_ms(kVactrolFallMs);
540 corners_ = kStageBaseHz;
541 }
542
543 void prepare(double sample_rate) {
544 sample_rate_ = sample_rate > 0.0 ? sample_rate : sample_rate_;
545 lfo_.prepare(sample_rate_);
546 vactrol_.prepare(sample_rate_);
547 for (auto& stage : stages_) stage.prepare(static_cast<SampleType>(sample_rate_));
548 }
549
550 void set_rate_hz(double hz) {
551 if (!std::isfinite(hz)) return;
552 rate_hz_ = std::clamp(hz, kMinRateHz, kMaxRateHz);
553 lfo_.set_rate_hz(rate_hz_);
554 }
555
556 double rate_hz() const { return rate_hz_; }
557
558 void set_depth(double depth) {
559 if (!std::isfinite(depth)) return;
560 depth_ = std::clamp(depth, 0.0, 1.0);
561 }
562 double depth() const { return depth_; }
563
564 void set_mode(Mode mode) { mode_ = mode; }
565 Mode mode() const { return mode_; }
566
568 double control() const { return vactrol_.control(); }
569
571 double stage_corner_hz(int index) const {
572 return corners_[static_cast<std::size_t>(std::clamp(index, 0, kStageCount - 1))];
573 }
574
578 static double corner_scale(double control, double depth) {
579 return std::exp2(depth * kSweepOctaves * (2.0 * std::pow(control, kControlExponent) - 1.0));
580 }
581
582 std::size_t latency_samples() const { return 0; }
583
584 void reset() {
585 lfo_.reset();
586 vactrol_.reset();
587 for (auto& stage : stages_) stage.reset();
588 corners_ = kStageBaseHz;
589 }
590
591 void process(SampleType input, SampleType& out_left, SampleType& out_right) {
592 if (!std::isfinite(static_cast<double>(input))) {
593 reset();
594 out_left = out_right = SampleType{0};
595 return;
596 }
597 const double lamp = static_cast<double>(lfo_.next_unipolar());
598 const double control = vactrol_.process(lamp);
599 const double scale = corner_scale(control, depth_);
600
601 double shifted = static_cast<double>(input);
602 for (int i = 0; i < kStageCount; ++i) {
603 const auto slot = static_cast<std::size_t>(i);
604 corners_[slot] = kStageBaseHz[slot] * scale;
605 stages_[slot].set_cutoff(static_cast<SampleType>(corners_[slot]));
606 shifted = static_cast<double>(
607 stages_[slot].process_allpass(static_cast<SampleType>(shifted)));
608 }
609
610 const double direct = static_cast<double>(input);
611 if (mode_ == Mode::vibrato) {
612 const auto wet =
613 static_cast<SampleType>(direct + kVibratoMix * (shifted - direct));
614 out_left = wet;
615 out_right = wet;
616 return;
617 }
618 // Chorus splits the paths: one output stays untouched, which is why it
619 // is asserted bit-exact rather than to a tolerance.
620 out_left = input;
621 out_right = static_cast<SampleType>(direct + kChorusBlend * (shifted - direct));
622 }
623
624private:
625 double sample_rate_ = 44100.0;
626 double rate_hz_ = kDefaultRateHz;
627 double depth_ = kDefaultDepth;
628 Mode mode_ = Mode::vibrato;
629
630 std::array<double, kStageCount> corners_{};
631
632 EffectLfoT<double> lfo_{};
633 VactrolConditionerT<SampleType> vactrol_{};
634 std::array<TptFilterT<SampleType>, kStageCount> stages_{};
635};
636
639
640} // namespace pulp::signal
True pitch vibrato: a fractional delay whose tap position is modulated by a sine, which scales the fr...
Definition vibrato.hpp:106
void prepare(double sample_rate)
Sizes the delay line for the longest modulated read any legal parameter combination can ask for,...
Definition vibrato.hpp:161
void discard_history() noexcept
Definition vibrato.hpp:234
static constexpr double kDefaultRateHz
Definition vibrato.hpp:109
std::size_t latency_samples() const
Exactly ceil((D0 + A) * fs): the longest delay the tap can read, which is the conservative reading of...
Definition vibrato.hpp:222
static constexpr double kMinBaseDelaySamples
Definition vibrato.hpp:129
void set_rate_hz(double hz)
Definition vibrato.hpp:170
DelayVibratoT()
Definition vibrato.hpp:146
double depth_cents() const
Definition vibrato.hpp:189
double rate_hz() const
Definition vibrato.hpp:177
static constexpr double kMaxDepthCents
Definition vibrato.hpp:118
SampleType process(SampleType input)
Definition vibrato.hpp:241
static constexpr int kInterpolatorMarginSamples
Definition vibrato.hpp:133
void set_fade_in_ms(double ms)
Time for the depth to ramp 0 -> 1 after the delay, in ms. Re-arms.
Definition vibrato.hpp:202
static constexpr double kDefaultDepthCents
Definition vibrato.hpp:117
double base_delay_samples() const
Base delay in samples — the centre the modulation swings around.
Definition vibrato.hpp:210
double depth_envelope() const
Current lifecycle depth scale in [0, 1].
Definition vibrato.hpp:217
void reset()
Definition vibrato.hpp:227
static constexpr double kMinRateHz
Definition vibrato.hpp:110
static constexpr double kMaxRateHz
Definition vibrato.hpp:111
void set_depth_cents(double cents)
Peak pitch excursion in cents.
Definition vibrato.hpp:183
static constexpr double kBaseDelayHeadroom
Definition vibrato.hpp:123
static constexpr double kInterpolatorPeakGain
Definition vibrato.hpp:144
static constexpr double kMaxLifecycleMs
Definition vibrato.hpp:137
void set_delay_ms(double ms)
Silence before the vibrato starts, in ms.
Definition vibrato.hpp:194
double modulation_amplitude_samples() const
Peak modulation excursion in samples, before the lifecycle scales it.
Definition vibrato.hpp:213
Definition lfo.hpp:777
Magnatone-style phase vibrato: N cascaded first-order allpass stages whose corner is driven straight ...
Definition vibrato.hpp:332
double rate_hz() const
Definition vibrato.hpp:387
static constexpr double kMinCenterHz
Definition vibrato.hpp:352
static constexpr double kDefaultMix
Definition vibrato.hpp:361
double mix() const
Definition vibrato.hpp:415
static constexpr int kDefaultStageCount
Definition vibrato.hpp:357
static constexpr double kSweepOctaves
Definition vibrato.hpp:347
SampleType process(SampleType input)
Definition vibrato.hpp:430
void prepare(double sample_rate)
Definition vibrato.hpp:375
double corner_hz() const
Corner the cascade is currently sitting at, in Hz.
Definition vibrato.hpp:419
static constexpr double kMaxRateHz
Definition vibrato.hpp:339
void set_rate_hz(double hz)
Definition vibrato.hpp:381
static constexpr int kMaxStages
Definition vibrato.hpp:358
void set_mix(double mix)
Definition vibrato.hpp:411
void reset()
Definition vibrato.hpp:424
double depth() const
Definition vibrato.hpp:393
static constexpr double kMaxCenterHz
Definition vibrato.hpp:353
static constexpr double kDefaultDepth
Definition vibrato.hpp:343
static constexpr double kDefaultRateHz
Definition vibrato.hpp:337
PhaseVibratoT()
Definition vibrato.hpp:370
void set_stage_count(int count)
Stages that become active are cleared first, so switching count in does not fold a stale state from a...
Definition vibrato.hpp:403
static constexpr double kDefaultCenterHz
Definition vibrato.hpp:351
static constexpr double kSinusoidalGainBound
Definition vibrato.hpp:368
double center_hz() const
Definition vibrato.hpp:399
void set_center_hz(double hz)
Definition vibrato.hpp:395
static constexpr double kMinRateHz
Definition vibrato.hpp:338
std::size_t latency_samples() const
Pure IIR: no bulk delay anywhere in the path.
Definition vibrato.hpp:422
void set_depth(double depth)
Definition vibrato.hpp:389
int stage_count() const
Definition vibrato.hpp:409
Univibe-style phase vibrato: four allpass stages at deliberately unequal corners, all swept together ...
Definition vibrato.hpp:490
UniVibeT()
Definition vibrato.hpp:535
double rate_hz() const
Definition vibrato.hpp:556
static constexpr double kChorusBlend
Definition vibrato.hpp:530
static constexpr std::array< double, kStageCount > kStageBaseHz
Definition vibrato.hpp:501
static constexpr double kDefaultDepth
Definition vibrato.hpp:509
void set_rate_hz(double hz)
Definition vibrato.hpp:550
Mode
The documented physical switch.
Definition vibrato.hpp:493
@ vibrato
Definition vibrato.hpp:493
@ chorus
Definition vibrato.hpp:493
static constexpr double kVactrolFallMs
Definition vibrato.hpp:518
double control() const
Conditioned control in [0, 1] — the photocell's state, after the lag.
Definition vibrato.hpp:568
static constexpr int kStageCount
Definition vibrato.hpp:495
static constexpr double kVactrolRiseMs
Definition vibrato.hpp:517
std::size_t latency_samples() const
Definition vibrato.hpp:582
void process(SampleType input, SampleType &out_left, SampleType &out_right)
Definition vibrato.hpp:591
static double corner_scale(double control, double depth)
The corner scale a given conditioned control produces.
Definition vibrato.hpp:578
void set_depth(double depth)
Definition vibrato.hpp:558
static constexpr double kVibratoMix
Definition vibrato.hpp:526
void set_mode(Mode mode)
Definition vibrato.hpp:564
static constexpr double kControlExponent
Definition vibrato.hpp:522
void prepare(double sample_rate)
Definition vibrato.hpp:543
Mode mode() const
Definition vibrato.hpp:565
double stage_corner_hz(int index) const
Live corner of stage index in Hz.
Definition vibrato.hpp:571
double depth() const
Definition vibrato.hpp:562
static constexpr double kSinusoidalGainBound
Definition vibrato.hpp:533
static constexpr double kMinRateHz
Definition vibrato.hpp:505
static constexpr double kDefaultRateHz
Definition vibrato.hpp:504
static constexpr double kMaxRateHz
Definition vibrato.hpp:506
void reset()
Definition vibrato.hpp:584
static constexpr double kSweepOctaves
Definition vibrato.hpp:512
The envelope family — AR, AD, AHD, DAHDSR, and a trigger-driven modulation envelope — plus a level-in...
High-quality sample interpolation for delay lines and resampling.
The house low-frequency oscillator: seven waveforms plus a continuous shape morph,...
T cents_to_ratio(T cents)
Cents → frequency ratio. +100 → 2^(1/12), +1200 → 2.
Definition units.hpp:118
Definition additive_bank.hpp:218
UniVibeT< double > UniVibe64
Definition vibrato.hpp:638
DelayVibratoT< double > DelayVibrato64
Definition vibrato.hpp:317
PhaseVibratoT< double > PhaseVibrato64
Definition vibrato.hpp:468
DelayVibratoT< float > DelayVibrato
Definition vibrato.hpp:316
@ delay
Definition envelope.hpp:57
PhaseVibratoT< float > PhaseVibrato
Definition vibrato.hpp:467
@ i
Definition formant_filter_bank.hpp:20
@ sine
Definition lfo_sampling.hpp:54
@ direct
set_shift_semitones. Detents and the pedal law are bypassed.
Definition pitch_shifter.hpp:194
UniVibeT< float > UniVibe
Definition vibrato.hpp:637
static SampleType lagrange(SampleType frac, SampleType ym1, SampleType y0, SampleType y1, SampleType y2)
4-point Lagrange interpolation.
Definition interpolator.hpp:60
First-order topology-preserving transform (TPT) filter.
The real-unit vocabulary shared by every DSP block in the catalog.
The vactrol control-conditioning law: an asymmetric one-pole whose rise and fall are deliberately dif...