Deluge Firmware 1.3.0
Build date: 2025.04.16
Loading...
Searching...
No Matches
delay_buffer.h
1/*
2 * Copyright © 2015-2023 Synthstrom Audible Limited
3 *
4 * This file is part of The Synthstrom Audible Deluge Firmware.
5 *
6 * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software Foundation,
8 * either version 3 of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 * See the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along with this program.
15 * If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#pragma once
19
20#include "definitions_cxx.hpp"
21#include "dsp/stereo_sample.h"
22#include <cstdint>
23#include <expected>
24#include <optional>
25
26class StereoSample;
27
28constexpr ptrdiff_t delaySpaceBetweenReadAndWrite = 20;
29
30class DelayBuffer {
31public:
32 constexpr static size_t kMaxSize = 88200;
33 constexpr static size_t kMinSize = 1;
34 constexpr static size_t kNeutralSize = 16384;
35
36 DelayBuffer() = default;
37 ~DelayBuffer() { discard(); }
38 Error init(uint32_t newRate, uint32_t failIfThisSize = 0, bool includeExtraSpace = true);
39
40 // Prevent the delaybuffer from deallocing the Sample array on destruction
41 // TODO (Kate): investigate a shared_ptr for start_
42 constexpr void invalidate() { start_ = nullptr; }
43
44 void makeNativeRatePrecise();
45 void makeNativeRatePreciseRelativeToOtherBuffer(const DelayBuffer& otherBuffer);
46
47 void discard();
48
49 template <typename C>
50 [[gnu::always_inline]] constexpr int32_t advance(C callback) {
51 longPos += resample_config_.value().actualSpinRate;
52 uint8_t newShortPos = longPos >> 24;
53 uint8_t shortPosDiff = newShortPos - lastShortPos;
54 lastShortPos = newShortPos;
55
56 while (shortPosDiff > 0) {
57 callback();
58 shortPosDiff--;
59 }
60 return (longPos >> 8) & 65535;
61 }
62 template <typename C>
63 [[gnu::always_inline]] constexpr int32_t retreat(C callback) {
64 longPos -= resample_config_.value().actualSpinRate;
65 uint8_t newShortPos = longPos >> 24;
66 uint8_t shortPosDiff = lastShortPos - newShortPos; // backward diff
67 lastShortPos = newShortPos;
68
69 while (shortPosDiff > 0) {
70 callback();
71 shortPosDiff--;
72 }
73 return (longPos >> 8) & 65535; // return pos
74 }
75
76 void setupForRender(int32_t rate);
77
78 static std::pair<int32_t, bool> getIdealBufferSizeFromRate(uint32_t rate);
79
80 [[nodiscard]] constexpr bool isActive() const { return (start_ != nullptr); }
81
82 inline bool clearAndMoveOn() {
83 current_->l = 0;
84 current_->r = 0;
85 return moveOn();
86 }
87
88 inline bool moveOn() {
89 ++current_;
90 bool wrapped = (current_ == end_);
91 if (wrapped) {
92 current_ = start_;
93 }
94 return wrapped;
95 }
96
97 inline bool moveBack() {
98 if (current_ == start_) {
99 current_ = end_ - 1; // wrap around to last element
100 return true;
101 }
102 else {
103 --current_; // move back
104 return false; // no wrap
105 }
106 }
107
108 inline void writeNative(StereoSample toDelay) {
109 StereoSample* writePos = current_ - delaySpaceBetweenReadAndWrite;
110 if (writePos < start_) {
111 writePos += sizeIncludingExtra;
112 }
113 writePos->l = toDelay.l;
114 writePos->r = toDelay.r;
115 }
116
117 inline void writeNativeAndMoveOn(StereoSample toDelay, StereoSample** writePos) {
118 (*writePos)->l = toDelay.l;
119 (*writePos)->r = toDelay.r;
120
121 (*writePos)++;
122 if (*writePos == end_) {
123 *writePos = start_;
124 }
125 }
126
127 [[gnu::always_inline]] void write(StereoSample toDelay, int32_t strength1, int32_t strength2) {
128 // If no speed adjustment
129 if (isNative()) {
130 StereoSample* writePos = current_ - delaySpaceBetweenReadAndWrite;
131 if (writePos < start_) {
132 writePos += sizeIncludingExtra;
133 }
134 writePos->l = toDelay.l;
135 writePos->r = toDelay.r;
136 return;
137 }
138
139 writeResampled(toDelay, strength1, strength2);
140 }
141
142 [[gnu::always_inline]] void writeResampled(StereoSample toDelay, int32_t strength1, int32_t strength2) {
143 if (!resample_config_) {
144 return;
145 }
146 // If delay buffer spinning above sample rate...
147 if (resample_config_->actualSpinRate >= kMaxSampleValue) {
148
149 // An improvement on that could be to only do the triangle-widening when we're down near the native rate -
150 // i.e. set a minimum width of double the native rate rather than always doubling the width. The difficulty
151 // would be ensuring that we compensate perfectly the strength of each write so that the volume remains the
152 // same. It could totally be done. The only real advantage would be that the number of memory writes would
153 // be halved at high speeds.
154
155 // For efficiency, we start far-right, then traverse to far-left.
156
157 // I rearranged some algebra to get this from the strengthThisWrite equation
158 int32_t howFarRightToStart = (strength2 + (resample_config_->spinRateForSpedUpWriting >> 8)) >> 16;
159
160 // This variable represents one "step" of the delay buffer as 65536.
161 // Always positive - absolute distance
162 int32_t distanceFromMainWrite = (int32_t)howFarRightToStart << 16;
163
164 // Initially is the far-right right pos, not the central "main" one
165 StereoSample* writePos = current_ - delaySpaceBetweenReadAndWrite + howFarRightToStart;
166 while (writePos < start_) {
167 writePos += sizeIncludingExtra;
168 }
169 while (writePos >= end_) {
170 writePos -= sizeIncludingExtra;
171 }
172
173 // Do all writes to the right of the main write pos
174 while (distanceFromMainWrite != 0) { // For as long as we haven't reached the "main" pos...
175 // Check my notebook for a rudimentary diagram
176 int32_t strengthThisWrite =
177 (0xFFFFFFFF >> 4) - (((distanceFromMainWrite - strength2) >> 4) * resample_config_->divideByRate);
178
179 writePos->l += multiply_32x32_rshift32(toDelay.l, strengthThisWrite) << 3;
180 writePos->r += multiply_32x32_rshift32(toDelay.r, strengthThisWrite) << 3;
181
182 if (--writePos < start_) {
183 writePos = end_ - 1;
184 }
185 // writePos = (writePos == start_) ? end_ - 1 : writePos - 1;
186 distanceFromMainWrite -= 65536;
187 }
188
189 // Do all writes to the left of (and including) the main write pos
190 while (true) {
191 int32_t strengthThisWrite =
192 (0xFFFFFFFF >> 4) - (((distanceFromMainWrite + strength2) >> 4) * resample_config_->divideByRate);
193 if (strengthThisWrite <= 0) {
194 break; // And stop when we've got far enough left that we shouldn't be squirting any more juice here
195 }
196
197 writePos->l += multiply_32x32_rshift32(toDelay.l, strengthThisWrite) << 3;
198 writePos->r += multiply_32x32_rshift32(toDelay.r, strengthThisWrite) << 3;
199
200 --writePos;
201
202 // loop around
203 if (writePos < start_) {
204 writePos = end_ - 1;
205 }
206 distanceFromMainWrite += 65536;
207 }
208 }
209
210 // Or if delay buffer spinning below sample rate...
211 else {
212 // The most basic version of this would be to write to the "main" pos with strength1 and the "main + 1" pos
213 // with strength 2. But this isn't immune to aliasing, so we instead "squirt" things a little bit wider -
214 // wide enough that our "triangle" is always at least as wide as 1 step / sample in the delay buffer. This
215 // means we potentially need a further 1 write in each direction.
216
217 // And because we're "arbitrarily" increasing the width and height (height is more a side-effect of the
218 // simple algorithm) of our squirt, and also how spaced out the squirts are, the value written at each step
219 // needs to be resized. See delayWriteSizeAdjustment, which is calculated above.
220
221 // We've also had to make sure that the "triangles"' corners exactly meet up. Unfortunately this means even
222 // a tiny slow-down causes half the bandwidth to be lost
223
224 // The furthest right we need to write is 2 steps right from the "main" write
225 StereoSample* writePos = current_ - delaySpaceBetweenReadAndWrite + 2;
226
227 while (writePos < start_) {
228 writePos += sizeIncludingExtra;
229 }
230
231 // Not needed - but be careful! Leave this here as a reminder
232 // while (writePos >= end_) writePos -= sizeIncludingExtra;
233
234 int32_t strength[4];
235
236 strength[1] = strength1 + resample_config_->rateMultiple - 65536; // For the "main" pos
237 strength[2] = strength2 + resample_config_->rateMultiple - 65536; // For the "main + 1" pos
238
239 // Strengths for the further 1 write in each direction
240 strength[0] = strength[1] - 65536;
241 strength[3] = strength[2] - 65536;
242
243 int8_t i = 3;
244 while (true) {
245 if (strength[i] > 0) {
246 writePos->l +=
247 multiply_32x32_rshift32(toDelay.l, (strength[i] >> 2) * resample_config_->writeSizeAdjustment)
248 << 2;
249 writePos->r +=
250 multiply_32x32_rshift32(toDelay.r, (strength[i] >> 2) * resample_config_->writeSizeAdjustment)
251 << 2;
252 }
253 if (--i < 0) {
254 break;
255 }
256
257 --writePos;
258
259 // loop around
260 if (writePos < start_) {
261 writePos = end_ - 1;
262 }
263 }
264 }
265 }
266
267 [[nodiscard]] constexpr bool isNative() const { return !resample_config_.has_value(); }
268 [[nodiscard]] constexpr bool resampling() const { return resample_config_.has_value(); }
269 [[nodiscard]] constexpr uint32_t nativeRate() const { return native_rate_; }
270
271 // Iterator access
272 [[nodiscard]] constexpr StereoSample& current() const { return *current_; }
273 [[nodiscard]] constexpr StereoSample* begin() const { return start_; }
274 [[nodiscard]] constexpr StereoSample* end() const { return end_; }
275 [[nodiscard]] constexpr size_t size() const { return size_; }
276
277 void clear();
278
279 constexpr void setCurrent(StereoSample* sample) { current_ = sample; }
280
281 // TODO (Kate): Make it so the ModControllableFX stuff isn't touching these.
282 // That behavior should either be contained in this class or Delay or a new Stutterer class
283 uint32_t longPos;
284 uint8_t lastShortPos;
285
286 size_t sizeIncludingExtra;
287
288private:
290 uint32_t actualSpinRate; // 1 is represented as 16777216
291 uint32_t spinRateForSpedUpWriting; // Normally the same as actualSpinRate, but subject to some limits for safety
292 uint32_t divideByRate; // 1 is represented as 65536
293 uint32_t rateMultiple;
294 uint32_t writeSizeAdjustment;
295 };
296
297 void setupResample();
298
299 uint32_t native_rate_ = 0;
300
301 StereoSample* start_ = nullptr;
302 StereoSample* end_;
303 StereoSample* current_;
304
305 size_t size_;
306
307public:
308 std::optional<ResampleConfig> resample_config_{};
309};
Definition delay_buffer.h:289
Definition stereo_sample.h:25