JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
CrossoverFilter.h
Go to the documentation of this file.
1//
2// ██╗██████╗ ██╗ ██╗██████╗ ███████╗
3// ██║██╔══██╗ ██║ ██║██╔══██╗██╔════╝ ** JPLSpatial **
4// ██║██████╔╝ ██║ ██║██████╔╝███████╗
5// ██ ██║██╔═══╝ ██║ ██║██╔══██╗╚════██║ https://github.com/Jaytheway/JPLSpatial
6// ╚█████╔╝██║ ███████╗██║██████╔╝███████║
7// ╚════╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚══════╝
8//
9// Copyright Jaroslav Pevno, JPLSpatial 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 <cmath>
30#include <numbers>
31#include <span>
32
33namespace JPL
34{
35 //==========================================================================
38 {
39 struct Params
40 {
41 float g = 0.0f;
42 float d = 0.0f;
43 float MinusRg = 0.0f;
44 };
45
46 // State
47 float s1 = 0.0f, s2 = 0.0f;
48
50 [[nodiscard]] static JPL_INLINE Params Prepare(float sampleRate, float frequency) noexcept
51 {
52 static constexpr float R = std::numbers::sqrt2_v<float>;
53 const float g = std::tan(JPL_PI * (frequency / sampleRate));
54 // d = 1.0f / (1.0f + R * g + g * g);
55 return Params{
56 .g = g,
57 .d = 1.0f / Math::FMA(g, R + g, 1.0f),
58 .MinusRg = -(R + g)
59 };
60 }
61
62 JPL_INLINE void Reset() noexcept { s1 = 0.0f; s2 = 0.0f; }
63
64 JPL_INLINE void Process(const Params& params, float x, float& yL, float& yB, float& yH) noexcept
65 {
66 // HP := (x-g1*s1-s2)*d; // g1=2R+g, d=1/(1+2Rg+g^2)
67 // v1 := g*HP; BP := v1+s1; s1 := BP+v1; // first integrator
68 // v2 := g*BP; LP := v2+s2; s2 := LP+v2; // second integrator
69
70 // yH = (x - (R + g) * s1 - s2) * d;
71 yH = Math::FMA(params.MinusRg, s1, x - s2) * params.d;
72 yB = Math::FMA(params.g, yH, s1);
73 s1 = Math::FMA(params.g, yH, yB);
74 yL = Math::FMA(params.g, yB, s2);
75 s2 = Math::FMA(params.g, yB, yL);
76 }
77
78 JPL_INLINE void ProcessBPLP(const Params& params, float x, float& yL, float& yB)
79 {
80 // BP: = (g * (x - s2) + s1) * d; // d=1/(1+2Rg+g^2)
81 // v1 := BP-s1; s1 := BP+v1; // first integrator
82 // v2 := g*BP; LP := v2+s2; s2 := LP+v2; // second integrator
83
84 yB = Math::FMA(params.g, (x - s2), s1) * params.d;
85 s1 = yB + yB - s1;
86 yL = Math::FMA(params.g, yB, s2);
87 s2 = Math::FMA(params.g, yB, yL);
88 }
89
90 JPL_INLINE void ProcessBPHP(const Params& params, float x, float& yB, float& yH)
91 {
92 // HP := (x-g1*s1-s2)*d; // g1=2R+g, d=1/(1+2Rg+g^2)
93 // v1 := g*HP; BP := v1+s1; s1 := BP+v1; // first integrator
94 // v22 := g*BP2; s2 := s2+v22; // second integrator
95
96 // yH = (x - (R + g) * s1 - s2) * d;
97 yH = Math::FMA(params.MinusRg, s1, x - s2) * params.d;
98 yB = Math::FMA(params.g, yH, s1);
99 s1 = Math::FMA(params.g, yH, yB);
100 s2 = Math::FMA(params.g, yB + yB, s2);
101 }
102
103 JPL_INLINE void ProcessBP(const Params& params, float x, float& yB)
104 {
105 // BP: = (g * (x - s2) + s1) * d; // d=1/(1+2Rg+g^2)
106 // BP2 := BP+BP; s1 := BP2-s1; // first integrator
107 // v22 := g*BP2; s2 := s2+v22; // second integrator
108
109 yB = Math::FMA(params.g, (x - s2), s1) * params.d;
110 yB = yB + yB;
111 s1 = yB - s1;
112 s2 = Math::FMA(params.g, yB, s2);
113 }
114
115 JPL_INLINE void ProcessAP(const Params& params, float x, float& yAP)
116 {
117 static constexpr float minusR = -std::numbers::sqrt2_v<float>;
118 float yL, yB, yH;
119 Process(params, x, yL, yB, yH);
120 yAP = Math::FMA(minusR, yB, yH + yL); // yL - R * yB + yH;
121 }
122 };
123
124 //==========================================================================
128 {
129 struct Params
130 {
131 simd g = 0.0f;
132 simd d = 0.0f;
133 simd MinusRg = 0.0f;
134 };
135
136 // State
137 simd s1 = 0.0f, s2 = 0.0f;
138
139 [[nodiscard]] static JPL_INLINE Params Prepare(const simd& sampleRate, const simd& frequency) noexcept
140 {
141 static const simd R = std::numbers::sqrt2_v<float>;
142 const simd g = tan(JPL_PI * (frequency / sampleRate));
143 // d = 1.0f / (1.0f + R * g + g * g);
144 return Params{
145 .g = g,
146 .d = simd::c_1() / Math::FMA(g, R + g, 1.0f),
147 .MinusRg = -(R + g)
148 };
149 }
150
151 JPL_INLINE void Reset() noexcept { s1 = 0.0f; s2 = 0.0f; }
152
153 JPL_INLINE void Process(const Params& params, const simd& x, simd& yL, simd& yB, simd& yH) noexcept
154 {
155 // yH = (x - (R + g) * s1 - s2) * d;
156 yH = Math::FMA(params.MinusRg, s1, x - s2) * params.d;
157 yB = Math::FMA(params.g, yH, s1);
158 s1 = Math::FMA(params.g, yH, yB);
159 yL = Math::FMA(params.g, yB, s2);
160 s2 = Math::FMA(params.g, yB, yL);
161 }
162
164 {
165 yB = Math::FMA(params.g, (x - s2), s1) * params.d;
166 s1 = yB + yB - s1;
167 yL = Math::FMA(params.g, yB, s2);
168 s2 = Math::FMA(params.g, yB, yL);
169 }
170
172 {
173 yH = Math::FMA(params.MinusRg, s1, x - s2) * params.d;
174 yB = Math::FMA(params.g, yH, s1);
175 s1 = Math::FMA(params.g, yH, yB);
176 s2 = Math::FMA(params.g, yB + yB, s2);
177 }
178
180 {
181 yB = Math::FMA(params.g, (x - s2), s1) * params.d;
182 yB = yB + yB;
183 s1 = yB - s1;
184 s2 = Math::FMA(params.g, yB, s2);
185 }
186
188 {
189 static const simd minusR = -std::numbers::sqrt2_v<float>;
190 simd yL, yB, yH;
191 Process(params, x, yL, yB, yH);
192 yAP = Math::FMA(minusR, yB, yH + yL); // yL - R * yB + yH;
193 }
194 };
195
196 //==========================================================================
198 {
200
205
210
211 [[nodiscard]] JPL_INLINE float Process(float x) noexcept
212 {
213 static constexpr float minusR = -std::numbers::sqrt2_v<float>;
214 float yL, yB, yH;
216 return Math::FMA(minusR, yB, yH + yL); // yL - R * yB + yH;
217 }
218 };
219
220 //==========================================================================
222 {
224
229
234
235 [[nodiscard]] JPL_INLINE simd Process(const simd& x) noexcept
236 {
237 simd yAP;
239 return yAP;
240 }
241 };
242
243 //==========================================================================
245 struct LR4Split
246 {
249
251
252 JPL_INLINE void Prepare(float sampleRate, float frequency) noexcept
253 {
254 frequency = std::clamp(frequency, 1.0f, 0.49f * sampleRate);
256 Reset();
257 }
258
260 {
261 Stage1.Reset();
262 Stage2.Reset();
263 }
264
265 [[nodiscard]] JPL_INLINE float ProcessLP(float x) noexcept
266 {
267 // 1. TPT-SVF stage
268 float yL, yB;
270
271 // 2. Cascade the second stage (to make LR4 LP)
272 float yL2, yB2;
274
275 return yL2;
276 }
277
278 [[nodiscard]] JPL_INLINE float ProcessHP(float x) noexcept
279 {
280 // 1. TPT-SVF stage
281 float yB, yH;
283
284 // 2. Cascade the second stage (to make LR4 LP)
285 float yB2, yH2;
287 // (for high pass the input to the second stage
288 // is high-pass from the first, the rest is the same)
289 return yH2;
290 }
291
292 // Process one sample through a proper LR4 crossover:
293 // returns {low, high} such that low+high == x (exact) and magnitudes are LR4
294 JPL_INLINE void Process(float x, float& low, float& high) noexcept
295 {
296 static constexpr float minusR = -std::numbers::sqrt2_v<float>;
297
298 // 1. TPT-SVF stage
299 float yL, yB, yH;
301
302 // 2. Cascade the second stage (to make LR4 LP)
303 float yL2, yB2, yH2;
305
306 // Outputs
307 low = yL2;
308 // high = yL - R * yB + yH - yL2;
309 high = Math::FMA(minusR, yB, yL + yH - yL2);
310 }
311 };
312
313 //==========================================================================
325 {
328
329 // 4 allpasses for phase equalization
330 // f3 AP on bands 1 & 2
331 // f1 AP on bands 3 & 4
333
334 // Reusing filter parameters for the two stages
336
337 // Input split frequencies must be as follows: f1 < f2 < f3,
338 // where `f2` is the frequency of the previous splitter,
339 // output of which will be fed into this splitter.
340 JPL_INLINE void Prepare(float sampleRate, float f1, float f3) noexcept
341 {
342 // Interleave f1 and f3 to be able to vectorize as much
343 // as possible with as little shuffling as possible
344 const simd frequency = clamp(simd(f1, f3, f1, f3),
345 simd(1.0f),
346 simd(0.49f) * sampleRate);
347
349
350 // Interleave frequencies according to 2-lane
351 // vectorized LR4 splitter output { b1, b3, b2, b4 }:
352 // { f3, f1, f3, f1 }
354 // ..reversing `frequency` to reuse the clamp above,
355 // however the relationship is not necessarily reversal)
356
357 Reset();
358 }
359
361 {
362 Stage1.Reset();
363 Stage2.Reset();
365 }
366
367 // Process `low` and `high` from the first split through proper LR4 crossover.
368 // @param low : low part of the output from the first middle split
369 // @param high : high part of the output from the first middle split
370 // @param out_b1324 : output frequency bands, interleaved { b1, b3, b2, b4 }
371 JPL_INLINE void ProcessInterleaved(float low, float high, simd& out_b1324) noexcept
372 {
373 // We SIMDI-fy this as follows:
374 // - Stage 1 works on the two input lanes interleaved (half saturated)
375 // - Stage1(low, high, low, high) -> L and H for f1 and f3
376 // - Stage 2 takes different inputs for each lane: (fully saturated)
377 // - Stage2(L_f1, L_f3, H_f1, H_f3) -> L2 and H2 for f1 and f3
378 //
379 // Therefore the biggest SIMD benefit is in the Stage 2,
380 // while Stage 1 gives only half benefit compared to scalar version.
381
382 const simd x(low, high, low, high);
383
384 // yL = { yL_f1, yL_f3, yL_f1, yL_f3 }
385 // yH = { yH_f1, yH_f3, yH_f1, yH_f3 }
386 simd yL, yB, yH;
388
389 // x2 = { yL_f1, yL_f3, yH_f1, yH_f3 }
390 const simd x2 = combine_lo(yL, yH);
391 simd yL2, yB2, yH2;
393
394 // out = { yL2_f1, yL2_f3, yH2_f1, yH2_f3 }
396 // (we leave it up to the caller to deinterleave the output if/when necessary)
397
398 // Equalize the messed up phase
399 //out_b1324 = AllpassEqualizer.Process(out_b1324);
401 }
402
403 // Same as ProcessInterleaved, but followed by deinterleaving
404 // the bands for the output, since the caller most likely
405 // expects bands in ascending frequency order.
406 // @param out_b1234 : output frequency bands in order { b1, b2, b3, b4 }
407 JPL_INLINE void Process(float low, float high, simd& out_b1234) noexcept
408 {
411 }
412 };
413
414 //==========================================================================
417 {
418 // First splitter at the mid frequency
420
421 // Combined splitter for the lower and higher
422 // parts of the first splitter output
424
432
434 {
435 X2.Reset();
436 X13.Reset();
437 }
438
439 inline void SplitSample(float sample, simd& outBands) noexcept
440 {
441 // Process the first split into `low` and `high` parts
442 float low, high;
444
445 // Process the secod layer splits from `low` and `high`
446 // to produce the bands { b1, b2, b3, b4 }
448 }
449
451 {
453 return bands;
454 }
455
456 JPL_INLINE float ProcessSample(float sample, simd gains) noexcept
457 {
458 return (SplitSample(sample) * gains).reduce();
459 }
460
461 // Process block of float samples and return 4 bands per sample packed into simd lanes
462 inline void ProcessBlock(std::span<const float> in, std::span<simd> outBands) noexcept
463 {
465
466 for (size_t i = 0; i < in.size(); ++i)
467 {
469 }
470 }
471
472 // Process block of float samples, apply per band gains and return processed float samples
473 inline void ProcessBlock(std::span<const float> in, simd gains, std::span<float> out) noexcept
474 {
475 // Interleave gains to match the 2-lane
476 // vectorized LR4 splitter output { b1, b3, b2, b4 }
477 // We do this here once, to avoid interleaving
478 // the output of the splitter for each sample iteration.
479 // Since we immediately recombine the bands here,
480 // their order doesn't matter.
482
483 for (size_t i = 0; i < in.size(); ++i)
484 {
485 // Process the first split into `low` and `high` parts
486 float low, high;
487 X2.Process(in[i], low, high);
488
489#if 0 //? debugging with just single split
490 out[i] = low * gains[0] + high * gains[3];
491#else
492 // Process the secod layer splits from `low` and `high`
493 // to produce interleaved bands { b1, b3, b2, b4 }
494 simd bands;
496
497 // Sum up the 4 bands togther into the final output sample
498 out[i] = (bands * gains).reduce();
499#endif
500 }
501 }
502
503 // Process block of float samples, apply per band gains with ramp from @startGains to @endGains
504 // and return processed float samples
505 inline void ProcessBlock(std::span<const float> in,
508 std::span<float> out) noexcept
509 {
510 // Interleave gains to match the 2-lane
511 // vectorized LR4 splitter output { b1, b3, b2, b4 }
512 // We do this here once, to avoid interleaving
513 // the output of the splitter for each sample iteration.
514 // Since we immediately recombine the bands here,
515 // their order doesn't matter.
518 const simd increment = (endGains - startGains) / static_cast<float>(in.size());
519
520 for (size_t i = 0; i < in.size(); ++i)
521 {
522 // Process the first split into `low` and `high` parts
523 float low, high;
524 X2.Process(in[i], low, high);
525 // Process the secod layer splits from `low` and `high`
526 // to produce interleaved bands { b1, b3, b2, b4 }
527 simd bands;
529
530 // Sum up the 4 bands togther into the final output sample
531 out[i] = (bands * startGains).reduce();
532
534 }
535 }
536 };
537} // namespace JPL
#define JPL_ASSERT(inExpression,...)
Main assert macro, usage: JPL_ASSERT(condition, message) or JPL_ASSERT(condition)
Definition ErrorReporting.h:76
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
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
constexpr SplitFrequencies cDefaultFrequencySplits
At the moment JPL Spatial uses these frequency sptits for sound propagation.
Definition FrequencyBands.h:48
JPL_INLINE simd reverse(const simd &vec) noexcept
Reverse the order of the lanes.
Definition SIMD.h:2005
JPL_INLINE simd combine_lohi(const simd &a, const simd &b) noexcept
Combine two low lanes of a and two high lanes from b as { a0, a1, b2, b3 }.
Definition SIMD.h:1994
JPL_INLINE simd interleave_lohi(const simd &vec) noexcept
Interleave two low lanes with the two high lanes (e.g. { 0, 1, 2, 3 } -> { 0, 2, 1,...
Definition SIMD.h:1950
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
JPL_INLINE simd combine_lo(const simd &a, const simd &b) noexcept
Combine two low lanes of input a and b as { a0, a1, b0, b1 }.
Definition SIMD.h:1962
simd tan(simd x) noexcept
Definition SIMDMath.h:643
4th order Linkwitz-Riley 4-band crossover
Definition CrossoverFilter.h:417
JPL_INLINE simd SplitSample(float sample) noexcept
Definition CrossoverFilter.h:450
void ProcessBlock(std::span< const float > in, simd startGains, simd endGains, std::span< float > out) noexcept
Definition CrossoverFilter.h:505
void ProcessBlock(std::span< const float > in, simd gains, std::span< float > out) noexcept
Definition CrossoverFilter.h:473
void SplitSample(float sample, simd &outBands) noexcept
Definition CrossoverFilter.h:439
void ProcessBlock(std::span< const float > in, std::span< simd > outBands) noexcept
Definition CrossoverFilter.h:462
JPL_INLINE float ProcessSample(float sample, simd gains) noexcept
Definition CrossoverFilter.h:456
JPL_INLINE void Prepare(float sampleRate, SplitFrequencies splits=cDefaultFrequencySplits) noexcept
Definition CrossoverFilter.h:425
LR4Split X2
Definition CrossoverFilter.h:419
JPL_INLINE void Reset() noexcept
Definition CrossoverFilter.h:433
LR4Split2Lanes X13
Definition CrossoverFilter.h:423
Definition CrossoverFilter.h:325
JPL_INLINE void Reset() noexcept
Definition CrossoverFilter.h:360
TPTSVFStageSIMD Stage1
Definition CrossoverFilter.h:326
TPTSVFStageSIMD Stage2
Definition CrossoverFilter.h:327
JPL_INLINE void Process(float low, float high, simd &out_b1234) noexcept
Definition CrossoverFilter.h:407
JPL_INLINE void Prepare(float sampleRate, float f1, float f3) noexcept
Definition CrossoverFilter.h:340
TPTSVFAllpassSIMD AllpassEqualizer
Definition CrossoverFilter.h:332
JPL_INLINE void ProcessInterleaved(float low, float high, simd &out_b1324) noexcept
Definition CrossoverFilter.h:371
TPTSVFStageSIMD::Params Params
Definition CrossoverFilter.h:335
4th order Linkwitz-Riley crossover
Definition CrossoverFilter.h:246
JPL_INLINE float ProcessLP(float x) noexcept
Definition CrossoverFilter.h:265
JPL_INLINE void Reset() noexcept
Definition CrossoverFilter.h:259
JPL_INLINE void Prepare(float sampleRate, float frequency) noexcept
Definition CrossoverFilter.h:252
JPL_INLINE void Process(float x, float &low, float &high) noexcept
Definition CrossoverFilter.h:294
JPL_INLINE float ProcessHP(float x) noexcept
Definition CrossoverFilter.h:278
StateVariableFilterTPT::Params Params
Definition CrossoverFilter.h:250
StateVariableFilterTPT Stage2
Definition CrossoverFilter.h:248
StateVariableFilterTPT Stage1
Definition CrossoverFilter.h:247
Split frequencies points for crossover.
Definition FrequencyBands.h:41
Definition CrossoverFilter.h:40
float g
Definition CrossoverFilter.h:41
float d
Definition CrossoverFilter.h:42
float MinusRg
Definition CrossoverFilter.h:43
State Variable Filter with Topology-Preserving Transform structure.
Definition CrossoverFilter.h:38
JPL_INLINE void Reset() noexcept
Definition CrossoverFilter.h:62
float s1
Definition CrossoverFilter.h:47
JPL_INLINE void Process(const Params &params, float x, float &yL, float &yB, float &yH) noexcept
Definition CrossoverFilter.h:64
JPL_INLINE void ProcessBP(const Params &params, float x, float &yB)
Definition CrossoverFilter.h:103
JPL_INLINE void ProcessBPLP(const Params &params, float x, float &yL, float &yB)
Definition CrossoverFilter.h:78
JPL_INLINE void ProcessBPHP(const Params &params, float x, float &yB, float &yH)
Definition CrossoverFilter.h:90
static JPL_INLINE Params Prepare(float sampleRate, float frequency) noexcept
Prepare parameters for a TPT SVGF Stage.
Definition CrossoverFilter.h:50
JPL_INLINE void ProcessAP(const Params &params, float x, float &yAP)
Definition CrossoverFilter.h:115
float s2
Definition CrossoverFilter.h:47
Definition CrossoverFilter.h:222
JPL_INLINE void Prepare(const simd &sampleRate, const simd &frequency) noexcept
Definition CrossoverFilter.h:225
TPTSVFStageSIMD::Params Params
Definition CrossoverFilter.h:223
JPL_INLINE simd Process(const simd &x) noexcept
Definition CrossoverFilter.h:235
JPL_INLINE void Reset() noexcept
Definition CrossoverFilter.h:230
Definition CrossoverFilter.h:198
StateVariableFilterTPT::Params Params
Definition CrossoverFilter.h:199
JPL_INLINE void Reset() noexcept
Definition CrossoverFilter.h:206
JPL_INLINE void Prepare(float sampleRate, float frequency) noexcept
Definition CrossoverFilter.h:201
JPL_INLINE float Process(float x) noexcept
Definition CrossoverFilter.h:211
Definition CrossoverFilter.h:130
simd d
Definition CrossoverFilter.h:132
simd MinusRg
Definition CrossoverFilter.h:133
simd g
Definition CrossoverFilter.h:131
Definition CrossoverFilter.h:128
JPL_INLINE void Reset() noexcept
Definition CrossoverFilter.h:151
JPL_INLINE void ProcessBP(const Params &params, simd x, simd &yB)
Definition CrossoverFilter.h:179
static JPL_INLINE Params Prepare(const simd &sampleRate, const simd &frequency) noexcept
Definition CrossoverFilter.h:139
JPL_INLINE void ProcessBPLP(const Params &params, simd x, simd &yL, simd &yB)
Definition CrossoverFilter.h:163
JPL_INLINE void ProcessAP(const Params &params, simd x, simd &yAP)
Definition CrossoverFilter.h:187
simd s1
Definition CrossoverFilter.h:137
JPL_INLINE void ProcessBPHP(const Params &params, simd x, simd &yB, simd &yH)
Definition CrossoverFilter.h:171
simd s2
Definition CrossoverFilter.h:137
JPL_INLINE void Process(const Params &params, const simd &x, simd &yL, simd &yB, simd &yH) noexcept
Definition CrossoverFilter.h:153
Minimal 4-wide 32-bit float vector implementation for SIMD.
Definition SIMD.h:60
static JPL_INLINE simd c_1() noexcept
Definition SIMD.h:461
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:97