Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pitched_feedback_delay.hpp
Go to the documentation of this file.
1#pragma once
2
30
31#include <algorithm>
32#include <cassert>
33#include <cmath>
34#include <cstdint>
35#include <vector>
36
37namespace pulp::signal {
38
40template <typename SampleType = float>
42public:
43 virtual ~FeedbackLoopProcessorT() = default;
44 virtual int loop_latency_samples() const = 0;
45 virtual bool loop_is_frozen() const { return false; }
47 virtual void loop_process(const SampleType* const* in, SampleType* const* out,
48 int num_samples) = 0;
49};
50
53
54template <typename SampleType = float>
56public:
57 struct Config {
58 float max_delay_seconds = 4.0f;
59 int channels = 1;
60 int max_block = 4096;
61 };
62
67 void prepare(double sample_rate, const Config& config) {
68 assert(sample_rate > 0.0 && config.channels >= 1);
69 config_ = config;
70 sample_rate_ = sample_rate;
71 ring_size_ = 1;
72 while (ring_size_ < static_cast<int>(config.max_delay_seconds * sample_rate) + 8)
73 ring_size_ <<= 1;
74 ring_mask_ = ring_size_ - 1;
75 ring_.assign(static_cast<size_t>(config.channels) * ring_size_, SampleType{0});
76 chunk_in_.assign(static_cast<size_t>(config.channels) * kMaxChunk, SampleType{0});
77 chunk_out_.assign(static_cast<size_t>(config.channels) * kMaxChunk, SampleType{0});
78 in_ptrs_.resize(static_cast<size_t>(config.channels));
79 out_ptrs_.resize(static_cast<size_t>(config.channels));
80 // ~50 ms gate and ~30 ms delay-glide one-pole coefficients.
81 gate_coeff_ = std::exp(-1.0 / (0.05 * sample_rate));
82 delay_coeff_ = std::exp(-1.0 / (0.03 * sample_rate));
83 reset();
84 }
85
86 void reset() {
87 std::fill(ring_.begin(), ring_.end(), SampleType{0});
88 write_pos_ = 0;
89 delay_current_ = delay_target_;
90 gate_ = 1.0;
91 }
92
93 void set_delay_ms(float ms) {
94 delay_target_ = std::clamp(static_cast<double>(ms) * 0.001 * sample_rate_,
95 static_cast<double>(min_delay_samples()),
96 static_cast<double>(ring_size_ - 4));
97 // Before the stream starts there is nothing to glide from.
98 if (write_pos_ == 0) delay_current_ = delay_target_;
99 }
100
102 void set_delay_sync(double tempo_bpm, double beats) {
103 assert(tempo_bpm > 0.0 && beats > 0.0);
104 set_delay_ms(static_cast<float>(beats * 60000.0 / tempo_bpm));
105 }
106
108 void set_feedback(float feedback) {
109 feedback_ = std::clamp(static_cast<SampleType>(feedback),
110 SampleType{0}, static_cast<SampleType>(0.99));
111 }
112
114 loop_ = processor;
115 // Re-apply the floor in case the new processor's latency raised it.
116 delay_target_ = std::max(delay_target_, static_cast<double>(min_delay_samples()));
117 }
118
120 int min_delay_samples() const {
121 return (loop_ != nullptr ? loop_->loop_latency_samples() : 0) + 1;
122 }
123
124 void process(const SampleType* const* in, SampleType* const* out, int num_samples) {
125 assert(num_samples <= config_.max_block);
126 int done = 0;
127 while (done < num_samples) {
128 // Sub-chunk below the loop's recursion length so feedback
129 // written in this chunk is never read within it.
130 const int recursion = std::max(
131 1, static_cast<int>(delay_current_) - min_delay_samples() + 1);
132 const int chunk =
133 std::min({num_samples - done, recursion, kMaxChunk});
134 process_chunk(in, out, done, chunk);
135 done += chunk;
136 }
137 }
138
139private:
140 void process_chunk(const SampleType* const* in, SampleType* const* out, int offset, int n) {
141 const bool frozen = loop_ != nullptr && loop_->loop_is_frozen();
142 const double gate_target = frozen ? 0.0 : 1.0;
143 const int read_latency = loop_ != nullptr ? loop_->loop_latency_samples() : 0;
144
145 // Read the delayed (pre-processor) signal into chunk_in_.
146 for (int ch = 0; ch < config_.channels; ++ch) {
147 in_ptrs_[static_cast<size_t>(ch)] =
148 chunk_in_.data() + static_cast<size_t>(ch) * kMaxChunk;
149 out_ptrs_[static_cast<size_t>(ch)] =
150 chunk_out_.data() + static_cast<size_t>(ch) * kMaxChunk;
151 }
152 double d = delay_current_;
153 for (int i = 0; i < n; ++i) {
154 d += (1.0 - delay_coeff_) * (delay_target_ - d);
155 const double read_at = static_cast<double>(write_pos_ + i)
156 - (d - static_cast<double>(read_latency));
157 const auto i0 = static_cast<std::int64_t>(std::floor(read_at));
158 const SampleType frac = static_cast<SampleType>(read_at - static_cast<double>(i0));
159 for (int ch = 0; ch < config_.channels; ++ch) {
160 const SampleType* ring = ring_.data() + static_cast<size_t>(ch) * ring_size_;
161 const SampleType a = ring[static_cast<size_t>(i0 & ring_mask_)];
162 const SampleType b = ring[static_cast<size_t>((i0 + 1) & ring_mask_)];
163 in_ptrs_[static_cast<size_t>(ch)][i] = a + frac * (b - a);
164 }
165 }
166 delay_current_ = d;
167
168 // Optional in-loop processor (adds back read_latency).
169 if (loop_ != nullptr) {
170 loop_->loop_process(
171 const_cast<const SampleType* const*>(in_ptrs_.data()),
172 out_ptrs_.data(), n);
173 } else {
174 for (int ch = 0; ch < config_.channels; ++ch)
175 std::copy(in_ptrs_[static_cast<size_t>(ch)],
176 in_ptrs_[static_cast<size_t>(ch)] + n,
177 out_ptrs_[static_cast<size_t>(ch)]);
178 }
179
180 // Write input + gated feedback; emit the wet signal.
181 for (int i = 0; i < n; ++i) {
182 gate_ += (1.0 - gate_coeff_) * (gate_target - gate_);
183 const SampleType g = feedback_ * static_cast<SampleType>(
184 std::sin(gate_ * 1.5707963));
185 for (int ch = 0; ch < config_.channels; ++ch) {
186 const SampleType wet = out_ptrs_[static_cast<size_t>(ch)][i];
187 SampleType* ring = ring_.data() + static_cast<size_t>(ch) * ring_size_;
188 ring[static_cast<size_t>((write_pos_ + i) & ring_mask_)] =
189 in[ch][offset + i] + g * wet;
190 out[ch][offset + i] = wet;
191 }
192 }
193 write_pos_ += n;
194 }
195
196 static constexpr int kMaxChunk = 256;
197
198 Config config_;
199 double sample_rate_ = 48000.0;
200 int ring_size_ = 0;
201 int ring_mask_ = 0;
202
203 std::vector<SampleType> ring_;
204 std::vector<SampleType> chunk_in_;
205 std::vector<SampleType> chunk_out_;
206 std::vector<SampleType*> in_ptrs_;
207 std::vector<SampleType*> out_ptrs_;
208
209 FeedbackLoopProcessorT<SampleType>* loop_ = nullptr;
210 SampleType feedback_ = SampleType{0};
211 double delay_target_ = 24000.0;
212 double delay_current_ = 24000.0;
213 double gate_ = 1.0;
214 double gate_coeff_ = 0.0;
215 double delay_coeff_ = 0.0;
216 std::int64_t write_pos_ = 0;
217};
218
221
222} // namespace pulp::signal
Interface for a processor placed inside the feedback loop.
Definition pitched_feedback_delay.hpp:41
virtual int loop_latency_samples() const =0
virtual void loop_process(const SampleType *const *in, SampleType *const *out, int num_samples)=0
Equal-length, RT-safe block processing.
virtual bool loop_is_frozen() const
Definition pitched_feedback_delay.hpp:45
virtual ~FeedbackLoopProcessorT()=default
Definition pitched_feedback_delay.hpp:55
int min_delay_samples() const
Minimum legal delay: the in-loop processor's latency plus one.
Definition pitched_feedback_delay.hpp:120
void process(const SampleType *const *in, SampleType *const *out, int num_samples)
Definition pitched_feedback_delay.hpp:124
void prepare(double sample_rate, const Config &config)
RT contract: prepare() allocates ring/chunk storage and is not audio-thread safe.
Definition pitched_feedback_delay.hpp:67
void set_feedback(float feedback)
Clamped below unity for stability.
Definition pitched_feedback_delay.hpp:108
void set_loop_processor(FeedbackLoopProcessorT< SampleType > *processor)
Definition pitched_feedback_delay.hpp:113
void set_delay_sync(double tempo_bpm, double beats)
Tempo-synced delay: beats at tempo_bpm (e.g. 0.5 = eighth note).
Definition pitched_feedback_delay.hpp:102
void reset()
Definition pitched_feedback_delay.hpp:86
void set_delay_ms(float ms)
Definition pitched_feedback_delay.hpp:93
@ d
Definition pitch.hpp:22
@ g
Definition pitch.hpp:27
@ ring
Definition string.hpp:18
Definition additive_bank.hpp:218
@ feedback
Definition comb_filter.hpp:18
FeedbackLoopProcessorT< float > FeedbackLoopProcessor
Definition pitched_feedback_delay.hpp:51
FeedbackLoopProcessorT< double > FeedbackLoopProcessor64
Definition pitched_feedback_delay.hpp:52
PitchedFeedbackDelayT< float > PitchedFeedbackDelay
Definition pitched_feedback_delay.hpp:219
@ a
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
PitchedFeedbackDelayT< double > PitchedFeedbackDelay64
Definition pitched_feedback_delay.hpp:220
@ n
Definition input_events.hpp:40
Definition pitched_feedback_delay.hpp:57
int channels
Definition pitched_feedback_delay.hpp:59
float max_delay_seconds
Definition pitched_feedback_delay.hpp:58
int max_block
Definition pitched_feedback_delay.hpp:60