JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
ReverbDesign.h
Go to the documentation of this file.
1//
2// ██╗██████╗ ██╗ ██╗██████╗ ███████╗
3// ██║██╔══██╗ ██║ ██║██╔══██╗██╔════╝ ** JPL Spatial **
4// ██║██████╔╝ ██║ ██║██████╔╝███████╗
5// ██ ██║██╔═══╝ ██║ ██║██╔══██╗╚════██║ https://github.com/Jaytheway/JPLSpatial
6// ╚█████╔╝██║ ███████╗██║██████╔╝███████║
7// ╚════╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚══════╝
8//
9// Copyright 2026 Jaroslav Pevno, JPL Spatial is offered under the terms of the ISC license:
10//
11// Permission to use, copy, modify, and/or distribute this software for any purpose with or
12// without fee is hereby granted, provided that the above copyright notice and this permission
13// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
14// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
16// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
17// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
18// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#pragma once
21
22#include <JPLSpatial/Core.h>
27
28#include <algorithm>
29#include <array>
30#include <bit>
31#include <concepts>
32#include <ranges>
33#include <span>
34#include <random>
35
36namespace JPL
37{
38 //==========================================================================
43
44 //==========================================================================
45 namespace Detail
46 {
47 inline constexpr auto cPrimes =[]() -> std::array<uint32, 2'000>
48 {
49 constexpr uint32 N = 2'000;
50
51 auto isPrime = [](uint32 num)
52 {
53 if (num <= 1) return false;
54 if (num == 2 || num == 3) return true;
55 if (num % 2 == 0 || num % 3 == 0) return false;
56 for (uint32 i = 5; i * i <= num; i += 6)
57 if (num % i == 0 || num % (i + 2) == 0) return false;
58 return true;
59 };
60
61 std::array<uint32, N> primes{};
62 std::size_t count = 0;
63 uint32 candidate = 2;
64
65 while (count < N)
66 {
67 if (isPrime(candidate))
68 {
70 count++;
71 }
72 candidate++;
73 }
74 return primes;
75 }();
76
78 {
79 auto found = std::ranges::lower_bound(cPrimes, number);
80 JPL_ASSERT(found != std::ranges::end(cPrimes));
81 return *found;
82 }
83
84 constexpr uint32 cMaxFDNOrder = 64;
85 constexpr uint32 cMaxDelayInSamples = cPrimes.back();
86
87 template<uint32 FDNOrder>
89
90 //======================================================================
91 template<class Sample>
93 {
94 // Deterministic pseudo-random +/- pattern
95 uint32 x = fdnChannel + 0x9E3779B9u * (outChannel + 1);
96 x ^= x >> 16; x *= 0x7FEB352Du;
97 x ^= x >> 15; x *= 0x846CA68Bu;
98 x ^= x >> 16;
99 return (x & 1u) ? Sample(1) : Sample(-1);
100 }
101
102 template<class Sample, std::size_t NumFDNChannels> requires (Detail::cValidFDNOrder<NumFDNChannels>)
103 static constexpr std::array<Sample, NumFDNChannels> MakeSignMatrix()
104 {
105 std::array<Sample, NumFDNChannels> signs;
106 for (uint32 i = 0; i < NumFDNChannels; ++i)
107 signs[i] = Detail::GenRandomSign<Sample>(0u, i);
108 return signs;
109 }
110
111 template<class Sample, std::size_t NumFDNChannels, uint32 MaxOutputChannels>
112 requires(Detail::cValidFDNOrder<NumFDNChannels> and MaxOutputChannels > 0)
113 static constexpr std::array<std::array<Sample, NumFDNChannels>, MaxOutputChannels> MakeSignMatrix()
114 {
115 std::array<std::array<Sample, NumFDNChannels>, MaxOutputChannels> signs;
116 for (uint32 o = 0; o < MaxOutputChannels; ++o)
117 {
118 for (uint32 i = 0; i < NumFDNChannels; ++i)
119 signs[o][i] = Detail::GenRandomSign<Sample>(o + 1, i);
120 }
121 return signs;
122 }
123 }
124
125 //==========================================================================
126 namespace Matrix
127 {
128 //======================================================================
129 template<class Sample, std::size_t Size>
130 JPL_INLINE void Multiply(const std::array<Sample, Size>& dataA, std::array<Sample, Size>& dataBOut)
131 {
132 if constexpr (std::same_as<Sample, float> and Size >= simd::size())
133 {
134 static constexpr uint32 simdFloor = FloorToSIMDSize(Size);
135
136 for (uint32 i = 0; i < simdFloor; i += simd::size())
137 {
138 (simd(&dataBOut[i]) * simd(dataA[i])).store(&dataBOut[i]);
139 }
140
141 if constexpr (Size > simdFloor)
142 {
143 for (uint32 i = simdFloor; i < Size; ++i)
144 dataBOut[i] *= dataA[i];
145 }
146 }
147 else
148 {
149 for (uint32 i = 0; i < Size; ++i)
150 dataBOut[i] *= dataA[i];
151 }
152 }
153
154 template<class Sample>
155 JPL_INLINE void Multiply(std::span<Sample> data, Sample value)
156 {
157 if constexpr (std::same_as<Sample, float>)
158 {
159 const uint32 simdFloor = FloorToSIMDSize(data.size());
160
161 uint32 i = 0;
162 const simd v(value);
163 for (; i < simdFloor; i += simd::size())
164 {
165 (simd(&data[i]) * v).store(&data[i]);
166 }
167
168 for (; i < data.size(); ++i)
169 data[i] *= value;
170 }
171 else
172 {
173 for (uint32 i = 0; i < data.size(); ++i)
174 data[i] *= value;
175 }
176 }
177
178 template<class Sample, std::size_t Size>
179 JPL_INLINE void Multiply(std::array<Sample, Size>& data, Sample value)
180 {
181 if constexpr (std::same_as<Sample, float> and Size >= simd::size())
182 {
183 static constexpr uint32 simdFloor = FloorToSIMDSize(Size);
184
185 const simd v(value);
186
187 for (uint32 i = 0; i < simdFloor; i += simd::size())
188 {
189 (simd(&data[i]) * v).store(&data[i]);
190 }
191
192 if constexpr (Size > simdFloor)
193 {
194 for (uint32 i = simdFloor; i < Size; ++i)
195 data[i] *= value;
196 }
197 }
198 else
199 {
200 for (uint32 i = 0; i < Size; ++i)
201 data[i] *= value;
202 }
203 }
204
205 template<class Sample, std::size_t Size>
206 JPL_INLINE void Add(std::array<Sample, Size>& data, Sample value)
207 {
208 if constexpr (std::same_as<Sample, float> and Size >= simd::size())
209 {
210 static constexpr uint32 simdFloor = FloorToSIMDSize(Size);
211
212 const simd v(value);
213
214 for (uint32 i = 0; i < simdFloor; i += simd::size())
215 {
216 (simd(&data[i]) + v).store(&data[i]);
217 }
218
219 if constexpr (Size > simdFloor)
220 {
221 for (uint32 i = simdFloor; i < Size; ++i)
222 data[i] += value;
223 }
224 }
225 else
226 {
227 for (uint32 i = 0; i < Size; ++i)
228 data[i] += value;
229 }
230 }
231
232 template<class Sample, std::size_t Size>
233 JPL_INLINE void Add(const std::array<Sample, Size>& dataA, std::array<Sample, Size>& dataBOut)
234 {
235 if constexpr (std::same_as<Sample, float> and Size >= simd::size())
236 {
237 static constexpr uint32 simdFloor = FloorToSIMDSize(Size);
238
239 for (uint32 i = 0; i < simdFloor; i += simd::size())
240 {
241 (simd(&dataBOut[i]) + simd(dataA[i])).store(&dataBOut[i]);
242 }
243
244 if constexpr (Size > simdFloor)
245 {
246 for (uint32 i = simdFloor; i < Size; ++i)
247 dataBOut[i] += dataA[i];
248 }
249 }
250 else
251 {
252 for (uint32 i = 0; i < Size; ++i)
253 dataBOut[i] += dataA[i];
254 }
255 }
256
257 template<class Sample, std::size_t Size>
258 [[nodiscard]] JPL_INLINE Sample ReduceSum(const std::array<Sample, Size>& data)
259 {
260 Sample sum(0);
261 if constexpr (std::same_as<Sample, float> and Size >= simd::size())
262 {
263 static constexpr uint32 simdFloor = FloorToSIMDSize(Size);
264
265 for (uint32 i = 0; i < simdFloor; i += simd::size())
266 {
267 sum += simd(&data[i]).reduce();
268 }
269
270 if constexpr (Size > simdFloor)
271 {
272 for (uint32 i = simdFloor; i < Size; ++i)
273 sum += data[i];
274 }
275 }
276 else
277 {
278 for (Sample s : data)
279 sum += s;
280 }
281 return sum;
282 }
283
284 template<class Sample, std::size_t Size>
285 JPL_INLINE void AssignRandomSign(std::array<Sample, Size>& data)
286 {
287 static constexpr auto signs = Detail::MakeSignMatrix<Sample, Size>();
288 Multiply(signs, data);
289 }
290
291 template<std::size_t MaxOutSize, class Sample, std::size_t Size>
292 JPL_INLINE void AssignRandomSign(std::array<Sample, Size>& data, uint32 outIndex)
293 {
294 static constexpr auto signs = Detail::MakeSignMatrix<Sample, Size, MaxOutSize>();
295 Multiply(signs[outIndex], data);
296 }
297
298 //======================================================================
300 {
301 public:
302 template<class Sample, std::size_t Size> requires(Detail::cValidFDNOrder<Size>)
303 static void MixInPlace(std::array<Sample, Size>& arr)
304 {
305 static constexpr Sample cMultiplier{ -2.0 / Size };
307 Add(arr, sum);
308 }
309
310 // Special case for 16th order
311 template<class Sample>
312 static void MixInPlace(std::array<Sample, 16>& arr)
313 {
314 if constexpr (std::same_as<Sample, float>)
315 {
316 auto mix4 = [](float* v)
317 {
318 simd vec(v);
319 const float sum = vec.reduce();
320
321 // Matrix with +1 on diagonal, -1 elsewhere:
322 // y_i = 2 * x_i - sum
323 vec = Math::FMA(simd(2.0f), vec, simd(-sum));
324 vec.store(v);
325 };
326
327 // First mix within each group of 4.
328 for (uint32 g = 0; g < 16; g += 4)
329 mix4(&arr[g]);
330
331 // Then mix across the 4 groups for each lane.
332 for (uint32 lane = 0; lane < 4; ++lane)
333 {
334 float v[4] = {
335 arr[lane + 0 * 4],
336 arr[lane + 1 * 4],
337 arr[lane + 2 * 4],
338 arr[lane + 3 * 4],
339 };
340
341 mix4(v);
342 (simd(v) * simd(0.25f)).store(v);
343
344 arr[lane + 0 * 4] = v[0];
345 arr[lane + 1 * 4] = v[1];
346 arr[lane + 2 * 4] = v[2];
347 arr[lane + 3 * 4] = v[3];
348 }
349 }
350 else
351 {
352 auto mix4 = [](Sample* v)
353 {
354 const Sample sum = v[0] + v[1] + v[2] + v[3];
355
356 // Matrix with +1 on diagonal, -1 elsewhere:
357 // y_i = 2 * x_i - sum
358 for (uint32 i = 0; i < 4; ++i)
359 v[i] = Math::FMA(Sample(2), v[i], -sum);
360 };
361
362 // First mix within each group of 4.
363 for (uint32 g = 0; g < 16; g += 4)
364 mix4(arr + g);
365
366 // Then mix across the 4 groups for each lane.
367 for (uint32 lane = 0; lane < 4; ++lane)
368 {
369 Sample v[4] = {
370 arr[lane + 0 * 4],
371 arr[lane + 1 * 4],
372 arr[lane + 2 * 4],
373 arr[lane + 3 * 4],
374 };
375
376 mix4(v);
377
378 arr[lane + 0 * 4] = v[0] * Sample(0.25);
379 arr[lane + 1 * 4] = v[1] * Sample(0.25);
380 arr[lane + 2 * 4] = v[2] * Sample(0.25);
381 arr[lane + 3 * 4] = v[3] * Sample(0.25);
382 }
383 }
384 }
385 };
386
387 //======================================================================
389 {
390 private:
391 template<class Sample, std::size_t Size>
392 static void RecursiveUnscaled(std::span<Sample, Size> data)
393 {
394 if (Size <= 1)
395 return;
396
397 static constexpr uint32 hSize = Size / 2;
398
399 // Two (unscaled) Hadamards of half the size
400 Hadamard::RecursiveUnscaled(data.template subspan<0, hSize>());
401 Hadamard::RecursiveUnscaled(data.template subspan<hSize, hSize>());
402
403 if constexpr (std::same_as<Sample, float> and hSize >= simd::size())
404 {
405 static constexpr uint32 simdFloor = FloorToSIMDSize(hSize);
406 for (uint32 i = 0; i < simdFloor; i += simd::size())
407 {
408 const simd a(&data[i]);
409 const simd b(&data[i + hSize]);
410 (a + b).store(&data[i]);
411 (a - b).store(&data[i + hSize]);
412 }
413
414 // Process potential tail
415 if constexpr (hSize > simdFloor)
416 {
417 for (uint32 i = simdFloor; i < hSize; ++i)
418 {
419 const Sample a = data[i];
420 const Sample b = data[i + hSize];
421 data[i] = (a + b);
422 data[i + hSize] = (a - b);
423 }
424 }
425
426 }
427 else
428 {
429 // Combine the two halves using sum/difference
430 for (uint32 i = 0; i < hSize; ++i)
431 {
432 const Sample a = data[i];
433 const Sample b = data[i + hSize];
434 data[i] = (a + b);
435 data[i + hSize] = (a - b);
436 }
437 }
438 }
439
440 public:
441 template<class Sample, std::size_t Size> requires(Detail::cValidFDNOrder<Size> and std::has_single_bit(Size))
442 static JPL_INLINE void MixInPlace(std::array<Sample, Size>& data)
443 {
444 RecursiveUnscaled(std::span<Sample, Size>(data));
445 static constexpr Sample scalingFactor = Math::Sqrt(1.0 / Size);
446 Multiply(data, scalingFactor);
447 }
448 };
449
450 //======================================================================
451 struct FDNInput
452 {
453 public:
454 template<class Sample, std::size_t NumFDNChannels> requires(Detail::cValidFDNOrder<NumFDNChannels>)
455 static JPL_INLINE void InjectNormalized(Sample monoInput, std::array<Sample, NumFDNChannels>& outFDNInput)
456 {
457 static constexpr Sample norm = Math::Sqrt(Sample(1) / Sample(NumFDNChannels));
458 outFDNInput.fill(monoInput * norm);
459 }
460
461 template<class Sample, std::size_t NumFDNChannels> requires(Detail::cValidFDNOrder<NumFDNChannels>)
462 static JPL_INLINE void InjectNormalizedRndSign(Sample monoInput, std::array<Sample, NumFDNChannels>& outFDNInput)
463 {
464 static constexpr Sample norm = Math::Sqrt(Sample(1) / Sample(NumFDNChannels));
465 outFDNInput.fill(monoInput * norm);
467 }
468 };
469
470 //======================================================================
471 // Hadamard mixer
472 // If num FDN channels guaranteed to be power of 2,
473 // this is a more stable choice than FDNOutputProjection
474 //
475 // Hadamard output mixer needs power-of-two FDN size.
477 {
478 template<class Sample, std::size_t NumFDNChannels>
479 requires(Detail::cValidFDNOrder<NumFDNChannels> and std::has_single_bit(NumFDNChannels))
480 static void Mix(std::array<Sample, NumFDNChannels> fdn, std::span<Sample> out)
481 {
482 // Decorrelate / spread the tank states first.
484
486
487 for (uint32 i = 0; i < NumFDNChannels; ++i)
488 {
490 out[outChannel] += fdn[i];
491 }
492 }
493
494 // Normalizing output may kill the volume.
495 // It is likely not necesary, if the input was already normalized,
496 // i.e. because the energy comming into the system is normalized by the factor
497 // it is increased when spreading into all FDN channels.
498 template<class Sample, std::size_t NumFDNChannels>
499 requires(Detail::cValidFDNOrder<NumFDNChannels> and std::has_single_bit(NumFDNChannels))
500 static void MixNormalize(std::array<Sample, NumFDNChannels> fdn, std::span<Sample> out)
501 {
502 Mix(fdn, out);
503
504 static constexpr Sample invFDNChannels = Sample(1.0) / NumFDNChannels;
505
506 // Hadamard is already normalized by 1 / sqrt(NumFDNChannels).
507 // This compensates for folding multiple FDN channels into each output.
508 const Sample foldNorm =
509 Math::Sqrt(static_cast<Sample>(out.size()) * invFDNChannels);
510
512 }
513 };
514
515 //======================================================================
516 template<uint32 MaxOutputChannels = 16u> requires(MaxOutputChannels > 0)
518 {
519 template<class Sample, std::size_t NumFDNChannels> requires(Detail::cValidFDNOrder<NumFDNChannels>)
520 static void Mix(const std::array<Sample, NumFDNChannels>& fdn, std::span<Sample> out)
521 {
523
524 for (uint32 outIdx = 0; outIdx < out.size(); ++outIdx)
525 {
526 std::array<Sample, NumFDNChannels> fdnWithRandSign = fdn;
528
530 }
531 }
532
533 // Note: see the comment in FDNOutputMixer about normalization
534 template<class Sample, std::size_t NumFDNChannels> requires(Detail::cValidFDNOrder<NumFDNChannels>)
535 static void MixNormalize(const std::array<Sample, NumFDNChannels>& fdn, std::span<Sample> out)
536 {
537 Mix(fdn, out);
538
539 static constexpr Sample norm = Math::Sqrt(Sample(1) / Sample(NumFDNChannels));
540
541 Multiply(out, norm);
542 }
543 };
544
545 } // namespace Matrix
546
547 //==========================================================================
548 struct Feedback
549 {
550 float DecayGain = 0.85f;
551 float DelayMs = 80.0f;
554
556 {
557 DelaySamples = DelayMs * 0.001f * sampleRate;
559 Delay.Clear();
560 }
561
563 {
564 const float delayed = Delay.GetReadWindow<1>(DelaySamples);
565 const float sum = Math::FMA(delayed, DecayGain, sample);
566 Delay.Push(sum);
567 return delayed;
568 }
569 };
570
571 //==================================================================================
572 // Using prime distribution within Min/Max range
573 template<uint32 NumChannels, class AttenuationFilter> requires(Detail::cValidFDNOrder<NumChannels>)
575 {
576 private:
577 // A nice sounding set in range [1500, 4500]
578 static constexpr uint32 cPrimeDelayLengths[] ={
579 2927, 2593, 2273, 3697, 1877, 3877, 2477, 3461,
580 1609, 3779, 3541, 4259, 1669, 3539, 3637, 4013,
581 3121, 4003, 1627, 3733, 3511, 4093, 2411, 3011,
582 2801, 2017, 4013, 1621, 3023, 2161, 4201, 3079,
583 1783, 4027, 1613, 1907, 2129, 3797, 2543, 1579,
584 3967, 2551, 3833, 4111, 3059, 4159, 1753, 1601,
585 2423, 1993, 4493, 2707, 3719, 3547, 3769, 4219,
586 2389, 2087, 1709, 3229, 3083, 3391, 3259, 2791
587 };
588 public:
589 using Channels = std::array<float, NumChannels>;
590
591#if 0
592 uint32 DelaySamplesMin = 1500;
593 uint32 DelaySamplesMax = 4500;
594#endif
595
596 template<class ...FilterArgs>
598 {
599 mInvSampleRate = 1.0f / sampleRate;
600#if 1
601 std::ranges::copy_n(cPrimeDelayLengths, NumChannels, mDelaySamples.begin());
602#else
603
604 auto first = std::ranges::lower_bound(cPrime, DelaySamplesMin);
605 auto last = std::ranges::lower_bound(cPrime, DelaySamplesMax);
606 JPL_ASSERT(std::distance(first, last) >= NumChannels);
607
608 std::mt19937 rng{ std::random_device{}() };
609 std::ranges::sample(std::ranges::subrange(first, last), mDelaySamples.begin(), NumChannels, rng);
610 std::ranges::shuffle(mDelaySamples, rng);
611#endif
612
613 for (uint32 c = 0; c < NumChannels; ++c)
614 {
615 mDelays[c].Resize(mDelaySamples[c] + 1);
616 mDelays[c].Clear();
617
618 mAttenuationFilters[c].Prepare(sampleRate, std::forward<FilterArgs>(filterPrepareArgs)...);
619 }
620 }
621
622 template<class ...FilterArgs>
624 {
625 for (uint32 c = 0; c < NumChannels; ++c)
626 {
627 // Recalculate exact gain for this specific loop length
628 const float delaySeconds = mDelaySamples[c] * mInvSampleRate;
629 mAttenuationFilters[c].UpdateParameters(delaySeconds, std::forward<FilterArgs>(filterUpdateArgs)...);
630 }
631 }
632
633 protected:
635 {
636 return mAttenuationFilters[fdnIndex].Process(sample);
637 }
638
639 protected:
640 std::array<uint32, NumChannels> mDelaySamples;
641 std::array<DelayLine<1>, NumChannels> mDelays;
642 std::array<AttenuationFilter, NumChannels> mAttenuationFilters;
643 float mInvSampleRate = 1.0f / 48'000.0f;
644 };
645
646 //==========================================================================
647 template<uint32 NumChannels, class AttenuationFilter> requires (Detail::cValidFDNOrder<NumChannels>)
648 class MultiChannelFeedback : public MultiChannelFeedbackBase<NumChannels, AttenuationFilter>
649 {
650 public:
652 using Channels = typename Base::Channels;
653
654 using Base::Base;
655
657 {
659 for (uint32 c = 0; c < NumChannels; ++c)
660 {
661 delayed[c] = Base::mDelays[c].template GetReadWindow<1>(Base::mDelaySamples[c]);
662 }
663
664 for (uint32 c = 0; c < NumChannels; ++c)
665 {
666 const float sum = Base::ApplyAttenuationFilter(delayed[c] + input[c], c);
667 Base::mDelays[c].Push(sum);
668 }
669
670 return delayed;
671 }
672 };
673
674 //==========================================================================
675 template<uint32 NumChannels, class AttenuationFilter> requires (Detail::cValidFDNOrder<NumChannels>)
676 class MultiChannelMixedFeedback : public MultiChannelFeedbackBase<NumChannels, AttenuationFilter>
677 {
678 public:
680 using Channels = typename Base::Channels;
681
682 using Base::Base;
683
685 {
687 for (uint32 c = 0; c < NumChannels; ++c)
688 {
689 delayed[c] = Base::mDelays[c].template GetReadWindow<1>(Base::mDelaySamples[c]);
690 }
691
692 // Mix using a Householder matrix
695
696 for (uint32 c = 0; c < NumChannels; ++c)
697 {
698 const float sum = Base::ApplyAttenuationFilter(mixed[c] + input[c], c);
699 Base::mDelays[c].Push(sum);
700 }
701
702 return delayed;
703 }
704 };
705
706 //==========================================================================
707 template<uint32 NumChannels = 8> requires(Detail::cValidFDNOrder<NumChannels>)
709 {
710 using Channels = std::array<float, NumChannels>;
711
712 float DelayMsRange = 50.0f;
713
714 void Prepare(float sampleRate)
715 {
716 static constexpr float invNumChannels = 1.0f / NumChannels;
717 const float delaySamplesRange = DelayMsRange * 0.001f * sampleRate;
718
719 auto randomInRange = [](float min, float max)
720 {
721 static std::mt19937 mt(std::random_device{}());
722 return std::uniform_real_distribution<float>{ min, max }(mt);
723 };
724
725 for (uint32 c = 0; c < NumChannels; ++c)
726 {
727 const float rangeLow = delaySamplesRange * c * invNumChannels;
728 const float rangeHigh = delaySamplesRange * (c + 1) * invNumChannels;
729 mDelaySamples[c] = Detail::CeilToPrime(static_cast<uint32>(randomInRange(rangeLow, rangeHigh)));
730 mDelays[c].Resize(mDelaySamples[c] + 1);
731 mDelays[c].Clear();
732 }
733 }
734
736 {
737 // Delay
739 for (uint32 c = 0; c < NumChannels; ++c)
740 {
741 mDelays[c].Push(input[c]);
742 delayed[c] = mDelays[c].template GetReadWindow<1>(mDelaySamples[c]);
743 }
744
745 // Mix with a Hadamard matrix
748
749 // Flip some polarities
751
752 return mixed;
753 }
754
755 private:
756 std::array<uint32, NumChannels> mDelaySamples;
757 std::array<DelayLine<1>, NumChannels> mDelays;
758 };
759
760 //==========================================================================
761 template<uint32 NumChannels = 8, uint32 StepCount = 4> requires(Detail::cValidFDNOrder<NumChannels> and StepCount > 0)
763 {
764 protected:
765 DiffuserBase() = default;
766
767 public:
768 using Channels = std::array<float, NumChannels>;
770
771 std::array<Step, StepCount> Steps;
772
773 void Prepare(float sampleRate)
774 {
775 for (Step& step : Steps)
776 step.Prepare(sampleRate);
777 }
778
780 {
781 for (Step& step : Steps)
782 samples = step.Process(samples);
783 return samples;
784 }
785 };
786
787 //==========================================================================
788 template<uint32 NumChannels = 8, uint32 StepCount = 4> requires(Detail::cValidFDNOrder<NumChannels> and StepCount > 0)
790 {
792
793 std::array<Step, StepCount> Steps;
794
796 {
798 for (Step& step : Steps)
799 step.DelayMsRange = delayMsRange;
800 }
801 };
802
803 //==========================================================================
804 template<uint32 NumChannels = 8, uint32 StepCount = 4> requires (Detail::cValidFDNOrder<NumChannels> and StepCount > 0)
806 {
808
809 std::array<Step, StepCount> Steps;
810
812 {
813 for (Step& step : Steps)
814 {
815 diffusionMs *= 0.5;
816 step.DelayMsRange = diffusionMs;
817 }
818 }
819 };
820
821 //==================================================================================
823#if 0
824 template<uint32 NumChannels = 8, uint32 NumDiffusionSteps = 4> requires(Detail::cValidFDNOrder<NumChannels>)
825 class FDNReverb
826 {
827 public:
828 using Channels = std::array<float, NumChannels>;
829
830 FDNReverb(float roomSizeMs, const simd& rt60, float dry = 0.0f, float wet = 1.0f)
832 , mDryAmount(dry)
833 , mWetAmount(wet)
834 , mDiffuserAmount(wet * 0.5f)
836 {
837 mFeedback.RT60 = rt60;
838 }
839
840 inline void Prepare(float sampleRate)
841 {
842 mSampleRate = sampleRate;
843 mFeedback.Prepare(sampleRate);
844 mDiffuser.Prepare(sampleRate);
845 }
846
847 [[nodiscard]] inline Channels Process(Channels input)
848 {
849 Channels output;
850 if (mUseDiffuser)
851 {
852 Channels diffuse = mDiffuser.Process(input);
853 Channels longLasting = mFeedback.Process(diffuse);
854
856 //Matrix::Hadamard<float, NumChannels>::MixInPlace(diffuse.data());
858
865 }
866 else
867 {
868 Channels longLasting = mFeedback.Process(input);
869
874 }
875 return output;
876 }
877
880 [[nodiscard]] JPL_INLINE float GetCurrentRT60() const { return mFeedback.RT60; }
883 JPL_INLINE void SetRT60(const simd& newRT60) { mFeedback.UpdateDecayGains(clamp(newRT60, 0.03f, 10.0f), mSampleRate); }
884
885 private:
889 bool mUseDiffuser;
890 float mSampleRate = 48'000.0f;
891 };
892#endif
893} // namespace JPL
#define JPL_ASSERT(inExpression,...)
Main assert macro, usage: JPL_ASSERT(condition, message) or JPL_ASSERT(condition)
Definition ErrorReporting.h:76
Forward declaration.
Definition DelayLine.h:131
void Resize(uint32_t newSize)
Definition DelayLine.h:150
std::span< const float, WindowLength > GetReadWindow(uint32_t intDelay) const
Definition DelayLine.h:202
void Clear()
Definition DelayLine.h:176
void Push(float sample)
Definition DelayLine.h:183
Definition ReverbDesign.h:389
static JPL_INLINE void MixInPlace(std::array< Sample, Size > &data)
Definition ReverbDesign.h:442
Definition ReverbDesign.h:300
static void MixInPlace(std::array< Sample, Size > &arr)
Definition ReverbDesign.h:303
static void MixInPlace(std::array< Sample, 16 > &arr)
Definition ReverbDesign.h:312
Definition ReverbDesign.h:649
Channels Process(Channels input)
Definition ReverbDesign.h:656
typename Base::Channels Channels
Definition ReverbDesign.h:652
Definition ReverbDesign.h:677
Channels Process(const Channels &input)
Definition ReverbDesign.h:684
typename Base::Channels Channels
Definition ReverbDesign.h:680
constexpr auto cPrimes
Definition ReverbDesign.h:47
constexpr bool cValidFDNOrder
Definition ReverbDesign.h:88
constexpr Sample GenRandomSign(uint32 outChannel, uint32 fdnChannel)
Definition ReverbDesign.h:92
constexpr uint32 cMaxDelayInSamples
Definition ReverbDesign.h:85
JPL_INLINE uint32 CeilToPrime(uint32 number)
Definition ReverbDesign.h:77
constexpr uint32 cMaxFDNOrder
Definition ReverbDesign.h:84
JPL_INLINE constexpr T FMA(T a, T b, T c) noexcept
Inlined fuse multiply-add. Compiler in some circumstances is more eager to optimize this than std::fm...
Definition Math.h:186
JPL_INLINE constexpr T Sqrt(T x) noexcept
Definition Math.h:269
JPL_INLINE void Add(std::array< Sample, Size > &data, Sample value)
Definition ReverbDesign.h:206
JPL_INLINE void Multiply(const std::array< Sample, Size > &dataA, std::array< Sample, Size > &dataBOut)
Definition ReverbDesign.h:130
JPL_INLINE void AssignRandomSign(std::array< Sample, Size > &data)
Definition ReverbDesign.h:285
JPL_INLINE Sample ReduceSum(const std::array< Sample, Size > &data)
Definition ReverbDesign.h:258
Definition AcousticMaterial.h:36
JPL_INLINE simd clamp(const simd &value, const simd &minV, const simd &maxV) noexcept
Element-wise clamp.
Definition SIMD.h:1838
JPL_INLINE constexpr auto FloorToSIMDSize(std::unsigned_integral auto count) noexcept
Floor count to 4-wide simd vector.
Definition SIMDMath.h:33
std::uint32_t uint32
Definition Core.h:311
JPL_INLINE simd max(const simd &a, const simd &b) noexcept
Element-wise max.
Definition SIMD.h:1799
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
Definition ReverbDesign.h:763
void Prepare(float sampleRate)
Definition ReverbDesign.h:773
Channels Process(Channels samples)
Definition ReverbDesign.h:779
DiffuserBase()=default
std::array< float, NumChannels > Channels
Definition ReverbDesign.h:768
std::array< Step, StepCount > Steps
Definition ReverbDesign.h:771
Definition ReverbDesign.h:790
std::array< Step, StepCount > Steps
Definition ReverbDesign.h:793
DiffuserEqualLengths(float totalDiffusionMs)
Definition ReverbDesign.h:795
Definition ReverbDesign.h:806
DiffuserHalfLengths(float diffusionMs)
Definition ReverbDesign.h:811
std::array< Step, StepCount > Steps
Definition ReverbDesign.h:809
Definition ReverbDesign.h:709
void Prepare(float sampleRate)
Definition ReverbDesign.h:714
std::array< float, NumChannels > Channels
Definition ReverbDesign.h:710
Channels Process(Channels input)
Definition ReverbDesign.h:735
Definition ReverbDesign.h:549
uint32 DelaySamples
Definition ReverbDesign.h:552
DelayLine< 1 > Delay
Definition ReverbDesign.h:553
float DelayMs
Definition ReverbDesign.h:551
JPL_INLINE float Process(float sample)
Definition ReverbDesign.h:562
float DecayGain
Definition ReverbDesign.h:550
JPL_INLINE void Prepare(float sampleRate)
Definition ReverbDesign.h:555
Definition ReverbDesign.h:452
static JPL_INLINE void InjectNormalizedRndSign(Sample monoInput, std::array< Sample, NumFDNChannels > &outFDNInput)
Definition ReverbDesign.h:462
static JPL_INLINE void InjectNormalized(Sample monoInput, std::array< Sample, NumFDNChannels > &outFDNInput)
Definition ReverbDesign.h:455
Definition ReverbDesign.h:477
static void Mix(std::array< Sample, NumFDNChannels > fdn, std::span< Sample > out)
Definition ReverbDesign.h:480
static void MixNormalize(std::array< Sample, NumFDNChannels > fdn, std::span< Sample > out)
Definition ReverbDesign.h:500
Definition ReverbDesign.h:518
static void MixNormalize(const std::array< Sample, NumFDNChannels > &fdn, std::span< Sample > out)
Definition ReverbDesign.h:535
static void Mix(const std::array< Sample, NumFDNChannels > &fdn, std::span< Sample > out)
Definition ReverbDesign.h:520
Definition ReverbDesign.h:575
std::array< AttenuationFilter, NumChannels > mAttenuationFilters
Definition ReverbDesign.h:642
std::array< uint32, NumChannels > mDelaySamples
Definition ReverbDesign.h:640
std::array< DelayLine< 1 >, NumChannels > mDelays
Definition ReverbDesign.h:641
void UpdateFilterParameters(FilterArgs &&...filterUpdateArgs)
Definition ReverbDesign.h:623
JPL_INLINE float ApplyAttenuationFilter(float sample, uint32 fdnIndex)
Definition ReverbDesign.h:634
void Prepare(float sampleRate, FilterArgs &&...filterPrepareArgs)
Definition ReverbDesign.h:597
std::array< float, NumChannels > Channels
Definition ReverbDesign.h:589
Minimal 4-wide 32-bit float vector implementation for SIMD.
Definition SIMD.h:60
JPL_INLINE float reduce() const noexcept
Returns sum of all components.
Definition SIMD.h:938
JPL_INLINE void store(float *mem) const
Store values from simd to provided memory location.
Definition SIMD.h:573
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:97