Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
freeze_loop_sampler.hpp
Go to the documentation of this file.
1#pragma once
2
22
23#include <algorithm>
24#include <cmath>
25#include <cstddef>
26#include <cstdint>
27#include <limits>
28#include <type_traits>
29#include <vector>
30
31namespace pulp::signal {
32
33template <typename SampleType = float> class FreezeLoopSamplerT {
34 static_assert(std::is_floating_point_v<SampleType>);
35
36 public:
43
52 void prepare(int channels, int capacity, int crossfade) {
53 if (channels <= 0 || capacity <= 0 || crossfade < 0 ||
54 crossfade > std::numeric_limits<int>::max() - 2) {
55 fail_prepare();
56 return;
57 }
58 const int bounded_capacity = std::max(capacity, crossfade + 2);
59 if constexpr (std::numeric_limits<SampleType>::digits < std::numeric_limits<int>::digits) {
60 constexpr auto exact_integer_limit = std::uint64_t{1}
61 << std::numeric_limits<SampleType>::digits;
62 if (static_cast<std::uint64_t>(bounded_capacity) > exact_integer_limit ||
63 static_cast<std::uint64_t>(channels) > exact_integer_limit) {
64 fail_prepare();
65 return;
66 }
67 }
68 const auto channel_count = static_cast<std::size_t>(channels);
69 const auto frame_capacity = static_cast<std::size_t>(bounded_capacity);
70 if (channel_count > std::numeric_limits<std::size_t>::max() / frame_capacity) {
71 fail_prepare();
72 return;
73 }
74 const auto elements = channel_count * frame_capacity;
75 if (elements > ring_.max_size() || elements > loop_.max_size()) {
76 fail_prepare();
77 return;
78 }
79
80 std::vector<SampleType> replacement_ring(elements, SampleType{});
81 std::vector<SampleType> replacement_loop(elements, SampleType{});
82 ring_.swap(replacement_ring);
83 loop_.swap(replacement_loop);
84 channels_ = channels;
85 capacity_ = bounded_capacity;
86 crossfade_ = crossfade;
87 write_pos_ = 0;
88 written_ = 0;
89 frozen_ = false;
90 loop_len_ = 0;
91 loop_start_ = 0;
92 play_pos_ = 0;
93 }
94
95 int channels() const noexcept {
96 return channels_;
97 }
98 int capacity() const noexcept {
99 return capacity_;
100 }
101 int available_history() const noexcept {
102 return static_cast<int>(std::min<long long>(written_, capacity_));
103 }
104 bool frozen() const noexcept {
105 return frozen_;
106 }
107 int loop_length() const noexcept {
108 return loop_len_;
109 }
110 std::size_t retained_bytes() const noexcept {
111 return (ring_.capacity() + loop_.capacity()) * sizeof(SampleType);
112 }
113
115 void write(const SampleType* const* in, int n) {
116 if (n <= 0 || channels_ <= 0 || capacity_ <= 0 || in == nullptr)
117 return;
118 for (int ch = 0; ch < channels_; ++ch) {
119 if (in[ch] == nullptr)
120 return;
121 }
122 for (int ch = 0; ch < channels_; ++ch) {
123 SampleType* r = ring_.data() + static_cast<size_t>(ch) * capacity_;
124 const SampleType* src = in[ch];
125 int wp = write_pos_;
126 for (int i = 0; i < n; ++i) {
127 r[wp] = std::isfinite(static_cast<double>(src[i])) ? src[i] : SampleType{};
128 if (++wp >= capacity_)
129 wp = 0;
130 }
131 }
132 write_pos_ = static_cast<int>((static_cast<std::int64_t>(write_pos_) + n) % capacity_);
133 written_ = std::min<long long>(capacity_, written_ + std::min(n, capacity_));
134 }
135
142 CaptureResult try_freeze_recent(int loop_samples) noexcept {
143 if (loop_samples <= 0)
145 if (loop_samples > capacity_)
147 if (loop_samples > available_history())
149
150 const int start = static_cast<int>(
151 (static_cast<std::int64_t>(write_pos_) - loop_samples + capacity_) % capacity_);
152 ring_.swap(loop_);
153 loop_len_ = loop_samples;
154 loop_start_ = start;
155 play_pos_ = 0;
156 frozen_ = true;
159 }
160
161 SampleType loop_sample(int channel, int frame) const noexcept {
162 if (!frozen_ || channel < 0 || channel >= channels_ || frame < 0 || frame >= loop_len_)
163 return SampleType{};
164 auto loop_index = static_cast<std::size_t>(loop_start_) + static_cast<std::size_t>(frame);
165 if (loop_index >= static_cast<std::size_t>(capacity_))
166 loop_index -= static_cast<std::size_t>(capacity_);
167 return loop_[static_cast<std::size_t>(channel) * capacity_ + loop_index];
168 }
169
172 void freeze(int loop_samples) {
173 const int avail = static_cast<int>(std::min<long long>(written_, capacity_));
174 if (avail <= 0) {
175 frozen_ = false;
176 loop_len_ = 0;
177 loop_start_ = 0;
178 play_pos_ = 0;
179 return;
180 }
181 const int xf = std::min(crossfade_, std::max(0, avail - 1));
182 int len = std::clamp(loop_samples, 1, std::max(1, avail - xf));
183 loop_len_ = len;
184 // The slice is the `len + xf` most-recent samples; index 0 is the
185 // oldest of that window.
186 const int span = len + xf;
187 const auto capacity = static_cast<std::size_t>(capacity_);
188 const auto start =
189 (static_cast<std::size_t>(write_pos_) + capacity - static_cast<std::size_t>(span)) %
190 capacity;
191 for (int ch = 0; ch < channels_; ++ch) {
192 const SampleType* r = ring_.data() + static_cast<size_t>(ch) * capacity_;
193 SampleType* lp = loop_.data() + static_cast<size_t>(ch) * capacity_;
194 for (int i = 0; i < len; ++i)
195 lp[i] = r[(start + static_cast<std::size_t>(i)) % capacity];
196 // Constant-power crossfade: blend the head with the tail that
197 // follows the loop so end -> start is seamless.
198 for (int i = 0; i < xf; ++i) {
199 const SampleType t =
200 (xf > 1) ? static_cast<SampleType>(i) / static_cast<SampleType>(xf - 1)
201 : SampleType{1};
202 const SampleType g_head = std::sin(static_cast<SampleType>(0.5) *
203 static_cast<SampleType>(3.14159265358979) * t);
204 const SampleType g_tail = std::cos(static_cast<SampleType>(0.5) *
205 static_cast<SampleType>(3.14159265358979) * t);
206 const SampleType head = lp[i];
207 const SampleType tail =
208 r[(start + static_cast<std::size_t>(len) + static_cast<std::size_t>(i)) %
209 capacity];
210 lp[i] = head * g_head + tail * g_tail;
211 }
212 }
213 loop_start_ = 0;
214 play_pos_ = 0;
215 frozen_ = true;
216 }
217
218 void release() {
219 frozen_ = false;
220 }
221
224 void read(SampleType* const* out, int n) {
225 if (!frozen_ || loop_len_ <= 0 || n <= 0 || out == nullptr)
226 return;
227 for (int ch = 0; ch < channels_; ++ch) {
228 if (out[ch] == nullptr)
229 return;
230 }
231 for (int ch = 0; ch < channels_; ++ch) {
232 SampleType* dst = out[ch];
233 int p = play_pos_;
234 for (int i = 0; i < n; ++i) {
235 dst[i] = loop_sample(ch, p);
236 if (++p >= loop_len_)
237 p = 0;
238 }
239 }
240 play_pos_ = static_cast<int>(
241 (static_cast<std::uint64_t>(play_pos_) + static_cast<std::uint64_t>(n)) %
242 static_cast<std::uint64_t>(loop_len_));
243 }
244
245 void reset() {
247 frozen_ = false;
248 loop_len_ = 0;
249 loop_start_ = 0;
250 play_pos_ = 0;
251 }
252
256 void clear_history() noexcept {
257 write_pos_ = 0;
258 written_ = 0;
259 }
260
261 // ── snapshot / restore (plugin state recall) ──
264 std::vector<SampleType> snapshot() const {
265 std::vector<SampleType> out;
266 if (!frozen_ || loop_len_ <= 0)
267 return out;
268 const auto payload =
269 static_cast<std::size_t>(loop_len_) * static_cast<std::size_t>(channels_);
270 if (payload > out.max_size() - 4)
271 return {};
272 out.reserve(4 + payload);
273 out.push_back(static_cast<SampleType>(channels_));
274 out.push_back(static_cast<SampleType>(loop_len_));
275 out.push_back(static_cast<SampleType>(crossfade_));
276 out.push_back(static_cast<SampleType>(play_pos_));
277 for (int ch = 0; ch < channels_; ++ch) {
278 for (int i = 0; i < loop_len_; ++i)
279 out.push_back(loop_sample(ch, i));
280 }
281 return out;
282 }
283
286 bool restore(const std::vector<SampleType>& blob) {
287 if (blob.size() < 4)
288 return false;
289 for (const auto value : blob) {
290 if (!std::isfinite(static_cast<double>(value)))
291 return false;
292 }
293 const auto valid_integer = [](SampleType value, SampleType minimum,
294 SampleType maximum) noexcept {
295 return value >= minimum && value <= maximum && std::trunc(value) == value;
296 };
297 if (!valid_integer(blob[0], SampleType{1}, static_cast<SampleType>(channels_)) ||
298 !valid_integer(blob[1], SampleType{1}, static_cast<SampleType>(capacity_)) ||
299 !valid_integer(blob[2], SampleType{}, static_cast<SampleType>(capacity_)))
300 return false;
301 const int ch = static_cast<int>(blob[0]);
302 const int len = static_cast<int>(blob[1]);
303 if (ch != channels_ ||
304 !valid_integer(blob[3], SampleType{}, static_cast<SampleType>(len - 1)))
305 return false;
306 const auto expected_size = static_cast<std::size_t>(4) +
307 static_cast<std::size_t>(len) * static_cast<std::size_t>(ch);
308 if (blob.size() != expected_size)
309 return false;
310 const int pp = static_cast<int>(blob[3]);
311 loop_len_ = len;
312 loop_start_ = 0;
313 play_pos_ = pp;
314 size_t idx = 4;
315 for (int c = 0; c < channels_; ++c) {
316 SampleType* lp = loop_.data() + static_cast<size_t>(c) * capacity_;
317 for (int i = 0; i < len; ++i)
318 lp[i] = blob[idx++];
319 }
320 frozen_ = true;
321 return true;
322 }
323
324 private:
325 void fail_prepare() noexcept {
326 channels_ = 0;
327 capacity_ = 0;
328 crossfade_ = 0;
329 ring_.clear();
330 loop_.clear();
331 write_pos_ = 0;
332 written_ = 0;
333 frozen_ = false;
334 loop_len_ = 0;
335 loop_start_ = 0;
336 play_pos_ = 0;
337 }
338
339 int channels_ = 0;
340 int capacity_ = 0;
341 int crossfade_ = 0;
342 std::vector<SampleType> ring_; // channels * capacity record ring
343 std::vector<SampleType> loop_; // channels * capacity baked loop
344 int write_pos_ = 0;
345 long long written_ = 0;
346 bool frozen_ = false;
347 int loop_len_ = 0;
348 int loop_start_ = 0;
349 int play_pos_ = 0;
350};
351
354
355} // namespace pulp::signal
Definition freeze_loop_sampler.hpp:33
void release()
Definition freeze_loop_sampler.hpp:218
CaptureResult try_freeze_recent(int loop_samples) noexcept
Capture exactly the most recent loop_samples frames.
Definition freeze_loop_sampler.hpp:142
CaptureResult
Definition freeze_loop_sampler.hpp:37
@ Success
Definition freeze_loop_sampler.hpp:38
@ InvalidLength
Definition freeze_loop_sampler.hpp:39
@ ExceedsCapacity
Definition freeze_loop_sampler.hpp:41
@ InsufficientHistory
Definition freeze_loop_sampler.hpp:40
std::vector< SampleType > snapshot() const
Serialize the frozen loop: [channels][loop_len][crossfade][play_pos] then loop_len * channels samples...
Definition freeze_loop_sampler.hpp:264
void prepare(int channels, int capacity, int crossfade)
Definition freeze_loop_sampler.hpp:52
bool frozen() const noexcept
Definition freeze_loop_sampler.hpp:104
bool restore(const std::vector< SampleType > &blob)
Restore a loop produced by snapshot().
Definition freeze_loop_sampler.hpp:286
void clear_history() noexcept
Invalidate rolling input continuity without changing a captured loop.
Definition freeze_loop_sampler.hpp:256
void read(SampleType *const *out, int n)
Play the loop into out (frozen only).
Definition freeze_loop_sampler.hpp:224
void reset()
Definition freeze_loop_sampler.hpp:245
int capacity() const noexcept
Definition freeze_loop_sampler.hpp:98
std::size_t retained_bytes() const noexcept
Definition freeze_loop_sampler.hpp:110
void freeze(int loop_samples)
Freeze the most recent loop_samples of recorded audio into a seamless loop.
Definition freeze_loop_sampler.hpp:172
int loop_length() const noexcept
Definition freeze_loop_sampler.hpp:107
void write(const SampleType *const *in, int n)
Record n frames into the ring (call every block, always).
Definition freeze_loop_sampler.hpp:115
SampleType loop_sample(int channel, int frame) const noexcept
Definition freeze_loop_sampler.hpp:161
int channels() const noexcept
Definition freeze_loop_sampler.hpp:95
int available_history() const noexcept
Definition freeze_loop_sampler.hpp:101
Definition additive_bank.hpp:218
@ c
Definition spectrum_trace.hpp:35
FreezeLoopSamplerT< float > FreezeLoopSampler
Definition freeze_loop_sampler.hpp:352
@ maximum
Definition spectrum_trace.hpp:34
@ i
Definition formant_filter_bank.hpp:20
FreezeLoopSamplerT< double > FreezeLoopSampler64
Definition freeze_loop_sampler.hpp:353
@ crossfade
Fade in over the configured crossfade, like a normal engage.
Definition freeze_hold.hpp:153