33#ifndef SBL_DSP_MODULES_TAPE_DELAY_HPP_
34#define SBL_DSP_MODULES_TAPE_DELAY_HPP_
101 void init(
float* buf_l,
float* buf_r, uint32_t max_samples) {
102 dl_l_.
init(buf_l, max_samples);
103 dl_r_.
init(buf_r, max_samples);
104 configure_rate_dependent();
116 if (su < 0.0f) su = 0.0f;
117 if (su > 1.0f) su = 1.0f;
121 set_time(
static_cast<uint32_t
>(su *
static_cast<float>(usable)));
133 if (bpm < 1.0f) bpm = 1.0f;
134 float seconds = (60.0f / bpm) * subdivision_beats(div);
148 if (su < 0.0f) su = 0.0f;
149 if (su > 1.0f) su = 1.0f;
154 ensure_fb_smoother();
156 op_feedback_.
reset(fb);
170 if (su < 0.0f) su = 0.0f;
171 if (su > 1.0f) su = 1.0f;
183 if (su < 0.0f) su = 0.0f;
184 if (su > 1.0f) su = 1.0f;
193 if (su < 0.0f) su = 0.0f;
194 if (su > 1.0f) su = 1.0f;
203 if (su < 0.0f) su = 0.0f;
204 if (su > 1.0f) su = 1.0f;
213 if (su < 0.0f) su = 0.0f;
214 if (su > 1.0f) su = 1.0f;
223 if (su < 0.0f) su = 0.0f;
224 if (su > 1.0f) su = 1.0f;
233 if (su < 0.0f) su = 0.0f;
234 if (su > 1.0f) su = 1.0f;
243 uint32_t
time()
const {
return static_cast<uint32_t
>(target_time_); }
256 void process(
float* left,
float* right, uint16_t frames) {
257 if (!smoothing_initialized_) {
258 smooth_feedback_ = feedback_;
259 smooth_drive_ = drive_;
260 block_drive_ = drive_;
261 smooth_tone_su_ = tone_su_;
262 smooth_hp_su_ = hp_su_;
263 smooth_time_ = target_time_;
264 smooth_wow_depth_ = wow_depth_;
265 smooth_flutter_depth_ = flutter_depth_;
266 smooth_hiss_level_ = hiss_level_;
267 smooth_drift_amount_ = drift_amount_;
268 op_feedback_.
reset(feedback_);
269 smoothing_initialized_ =
true;
271 if (!time_initialized_) {
272 time_slew_.
reset(target_time_);
273 time_initialized_ =
true;
282 smooth_time_ += TIME_SMOOTH * (target_time_ - smooth_time_);
285 block_drive_ += TIME_SMOOTH * (drive_ - block_drive_);
286 smooth_wow_depth_ += TIME_SMOOTH * (wow_depth_ - smooth_wow_depth_);
287 smooth_flutter_depth_ += TIME_SMOOTH * (flutter_depth_ - smooth_flutter_depth_);
288 smooth_hiss_level_ += TIME_SMOOTH * (hiss_level_ - smooth_hiss_level_);
289 smooth_drift_amount_ += TIME_SMOOTH * (drift_amount_ - smooth_drift_amount_);
291 smooth_tone_su_ += BLOCK_SMOOTH * (tone_su_ - smooth_tone_su_);
296 smooth_hp_su_ += BLOCK_SMOOTH * (hp_su_ - smooth_hp_su_);
301 const bool hp = hp_su_ > 0.0f;
302 const float hiss = smooth_hiss_level_;
307 float smoothed_fb = op_feedback_.
process(feedback_);
309 float prev_fb = smooth_feedback_;
310 float prev_drv = smooth_drive_;
311 smooth_feedback_ = smoothed_fb;
312 smooth_drive_ = block_drive_;
314 float fb_inc = (smooth_feedback_ - prev_fb) /
static_cast<float>(frames);
315 float drv_inc = (smooth_drive_ - prev_drv) /
static_cast<float>(frames);
316 float fb_val = prev_fb;
317 float drv_val = prev_drv;
319 for (uint16_t i = 0; i < frames; ++i) {
321 float in_l = left[i];
322 float in_r = right[i];
333 float motor_distance = smooth_time_ - time_slew_.
value();
334 if (motor_distance < 0.0f) motor_distance = -motor_distance;
335 float accel = motor_distance * (1.0f / 4800.0f);
336 float effective_rate = motor_slew_rate_ * (1.0f + accel * 1.0f);
337 time_slew_.
set_rate(effective_rate);
340 float slewed = time_slew_.
process(smooth_time_);
344 float slew_delta = time_slew_.
delta();
345 float slew_scale = (slew_delta < 1.0f) ? 1.0f :
346 (slew_delta > 4800.0f) ? 0.5f :
347 1.0f - 0.5f * (slew_delta / 4800.0f);
352 float time_mod = wow_reader_.
read(wow_phase_.
phase()) * smooth_wow_depth_ * slew_scale
353 + flutter_reader_.
read(flutter_phase_.
phase()) * smooth_flutter_depth_ * slew_scale
354 + time_drift_.
value() * smooth_drift_amount_;
356 float read_pos = slewed + time_mod;
364 wet_l = tone_lp_l_.
process(wet_l);
365 wet_r = tone_lp_r_.
process(wet_r);
374 float fb_l = hp ? feedback_hp_l_.
process(wet_l) : wet_l;
375 float fb_r = hp ? feedback_hp_r_.
process(wet_r) : wet_r;
389 if (drv_val > 1.01f) {
390 fb_l = saturator_.
process(fb_l, drv_val);
391 fb_r = saturator_.
process(fb_r, drv_val);
413 dl_l_.
write(in_l + fb_l);
414 dl_r_.
write(in_r + fb_r);
433 feedback_hp_l_.
reset();
434 feedback_hp_r_.
reset();
437 flutter_phase_.
reset();
442 smooth_wow_depth_ = 0.0f;
443 smooth_flutter_depth_ = 0.0f;
444 smooth_hiss_level_ = 0.0f;
445 smooth_drift_amount_ = 0.0f;
446 time_initialized_ =
false;
447 smoothing_initialized_ =
false;
461 void set_time(uint32_t samples) {
463 target_time_ =
static_cast<float>((samples < max) ? samples : max - 1);
464 if (!time_initialized_) {
465 time_slew_.
reset(target_time_);
466 time_initialized_ =
true;
471 void set_time_ms(
float ms) {
480 void set_feedback(
float fb) {
482 op_feedback_.
reset(feedback_);
488 void set_tone(
float freq_hz) {
492 if (su < 0.0f) su = 0.0f;
493 if (su > 1.0f) su = 1.0f;
500 void set_bass_loss(
float freq_hz) {
503 if (su < 0.0f) su = 0.0f;
504 if (su > 1.0f) su = 1.0f;
511 void set_drive(
float drive) {
512 drive_ = (drive < 1.0f) ? 1.0f : drive;
518 void set_wobble(
float amount) {
519 wow_depth_ = amount * 3.0f;
520 flutter_depth_ = amount * 2.0f;
526 void set_hiss(
float amount) {
527 hiss_level_ = amount * 0.003f;
533 void set_drift(
float amount) {
534 drift_amount_ = amount * 10.0f;
540 void set_transport_speed(
float samples_per_sample) {
541 motor_slew_rate_ = (samples_per_sample < 0.5f) ? 0.5f : samples_per_sample;
542 time_slew_.
set_rate(motor_slew_rate_);
545 void ensure_fb_smoother() {
546 if (!fb_smoother_configured_) {
548 fb_smoother_configured_ =
true;
552 static constexpr float BLOCK_SMOOTH = 0.2f;
558 static constexpr float TIME_SMOOTH = 0.02f;
563 static constexpr float MOTOR_SLEW_RATE = 5.0f;
565 void configure_rate_dependent() {
571 time_slew_.
set_rate(motor_slew_rate_);
591 op_feedback_.
reset(feedback_);
592 fb_smoother_configured_ =
true;
608 default:
return 1.0f;
613 primitives::DelayLine dl_l_;
614 primitives::DelayLine dl_r_;
615 primitives::OnePole tone_lp_l_;
616 primitives::OnePole tone_lp_r_;
617 primitives::OnePoleHP feedback_hp_l_;
618 primitives::OnePoleHP feedback_hp_r_;
621 primitives::PhaseAccumulator wow_phase_;
622 primitives::PhaseAccumulator flutter_phase_;
623 primitives::WavetableReader<256> wow_reader_;
624 primitives::WavetableReader<256> flutter_reader_;
625 comp::AnalogDrift time_drift_{0xDEADCAFE};
626 primitives::SlewLimiter time_slew_;
627 primitives::Rng hiss_rng_{0xBEEFF00D};
628 primitives::OnePole hiss_pink_;
629 comp::WarmSaturator saturator_;
630 primitives::SoftLimiter limiter_;
631 primitives::DcBlocker dc_l_;
632 primitives::DcBlocker dc_r_;
635 float feedback_ = 0.4f;
637 float tone_su_ = 0.5f;
639 float wow_depth_ = 0.0f;
640 float flutter_depth_ = 0.0f;
641 float hiss_level_ = 0.0f;
642 float drift_amount_ = 0.0f;
643 bool ping_pong_ =
false;
644 float motor_slew_rate_ = MOTOR_SLEW_RATE;
645 float target_time_ = 12000.0f;
648 primitives::ParameterSmoother op_feedback_;
649 bool fb_init_ =
false;
650 bool fb_smoother_configured_ =
false;
653 float smooth_feedback_ = 0.0f;
654 float smooth_drive_ = 0.0f;
655 float block_drive_ = 0.0f;
656 float smooth_tone_su_ = 0.0f;
657 float smooth_hp_su_ = 0.0f;
658 float smooth_time_ = 0.0f;
659 float smooth_wow_depth_ = 0.0f;
660 float smooth_flutter_depth_ = 0.0f;
661 float smooth_hiss_level_ = 0.0f;
662 float smooth_drift_amount_ = 0.0f;
663 bool smoothing_initialized_ =
false;
664 bool time_initialized_ =
false;
Analog oscillator drift simulation.
void set_rate(float control_rate_hz)
void reset()
Reset drift state to zero.
float value() const
Current drift value (before amount scaling), for diagnostics.
float process(float amount)
void set_asymmetry(float k)
Set asymmetry depth (structural character parameter)
float process(float x, float drive)
Process a single sample with per-sample drive.
static constexpr float kMinFeedback
void init(float *buf_l, float *buf_r, uint32_t max_samples)
Initialize with pre-allocated stereo delay buffers.
static constexpr float kMaxFeedback
static constexpr float kMinDrive
void tick()
Tick control-rate modulation (~200 Hz)
static constexpr float kMinBassLossFilterHz
static constexpr float kMinToneHz
static constexpr float kMinBassLossHz
static constexpr float kMaxDrift
void set_drive_su(float su)
Set drive (magnetic saturation) from normalized [0,1] signal unit.
static constexpr float kMaxToneHz
static constexpr float kMaxTransportSpeed
void set_transport_speed_su(float su)
Set transport speed (motor inertia) from normalized [0,1] signal unit.
void set_time_su(float su)
Set delay time from normalized [0,1] signal unit.
static constexpr float kMaxDrive
void set_wobble_su(float su)
Set wow+flutter from normalized [0,1] signal unit.
void set_hiss_su(float su)
Set tape hiss from normalized [0,1] signal unit.
void set_drift_su(float su)
Set speed drift from normalized [0,1] signal unit.
friend struct TapeDelayTestAccess
void reset()
Clear delay buffers and reset all internal state.
static constexpr float kMinTransportSpeed
static constexpr float kMaxHiss
void set_bass_loss_su(float su)
Set bass loss (feedback HP) from normalized [0,1] signal unit.
static constexpr float kMaxBassLossHz
void set_time_bpm(float bpm, Subdivision div)
Tempo-synced delay time (internally slewed)
void set_ping_pong(bool enable)
Enable stereo ping-pong (alternating L/R repeats). Default: false.
static constexpr uint32_t kModHeadroom
void set_feedback_su(float su)
Set feedback from normalized [0,1] signal unit.
static constexpr float kMaxWobble
void set_tone_su(float su)
Set tone (head gap loss LP) from normalized [0,1] signal unit.
void process(float *left, float *right, uint16_t frames)
Process a stereo block in-place (wet-only output)
float process(float x)
Process one sample through the DC blocker.
void reset()
Reset filter state to zero.
uint32_t max_delay() const
Maximum delay in samples (buffer size)
void write(float sample)
Write a sample to the delay line.
float read_cubic(float delay_samples) const
Read at fractional delay with 4-point Hermite cubic interpolation.
void init(float *buffer, uint32_t max_delay)
Initialize after default construction.
void reset()
Zero all samples in the buffer and reset write position.
void set_cutoff_su(float su)
void set_range(float min_hz, float max_hz)
void reset()
Reset filter state to zero.
void set_range(float min_hz, float max_hz)
Set frequency range for _su mapping (configuration, not a signal)
float process(float x)
Process a single sample.
void set_cutoff_su(float su)
Set cutoff frequency via signal unit.
void reset()
Reset to zero.
void set_time_ms(float ms, float rate_hz)
Compute coefficient from settling time in milliseconds.
float process(float target)
Process one sample toward target.
void set_increment(uint32_t inc)
Set the phase increment per sample.
static uint32_t freq_to_inc(float freq_hz, float sr=types::SAMPLE_RATE_F)
Convert frequency in Hz to phase increment.
void reset()
Reset phase to zero.
uint32_t phase() const
Current phase value.
void advance()
Advance phase by one sample.
float bipolar()
Random float in [-1.0, 1.0].
void set_rate(float rate)
Set both rise and fall rates to the same value.
float value() const
Current output value.
float process(float target)
Process one sample toward target with rate limiting.
void reset()
Reset to zero.
float delta() const
Distance from current state to last processed target (absolute)
float process(float x)
Process a single sample.
void set_table(const float *table)
Set the wavetable to read from (must have Size+1 guard points for linear)
float read(uint32_t phase) const
Read a single sample with linear interpolation.
DC offset removal filter.
Circular buffer delay line.
Fast analytical approximations (Audio Stack — Atoms)
Fixed-point constants and audio sample types.
constexpr float sin_256[258]
constexpr float triangle_256[258]
Subdivision
Musical note subdivisions for tempo-synced delay.
Single-pole IIR filters (LP and HP)
One-pole parameter smoother.
Phase accumulator for oscillators and LFOs.
Lightweight pseudorandom number generator.
Generated by LUTE — do not edit.
Slew rate limiter (float)
static void set_frequency(OnePole &f, float hz)
Generated by LUTE — do not edit.
Warm saturation composition.
Wavetable reader with interpolation.