react-native-audio-api 0.4.12-beta.1 → 0.4.12-beta.2
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/RNAudioAPI.podspec +11 -1
- package/android/CMakeLists.txt +9 -48
- package/android/src/main/cpp/audioapi/CMakeLists.txt +44 -0
- package/android/src/main/cpp/{AudioAPIModule.cpp → audioapi/android/AudioAPIModule.cpp} +1 -4
- package/android/src/main/cpp/{AudioAPIModule.h → audioapi/android/AudioAPIModule.h} +2 -1
- package/android/src/main/cpp/{OnLoad.cpp → audioapi/android/OnLoad.cpp} +2 -1
- package/android/src/main/cpp/{core → audioapi/android/core}/AudioDecoder.cpp +5 -5
- package/android/src/main/cpp/{core → audioapi/android/core}/AudioPlayer.cpp +5 -7
- package/android/src/main/cpp/{core → audioapi/android/core}/AudioPlayer.h +1 -0
- package/android/src/main/cpp/{libs → audioapi/android/libs}/pffft.c +2 -2
- package/android/src/main/java/com/swmansion/audioapi/AudioAPIPackage.kt +1 -0
- package/common/cpp/{installer → audioapi}/AudioAPIModuleInstaller.h +5 -3
- package/common/cpp/{HostObjects → audioapi/HostObjects}/AnalyserNodeHostObject.h +3 -3
- package/common/cpp/{HostObjects → audioapi/HostObjects}/AudioBufferHostObject.h +3 -3
- package/common/cpp/{HostObjects → audioapi/HostObjects}/AudioBufferSourceNodeHostObject.h +5 -5
- package/common/cpp/{HostObjects → audioapi/HostObjects}/AudioContextHostObject.h +3 -3
- package/common/cpp/{HostObjects → audioapi/HostObjects}/AudioDestinationNodeHostObject.h +3 -3
- package/common/cpp/{HostObjects → audioapi/HostObjects}/AudioNodeHostObject.h +3 -3
- package/common/cpp/{HostObjects → audioapi/HostObjects}/AudioParamHostObject.h +3 -3
- package/common/cpp/{HostObjects → audioapi/HostObjects}/AudioScheduledSourceNodeHostObject.h +3 -3
- package/common/cpp/{HostObjects → audioapi/HostObjects}/BaseAudioContextHostObject.h +14 -15
- package/common/cpp/{HostObjects → audioapi/HostObjects}/BiquadFilterNodeHostObject.h +4 -4
- package/common/cpp/{HostObjects → audioapi/HostObjects}/GainNodeHostObject.h +4 -4
- package/common/cpp/{HostObjects → audioapi/HostObjects}/OscillatorNodeHostObject.h +5 -5
- package/common/cpp/{HostObjects → audioapi/HostObjects}/PeriodicWaveHostObject.h +2 -2
- package/common/cpp/{HostObjects → audioapi/HostObjects}/StereoPannerNodeHostObject.h +4 -4
- package/common/cpp/audioapi/HostObjects/StretcherNodeHostObject.h +35 -0
- package/common/cpp/{core → audioapi/core}/AudioContext.cpp +6 -6
- package/common/cpp/{core → audioapi/core}/AudioContext.h +2 -2
- package/common/cpp/{core → audioapi/core}/AudioNode.cpp +5 -7
- package/common/cpp/{core → audioapi/core}/AudioNode.h +5 -4
- package/common/cpp/{core → audioapi/core}/AudioParam.cpp +3 -4
- package/common/cpp/{core → audioapi/core}/AudioParam.h +3 -3
- package/common/cpp/{core → audioapi/core}/BaseAudioContext.cpp +14 -17
- package/common/cpp/{core → audioapi/core}/BaseAudioContext.h +4 -3
- package/common/cpp/{core → audioapi/core/analysis}/AnalyserNode.cpp +6 -10
- package/common/cpp/{core → audioapi/core/analysis}/AnalyserNode.h +4 -3
- package/common/cpp/{core → audioapi/core/destinations}/AudioDestinationNode.cpp +5 -5
- package/common/cpp/{core → audioapi/core/destinations}/AudioDestinationNode.h +2 -2
- package/common/cpp/{core → audioapi/core/effects}/BiquadFilterNode.cpp +4 -4
- package/common/cpp/{core → audioapi/core/effects}/BiquadFilterNode.h +4 -4
- package/common/cpp/{core → audioapi/core/effects}/GainNode.cpp +4 -4
- package/common/cpp/{core → audioapi/core/effects}/GainNode.h +3 -3
- package/common/cpp/{core → audioapi/core/effects}/PeriodicWave.cpp +4 -2
- package/common/cpp/{core → audioapi/core/effects}/PeriodicWave.h +2 -4
- package/common/cpp/{core → audioapi/core/effects}/StereoPannerNode.cpp +5 -7
- package/common/cpp/{core → audioapi/core/effects}/StereoPannerNode.h +4 -3
- package/common/cpp/{core → audioapi/core/effects}/StretcherNode.cpp +4 -6
- package/common/cpp/audioapi/core/effects/StretcherNode.h +35 -0
- package/common/cpp/{core → audioapi/core/sources}/AudioBuffer.cpp +3 -3
- package/common/cpp/{core → audioapi/core/sources}/AudioBufferSourceNode.cpp +8 -11
- package/common/cpp/{core → audioapi/core/sources}/AudioBufferSourceNode.h +5 -3
- package/common/cpp/{core → audioapi/core/sources}/AudioScheduledSourceNode.cpp +6 -8
- package/common/cpp/{core → audioapi/core/sources}/AudioScheduledSourceNode.h +3 -2
- package/common/cpp/{core → audioapi/core/sources}/OscillatorNode.cpp +4 -4
- package/common/cpp/{core → audioapi/core/sources}/OscillatorNode.h +5 -5
- package/common/cpp/{core → audioapi/core/utils}/AudioArray.cpp +2 -4
- package/common/cpp/{core → audioapi/core/utils}/AudioBus.cpp +4 -7
- package/common/cpp/{core → audioapi/core/utils}/AudioBus.h +2 -2
- package/common/cpp/{core → audioapi/core/utils}/AudioNodeManager.cpp +3 -4
- package/common/cpp/{core → audioapi/core/utils}/ParamChangeEvent.cpp +1 -3
- package/common/cpp/{core → audioapi/core/utils}/ParamChangeEvent.h +3 -1
- package/common/cpp/{utils → audioapi/dsp}/AudioUtils.cpp +1 -1
- package/common/cpp/{utils → audioapi/dsp}/FFTFrame.cpp +1 -10
- package/common/cpp/{utils → audioapi/dsp}/FFTFrame.h +4 -3
- package/common/cpp/{utils → audioapi/dsp}/VectorMath.cpp +2 -5
- package/common/cpp/{utils → audioapi/dsp}/VectorMath.h +2 -0
- package/common/cpp/{jsi → audioapi/jsi}/JsiHostObject.cpp +1 -1
- package/common/cpp/{jsi → audioapi/jsi}/JsiHostObject.h +2 -2
- package/common/cpp/{jsi → audioapi/jsi}/JsiPromise.cpp +2 -6
- package/common/cpp/{jsi → audioapi/jsi}/RuntimeAwareCache.h +2 -3
- package/common/cpp/{jsi → audioapi/jsi}/RuntimeLifecycleMonitor.cpp +1 -5
- package/common/cpp/{jsi → audioapi/jsi}/RuntimeLifecycleMonitor.h +3 -0
- package/common/cpp/{libs → audioapi/libs}/dsp/delay.h +35 -35
- package/common/cpp/{libs → audioapi/libs}/dsp/fft.h +21 -21
- package/common/cpp/{libs → audioapi/libs}/dsp/perf.h +3 -3
- package/common/cpp/{libs → audioapi/libs}/dsp/spectral.h +39 -39
- package/common/cpp/{libs → audioapi/libs}/dsp/windows.h +11 -11
- package/common/cpp/{libs → audioapi/libs}/signalsmith-stretch.h +30 -30
- package/ios/{AudioAPIModule.mm → audioapi/ios/AudioAPIModule.mm} +1 -1
- package/ios/{core → audioapi/ios/core}/AudioDecoder.mm +4 -4
- package/ios/{core → audioapi/ios/core}/AudioPlayer.m +1 -1
- package/ios/{core → audioapi/ios/core}/IOSAudioPlayer.mm +4 -4
- package/package.json +6 -6
- package/common/cpp/HostObjects/StretcherNodeHostObject.h +0 -35
- package/common/cpp/core/StretcherNode.h +0 -63
- package/common/cpp/libs/dsp/LICENSE.txt +0 -21
- package/common/cpp/libs/dsp/README.md +0 -40
- package/common/cpp/libs/dsp/curves.h +0 -371
- package/common/cpp/libs/dsp/envelopes.h +0 -523
- package/common/cpp/libs/dsp/filters.h +0 -436
- package/common/cpp/libs/dsp/mix.h +0 -218
- package/common/cpp/libs/dsp/rates.h +0 -184
- package/common/cpp/types/TimeStretchType.h +0 -6
- /package/android/src/main/cpp/{libs → audioapi/android/libs}/pffft.h +0 -0
- /package/common/cpp/{core → audioapi/core}/Constants.h +0 -0
- /package/common/cpp/{core → audioapi/core/sources}/AudioBuffer.h +0 -0
- /package/common/cpp/{types → audioapi/core/types}/BiquadFilterType.h +0 -0
- /package/common/cpp/{types → audioapi/core/types}/ChannelCountMode.h +0 -0
- /package/common/cpp/{types → audioapi/core/types}/ChannelInterpretation.h +0 -0
- /package/common/cpp/{types → audioapi/core/types}/ContextState.h +0 -0
- /package/common/cpp/{types → audioapi/core/types}/OscillatorType.h +0 -0
- /package/common/cpp/{types → audioapi/core/types}/ParamChangeEventType.h +0 -0
- /package/common/cpp/{core → audioapi/core/utils}/AudioArray.h +0 -0
- /package/common/cpp/{core → audioapi/core/utils}/AudioDecoder.h +0 -0
- /package/common/cpp/{core → audioapi/core/utils}/AudioNodeManager.h +0 -0
- /package/common/cpp/{utils → audioapi/core/utils}/Locker.h +0 -0
- /package/common/cpp/{utils → audioapi/dsp}/AudioUtils.h +0 -0
- /package/common/cpp/{jsi → audioapi/jsi}/JsiPromise.h +0 -0
- /package/common/cpp/{libs → audioapi/libs}/dsp/common.h +0 -0
- /package/common/cpp/{libs → audioapi/libs}/miniaudio.h +0 -0
- /package/ios/{AudioAPIModule.h → audioapi/ios/AudioAPIModule.h} +0 -0
- /package/ios/{core → audioapi/ios/core}/AudioPlayer.h +0 -0
- /package/ios/{core → audioapi/ios/core}/IOSAudioPlayer.h +0 -0
|
@@ -1,523 +0,0 @@
|
|
|
1
|
-
#include "./common.h"
|
|
2
|
-
|
|
3
|
-
#ifndef SIGNALSMITH_DSP_ENVELOPES_H
|
|
4
|
-
#define SIGNALSMITH_DSP_ENVELOPES_H
|
|
5
|
-
|
|
6
|
-
#include <cmath>
|
|
7
|
-
#include <random>
|
|
8
|
-
#include <vector>
|
|
9
|
-
#include <iterator>
|
|
10
|
-
|
|
11
|
-
namespace signalsmith {
|
|
12
|
-
namespace envelopes {
|
|
13
|
-
/** @defgroup Envelopes Envelopes and LFOs
|
|
14
|
-
@brief LFOs, envelopes and filters for manipulating them
|
|
15
|
-
|
|
16
|
-
@{
|
|
17
|
-
@file
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/** An LFO based on cubic segments.
|
|
21
|
-
You can randomise the rate and/or the depth. Randomising the depth past `0.5` means it no longer neatly alternates sides:
|
|
22
|
-
\diagram{cubic-lfo-example.svg,Some example LFO curves.}
|
|
23
|
-
Without randomisation, it is approximately sine-like:
|
|
24
|
-
\diagram{cubic-lfo-spectrum-pure.svg}
|
|
25
|
-
*/
|
|
26
|
-
class CubicLfo {
|
|
27
|
-
float ratio = 0;
|
|
28
|
-
float ratioStep = 0;
|
|
29
|
-
|
|
30
|
-
float valueFrom = 0, valueTo = 1, valueRange = 1;
|
|
31
|
-
float targetLow = 0, targetHigh = 1;
|
|
32
|
-
float targetRate = 0;
|
|
33
|
-
float rateRandom = 0.5, depthRandom = 0;
|
|
34
|
-
bool freshReset = true;
|
|
35
|
-
|
|
36
|
-
std::default_random_engine randomEngine;
|
|
37
|
-
std::uniform_real_distribution<float> randomUnit;
|
|
38
|
-
float random() {
|
|
39
|
-
return randomUnit(randomEngine);
|
|
40
|
-
}
|
|
41
|
-
float randomRate() {
|
|
42
|
-
return targetRate*exp(rateRandom*(random() - 0.5));
|
|
43
|
-
}
|
|
44
|
-
float randomTarget(float previous) {
|
|
45
|
-
float randomOffset = depthRandom*random()*(targetLow - targetHigh);
|
|
46
|
-
if (previous < (targetLow + targetHigh)*0.5f) {
|
|
47
|
-
return targetHigh + randomOffset;
|
|
48
|
-
} else {
|
|
49
|
-
return targetLow - randomOffset;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
public:
|
|
53
|
-
CubicLfo() : randomEngine(std::random_device()()), randomUnit(0, 1) {
|
|
54
|
-
reset();
|
|
55
|
-
}
|
|
56
|
-
CubicLfo(long seed) : randomUnit(0, 1) {
|
|
57
|
-
randomEngine.seed(seed);
|
|
58
|
-
reset();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/// Resets the LFO state, starting with random phase.
|
|
62
|
-
void reset() {
|
|
63
|
-
ratio = random();
|
|
64
|
-
ratioStep = randomRate();
|
|
65
|
-
if (random() < 0.5) {
|
|
66
|
-
valueFrom = targetLow;
|
|
67
|
-
valueTo = targetHigh;
|
|
68
|
-
} else {
|
|
69
|
-
valueFrom = targetHigh;
|
|
70
|
-
valueTo = targetLow;
|
|
71
|
-
}
|
|
72
|
-
valueRange = valueTo - valueFrom;
|
|
73
|
-
freshReset = true;
|
|
74
|
-
}
|
|
75
|
-
/** Smoothly updates the LFO parameters.
|
|
76
|
-
|
|
77
|
-
If called directly after `.reset()`, oscillation will immediately start within the specified range. Otherwise, it will remain smooth and fit within the new range after at most one cycle:
|
|
78
|
-
\diagram{cubic-lfo-changes.svg}
|
|
79
|
-
|
|
80
|
-
The LFO will complete a full oscillation in (approximately) `1/rate` samples. `rateVariation` can be any number, but 0-1 is a good range.
|
|
81
|
-
|
|
82
|
-
`depthVariation` must be in the range [0, 1], where ≤ 0.5 produces random amplitude but still alternates up/down.
|
|
83
|
-
\diagram{cubic-lfo-spectrum.svg,Spectra for the two types of randomisation - note the jump as depth variation goes past 50%}
|
|
84
|
-
*/
|
|
85
|
-
void set(float low, float high, float rate, float rateVariation=0, float depthVariation=0) {
|
|
86
|
-
rate *= 2; // We want to go up and down during this period
|
|
87
|
-
targetRate = rate;
|
|
88
|
-
targetLow = std::min(low, high);
|
|
89
|
-
targetHigh = std::max(low, high);
|
|
90
|
-
rateRandom = rateVariation;
|
|
91
|
-
depthRandom = std::min<float>(1, std::max<float>(0, depthVariation));
|
|
92
|
-
|
|
93
|
-
// If we haven't called .next() yet, don't bother being smooth.
|
|
94
|
-
if (freshReset) return reset();
|
|
95
|
-
|
|
96
|
-
// Only update the current rate if it's outside our new random-variation range
|
|
97
|
-
float maxRandomRatio = exp((float)0.5*rateRandom);
|
|
98
|
-
if (ratioStep > rate*maxRandomRatio || ratioStep < rate/maxRandomRatio) {
|
|
99
|
-
ratioStep = randomRate();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/// Returns the next output sample
|
|
104
|
-
float next() {
|
|
105
|
-
freshReset = false;
|
|
106
|
-
float result = ratio*ratio*(3 - 2*ratio)*valueRange + valueFrom;
|
|
107
|
-
|
|
108
|
-
ratio += ratioStep;
|
|
109
|
-
while (ratio >= 1) {
|
|
110
|
-
ratio -= 1;
|
|
111
|
-
ratioStep = randomRate();
|
|
112
|
-
valueFrom = valueTo;
|
|
113
|
-
valueTo = randomTarget(valueFrom);
|
|
114
|
-
valueRange = valueTo - valueFrom;
|
|
115
|
-
}
|
|
116
|
-
return result;
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
/** Variable-width rectangular sum */
|
|
121
|
-
template<typename Sample=double>
|
|
122
|
-
class BoxSum {
|
|
123
|
-
int bufferLength, index;
|
|
124
|
-
std::vector<Sample> buffer;
|
|
125
|
-
Sample sum = 0, wrapJump = 0;
|
|
126
|
-
public:
|
|
127
|
-
BoxSum(int maxLength) {
|
|
128
|
-
resize(maxLength);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/// Sets the maximum size (and reset contents)
|
|
132
|
-
void resize(int maxLength) {
|
|
133
|
-
bufferLength = maxLength + 1;
|
|
134
|
-
buffer.resize(bufferLength);
|
|
135
|
-
if (maxLength != 0) buffer.shrink_to_fit();
|
|
136
|
-
reset();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/// Resets (with an optional "fill" value)
|
|
140
|
-
void reset(Sample value=Sample()) {
|
|
141
|
-
index = 0;
|
|
142
|
-
sum = 0;
|
|
143
|
-
for (size_t i = 0; i < buffer.size(); ++i) {
|
|
144
|
-
buffer[i] = sum;
|
|
145
|
-
sum += value;
|
|
146
|
-
}
|
|
147
|
-
wrapJump = sum;
|
|
148
|
-
sum = 0;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
Sample read(int width) {
|
|
152
|
-
int readIndex = index - width;
|
|
153
|
-
double result = sum;
|
|
154
|
-
if (readIndex < 0) {
|
|
155
|
-
result += wrapJump;
|
|
156
|
-
readIndex += bufferLength;
|
|
157
|
-
}
|
|
158
|
-
return result - buffer[readIndex];
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
void write(Sample value) {
|
|
162
|
-
++index;
|
|
163
|
-
if (index == bufferLength) {
|
|
164
|
-
index = 0;
|
|
165
|
-
wrapJump = sum;
|
|
166
|
-
sum = 0;
|
|
167
|
-
}
|
|
168
|
-
sum += value;
|
|
169
|
-
buffer[index] = sum;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
Sample readWrite(Sample value, int width) {
|
|
173
|
-
write(value);
|
|
174
|
-
return read(width);
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
/** Rectangular moving average filter (FIR).
|
|
179
|
-
\diagram{box-filter-example.svg}
|
|
180
|
-
A filter of length 1 has order 0 (i.e. does nothing). */
|
|
181
|
-
template<typename Sample=double>
|
|
182
|
-
class BoxFilter {
|
|
183
|
-
BoxSum<Sample> boxSum;
|
|
184
|
-
int _length, _maxLength;
|
|
185
|
-
Sample multiplier;
|
|
186
|
-
public:
|
|
187
|
-
BoxFilter(int maxLength) : boxSum(maxLength) {
|
|
188
|
-
resize(maxLength);
|
|
189
|
-
}
|
|
190
|
-
/// Sets the maximum size (and current size, and resets)
|
|
191
|
-
void resize(int maxLength) {
|
|
192
|
-
_maxLength = maxLength;
|
|
193
|
-
boxSum.resize(maxLength);
|
|
194
|
-
set(maxLength);
|
|
195
|
-
}
|
|
196
|
-
/// Sets the current size (expanding/allocating only if needed)
|
|
197
|
-
void set(int length) {
|
|
198
|
-
_length = length;
|
|
199
|
-
multiplier = Sample(1)/length;
|
|
200
|
-
if (length > _maxLength) resize(length);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/// Resets (with an optional "fill" value)
|
|
204
|
-
void reset(Sample fill=Sample()) {
|
|
205
|
-
boxSum.reset(fill);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
Sample operator()(Sample v) {
|
|
209
|
-
return boxSum.readWrite(v, _length)*multiplier;
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
/** FIR filter made from a stack of `BoxFilter`s.
|
|
214
|
-
This filter has a non-negative impulse (monotonic step response), making it useful for smoothing positive-only values. It provides an optimal set of box-lengths, chosen to minimise peaks in the stop-band:
|
|
215
|
-
\diagram{box-stack-long.svg,Impulse responses for various stack sizes at length N=1000}
|
|
216
|
-
Since the underlying box-averages must have integer width, the peaks are slightly higher for shorter lengths with higher numbers of layers:
|
|
217
|
-
\diagram{box-stack-short-freq.svg,Frequency responses for various stack sizes at length N=30}
|
|
218
|
-
*/
|
|
219
|
-
template<typename Sample=double>
|
|
220
|
-
class BoxStackFilter {
|
|
221
|
-
struct Layer {
|
|
222
|
-
double ratio = 0, lengthError = 0;
|
|
223
|
-
int length = 0;
|
|
224
|
-
BoxFilter<Sample> filter{0};
|
|
225
|
-
Layer() {}
|
|
226
|
-
};
|
|
227
|
-
int _size;
|
|
228
|
-
std::vector<Layer> layers;
|
|
229
|
-
|
|
230
|
-
template<class Iterable>
|
|
231
|
-
void setupLayers(const Iterable &ratios) {
|
|
232
|
-
layers.resize(0);
|
|
233
|
-
double sum = 0;
|
|
234
|
-
for (auto ratio : ratios) {
|
|
235
|
-
Layer layer;
|
|
236
|
-
layer.ratio = ratio;
|
|
237
|
-
layers.push_back(layer);
|
|
238
|
-
sum += ratio;
|
|
239
|
-
}
|
|
240
|
-
double factor = 1/sum;
|
|
241
|
-
for (auto &l : layers) {
|
|
242
|
-
l.ratio *= factor;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
public:
|
|
246
|
-
BoxStackFilter(int maxSize, int layers=4) {
|
|
247
|
-
resize(maxSize, layers);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/// Returns an optimal set of length ratios (heuristic for larger depths)
|
|
251
|
-
static std::vector<double> optimalRatios(int layerCount) {
|
|
252
|
-
// Coefficients up to 6, found through numerical search
|
|
253
|
-
static double hardcoded[] = {1, 0.58224186169, 0.41775813831, 0.404078562416, 0.334851475794, 0.261069961789, 0.307944914938, 0.27369945234, 0.22913263601, 0.189222996712, 0.248329349789, 0.229253789144, 0.201191468123, 0.173033035122, 0.148192357821, 0.205275202874, 0.198413552119, 0.178256637764, 0.157821404506, 0.138663023387, 0.121570179349 /*, 0.178479592135, 0.171760666359, 0.158434068954, 0.143107825806, 0.125907148711, 0.11853946895, 0.103771229086, 0.155427880834, 0.153063152848, 0.142803459422, 0.131358358458, 0.104157805178, 0.119338029601, 0.0901675284678, 0.103683785192, 0.143949349747, 0.139813248378, 0.132051305252, 0.122216776152, 0.112888320989, 0.102534988632, 0.0928386714364, 0.0719750997699, 0.0817322396428, 0.130587011572, 0.127244563184, 0.121228748787, 0.113509941974, 0.105000272288, 0.0961938290157, 0.0880639725438, 0.0738389766046, 0.0746781936619, 0.0696544903682 */};
|
|
254
|
-
if (layerCount <= 0) {
|
|
255
|
-
return {};
|
|
256
|
-
} else if (layerCount <= 6) {
|
|
257
|
-
double *start = &hardcoded[layerCount*(layerCount - 1)/2];
|
|
258
|
-
return std::vector<double>(start, start + layerCount);
|
|
259
|
-
}
|
|
260
|
-
std::vector<double> result(layerCount);
|
|
261
|
-
|
|
262
|
-
double invN = 1.0/layerCount, sqrtN = std::sqrt(layerCount);
|
|
263
|
-
double p = 1 - invN;
|
|
264
|
-
double k = 1 + 4.5/sqrtN + 0.08*sqrtN;
|
|
265
|
-
|
|
266
|
-
double sum = 0;
|
|
267
|
-
for (int i = 0; i < layerCount; ++i) {
|
|
268
|
-
double x = i*invN;
|
|
269
|
-
double power = -x*(1 - p*std::exp(-x*k));
|
|
270
|
-
double length = std::pow(2, power);
|
|
271
|
-
result[i] = length;
|
|
272
|
-
sum += length;
|
|
273
|
-
}
|
|
274
|
-
double factor = 1/sum;
|
|
275
|
-
for (auto &r : result) r *= factor;
|
|
276
|
-
return result;
|
|
277
|
-
}
|
|
278
|
-
/** Approximate (optimal) bandwidth for a given number of layers
|
|
279
|
-
\diagram{box-stack-bandwidth.svg,Approximate main lobe width (bandwidth)}
|
|
280
|
-
*/
|
|
281
|
-
static constexpr double layersToBandwidth(int layers) {
|
|
282
|
-
return 1.58*(layers + 0.1);
|
|
283
|
-
}
|
|
284
|
-
/** Approximate (optimal) peak in the stop-band
|
|
285
|
-
\diagram{box-stack-peak.svg,Heuristic stop-band peak}
|
|
286
|
-
*/
|
|
287
|
-
static constexpr double layersToPeakDb(int layers) {
|
|
288
|
-
return 5 - layers*18;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/// Sets size using an optimal (heuristic at larger sizes) set of length ratios
|
|
292
|
-
void resize(int maxSize, int layerCount) {
|
|
293
|
-
resize(maxSize, optimalRatios(layerCount));
|
|
294
|
-
}
|
|
295
|
-
/// Sets the maximum (and current) impulse response length and explicit length ratios
|
|
296
|
-
template<class List>
|
|
297
|
-
auto resize(int maxSize, List ratios) -> decltype(void(std::begin(ratios)), void(std::end(ratios))) {
|
|
298
|
-
setupLayers(ratios);
|
|
299
|
-
for (auto &layer : layers) layer.filter.resize(0); // .set() will expand it later
|
|
300
|
-
_size = -1;
|
|
301
|
-
set(maxSize);
|
|
302
|
-
reset();
|
|
303
|
-
}
|
|
304
|
-
void resize(int maxSize, std::initializer_list<double> ratios) {
|
|
305
|
-
resize<const std::initializer_list<double> &>(maxSize, ratios);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/// Sets the impulse response length (does not reset if `size` ≤ `maxSize`)
|
|
309
|
-
void set(int size) {
|
|
310
|
-
if (layers.size() == 0) return; // meaningless
|
|
311
|
-
|
|
312
|
-
if (_size == size) return;
|
|
313
|
-
_size = size;
|
|
314
|
-
int order = size - 1;
|
|
315
|
-
int totalOrder = 0;
|
|
316
|
-
|
|
317
|
-
for (auto &layer : layers) {
|
|
318
|
-
double layerOrderFractional = layer.ratio*order;
|
|
319
|
-
int layerOrder = int(layerOrderFractional);
|
|
320
|
-
layer.length = layerOrder + 1;
|
|
321
|
-
layer.lengthError = layerOrder - layerOrderFractional;
|
|
322
|
-
totalOrder += layerOrder;
|
|
323
|
-
}
|
|
324
|
-
// Round some of them up, so the total is correct - this is O(N²), but `layers.size()` is small
|
|
325
|
-
while (totalOrder < order) {
|
|
326
|
-
int minIndex = 0;
|
|
327
|
-
double minError = layers[0].lengthError;
|
|
328
|
-
for (size_t i = 1; i < layers.size(); ++i) {
|
|
329
|
-
if (layers[i].lengthError < minError) {
|
|
330
|
-
minError = layers[i].lengthError;
|
|
331
|
-
minIndex = i;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
layers[minIndex].length++;
|
|
335
|
-
layers[minIndex].lengthError += 1;
|
|
336
|
-
totalOrder++;
|
|
337
|
-
}
|
|
338
|
-
for (auto &layer : layers) layer.filter.set(layer.length);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/// Resets the filter
|
|
342
|
-
void reset(Sample fill=Sample()) {
|
|
343
|
-
for (auto &layer : layers) layer.filter.reset(fill);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
Sample operator()(Sample v) {
|
|
347
|
-
for (auto &layer : layers) {
|
|
348
|
-
v = layer.filter(v);
|
|
349
|
-
}
|
|
350
|
-
return v;
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
/** Peak-hold filter.
|
|
355
|
-
\diagram{peak-hold.svg}
|
|
356
|
-
|
|
357
|
-
The size is variable, and can be changed instantly with `.set()`, or by using `.push()`/`.pop()` in an unbalanced way.
|
|
358
|
-
|
|
359
|
-
This has complexity O(1) every sample when the length remains constant (balanced `.push()`/`.pop()`, or using `filter(v)`), and amortised O(1) complexity otherwise. To avoid allocations while running, it pre-allocates a vector (not a `std::deque`) which determines the maximum length.
|
|
360
|
-
*/
|
|
361
|
-
template<typename Sample>
|
|
362
|
-
class PeakHold {
|
|
363
|
-
static constexpr Sample lowest = std::numeric_limits<Sample>::lowest();
|
|
364
|
-
int bufferMask;
|
|
365
|
-
std::vector<Sample> buffer;
|
|
366
|
-
int backIndex = 0, middleStart = 0, workingIndex = 0, middleEnd = 0, frontIndex = 0;
|
|
367
|
-
Sample frontMax = lowest, workingMax = lowest, middleMax = lowest;
|
|
368
|
-
|
|
369
|
-
public:
|
|
370
|
-
PeakHold(int maxLength) {
|
|
371
|
-
resize(maxLength);
|
|
372
|
-
}
|
|
373
|
-
int size() {
|
|
374
|
-
return frontIndex - backIndex;
|
|
375
|
-
}
|
|
376
|
-
void resize(int maxLength) {
|
|
377
|
-
int bufferLength = 1;
|
|
378
|
-
while (bufferLength < maxLength) bufferLength *= 2;
|
|
379
|
-
buffer.resize(bufferLength);
|
|
380
|
-
bufferMask = bufferLength - 1;
|
|
381
|
-
|
|
382
|
-
frontIndex = backIndex + maxLength;
|
|
383
|
-
reset();
|
|
384
|
-
}
|
|
385
|
-
void reset(Sample fill=lowest) {
|
|
386
|
-
int prevSize = size();
|
|
387
|
-
buffer.assign(buffer.size(), fill);
|
|
388
|
-
frontMax = workingMax = middleMax = lowest;
|
|
389
|
-
middleEnd = workingIndex = frontIndex = 0;
|
|
390
|
-
middleStart = middleEnd - (prevSize/2);
|
|
391
|
-
backIndex = frontIndex - prevSize;
|
|
392
|
-
}
|
|
393
|
-
/** Sets the size immediately.
|
|
394
|
-
Must be `0 <= newSize <= maxLength` (see constructor and `.resize()`).
|
|
395
|
-
|
|
396
|
-
Shrinking doesn't destroy information, and if you expand again (with `preserveCurrentPeak=false`), you will get the same output as before shrinking. Expanding when `preserveCurrentPeak` is enabled is destructive, re-writing its history such that the current output value is unchanged.*/
|
|
397
|
-
void set(int newSize, bool preserveCurrentPeak=false) {
|
|
398
|
-
while (size() < newSize) {
|
|
399
|
-
Sample &backPrev = buffer[backIndex&bufferMask];
|
|
400
|
-
--backIndex;
|
|
401
|
-
Sample &back = buffer[backIndex&bufferMask];
|
|
402
|
-
back = preserveCurrentPeak ? backPrev : std::max(back, backPrev);
|
|
403
|
-
}
|
|
404
|
-
while (size() > newSize) {
|
|
405
|
-
pop();
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
void push(Sample v) {
|
|
410
|
-
buffer[frontIndex&bufferMask] = v;
|
|
411
|
-
++frontIndex;
|
|
412
|
-
frontMax = std::max(frontMax, v);
|
|
413
|
-
}
|
|
414
|
-
void pop() {
|
|
415
|
-
if (backIndex == middleStart) {
|
|
416
|
-
// Move along the maximums
|
|
417
|
-
workingMax = lowest;
|
|
418
|
-
middleMax = frontMax;
|
|
419
|
-
frontMax = lowest;
|
|
420
|
-
|
|
421
|
-
int prevFrontLength = frontIndex - middleEnd;
|
|
422
|
-
int prevMiddleLength = middleEnd - middleStart;
|
|
423
|
-
if (prevFrontLength <= prevMiddleLength + 1) {
|
|
424
|
-
// Swap over simply
|
|
425
|
-
middleStart = middleEnd;
|
|
426
|
-
middleEnd = frontIndex;
|
|
427
|
-
workingIndex = middleEnd;
|
|
428
|
-
} else {
|
|
429
|
-
// The front is longer than the middle - only happens if unbalanced
|
|
430
|
-
// We don't move *all* of the front over, keeping half the surplus in the front
|
|
431
|
-
int middleLength = (frontIndex - middleStart)/2;
|
|
432
|
-
middleStart = middleEnd;
|
|
433
|
-
middleEnd += middleLength;
|
|
434
|
-
|
|
435
|
-
// Working index is close enough that it will be finished by the time the back is empty
|
|
436
|
-
int backLength = middleStart - backIndex;
|
|
437
|
-
int workingLength = std::min(backLength, middleEnd - middleStart);
|
|
438
|
-
workingIndex = middleStart + workingLength;
|
|
439
|
-
|
|
440
|
-
// Since the front was not completely consumed, we re-calculate the front's maximum
|
|
441
|
-
for (int i = middleEnd; i != frontIndex; ++i) {
|
|
442
|
-
frontMax = std::max(frontMax, buffer[i&bufferMask]);
|
|
443
|
-
}
|
|
444
|
-
// The index might not start at the end of the working block - compute the last bit immediately
|
|
445
|
-
for (int i = middleEnd - 1; i != workingIndex - 1; --i) {
|
|
446
|
-
buffer[i&bufferMask] = workingMax = std::max(workingMax, buffer[i&bufferMask]);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Is the new back (previous middle) empty? Only happens if unbalanced
|
|
451
|
-
if (backIndex == middleStart) {
|
|
452
|
-
// swap over again (front's empty, no change)
|
|
453
|
-
workingMax = lowest;
|
|
454
|
-
middleMax = frontMax;
|
|
455
|
-
frontMax = lowest;
|
|
456
|
-
middleStart = workingIndex = middleEnd;
|
|
457
|
-
|
|
458
|
-
if (backIndex == middleStart) {
|
|
459
|
-
--backIndex; // Only happens if you pop from an empty list - fail nicely
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
buffer[frontIndex&bufferMask] = lowest; // In case of length 0, when everything points at this value
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
++backIndex;
|
|
467
|
-
if (workingIndex != middleStart) {
|
|
468
|
-
--workingIndex;
|
|
469
|
-
buffer[workingIndex&bufferMask] = workingMax = std::max(workingMax, buffer[workingIndex&bufferMask]);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
Sample read() {
|
|
473
|
-
Sample backMax = buffer[backIndex&bufferMask];
|
|
474
|
-
return std::max(backMax, std::max(middleMax, frontMax));
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// For simple use as a constant-length filter
|
|
478
|
-
Sample operator ()(Sample v) {
|
|
479
|
-
push(v);
|
|
480
|
-
pop();
|
|
481
|
-
return read();
|
|
482
|
-
}
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
/** Peak-decay filter with a linear shape and fixed-time return to constant value.
|
|
486
|
-
\diagram{peak-decay-linear.svg}
|
|
487
|
-
This is equivalent to a `BoxFilter` which resets itself whenever the output would be less than the input.
|
|
488
|
-
*/
|
|
489
|
-
template<typename Sample=double>
|
|
490
|
-
class PeakDecayLinear {
|
|
491
|
-
static constexpr Sample lowest = std::numeric_limits<Sample>::lowest();
|
|
492
|
-
PeakHold<Sample> peakHold;
|
|
493
|
-
Sample value = lowest;
|
|
494
|
-
Sample stepMultiplier = 1;
|
|
495
|
-
public:
|
|
496
|
-
PeakDecayLinear(int maxLength) : peakHold(maxLength) {
|
|
497
|
-
set(maxLength);
|
|
498
|
-
}
|
|
499
|
-
void resize(int maxLength) {
|
|
500
|
-
peakHold.resize(maxLength);
|
|
501
|
-
reset();
|
|
502
|
-
}
|
|
503
|
-
void set(double length) {
|
|
504
|
-
peakHold.set(std::ceil(length));
|
|
505
|
-
// Overshoot slightly but don't exceed 1
|
|
506
|
-
stepMultiplier = Sample(1.0001)/std::max(1.0001, length);
|
|
507
|
-
}
|
|
508
|
-
void reset(Sample start=lowest) {
|
|
509
|
-
peakHold.reset(start);
|
|
510
|
-
set(peakHold.size());
|
|
511
|
-
value = start;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
Sample operator ()(Sample v) {
|
|
515
|
-
Sample peak = peakHold.read();
|
|
516
|
-
peakHold(v);
|
|
517
|
-
return value = std::max<Sample>(v, value + (v - peak)*stepMultiplier);
|
|
518
|
-
}
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
/** @} */
|
|
522
|
-
}} // signalsmith::envelopes::
|
|
523
|
-
#endif // include guard
|