react-native-audio-api 0.4.10 → 0.4.12-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +2 -0
- package/android/src/main/cpp/{core/AudioAPIInstaller.cpp → AudioAPIModule.cpp} +12 -11
- package/android/src/main/cpp/{core/AudioAPIInstaller.h → AudioAPIModule.h} +9 -11
- package/android/src/main/cpp/OnLoad.cpp +2 -2
- package/android/src/main/cpp/core/AudioDecoder.cpp +5 -5
- package/android/src/main/cpp/core/AudioPlayer.cpp +12 -0
- package/android/src/main/cpp/core/AudioPlayer.h +2 -0
- package/android/src/main/java/com/swmansion/audioapi/{module/AudioAPIInstaller.kt → AudioAPIModule.kt} +22 -10
- package/android/src/main/java/com/swmansion/audioapi/AudioAPIPackage.kt +31 -6
- package/android/src/oldarch/NativeAudioAPIModuleSpec.java +37 -0
- package/common/cpp/HostObjects/AudioBufferSourceNodeHostObject.h +1 -2
- package/common/cpp/HostObjects/AudioContextHostObject.h +34 -1
- package/common/cpp/HostObjects/BaseAudioContextHostObject.h +8 -0
- package/common/cpp/HostObjects/GainNodeHostObject.h +2 -2
- package/common/cpp/HostObjects/StretcherNodeHostObject.h +35 -0
- package/common/cpp/core/AnalyserNode.cpp +2 -2
- package/common/cpp/core/AnalyserNode.h +1 -1
- package/common/cpp/core/AudioBuffer.cpp +4 -2
- package/common/cpp/core/AudioBuffer.h +1 -1
- package/common/cpp/core/AudioBufferSourceNode.cpp +3 -3
- package/common/cpp/core/AudioBufferSourceNode.h +3 -3
- package/common/cpp/core/AudioBus.cpp +8 -0
- package/common/cpp/core/AudioBus.h +3 -0
- package/common/cpp/core/AudioContext.cpp +10 -0
- package/common/cpp/core/AudioContext.h +2 -0
- package/common/cpp/core/AudioDecoder.h +2 -1
- package/common/cpp/core/AudioDestinationNode.cpp +1 -1
- package/common/cpp/core/AudioDestinationNode.h +1 -1
- package/common/cpp/core/AudioNode.cpp +10 -6
- package/common/cpp/core/AudioNode.h +5 -3
- package/common/cpp/core/AudioScheduledSourceNode.cpp +1 -1
- package/common/cpp/core/AudioScheduledSourceNode.h +1 -1
- package/common/cpp/core/BaseAudioContext.cpp +7 -0
- package/common/cpp/core/BaseAudioContext.h +2 -0
- package/common/cpp/core/BiquadFilterNode.cpp +1 -1
- package/common/cpp/core/BiquadFilterNode.h +1 -1
- package/common/cpp/core/GainNode.cpp +3 -1
- package/common/cpp/core/GainNode.h +1 -1
- package/common/cpp/core/OscillatorNode.cpp +3 -1
- package/common/cpp/core/OscillatorNode.h +1 -1
- package/common/cpp/core/StereoPannerNode.cpp +1 -1
- package/common/cpp/core/StereoPannerNode.h +1 -1
- package/common/cpp/core/StretcherNode.cpp +96 -0
- package/common/cpp/core/StretcherNode.h +63 -0
- package/common/cpp/installer/AudioAPIModuleInstaller.h +49 -0
- package/common/cpp/libs/dsp/LICENSE.txt +21 -0
- package/common/cpp/libs/dsp/README.md +40 -0
- package/common/cpp/libs/dsp/common.h +47 -0
- package/common/cpp/libs/dsp/curves.h +371 -0
- package/common/cpp/libs/dsp/delay.h +717 -0
- package/common/cpp/libs/dsp/envelopes.h +523 -0
- package/common/cpp/libs/dsp/fft.h +523 -0
- package/common/cpp/libs/dsp/filters.h +436 -0
- package/common/cpp/libs/dsp/mix.h +218 -0
- package/common/cpp/libs/dsp/perf.h +84 -0
- package/common/cpp/libs/dsp/rates.h +184 -0
- package/common/cpp/libs/dsp/spectral.h +496 -0
- package/common/cpp/libs/dsp/windows.h +219 -0
- package/common/cpp/libs/signalsmith-stretch.h +637 -0
- package/common/cpp/types/TimeStretchType.h +6 -0
- package/ios/AudioAPIModule.h +1 -1
- package/ios/AudioAPIModule.mm +10 -3
- package/ios/core/AudioDecoder.mm +2 -3
- package/ios/core/AudioPlayer.h +14 -0
- package/ios/core/AudioPlayer.m +86 -25
- package/ios/core/IOSAudioPlayer.h +2 -0
- package/ios/core/IOSAudioPlayer.mm +10 -0
- package/lib/module/core/AudioContext.js +7 -1
- package/lib/module/core/AudioContext.js.map +1 -1
- package/lib/module/core/BaseAudioContext.js +4 -0
- package/lib/module/core/BaseAudioContext.js.map +1 -1
- package/lib/module/core/StretcherNode.js +12 -0
- package/lib/module/core/StretcherNode.js.map +1 -0
- package/lib/module/index.js +12 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/specs/NativeAudioAPIModule.js +5 -0
- package/lib/module/specs/NativeAudioAPIModule.js.map +1 -0
- package/lib/module/web-core/AudioContext.js +6 -0
- package/lib/module/web-core/AudioContext.js.map +1 -1
- package/lib/typescript/core/AudioContext.d.ts +2 -0
- package/lib/typescript/core/AudioContext.d.ts.map +1 -1
- package/lib/typescript/core/BaseAudioContext.d.ts +2 -0
- package/lib/typescript/core/BaseAudioContext.d.ts.map +1 -1
- package/lib/typescript/core/StretcherNode.d.ts +10 -0
- package/lib/typescript/core/StretcherNode.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +5 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +1 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/interfaces.d.ts +11 -1
- package/lib/typescript/interfaces.d.ts.map +1 -1
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts +7 -0
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -0
- package/lib/typescript/web-core/AudioContext.d.ts +3 -1
- package/lib/typescript/web-core/AudioContext.d.ts.map +1 -1
- package/package.json +9 -7
- package/src/core/AudioContext.ts +9 -1
- package/src/core/BaseAudioContext.ts +5 -0
- package/src/core/StretcherNode.ts +15 -0
- package/src/index.ts +17 -3
- package/src/index.web.ts +1 -0
- package/src/interfaces.ts +13 -1
- package/src/specs/NativeAudioAPIModule.ts +7 -0
- package/src/web-core/AudioContext.tsx +9 -1
- package/android/src/main/java/com/swmansion/audioapi/nativemodules/AudioAPIModule.kt +0 -26
- package/common/cpp/HostObjects/AudioAPIInstallerHostObject.h +0 -56
- package/lib/module/specs/global.d.js +0 -4
- package/lib/module/specs/global.d.js.map +0 -1
- package/lib/module/specs/install.js +0 -18
- package/lib/module/specs/install.js.map +0 -1
- package/lib/typescript/specs/install.d.ts +0 -7
- package/lib/typescript/specs/install.d.ts.map +0 -1
- package/src/specs/global.d.ts +0 -12
- package/src/specs/install.ts +0 -32
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
#include "./common.h"
|
|
2
|
+
|
|
3
|
+
#ifndef SIGNALSMITH_DSP_FILTERS_H
|
|
4
|
+
#define SIGNALSMITH_DSP_FILTERS_H
|
|
5
|
+
|
|
6
|
+
#include "./perf.h"
|
|
7
|
+
|
|
8
|
+
#include <cmath>
|
|
9
|
+
#include <complex>
|
|
10
|
+
|
|
11
|
+
namespace signalsmith {
|
|
12
|
+
namespace filters {
|
|
13
|
+
/** @defgroup Filters Basic filters
|
|
14
|
+
@brief Classes for some common filter types
|
|
15
|
+
|
|
16
|
+
@{
|
|
17
|
+
@file
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Filter design methods.
|
|
21
|
+
These differ mostly in how they handle frequency-warping near Nyquist:
|
|
22
|
+
\diagram{filters-lowpass.svg}
|
|
23
|
+
\diagram{filters-highpass.svg}
|
|
24
|
+
\diagram{filters-peak.svg}
|
|
25
|
+
\diagram{filters-bandpass.svg}
|
|
26
|
+
\diagram{filters-notch.svg}
|
|
27
|
+
\diagram{filters-high-shelf.svg}
|
|
28
|
+
\diagram{filters-low-shelf.svg}
|
|
29
|
+
\diagram{filters-allpass.svg}
|
|
30
|
+
*/
|
|
31
|
+
enum class BiquadDesign {
|
|
32
|
+
bilinear, ///< Bilinear transform, adjusting for centre frequency but not bandwidth
|
|
33
|
+
cookbook, ///< RBJ's "Audio EQ Cookbook". Based on `bilinear`, adjusting bandwidth (for peak/notch/bandpass) to preserve the ratio between upper/lower boundaries. This performs oddly near Nyquist.
|
|
34
|
+
oneSided, ///< Based on `bilinear`, adjusting bandwidth to preserve the lower boundary (leaving the upper one loose).
|
|
35
|
+
vicanek ///< From Martin Vicanek's [Matched Second Order Digital Filters](https://vicanek.de/articles/BiquadFits.pdf). Falls back to `oneSided` for shelf and allpass filters. This takes the poles from the impulse-invariant approach, and then picks the zeros to create a better match. This means that Nyquist is not 0dB for peak/notch (or -Inf for lowpass), but it is a decent match to the analogue prototype.
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** A standard biquad.
|
|
39
|
+
|
|
40
|
+
This is not guaranteed to be stable if modulated at audio rate.
|
|
41
|
+
|
|
42
|
+
The default highpass/lowpass bandwidth (`defaultBandwidth`) produces a Butterworth filter when bandwidth-compensation is disabled.
|
|
43
|
+
|
|
44
|
+
Bandwidth compensation defaults to `BiquadDesign::oneSided` (or `BiquadDesign::cookbook` if `cookbookBandwidth` is enabled) for all filter types aside from highpass/lowpass (which use `BiquadDesign::bilinear`).*/
|
|
45
|
+
template<typename Sample, bool cookbookBandwidth=false>
|
|
46
|
+
class BiquadStatic {
|
|
47
|
+
static constexpr BiquadDesign bwDesign = cookbookBandwidth ? BiquadDesign::cookbook : BiquadDesign::oneSided;
|
|
48
|
+
Sample a1 = 0, a2 = 0, b0 = 1, b1 = 0, b2 = 0;
|
|
49
|
+
Sample x1 = 0, x2 = 0, y1 = 0, y2 = 0;
|
|
50
|
+
|
|
51
|
+
enum class Type {highpass, lowpass, highShelf, lowShelf, bandpass, notch, peak, allpass};
|
|
52
|
+
|
|
53
|
+
struct FreqSpec {
|
|
54
|
+
double scaledFreq;
|
|
55
|
+
double w0, sinW0, cosW0;
|
|
56
|
+
double inv2Q;
|
|
57
|
+
|
|
58
|
+
FreqSpec(double freq, BiquadDesign design) {
|
|
59
|
+
scaledFreq = std::max(1e-6, std::min(0.4999, freq));
|
|
60
|
+
if (design == BiquadDesign::cookbook) {
|
|
61
|
+
scaledFreq = std::min(0.45, scaledFreq);
|
|
62
|
+
}
|
|
63
|
+
w0 = 2*M_PI*scaledFreq;
|
|
64
|
+
cosW0 = std::cos(w0);
|
|
65
|
+
sinW0 = std::sin(w0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
void oneSidedCompQ() {
|
|
69
|
+
// Ratio between our (digital) lower boundary f1 and centre f0
|
|
70
|
+
double f1Factor = std::sqrt(inv2Q*inv2Q + 1) - inv2Q;
|
|
71
|
+
// Bilinear means discrete-time freq f = continuous-time freq tan(pi*xf/pi)
|
|
72
|
+
double ctF1 = std::tan(M_PI*scaledFreq*f1Factor), invCtF0 = (1 + cosW0)/sinW0;
|
|
73
|
+
double ctF1Factor = ctF1*invCtF0;
|
|
74
|
+
inv2Q = 0.5/ctF1Factor - 0.5*ctF1Factor;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
SIGNALSMITH_INLINE static FreqSpec octaveSpec(double scaledFreq, double octaves, BiquadDesign design) {
|
|
78
|
+
FreqSpec spec(scaledFreq, design);
|
|
79
|
+
|
|
80
|
+
if (design == BiquadDesign::cookbook) {
|
|
81
|
+
// Approximately preserves bandwidth between halfway points
|
|
82
|
+
octaves *= spec.w0/spec.sinW0;
|
|
83
|
+
}
|
|
84
|
+
spec.inv2Q = std::sinh(std::log(2)*0.5*octaves); // 1/(2Q)
|
|
85
|
+
if (design == BiquadDesign::oneSided) spec.oneSidedCompQ();
|
|
86
|
+
return spec;
|
|
87
|
+
}
|
|
88
|
+
SIGNALSMITH_INLINE static FreqSpec qSpec(double scaledFreq, double q, BiquadDesign design) {
|
|
89
|
+
FreqSpec spec(scaledFreq, design);
|
|
90
|
+
|
|
91
|
+
spec.inv2Q = 0.5/q;
|
|
92
|
+
if (design == BiquadDesign::oneSided) spec.oneSidedCompQ();
|
|
93
|
+
return spec;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
SIGNALSMITH_INLINE double dbToSqrtGain(double db) {
|
|
97
|
+
return std::pow(10, db*0.025);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
SIGNALSMITH_INLINE BiquadStatic & configure(Type type, FreqSpec calc, double sqrtGain, BiquadDesign design) {
|
|
101
|
+
double w0 = calc.w0;
|
|
102
|
+
|
|
103
|
+
if (design == BiquadDesign::vicanek) {
|
|
104
|
+
if (type == Type::notch) { // Heuristic for notches near Nyquist
|
|
105
|
+
calc.inv2Q *= (1 - calc.scaledFreq*0.5);
|
|
106
|
+
}
|
|
107
|
+
double Q = (type == Type::peak ? 0.5*sqrtGain : 0.5)/calc.inv2Q;
|
|
108
|
+
double q = (type == Type::peak ? 1/sqrtGain : 1)*calc.inv2Q;
|
|
109
|
+
double expmqw = std::exp(-q*w0);
|
|
110
|
+
double da1, da2;
|
|
111
|
+
if (q <= 1) {
|
|
112
|
+
a1 = da1 = -2*expmqw*std::cos(std::sqrt(1 - q*q)*w0);
|
|
113
|
+
} else {
|
|
114
|
+
a1 = da1 = -2*expmqw*std::cosh(std::sqrt(q*q - 1)*w0);
|
|
115
|
+
}
|
|
116
|
+
a2 = da2 = expmqw*expmqw;
|
|
117
|
+
double sinpd2 = std::sin(w0/2);
|
|
118
|
+
double p0 = 1 - sinpd2*sinpd2, p1 = sinpd2*sinpd2, p2 = 4*p0*p1;
|
|
119
|
+
double A0 = 1 + da1 + da2, A1 = 1 - da1 + da2, A2 = -4*da2;
|
|
120
|
+
A0 *= A0;
|
|
121
|
+
A1 *= A1;
|
|
122
|
+
if (type == Type::lowpass) {
|
|
123
|
+
double R1 = (A0*p0 + A1*p1 + A2*p2)*Q*Q;
|
|
124
|
+
double B0 = A0, B1 = (R1 - B0*p0)/p1;
|
|
125
|
+
b0 = 0.5*(std::sqrt(B0) + std::sqrt(std::max(0.0, B1)));
|
|
126
|
+
b1 = std::sqrt(B0) - b0;
|
|
127
|
+
b2 = 0;
|
|
128
|
+
return *this;
|
|
129
|
+
} else if (type == Type::highpass) {
|
|
130
|
+
b2 = b0 = std::sqrt(A0*p0 + A1*p1 + A2*p2)*Q/(4*p1);
|
|
131
|
+
b1 = -2*b0;
|
|
132
|
+
return *this;
|
|
133
|
+
} else if (type == Type::bandpass) {
|
|
134
|
+
double R1 = A0*p0 + A1*p1 + A2*p2;
|
|
135
|
+
double R2 = -A0 + A1 + 4*(p0 - p1)*A2;
|
|
136
|
+
double B2 = (R1 - R2*p1)/(4*p1*p1);
|
|
137
|
+
double B1 = R2 + 4*(p1 - p0)*B2;
|
|
138
|
+
b1 = -0.5*std::sqrt(std::max(0.0, B1));
|
|
139
|
+
b0 = 0.5*(std::sqrt(std::max(0.0, B2 + 0.25*B1)) - b1);
|
|
140
|
+
b2 = -b0 - b1;
|
|
141
|
+
return *this;
|
|
142
|
+
} else if (type == Type::notch) {
|
|
143
|
+
// The Vicanek paper doesn't cover notches (band-stop), but we know where the zeros should be:
|
|
144
|
+
b0 = 1;
|
|
145
|
+
double db1 = -2*std::cos(w0); // might be higher precision
|
|
146
|
+
b1 = db1;
|
|
147
|
+
b2 = 1;
|
|
148
|
+
// Scale so that B0 == A0 to get 0dB at f=0
|
|
149
|
+
double scale = std::sqrt(A0)/(b0 + db1 + b2);
|
|
150
|
+
b0 *= scale;
|
|
151
|
+
b1 *= scale;
|
|
152
|
+
b2 *= scale;
|
|
153
|
+
return *this;
|
|
154
|
+
} else if (type == Type::peak) {
|
|
155
|
+
double G2 = (sqrtGain*sqrtGain)*(sqrtGain*sqrtGain);
|
|
156
|
+
double R1 = (A0*p0 + A1*p1 + A2*p2)*G2;
|
|
157
|
+
double R2 = (-A0 + A1 + 4*(p0 - p1)*A2)*G2;
|
|
158
|
+
double B0 = A0;
|
|
159
|
+
double B2 = (R1 - R2*p1 - B0)/(4*p1*p1);
|
|
160
|
+
double B1 = R2 + B0 + 4*(p1 - p0)*B2;
|
|
161
|
+
double W = 0.5*(std::sqrt(B0) + std::sqrt(std::max(0.0, B1)));
|
|
162
|
+
b0 = 0.5*(W + std::sqrt(std::max(0.0, W*W + B2)));
|
|
163
|
+
b1 = 0.5*(std::sqrt(B0) - std::sqrt(std::max(0.0, B1)));
|
|
164
|
+
b2 = -B2/(4*b0);
|
|
165
|
+
return *this;
|
|
166
|
+
}
|
|
167
|
+
// All others fall back to `oneSided`
|
|
168
|
+
design = BiquadDesign::oneSided;
|
|
169
|
+
calc.oneSidedCompQ();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
double alpha = calc.sinW0*calc.inv2Q;
|
|
173
|
+
double A = sqrtGain, sqrtA2alpha = 2*std::sqrt(A)*alpha;
|
|
174
|
+
|
|
175
|
+
double a0;
|
|
176
|
+
if (type == Type::highpass) {
|
|
177
|
+
b1 = -1 - calc.cosW0;
|
|
178
|
+
b0 = b2 = (1 + calc.cosW0)*0.5;
|
|
179
|
+
a0 = 1 + alpha;
|
|
180
|
+
a1 = -2*calc.cosW0;
|
|
181
|
+
a2 = 1 - alpha;
|
|
182
|
+
} else if (type == Type::lowpass) {
|
|
183
|
+
b1 = 1 - calc.cosW0;
|
|
184
|
+
b0 = b2 = b1*0.5;
|
|
185
|
+
a0 = 1 + alpha;
|
|
186
|
+
a1 = -2*calc.cosW0;
|
|
187
|
+
a2 = 1 - alpha;
|
|
188
|
+
} else if (type == Type::highShelf) {
|
|
189
|
+
b0 = A*((A+1)+(A-1)*calc.cosW0+sqrtA2alpha);
|
|
190
|
+
b2 = A*((A+1)+(A-1)*calc.cosW0-sqrtA2alpha);
|
|
191
|
+
b1 = -2*A*((A-1)+(A+1)*calc.cosW0);
|
|
192
|
+
a0 = (A+1)-(A-1)*calc.cosW0+sqrtA2alpha;
|
|
193
|
+
a2 = (A+1)-(A-1)*calc.cosW0-sqrtA2alpha;
|
|
194
|
+
a1 = 2*((A-1)-(A+1)*calc.cosW0);
|
|
195
|
+
} else if (type == Type::lowShelf) {
|
|
196
|
+
b0 = A*((A+1)-(A-1)*calc.cosW0+sqrtA2alpha);
|
|
197
|
+
b2 = A*((A+1)-(A-1)*calc.cosW0-sqrtA2alpha);
|
|
198
|
+
b1 = 2*A*((A-1)-(A+1)*calc.cosW0);
|
|
199
|
+
a0 = (A+1)+(A-1)*calc.cosW0+sqrtA2alpha;
|
|
200
|
+
a2 = (A+1)+(A-1)*calc.cosW0-sqrtA2alpha;
|
|
201
|
+
a1 = -2*((A-1)+(A+1)*calc.cosW0);
|
|
202
|
+
} else if (type == Type::bandpass) {
|
|
203
|
+
b0 = alpha;
|
|
204
|
+
b1 = 0;
|
|
205
|
+
b2 = -alpha;
|
|
206
|
+
a0 = 1 + alpha;
|
|
207
|
+
a1 = -2*calc.cosW0;
|
|
208
|
+
a2 = 1 - alpha;
|
|
209
|
+
} else if (type == Type::notch) {
|
|
210
|
+
b0 = 1;
|
|
211
|
+
b1 = -2*calc.cosW0;
|
|
212
|
+
b2 = 1;
|
|
213
|
+
a0 = 1 + alpha;
|
|
214
|
+
a1 = b1;
|
|
215
|
+
a2 = 1 - alpha;
|
|
216
|
+
} else if (type == Type::peak) {
|
|
217
|
+
b0 = 1 + alpha*A;
|
|
218
|
+
b1 = -2*calc.cosW0;
|
|
219
|
+
b2 = 1 - alpha*A;
|
|
220
|
+
a0 = 1 + alpha/A;
|
|
221
|
+
a1 = b1;
|
|
222
|
+
a2 = 1 - alpha/A;
|
|
223
|
+
} else if (type == Type::allpass) {
|
|
224
|
+
a0 = b2 = 1 + alpha;
|
|
225
|
+
a1 = b1 = -2*calc.cosW0;
|
|
226
|
+
a2 = b0 = 1 - alpha;
|
|
227
|
+
} else {
|
|
228
|
+
// reset to neutral
|
|
229
|
+
a1 = a2 = b1 = b2 = 0;
|
|
230
|
+
a0 = b0 = 1;
|
|
231
|
+
}
|
|
232
|
+
double invA0 = 1/a0;
|
|
233
|
+
b0 *= invA0;
|
|
234
|
+
b1 *= invA0;
|
|
235
|
+
b2 *= invA0;
|
|
236
|
+
a1 *= invA0;
|
|
237
|
+
a2 *= invA0;
|
|
238
|
+
return *this;
|
|
239
|
+
}
|
|
240
|
+
public:
|
|
241
|
+
static constexpr double defaultQ = 0.7071067811865476; // sqrt(0.5)
|
|
242
|
+
static constexpr double defaultBandwidth = 1.8999686269529916; // equivalent to above Q
|
|
243
|
+
|
|
244
|
+
Sample operator ()(Sample x0) {
|
|
245
|
+
Sample y0 = x0*b0 + x1*b1 + x2*b2 - y1*a1 - y2*a2;
|
|
246
|
+
y2 = y1;
|
|
247
|
+
y1 = y0;
|
|
248
|
+
x2 = x1;
|
|
249
|
+
x1 = x0;
|
|
250
|
+
return y0;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
void reset() {
|
|
254
|
+
x1 = x2 = y1 = y2 = 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
std::complex<Sample> response(Sample scaledFreq) const {
|
|
258
|
+
Sample w = scaledFreq*Sample(2*M_PI);
|
|
259
|
+
std::complex<Sample> invZ = {std::cos(w), -std::sin(w)}, invZ2 = invZ*invZ;
|
|
260
|
+
return (b0 + invZ*b1 + invZ2*b2)/(Sample(1) + invZ*a1 + invZ2*a2);
|
|
261
|
+
}
|
|
262
|
+
Sample responseDb(Sample scaledFreq) const {
|
|
263
|
+
Sample w = scaledFreq*Sample(2*M_PI);
|
|
264
|
+
std::complex<Sample> invZ = {std::cos(w), -std::sin(w)}, invZ2 = invZ*invZ;
|
|
265
|
+
Sample energy = std::norm(b0 + invZ*b1 + invZ2*b2)/std::norm(Sample(1) + invZ*a1 + invZ2*a2);
|
|
266
|
+
return 10*std::log10(energy);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/// @name Lowpass
|
|
270
|
+
/// @{
|
|
271
|
+
BiquadStatic & lowpass(double scaledFreq, double octaves=defaultBandwidth, BiquadDesign design=BiquadDesign::bilinear) {
|
|
272
|
+
return configure(Type::lowpass, octaveSpec(scaledFreq, octaves, design), 0, design);
|
|
273
|
+
}
|
|
274
|
+
BiquadStatic & lowpassQ(double scaledFreq, double q, BiquadDesign design=BiquadDesign::bilinear) {
|
|
275
|
+
return configure(Type::lowpass, qSpec(scaledFreq, q, design), 0, design);
|
|
276
|
+
}
|
|
277
|
+
/// @deprecated use `BiquadDesign` instead
|
|
278
|
+
void lowpass(double scaledFreq, double octaves, bool correctBandwidth) {
|
|
279
|
+
lowpass(scaledFreq, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
280
|
+
}
|
|
281
|
+
/// @deprecated By the time you care about `design`, you should care about the bandwidth
|
|
282
|
+
BiquadStatic & lowpass(double scaledFreq, BiquadDesign design) {
|
|
283
|
+
return lowpass(scaledFreq, defaultBandwidth, design);
|
|
284
|
+
}
|
|
285
|
+
/// @}
|
|
286
|
+
|
|
287
|
+
/// @name Highpass
|
|
288
|
+
/// @{
|
|
289
|
+
BiquadStatic & highpass(double scaledFreq, double octaves=defaultBandwidth, BiquadDesign design=BiquadDesign::bilinear) {
|
|
290
|
+
return configure(Type::highpass, octaveSpec(scaledFreq, octaves, design), 0, design);
|
|
291
|
+
}
|
|
292
|
+
BiquadStatic & highpassQ(double scaledFreq, double q, BiquadDesign design=BiquadDesign::bilinear) {
|
|
293
|
+
return configure(Type::highpass, qSpec(scaledFreq, q, design), 0, design);
|
|
294
|
+
}
|
|
295
|
+
/// @deprecated use `BiquadDesign` instead
|
|
296
|
+
void highpass(double scaledFreq, double octaves, bool correctBandwidth) {
|
|
297
|
+
highpass(scaledFreq, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
298
|
+
}
|
|
299
|
+
/// @deprecated By the time you care about `design`, you should care about the bandwidth
|
|
300
|
+
BiquadStatic & highpass(double scaledFreq, BiquadDesign design) {
|
|
301
|
+
return highpass(scaledFreq, defaultBandwidth, design);
|
|
302
|
+
}
|
|
303
|
+
/// @}
|
|
304
|
+
|
|
305
|
+
/// @name Bandpass
|
|
306
|
+
/// @{
|
|
307
|
+
BiquadStatic & bandpass(double scaledFreq, double octaves=defaultBandwidth, BiquadDesign design=bwDesign) {
|
|
308
|
+
return configure(Type::bandpass, octaveSpec(scaledFreq, octaves, design), 0, design);
|
|
309
|
+
}
|
|
310
|
+
BiquadStatic & bandpassQ(double scaledFreq, double q, BiquadDesign design=bwDesign) {
|
|
311
|
+
return configure(Type::bandpass, qSpec(scaledFreq, q, design), 0, design);
|
|
312
|
+
}
|
|
313
|
+
/// @deprecated use `BiquadDesign` instead
|
|
314
|
+
void bandpass(double scaledFreq, double octaves, bool correctBandwidth) {
|
|
315
|
+
bandpass(scaledFreq, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
316
|
+
}
|
|
317
|
+
/// @deprecated By the time you care about `design`, you should care about the bandwidth
|
|
318
|
+
BiquadStatic & bandpass(double scaledFreq, BiquadDesign design) {
|
|
319
|
+
return bandpass(scaledFreq, defaultBandwidth, design);
|
|
320
|
+
}
|
|
321
|
+
/// @}
|
|
322
|
+
|
|
323
|
+
/// @name Notch
|
|
324
|
+
/// @{
|
|
325
|
+
BiquadStatic & notch(double scaledFreq, double octaves=defaultBandwidth, BiquadDesign design=bwDesign) {
|
|
326
|
+
return configure(Type::notch, octaveSpec(scaledFreq, octaves, design), 0, design);
|
|
327
|
+
}
|
|
328
|
+
BiquadStatic & notchQ(double scaledFreq, double q, BiquadDesign design=bwDesign) {
|
|
329
|
+
return configure(Type::notch, qSpec(scaledFreq, q, design), 0, design);
|
|
330
|
+
}
|
|
331
|
+
/// @deprecated use `BiquadDesign` instead
|
|
332
|
+
void notch(double scaledFreq, double octaves, bool correctBandwidth) {
|
|
333
|
+
notch(scaledFreq, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
334
|
+
}
|
|
335
|
+
/// @deprecated By the time you care about `design`, you should care about the bandwidth
|
|
336
|
+
BiquadStatic & notch(double scaledFreq, BiquadDesign design) {
|
|
337
|
+
return notch(scaledFreq, defaultBandwidth, design);
|
|
338
|
+
}
|
|
339
|
+
/// @deprecated alias for `.notch()`
|
|
340
|
+
void bandStop(double scaledFreq, double octaves=1, bool correctBandwidth=true) {
|
|
341
|
+
notch(scaledFreq, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
342
|
+
}
|
|
343
|
+
/// @}
|
|
344
|
+
|
|
345
|
+
/// @name Peak
|
|
346
|
+
/// @{
|
|
347
|
+
BiquadStatic & peak(double scaledFreq, double gain, double octaves=1, BiquadDesign design=bwDesign) {
|
|
348
|
+
return configure(Type::peak, octaveSpec(scaledFreq, octaves, design), std::sqrt(gain), design);
|
|
349
|
+
}
|
|
350
|
+
BiquadStatic & peakDb(double scaledFreq, double db, double octaves=1, BiquadDesign design=bwDesign) {
|
|
351
|
+
return configure(Type::peak, octaveSpec(scaledFreq, octaves, design), dbToSqrtGain(db), design);
|
|
352
|
+
}
|
|
353
|
+
BiquadStatic & peakQ(double scaledFreq, double gain, double q, BiquadDesign design=bwDesign) {
|
|
354
|
+
return configure(Type::peak, qSpec(scaledFreq, q, design), std::sqrt(gain), design);
|
|
355
|
+
}
|
|
356
|
+
BiquadStatic & peakDbQ(double scaledFreq, double db, double q, BiquadDesign design=bwDesign) {
|
|
357
|
+
return configure(Type::peak, qSpec(scaledFreq, q, design), dbToSqrtGain(db), design);
|
|
358
|
+
}
|
|
359
|
+
/// @deprecated By the time you care about `design`, you should care about the bandwidth
|
|
360
|
+
BiquadStatic & peak(double scaledFreq, double gain, BiquadDesign design) {
|
|
361
|
+
return peak(scaledFreq, gain, 1, design);
|
|
362
|
+
}
|
|
363
|
+
/// @}
|
|
364
|
+
|
|
365
|
+
/// @name High shelf
|
|
366
|
+
/// @{
|
|
367
|
+
BiquadStatic & highShelf(double scaledFreq, double gain, double octaves=defaultBandwidth, BiquadDesign design=bwDesign) {
|
|
368
|
+
return configure(Type::highShelf, octaveSpec(scaledFreq, octaves, design), std::sqrt(gain), design);
|
|
369
|
+
}
|
|
370
|
+
BiquadStatic & highShelfDb(double scaledFreq, double db, double octaves=defaultBandwidth, BiquadDesign design=bwDesign) {
|
|
371
|
+
return configure(Type::highShelf, octaveSpec(scaledFreq, octaves, design), dbToSqrtGain(db), design);
|
|
372
|
+
}
|
|
373
|
+
BiquadStatic & highShelfQ(double scaledFreq, double gain, double q, BiquadDesign design=bwDesign) {
|
|
374
|
+
return configure(Type::highShelf, qSpec(scaledFreq, q, design), std::sqrt(gain), design);
|
|
375
|
+
}
|
|
376
|
+
BiquadStatic & highShelfDbQ(double scaledFreq, double db, double q, BiquadDesign design=bwDesign) {
|
|
377
|
+
return configure(Type::highShelf, qSpec(scaledFreq, q, design), dbToSqrtGain(db), design);
|
|
378
|
+
}
|
|
379
|
+
/// @deprecated use `BiquadDesign` instead
|
|
380
|
+
BiquadStatic & highShelf(double scaledFreq, double gain, double octaves, bool correctBandwidth) {
|
|
381
|
+
return highShelf(scaledFreq, gain, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
382
|
+
}
|
|
383
|
+
/// @deprecated use `BiquadDesign` instead
|
|
384
|
+
BiquadStatic & highShelfDb(double scaledFreq, double db, double octaves, bool correctBandwidth) {
|
|
385
|
+
return highShelfDb(scaledFreq, db, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
386
|
+
}
|
|
387
|
+
/// @}
|
|
388
|
+
|
|
389
|
+
/// @name Low shelf
|
|
390
|
+
/// @{
|
|
391
|
+
BiquadStatic & lowShelf(double scaledFreq, double gain, double octaves=2, BiquadDesign design=bwDesign) {
|
|
392
|
+
return configure(Type::lowShelf, octaveSpec(scaledFreq, octaves, design), std::sqrt(gain), design);
|
|
393
|
+
}
|
|
394
|
+
BiquadStatic & lowShelfDb(double scaledFreq, double db, double octaves=2, BiquadDesign design=bwDesign) {
|
|
395
|
+
return configure(Type::lowShelf, octaveSpec(scaledFreq, octaves, design), dbToSqrtGain(db), design);
|
|
396
|
+
}
|
|
397
|
+
BiquadStatic & lowShelfQ(double scaledFreq, double gain, double q, BiquadDesign design=bwDesign) {
|
|
398
|
+
return configure(Type::lowShelf, qSpec(scaledFreq, q, design), std::sqrt(gain), design);
|
|
399
|
+
}
|
|
400
|
+
BiquadStatic & lowShelfDbQ(double scaledFreq, double db, double q, BiquadDesign design=bwDesign) {
|
|
401
|
+
return configure(Type::lowShelf, qSpec(scaledFreq, q, design), dbToSqrtGain(db), design);
|
|
402
|
+
}
|
|
403
|
+
/// @deprecated use `BiquadDesign` instead
|
|
404
|
+
BiquadStatic & lowShelf(double scaledFreq, double gain, double octaves, bool correctBandwidth) {
|
|
405
|
+
return lowShelf(scaledFreq, gain, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
406
|
+
}
|
|
407
|
+
/// @deprecated use `BiquadDesign` instead
|
|
408
|
+
BiquadStatic & lowShelfDb(double scaledFreq, double db, double octaves, bool correctBandwidth) {
|
|
409
|
+
return lowShelfDb(scaledFreq, db, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
410
|
+
}
|
|
411
|
+
/// @}
|
|
412
|
+
|
|
413
|
+
/// @name Allpass
|
|
414
|
+
/// @{
|
|
415
|
+
BiquadStatic & allpass(double scaledFreq, double octaves=1, BiquadDesign design=bwDesign) {
|
|
416
|
+
return configure(Type::allpass, octaveSpec(scaledFreq, octaves, design), 0, design);
|
|
417
|
+
}
|
|
418
|
+
BiquadStatic & allpassQ(double scaledFreq, double q, BiquadDesign design=bwDesign) {
|
|
419
|
+
return configure(Type::allpass, qSpec(scaledFreq, q, design), 0, design);
|
|
420
|
+
}
|
|
421
|
+
/// @}
|
|
422
|
+
|
|
423
|
+
BiquadStatic & addGain(double factor) {
|
|
424
|
+
b0 *= factor;
|
|
425
|
+
b1 *= factor;
|
|
426
|
+
b2 *= factor;
|
|
427
|
+
return *this;
|
|
428
|
+
}
|
|
429
|
+
BiquadStatic & addGainDb(double db) {
|
|
430
|
+
return addGain(std::pow(10, db*0.05));
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
/** @} */
|
|
435
|
+
}} // signalsmith::filters::
|
|
436
|
+
#endif // include guard
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#include "./common.h"
|
|
2
|
+
|
|
3
|
+
#ifndef SIGNALSMITH_DSP_MULTI_CHANNEL_H
|
|
4
|
+
#define SIGNALSMITH_DSP_MULTI_CHANNEL_H
|
|
5
|
+
|
|
6
|
+
#include <array>
|
|
7
|
+
|
|
8
|
+
namespace signalsmith {
|
|
9
|
+
namespace mix {
|
|
10
|
+
/** @defgroup Mix Multichannel mixing
|
|
11
|
+
@brief Utilities for stereo/multichannel mixing operations
|
|
12
|
+
|
|
13
|
+
@{
|
|
14
|
+
@file
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** @defgroup Matrices Orthogonal matrices
|
|
18
|
+
@brief Some common matrices used for audio
|
|
19
|
+
@ingroup Mix
|
|
20
|
+
@{ */
|
|
21
|
+
|
|
22
|
+
/// @brief Hadamard: high mixing levels, N log(N) operations
|
|
23
|
+
template<typename Sample, int size=-1>
|
|
24
|
+
class Hadamard {
|
|
25
|
+
public:
|
|
26
|
+
static_assert(size >= 0, "Size must be positive (or -1 for dynamic)");
|
|
27
|
+
/// Applies the matrix, scaled so it's orthogonal
|
|
28
|
+
template<class Data>
|
|
29
|
+
static void inPlace(Data &&data) {
|
|
30
|
+
unscaledInPlace(data);
|
|
31
|
+
|
|
32
|
+
Sample factor = scalingFactor();
|
|
33
|
+
for (int c = 0; c < size; ++c) {
|
|
34
|
+
data[c] *= factor;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Scaling factor applied to make it orthogonal
|
|
39
|
+
static Sample scalingFactor() {
|
|
40
|
+
/// TODO: test for C++20, or whatever makes this constexpr. Maybe a `#define` in `common.h`?
|
|
41
|
+
return std::sqrt(Sample(1)/(size ? size : 1));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Skips the scaling, so it's a matrix full of `1`s
|
|
45
|
+
template<class Data, int startIndex=0>
|
|
46
|
+
static void unscaledInPlace(Data &&data) {
|
|
47
|
+
if (size <= 1) return;
|
|
48
|
+
constexpr int hSize = size/2;
|
|
49
|
+
|
|
50
|
+
Hadamard<Sample, hSize>::template unscaledInPlace<Data, startIndex>(data);
|
|
51
|
+
Hadamard<Sample, hSize>::template unscaledInPlace<Data, startIndex + hSize>(data);
|
|
52
|
+
|
|
53
|
+
for (int i = 0; i < hSize; ++i) {
|
|
54
|
+
Sample a = data[i + startIndex], b = data[i + startIndex + hSize];
|
|
55
|
+
data[i + startIndex] = (a + b);
|
|
56
|
+
data[i + startIndex + hSize] = (a - b);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
/// @brief Hadamard with dynamic size
|
|
61
|
+
template<typename Sample>
|
|
62
|
+
class Hadamard<Sample, -1> {
|
|
63
|
+
int size;
|
|
64
|
+
public:
|
|
65
|
+
Hadamard(int size) : size(size) {}
|
|
66
|
+
|
|
67
|
+
/// Applies the matrix, scaled so it's orthogonal
|
|
68
|
+
template<class Data>
|
|
69
|
+
void inPlace(Data &&data) const {
|
|
70
|
+
unscaledInPlace(data);
|
|
71
|
+
|
|
72
|
+
Sample factor = scalingFactor();
|
|
73
|
+
for (int c = 0; c < size; ++c) {
|
|
74
|
+
data[c] *= factor;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// Scaling factor applied to make it orthogonal
|
|
79
|
+
Sample scalingFactor() const {
|
|
80
|
+
return std::sqrt(Sample(1)/(size ? size : 1));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Skips the scaling, so it's a matrix full of `1`s
|
|
84
|
+
template<class Data>
|
|
85
|
+
void unscaledInPlace(Data &&data) const {
|
|
86
|
+
int hSize = size/2;
|
|
87
|
+
while (hSize > 0) {
|
|
88
|
+
for (int startIndex = 0; startIndex < size; startIndex += hSize*2) {
|
|
89
|
+
for (int i = startIndex; i < startIndex + hSize; ++i) {
|
|
90
|
+
Sample a = data[i], b = data[i + hSize];
|
|
91
|
+
data[i] = (a + b);
|
|
92
|
+
data[i + hSize] = (a - b);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
hSize /= 2;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
/// @brief Householder: moderate mixing, 2N operations
|
|
100
|
+
template<typename Sample, int size=-1>
|
|
101
|
+
class Householder {
|
|
102
|
+
public:
|
|
103
|
+
static_assert(size >= 0, "Size must be positive (or -1 for dynamic)");
|
|
104
|
+
template<class Data>
|
|
105
|
+
static void inPlace(Data &&data) {
|
|
106
|
+
if (size < 1) return;
|
|
107
|
+
/// TODO: test for C++20, which makes `std::complex::operator/` constexpr
|
|
108
|
+
const Sample factor = Sample(-2)/Sample(size ? size : 1);
|
|
109
|
+
|
|
110
|
+
Sample sum = data[0];
|
|
111
|
+
for (int i = 1; i < size; ++i) {
|
|
112
|
+
sum += data[i];
|
|
113
|
+
}
|
|
114
|
+
sum *= factor;
|
|
115
|
+
for (int i = 0; i < size; ++i) {
|
|
116
|
+
data[i] += sum;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/// @deprecated The matrix is already orthogonal, but this is here for compatibility with Hadamard
|
|
120
|
+
constexpr static Sample scalingFactor() {
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
/// @brief Householder with dynamic size
|
|
125
|
+
template<typename Sample>
|
|
126
|
+
class Householder<Sample, -1> {
|
|
127
|
+
int size;
|
|
128
|
+
public:
|
|
129
|
+
Householder(int size) : size(size) {}
|
|
130
|
+
|
|
131
|
+
template<class Data>
|
|
132
|
+
void inPlace(Data &&data) const {
|
|
133
|
+
if (size < 1) return;
|
|
134
|
+
const Sample factor = Sample(-2)/Sample(size ? size : 1);
|
|
135
|
+
|
|
136
|
+
Sample sum = data[0];
|
|
137
|
+
for (int i = 1; i < size; ++i) {
|
|
138
|
+
sum += data[i];
|
|
139
|
+
}
|
|
140
|
+
sum *= factor;
|
|
141
|
+
for (int i = 0; i < size; ++i) {
|
|
142
|
+
data[i] += sum;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/// @deprecated The matrix is already orthogonal, but this is here for compatibility with Hadamard
|
|
146
|
+
constexpr static Sample scalingFactor() {
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
/// @}
|
|
151
|
+
|
|
152
|
+
/** @brief Upmix/downmix a stereo signal to an (even) multi-channel signal
|
|
153
|
+
|
|
154
|
+
When spreading out, it rotates the input by various amounts (e.g. a four-channel signal would produce `(left, right, mid side)`), such that energy is preserved for each pair.
|
|
155
|
+
|
|
156
|
+
When mixing together, it uses the opposite rotations, such that upmix → downmix produces the same stereo signal (when scaled by `.scalingFactor1()`.
|
|
157
|
+
*/
|
|
158
|
+
template<typename Sample, int channels>
|
|
159
|
+
class StereoMultiMixer {
|
|
160
|
+
static_assert((channels/2)*2 == channels, "StereoMultiMixer must have an even number of channels");
|
|
161
|
+
static_assert(channels > 0, "StereoMultiMixer must have a positive number of channels");
|
|
162
|
+
static constexpr int hChannels = channels/2;
|
|
163
|
+
std::array<Sample, channels> coeffs;
|
|
164
|
+
public:
|
|
165
|
+
StereoMultiMixer() {
|
|
166
|
+
coeffs[0] = 1;
|
|
167
|
+
coeffs[1] = 0;
|
|
168
|
+
for (int i = 1; i < hChannels; ++i) {
|
|
169
|
+
double phase = M_PI*i/channels;
|
|
170
|
+
coeffs[2*i] = std::cos(phase);
|
|
171
|
+
coeffs[2*i + 1] = std::sin(phase);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
template<class In, class Out>
|
|
176
|
+
void stereoToMulti(In &input, Out &output) const {
|
|
177
|
+
output[0] = input[0];
|
|
178
|
+
output[1] = input[1];
|
|
179
|
+
for (int i = 2; i < channels; i += 2) {
|
|
180
|
+
output[i] = input[0]*coeffs[i] + input[1]*coeffs[i + 1];
|
|
181
|
+
output[i + 1] = input[1]*coeffs[i] - input[0]*coeffs[i + 1];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
template<class In, class Out>
|
|
185
|
+
void multiToStereo(In &input, Out &output) const {
|
|
186
|
+
output[0] = input[0];
|
|
187
|
+
output[1] = input[1];
|
|
188
|
+
for (int i = 2; i < channels; i += 2) {
|
|
189
|
+
output[0] += input[i]*coeffs[i] - input[i + 1]*coeffs[i + 1];
|
|
190
|
+
output[1] += input[i + 1]*coeffs[i] + input[i]*coeffs[i + 1];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/// Scaling factor for the downmix, if channels are phase-aligned
|
|
194
|
+
static constexpr Sample scalingFactor1() {
|
|
195
|
+
return 2/Sample(channels);
|
|
196
|
+
}
|
|
197
|
+
/// Scaling factor for the downmix, if channels are independent
|
|
198
|
+
static Sample scalingFactor2() {
|
|
199
|
+
return std::sqrt(scalingFactor1());
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/// A cheap (polynomial) almost-energy-preserving crossfade
|
|
204
|
+
/// Maximum energy error: 1.06%, average 0.64%, curves overshoot by 0.3%
|
|
205
|
+
/// See: http://signalsmith-audio.co.uk/writing/2021/cheap-energy-crossfade/
|
|
206
|
+
template<typename Sample, typename Result>
|
|
207
|
+
void cheapEnergyCrossfade(Sample x, Result &toCoeff, Result &fromCoeff) {
|
|
208
|
+
Sample x2 = 1 - x;
|
|
209
|
+
// Other powers p can be approximated by: k = -6.0026608 + p*(6.8773512 - 1.5838104*p)
|
|
210
|
+
Sample A = x*x2, B = A*(1 + (Sample)1.4186*A);
|
|
211
|
+
Sample C = (B + x), D = (B + x2);
|
|
212
|
+
toCoeff = C*C;
|
|
213
|
+
fromCoeff = D*D;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** @} */
|
|
217
|
+
}} // signalsmith::delay::
|
|
218
|
+
#endif // include guard
|