Sound Byte Libs 29c5ff3
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
delay.hpp
Go to the documentation of this file.
1// sbl/widgets/proc/delay.hpp — Stereo delay effect (Audio Stack — Widgets)
2//
3// Stereo delay with filtered feedback, dry/wet mix, and ping-pong mode.
4// Tone control uses a one-pole lowpass in the feedback path — same damping
5// formulation as the comb filter — simulating high-frequency absorption.
6//
7// Uses ModulatedDelayLine internally for buffer management and interpolated
8// reads. The delay time glides smoothly via per-sample coefficient, producing
9// tape-style pitch effects on time changes.
10//
11// Float-domain processing — STM32H7 (Cortex-M7) has hardware FPU with
12// single-cycle float ops.
13//
14// Usage:
15// float buf_l[24000] = {}, buf_r[24000] = {}; // 500ms @ 48kHz
16// sbl::widgets::proc::Delay delay;
17// delay.init(buf_l, buf_r, 24000);
18// delay.set_sample_rate(48000);
19// delay.set_time_ms(300.0f);
20// delay.set_feedback(0.4f);
21// delay.set_mix(0.3f);
22// delay.process(left, right, frames);
23
24#pragma once
25
26#include <cstdint>
27
29#include <sbl/dsp/one_pole.hpp>
30
32
33class Delay {
34public:
35 /// @note All public methods are ISR-safe — bounded computation, no I/O.
36
37 /**
38 * @brief Construct with caller-provided float buffers
39 * @param buf_l Left delay buffer (must outlive the Delay)
40 * @param buf_r Right delay buffer (must outlive the Delay)
41 * @param max_samples Maximum delay in samples (buffer size)
42 */
43 Delay(float* buf_l, float* buf_r, uint32_t max_samples)
44 : dl_l_(buf_l, max_samples), dl_r_(buf_r, max_samples) {}
45
46 /// Default constructor (must call init() before use)
47 Delay() = default;
48
49 /**
50 * @brief Initialize after default construction
51 * @param buf_l Left delay buffer
52 * @param buf_r Right delay buffer
53 * @param max_samples Maximum delay in samples
54 */
55 void init(float* buf_l, float* buf_r, uint32_t max_samples) {
56 dl_l_.init(buf_l, max_samples);
57 dl_r_.init(buf_r, max_samples);
58 }
59
60 void set_sample_rate(uint32_t sr) { sample_rate_ = sr; }
61
62 /**
63 * @brief Set delay time in samples
64 * @param samples Delay length (clamped to max_samples - 1)
65 */
66 void set_time(uint32_t samples) {
67 uint32_t max = dl_l_.max_delay();
68 target_samples_ = (samples < max) ? samples : max - 1;
69 if (!time_initialized_) {
70 current_samples_ = static_cast<float>(target_samples_);
71 time_initialized_ = true;
72 }
73 }
74
75 /**
76 * @brief Set delay time in milliseconds
77 * @param ms Delay time in milliseconds
78 */
79 void set_time_ms(float ms) {
80 set_time(static_cast<uint32_t>(ms * static_cast<float>(sample_rate_) / 1000.0f));
81 }
82
83 /**
84 * @brief Set delay time from BPM and note division
85 * @param bpm Tempo in beats per minute
86 * @param division Note division (1=whole, 2=half, 4=quarter, 8=eighth, etc.)
87 */
88 void set_time_bpm(float bpm, uint8_t division) {
89 if (bpm < 1.0f) bpm = 1.0f;
90 float seconds = 60.0f / (bpm * static_cast<float>(division));
91 uint32_t samples = static_cast<uint32_t>(seconds * static_cast<float>(sample_rate_));
92 set_time(samples);
93 }
94
95 /**
96 * @brief Set feedback gain
97 * @param fb Feedback 0.0–1.0 (clamped to 0.95 for stability)
98 */
99 void set_feedback(float fb) {
100 feedback_ = (fb > 0.95f) ? 0.95f : (fb < 0.0f ? 0.0f : fb);
101 }
102
103 /**
104 * @brief Set dry/wet mix
105 * @param mix 0.0 = fully dry, 1.0 = fully wet
106 */
107 void set_mix(float mix) {
108 mix_ = (mix > 1.0f) ? 1.0f : (mix < 0.0f ? 0.0f : mix);
109 }
110
111 /**
112 * @brief Set tone (feedback damping)
113 * @param tone 0.0 = dark (heavy HF absorption), 1.0 = bright (no filtering)
114 */
115 void set_tone(float tone) {
116 if (tone > 1.0f) tone = 1.0f;
117 if (tone < 0.0f) tone = 0.0f;
118 // tone=1.0 (bright) → coeff=1.0 (passthrough)
119 // tone=0.0 (dark) → coeff=0.1 (heavy LP filtering)
120 float coeff = 1.0f - (1.0f - tone) * 0.9f;
121 tone_l_.set_coefficient(coeff);
122 tone_r_.set_coefficient(coeff);
123 }
124
125 /** @brief Enable/disable ping-pong stereo bounce */
126 void set_ping_pong(bool enable) { ping_pong_ = enable; }
127
128 /** @brief Current delay time in samples */
129 uint32_t time() const { return target_samples_; }
130 float feedback() const { return feedback_; }
131 float mix() const { return mix_; }
132 bool ping_pong() const { return ping_pong_; }
133
134 /**
135 * @brief Process a stereo block in-place (float)
136 *
137 * @param left Left channel buffer (float, in-place)
138 * @param right Right channel buffer (float, in-place)
139 * @param frames Number of sample frames
140 */
141 void process(float* left, float* right, uint16_t frames) {
142 const float fb = feedback_;
143 const float wet = mix_;
144 const float dry = 1.0f - wet;
145 const float target = static_cast<float>(target_samples_);
146
147 for (uint16_t i = 0; i < frames; ++i) {
148 current_samples_ += TIME_SMOOTH * (target - current_samples_);
149
150 float in_l = left[i];
151 float in_r = right[i];
152
153 float delayed_l = dl_l_.read(current_samples_);
154 float delayed_r = dl_r_.read(current_samples_);
155
156 float filtered_l = tone_l_.process(delayed_l);
157 float filtered_r = tone_r_.process(delayed_r);
158
159 float fb_l, fb_r;
160 if (ping_pong_) {
161 fb_l = filtered_r * fb;
162 fb_r = filtered_l * fb;
163 } else {
164 fb_l = filtered_l * fb;
165 fb_r = filtered_r * fb;
166 }
167
168 dl_l_.write(in_l + fb_l);
169 dl_r_.write(in_r + fb_r);
170
171 left[i] = dry * in_l + wet * delayed_l;
172 right[i] = dry * in_r + wet * delayed_r;
173 }
174 }
175
176 /** @brief Clear delay buffers and reset all state */
177 void reset() {
178 dl_l_.clear();
179 dl_r_.clear();
180 current_samples_ = static_cast<float>(target_samples_);
181 time_initialized_ = (target_samples_ > 0);
182 tone_l_.reset();
183 tone_r_.reset();
184 }
185
186private:
187 static constexpr float TIME_SMOOTH = 0.002f;
188
191
192 uint32_t target_samples_ = 0;
193 float current_samples_ = 0.0f;
194 bool time_initialized_ = false;
195 float feedback_ = 0.4f;
196 float mix_ = 0.3f;
197 dsp::OnePole tone_l_;
198 dsp::OnePole tone_r_;
199 bool ping_pong_ = false;
200 uint32_t sample_rate_ = 48000;
201};
202
203} // namespace sbl::widgets::proc
void init(float *buffer, uint32_t max_delay)
Initialize after default construction.
float read(float delay_samples) const
Read at fractional delay with linear interpolation.
uint32_t max_delay() const
Maximum delay in samples (buffer size)
void clear()
Zero all samples in the buffer and reset write position.
void write(float sample)
Write a sample to the delay line.
void reset()
Reset filter state to zero.
Definition one_pole.hpp:82
float process(float x)
Process a single sample.
Definition one_pole.hpp:58
void set_coefficient(float a)
Set filter coefficient directly.
Definition one_pole.hpp:31
void set_tone(float tone)
Set tone (feedback damping)
Definition delay.hpp:115
bool ping_pong() const
Definition delay.hpp:132
Delay(float *buf_l, float *buf_r, uint32_t max_samples)
Construct with caller-provided float buffers.
Definition delay.hpp:43
void init(float *buf_l, float *buf_r, uint32_t max_samples)
Initialize after default construction.
Definition delay.hpp:55
void set_ping_pong(bool enable)
Enable/disable ping-pong stereo bounce.
Definition delay.hpp:126
void set_time(uint32_t samples)
Set delay time in samples.
Definition delay.hpp:66
void process(float *left, float *right, uint16_t frames)
Process a stereo block in-place (float)
Definition delay.hpp:141
void set_time_ms(float ms)
Set delay time in milliseconds.
Definition delay.hpp:79
void set_time_bpm(float bpm, uint8_t division)
Set delay time from BPM and note division.
Definition delay.hpp:88
Delay()=default
Default constructor (must call init() before use)
void set_feedback(float fb)
Set feedback gain.
Definition delay.hpp:99
uint32_t time() const
Current delay time in samples.
Definition delay.hpp:129
void set_sample_rate(uint32_t sr)
Definition delay.hpp:60
float feedback() const
Definition delay.hpp:130
void reset()
Clear delay buffers and reset all state.
Definition delay.hpp:177
void set_mix(float mix)
Set dry/wet mix.
Definition delay.hpp:107
Signal processing widgets.
Definition delay.hpp:31