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