react-native-audio-api 0.4.11 → 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/common/cpp/HostObjects/AudioBufferSourceNodeHostObject.h +1 -2
- 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/AudioBus.cpp +8 -0
- package/common/cpp/core/AudioBus.h +3 -0
- package/common/cpp/core/AudioDestinationNode.cpp +1 -1
- package/common/cpp/core/AudioNode.cpp +9 -5
- package/common/cpp/core/AudioNode.h +4 -2
- package/common/cpp/core/BaseAudioContext.cpp +7 -0
- package/common/cpp/core/BaseAudioContext.h +2 -0
- package/common/cpp/core/StretcherNode.cpp +96 -0
- package/common/cpp/core/StretcherNode.h +63 -0
- package/common/cpp/installer/AudioAPIModuleInstaller.h +4 -4
- 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/core/AudioPlayer.m +2 -2
- 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 +1 -0
- 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/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 +1 -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 +5 -0
- package/lib/typescript/interfaces.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/BaseAudioContext.ts +5 -0
- package/src/core/StretcherNode.ts +15 -0
- package/src/index.ts +1 -0
- package/src/index.web.ts +1 -0
- package/src/interfaces.ts +6 -0
- package/common/cpp/installer/AudioAPIModuleInstaller.cpp +0 -49
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
#ifndef SIGNALSMITH_STRETCH_H
|
|
2
|
+
#define SIGNALSMITH_STRETCH_H
|
|
3
|
+
|
|
4
|
+
#include "dsp/spectral.h"
|
|
5
|
+
#include "dsp/delay.h"
|
|
6
|
+
#include "dsp/perf.h"
|
|
7
|
+
SIGNALSMITH_DSP_VERSION_CHECK(1, 6, 0); // Check version is compatible
|
|
8
|
+
#include <vector>
|
|
9
|
+
#include <algorithm>
|
|
10
|
+
#include <functional>
|
|
11
|
+
#include <random>
|
|
12
|
+
|
|
13
|
+
namespace signalsmith { namespace stretch {
|
|
14
|
+
|
|
15
|
+
template<typename Sample=float, class RandomEngine=std::default_random_engine>
|
|
16
|
+
struct SignalsmithStretch {
|
|
17
|
+
static constexpr size_t version[3] = {1, 1, 1};
|
|
18
|
+
|
|
19
|
+
SignalsmithStretch() : randomEngine(std::random_device{}()) {}
|
|
20
|
+
SignalsmithStretch(long seed) : randomEngine(seed) {}
|
|
21
|
+
|
|
22
|
+
int blockSamples() const {
|
|
23
|
+
return stft.windowSize();
|
|
24
|
+
}
|
|
25
|
+
int intervalSamples() const {
|
|
26
|
+
return stft.interval();
|
|
27
|
+
}
|
|
28
|
+
int inputLatency() const {
|
|
29
|
+
return stft.windowSize()/2;
|
|
30
|
+
}
|
|
31
|
+
int outputLatency() const {
|
|
32
|
+
return stft.windowSize() - inputLatency();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
void reset() {
|
|
36
|
+
stft.reset();
|
|
37
|
+
inputBuffer.reset();
|
|
38
|
+
prevInputOffset = -1;
|
|
39
|
+
channelBands.assign(channelBands.size(), Band());
|
|
40
|
+
silenceCounter = 0;
|
|
41
|
+
didSeek = false;
|
|
42
|
+
flushed = true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Configures using a default preset
|
|
46
|
+
void presetDefault(int nChannels, Sample sampleRate) {
|
|
47
|
+
configure(nChannels, sampleRate*0.12, sampleRate*0.03);
|
|
48
|
+
}
|
|
49
|
+
void presetCheaper(int nChannels, Sample sampleRate) {
|
|
50
|
+
configure(nChannels, sampleRate*0.1, sampleRate*0.04);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Manual setup
|
|
54
|
+
void configure(int nChannels, int blockSamples, int intervalSamples) {
|
|
55
|
+
channels = nChannels;
|
|
56
|
+
stft.setWindow(stft.kaiser, true);
|
|
57
|
+
stft.resize(channels, blockSamples, intervalSamples);
|
|
58
|
+
bands = stft.bands();
|
|
59
|
+
inputBuffer.resize(channels, blockSamples + intervalSamples + 1);
|
|
60
|
+
timeBuffer.assign(stft.fftSize(), 0);
|
|
61
|
+
channelBands.assign(bands*channels, Band());
|
|
62
|
+
|
|
63
|
+
peaks.reserve(bands/2);
|
|
64
|
+
energy.resize(bands);
|
|
65
|
+
smoothedEnergy.resize(bands);
|
|
66
|
+
outputMap.resize(bands);
|
|
67
|
+
channelPredictions.resize(channels*bands);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Frequency multiplier, and optional tonality limit (as multiple of sample-rate)
|
|
71
|
+
void setTransposeFactor(Sample multiplier, Sample tonalityLimit=0) {
|
|
72
|
+
freqMultiplier = multiplier;
|
|
73
|
+
if (tonalityLimit > 0) {
|
|
74
|
+
freqTonalityLimit = tonalityLimit/std::sqrt(multiplier); // compromise between input and output limits
|
|
75
|
+
} else {
|
|
76
|
+
freqTonalityLimit = 1;
|
|
77
|
+
}
|
|
78
|
+
customFreqMap = nullptr;
|
|
79
|
+
}
|
|
80
|
+
void setTransposeSemitones(Sample semitones, Sample tonalityLimit=0) {
|
|
81
|
+
setTransposeFactor(std::pow(2, semitones/12), tonalityLimit);
|
|
82
|
+
customFreqMap = nullptr;
|
|
83
|
+
}
|
|
84
|
+
// Sets a custom frequency map - should be monotonically increasing
|
|
85
|
+
void setFreqMap(std::function<Sample(Sample)> inputToOutput) {
|
|
86
|
+
customFreqMap = inputToOutput;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Provide previous input ("pre-roll"), without affecting the speed calculation. You should ideally feed it one block-length + one interval
|
|
90
|
+
template<class Inputs>
|
|
91
|
+
void seek(Inputs &&inputs, int inputSamples, double playbackRate) {
|
|
92
|
+
inputBuffer.reset();
|
|
93
|
+
Sample totalEnergy = 0;
|
|
94
|
+
for (int c = 0; c < channels; ++c) {
|
|
95
|
+
auto &&inputChannel = inputs[c];
|
|
96
|
+
auto &&bufferChannel = inputBuffer[c];
|
|
97
|
+
int startIndex = std::max<int>(0, inputSamples - stft.windowSize() - stft.interval());
|
|
98
|
+
for (int i = startIndex; i < inputSamples; ++i) {
|
|
99
|
+
Sample s = inputChannel[i];
|
|
100
|
+
totalEnergy += s*s;
|
|
101
|
+
bufferChannel[i] = s;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (totalEnergy >= noiseFloor) {
|
|
105
|
+
silenceCounter = 0;
|
|
106
|
+
silenceFirst = true;
|
|
107
|
+
}
|
|
108
|
+
inputBuffer += inputSamples;
|
|
109
|
+
didSeek = true;
|
|
110
|
+
seekTimeFactor = (playbackRate*stft.interval() > 1) ? 1/playbackRate : stft.interval();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
template<class Inputs, class Outputs>
|
|
114
|
+
void process(Inputs &&inputs, int inputSamples, Outputs &&outputs, int outputSamples) {
|
|
115
|
+
Sample totalEnergy = 0;
|
|
116
|
+
for (int c = 0; c < channels; ++c) {
|
|
117
|
+
auto &&inputChannel = inputs[c];
|
|
118
|
+
for (int i = 0; i < inputSamples; ++i) {
|
|
119
|
+
Sample s = inputChannel[i];
|
|
120
|
+
totalEnergy += s*s;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (totalEnergy < noiseFloor) {
|
|
124
|
+
if (silenceCounter >= 2*stft.windowSize()) {
|
|
125
|
+
if (silenceFirst) {
|
|
126
|
+
silenceFirst = false;
|
|
127
|
+
for (auto &b : channelBands) {
|
|
128
|
+
b.input = b.prevInput = b.output = 0;
|
|
129
|
+
b.inputEnergy = 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (inputSamples > 0) {
|
|
134
|
+
// copy from the input, wrapping around if needed
|
|
135
|
+
for (int outputIndex = 0; outputIndex < outputSamples; ++outputIndex) {
|
|
136
|
+
int inputIndex = outputIndex%inputSamples;
|
|
137
|
+
for (int c = 0; c < channels; ++c) {
|
|
138
|
+
outputs[c][outputIndex] = inputs[c][inputIndex];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
for (int c = 0; c < channels; ++c) {
|
|
143
|
+
auto &&outputChannel = outputs[c];
|
|
144
|
+
for (int outputIndex = 0; outputIndex < outputSamples; ++outputIndex) {
|
|
145
|
+
outputChannel[outputIndex] = 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Store input in history buffer
|
|
151
|
+
for (int c = 0; c < channels; ++c) {
|
|
152
|
+
auto &&inputChannel = inputs[c];
|
|
153
|
+
auto &&bufferChannel = inputBuffer[c];
|
|
154
|
+
int startIndex = std::max<int>(0, inputSamples - stft.windowSize() - stft.interval());
|
|
155
|
+
for (int i = startIndex; i < inputSamples; ++i) {
|
|
156
|
+
bufferChannel[i] = inputChannel[i];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
inputBuffer += inputSamples;
|
|
160
|
+
return;
|
|
161
|
+
} else {
|
|
162
|
+
silenceCounter += inputSamples;
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
silenceCounter = 0;
|
|
166
|
+
silenceFirst = true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (int outputIndex = 0; outputIndex < outputSamples; ++outputIndex) {
|
|
170
|
+
stft.ensureValid(outputIndex, [&](int outputOffset) {
|
|
171
|
+
// Time to process a spectrum! Where should it come from in the input?
|
|
172
|
+
int inputOffset = std::round(outputOffset*Sample(inputSamples)/outputSamples) - stft.windowSize();
|
|
173
|
+
int inputInterval = inputOffset - prevInputOffset;
|
|
174
|
+
prevInputOffset = inputOffset;
|
|
175
|
+
|
|
176
|
+
bool newSpectrum = didSeek || (inputInterval > 0);
|
|
177
|
+
if (newSpectrum) {
|
|
178
|
+
for (int c = 0; c < channels; ++c) {
|
|
179
|
+
// Copy from the history buffer, if needed
|
|
180
|
+
auto &&bufferChannel = inputBuffer[c];
|
|
181
|
+
for (int i = 0; i < -inputOffset; ++i) {
|
|
182
|
+
timeBuffer[i] = bufferChannel[i + inputOffset];
|
|
183
|
+
}
|
|
184
|
+
// Copy the rest from the input
|
|
185
|
+
auto &&inputChannel = inputs[c];
|
|
186
|
+
for (int i = std::max<int>(0, -inputOffset); i < stft.windowSize(); ++i) {
|
|
187
|
+
timeBuffer[i] = inputChannel[i + inputOffset];
|
|
188
|
+
}
|
|
189
|
+
stft.analyse(c, timeBuffer);
|
|
190
|
+
}
|
|
191
|
+
flushed = false; // TODO: first block after a flush should be gain-compensated
|
|
192
|
+
|
|
193
|
+
for (int c = 0; c < channels; ++c) {
|
|
194
|
+
auto channelBands = bandsForChannel(c);
|
|
195
|
+
auto &&spectrumBands = stft.spectrum[c];
|
|
196
|
+
for (int b = 0; b < bands; ++b) {
|
|
197
|
+
channelBands[b].input = spectrumBands[b];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (didSeek || inputInterval != stft.interval()) { // make sure the previous input is the correct distance in the past
|
|
202
|
+
int prevIntervalOffset = inputOffset - stft.interval();
|
|
203
|
+
for (int c = 0; c < channels; ++c) {
|
|
204
|
+
// Copy from the history buffer, if needed
|
|
205
|
+
auto &&bufferChannel = inputBuffer[c];
|
|
206
|
+
for (int i = 0; i < std::min(-prevIntervalOffset, stft.windowSize()); ++i) {
|
|
207
|
+
timeBuffer[i] = bufferChannel[i + prevIntervalOffset];
|
|
208
|
+
}
|
|
209
|
+
// Copy the rest from the input
|
|
210
|
+
auto &&inputChannel = inputs[c];
|
|
211
|
+
for (int i = std::max<int>(0, -prevIntervalOffset); i < stft.windowSize(); ++i) {
|
|
212
|
+
timeBuffer[i] = inputChannel[i + prevIntervalOffset];
|
|
213
|
+
}
|
|
214
|
+
stft.analyse(c, timeBuffer);
|
|
215
|
+
}
|
|
216
|
+
for (int c = 0; c < channels; ++c) {
|
|
217
|
+
auto channelBands = bandsForChannel(c);
|
|
218
|
+
auto &&spectrumBands = stft.spectrum[c];
|
|
219
|
+
for (int b = 0; b < bands; ++b) {
|
|
220
|
+
channelBands[b].prevInput = spectrumBands[b];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
Sample timeFactor = didSeek ? seekTimeFactor : stft.interval()/std::max<Sample>(1, inputInterval);
|
|
227
|
+
processSpectrum(newSpectrum, timeFactor);
|
|
228
|
+
didSeek = false;
|
|
229
|
+
|
|
230
|
+
for (int c = 0; c < channels; ++c) {
|
|
231
|
+
auto channelBands = bandsForChannel(c);
|
|
232
|
+
auto &&spectrumBands = stft.spectrum[c];
|
|
233
|
+
for (int b = 0; b < bands; ++b) {
|
|
234
|
+
spectrumBands[b] = channelBands[b].output;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
for (int c = 0; c < channels; ++c) {
|
|
240
|
+
auto &&outputChannel = outputs[c];
|
|
241
|
+
auto &&stftChannel = stft[c];
|
|
242
|
+
outputChannel[outputIndex] = stftChannel[outputIndex];
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Store input in history buffer
|
|
247
|
+
for (int c = 0; c < channels; ++c) {
|
|
248
|
+
auto &&inputChannel = inputs[c];
|
|
249
|
+
auto &&bufferChannel = inputBuffer[c];
|
|
250
|
+
int startIndex = std::max<int>(0, inputSamples - stft.windowSize());
|
|
251
|
+
for (int i = startIndex; i < inputSamples; ++i) {
|
|
252
|
+
bufferChannel[i] = inputChannel[i];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
inputBuffer += inputSamples;
|
|
256
|
+
stft += outputSamples;
|
|
257
|
+
prevInputOffset -= inputSamples;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Read the remaining output, providing no further input. `outputSamples` should ideally be at least `.outputLatency()`
|
|
261
|
+
template<class Outputs>
|
|
262
|
+
void flush(Outputs &&outputs, int outputSamples) {
|
|
263
|
+
int plainOutput = std::min<int>(outputSamples, stft.windowSize());
|
|
264
|
+
int foldedBackOutput = std::min<int>(outputSamples, stft.windowSize() - plainOutput);
|
|
265
|
+
for (int c = 0; c < channels; ++c) {
|
|
266
|
+
auto &&outputChannel = outputs[c];
|
|
267
|
+
auto &&stftChannel = stft[c];
|
|
268
|
+
for (int i = 0; i < plainOutput; ++i) {
|
|
269
|
+
// TODO: plain output should be gain-
|
|
270
|
+
outputChannel[i] = stftChannel[i];
|
|
271
|
+
}
|
|
272
|
+
for (int i = 0; i < foldedBackOutput; ++i) {
|
|
273
|
+
outputChannel[outputSamples - 1 - i] -= stftChannel[plainOutput + i];
|
|
274
|
+
}
|
|
275
|
+
for (int i = 0; i < plainOutput + foldedBackOutput; ++i) {
|
|
276
|
+
stftChannel[i] = 0;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Skip the output we just used/cleared
|
|
280
|
+
stft += plainOutput + foldedBackOutput;
|
|
281
|
+
// Reset the phase-vocoder stuff, so the next block gets a fresh start
|
|
282
|
+
for (int c = 0; c < channels; ++c) {
|
|
283
|
+
auto channelBands = bandsForChannel(c);
|
|
284
|
+
for (int b = 0; b < bands; ++b) {
|
|
285
|
+
channelBands[b].prevInput = channelBands[b].output = 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
flushed = true;
|
|
289
|
+
}
|
|
290
|
+
private:
|
|
291
|
+
using Complex = std::complex<Sample>;
|
|
292
|
+
static constexpr Sample noiseFloor{1e-15};
|
|
293
|
+
static constexpr Sample maxCleanStretch{2}; // time-stretch ratio before we start randomising phases
|
|
294
|
+
int silenceCounter = 0;
|
|
295
|
+
bool silenceFirst = true;
|
|
296
|
+
|
|
297
|
+
Sample freqMultiplier = 1, freqTonalityLimit = 0.5;
|
|
298
|
+
std::function<Sample(Sample)> customFreqMap = nullptr;
|
|
299
|
+
|
|
300
|
+
signalsmith::spectral::STFT<Sample> stft{0, 1, 1};
|
|
301
|
+
signalsmith::delay::MultiBuffer<Sample> inputBuffer;
|
|
302
|
+
int channels = 0, bands = 0;
|
|
303
|
+
int prevInputOffset = -1;
|
|
304
|
+
std::vector<Sample> timeBuffer;
|
|
305
|
+
bool didSeek = false, flushed = true;
|
|
306
|
+
Sample seekTimeFactor = 1;
|
|
307
|
+
|
|
308
|
+
Sample bandToFreq(Sample b) const {
|
|
309
|
+
return (b + Sample(0.5))/stft.fftSize();
|
|
310
|
+
}
|
|
311
|
+
Sample freqToBand(Sample f) const {
|
|
312
|
+
return f*stft.fftSize() - Sample(0.5);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
struct Band {
|
|
316
|
+
Complex input, prevInput{0};
|
|
317
|
+
Complex output{0};
|
|
318
|
+
Sample inputEnergy;
|
|
319
|
+
};
|
|
320
|
+
std::vector<Band> channelBands;
|
|
321
|
+
Band * bandsForChannel(int channel) {
|
|
322
|
+
return channelBands.data() + channel*bands;
|
|
323
|
+
}
|
|
324
|
+
template<Complex Band::*member>
|
|
325
|
+
Complex getBand(int channel, int index) {
|
|
326
|
+
if (index < 0 || index >= bands) return 0;
|
|
327
|
+
return channelBands[index + channel*bands].*member;
|
|
328
|
+
}
|
|
329
|
+
template<Complex Band::*member>
|
|
330
|
+
Complex getFractional(int channel, int lowIndex, Sample fractional) {
|
|
331
|
+
Complex low = getBand<member>(channel, lowIndex);
|
|
332
|
+
Complex high = getBand<member>(channel, lowIndex + 1);
|
|
333
|
+
return low + (high - low)*fractional;
|
|
334
|
+
}
|
|
335
|
+
template<Complex Band::*member>
|
|
336
|
+
Complex getFractional(int channel, Sample inputIndex) {
|
|
337
|
+
int lowIndex = std::floor(inputIndex);
|
|
338
|
+
Sample fracIndex = inputIndex - lowIndex;
|
|
339
|
+
return getFractional<member>(channel, lowIndex, fracIndex);
|
|
340
|
+
}
|
|
341
|
+
template<Sample Band::*member>
|
|
342
|
+
Sample getBand(int channel, int index) {
|
|
343
|
+
if (index < 0 || index >= bands) return 0;
|
|
344
|
+
return channelBands[index + channel*bands].*member;
|
|
345
|
+
}
|
|
346
|
+
template<Sample Band::*member>
|
|
347
|
+
Sample getFractional(int channel, int lowIndex, Sample fractional) {
|
|
348
|
+
Sample low = getBand<member>(channel, lowIndex);
|
|
349
|
+
Sample high = getBand<member>(channel, lowIndex + 1);
|
|
350
|
+
return low + (high - low)*fractional;
|
|
351
|
+
}
|
|
352
|
+
template<Sample Band::*member>
|
|
353
|
+
Sample getFractional(int channel, Sample inputIndex) {
|
|
354
|
+
int lowIndex = std::floor(inputIndex);
|
|
355
|
+
Sample fracIndex = inputIndex - lowIndex;
|
|
356
|
+
return getFractional<member>(channel, lowIndex, fracIndex);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
struct Peak {
|
|
360
|
+
Sample input, output;
|
|
361
|
+
};
|
|
362
|
+
std::vector<Peak> peaks;
|
|
363
|
+
std::vector<Sample> energy, smoothedEnergy;
|
|
364
|
+
struct PitchMapPoint {
|
|
365
|
+
Sample inputBin, freqGrad;
|
|
366
|
+
};
|
|
367
|
+
std::vector<PitchMapPoint> outputMap;
|
|
368
|
+
|
|
369
|
+
struct Prediction {
|
|
370
|
+
Sample energy = 0;
|
|
371
|
+
Complex input;
|
|
372
|
+
|
|
373
|
+
Complex makeOutput(Complex phase) {
|
|
374
|
+
Sample phaseNorm = std::norm(phase);
|
|
375
|
+
if (phaseNorm <= noiseFloor) {
|
|
376
|
+
phase = input; // prediction is too weak, fall back to the input
|
|
377
|
+
phaseNorm = std::norm(input) + noiseFloor;
|
|
378
|
+
}
|
|
379
|
+
return phase*std::sqrt(energy/phaseNorm);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
std::vector<Prediction> channelPredictions;
|
|
383
|
+
Prediction * predictionsForChannel(int c) {
|
|
384
|
+
return channelPredictions.data() + c*bands;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
RandomEngine randomEngine;
|
|
388
|
+
|
|
389
|
+
void processSpectrum(bool newSpectrum, Sample timeFactor) {
|
|
390
|
+
timeFactor = std::max<Sample>(timeFactor, 1/maxCleanStretch);
|
|
391
|
+
bool randomTimeFactor = (timeFactor > maxCleanStretch);
|
|
392
|
+
std::uniform_real_distribution<Sample> timeFactorDist(maxCleanStretch*2*randomTimeFactor - timeFactor, timeFactor);
|
|
393
|
+
|
|
394
|
+
if (newSpectrum) {
|
|
395
|
+
for (int c = 0; c < channels; ++c) {
|
|
396
|
+
auto bins = bandsForChannel(c);
|
|
397
|
+
|
|
398
|
+
Complex rot = std::polar(Sample(1), bandToFreq(0)*stft.interval()*Sample(2*M_PI));
|
|
399
|
+
Sample freqStep = bandToFreq(1) - bandToFreq(0);
|
|
400
|
+
Complex rotStep = std::polar(Sample(1), freqStep*stft.interval()*Sample(2*M_PI));
|
|
401
|
+
|
|
402
|
+
for (int b = 0; b < bands; ++b) {
|
|
403
|
+
auto &bin = bins[b];
|
|
404
|
+
bin.output = signalsmith::perf::mul(bin.output, rot);
|
|
405
|
+
bin.prevInput = signalsmith::perf::mul(bin.prevInput, rot);
|
|
406
|
+
rot = signalsmith::perf::mul(rot, rotStep);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
Sample smoothingBins = Sample(stft.fftSize())/stft.interval();
|
|
412
|
+
int longVerticalStep = std::round(smoothingBins);
|
|
413
|
+
if (customFreqMap || freqMultiplier != 1) {
|
|
414
|
+
findPeaks(smoothingBins);
|
|
415
|
+
updateOutputMap();
|
|
416
|
+
} else { // we're not pitch-shifting, so no need to find peaks etc.
|
|
417
|
+
for (int c = 0; c < channels; ++c) {
|
|
418
|
+
Band *bins = bandsForChannel(c);
|
|
419
|
+
for (int b = 0; b < bands; ++b) {
|
|
420
|
+
bins[b].inputEnergy = std::norm(bins[b].input);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
for (int b = 0; b < bands; ++b) {
|
|
424
|
+
outputMap[b] = {Sample(b), 1};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Preliminary output prediction from phase-vocoder
|
|
429
|
+
for (int c = 0; c < channels; ++c) {
|
|
430
|
+
Band *bins = bandsForChannel(c);
|
|
431
|
+
auto *predictions = predictionsForChannel(c);
|
|
432
|
+
for (int b = 0; b < bands; ++b) {
|
|
433
|
+
auto mapPoint = outputMap[b];
|
|
434
|
+
int lowIndex = std::floor(mapPoint.inputBin);
|
|
435
|
+
Sample fracIndex = mapPoint.inputBin - lowIndex;
|
|
436
|
+
|
|
437
|
+
Prediction &prediction = predictions[b];
|
|
438
|
+
Sample prevEnergy = prediction.energy;
|
|
439
|
+
prediction.energy = getFractional<&Band::inputEnergy>(c, lowIndex, fracIndex);
|
|
440
|
+
prediction.energy *= std::max<Sample>(0, mapPoint.freqGrad); // scale the energy according to local stretch factor
|
|
441
|
+
prediction.input = getFractional<&Band::input>(c, lowIndex, fracIndex);
|
|
442
|
+
|
|
443
|
+
auto &outputBin = bins[b];
|
|
444
|
+
Complex prevInput = getFractional<&Band::prevInput>(c, lowIndex, fracIndex);
|
|
445
|
+
Complex freqTwist = signalsmith::perf::mul<true>(prediction.input, prevInput);
|
|
446
|
+
Complex phase = signalsmith::perf::mul(outputBin.output, freqTwist);
|
|
447
|
+
outputBin.output = phase/(std::max(prevEnergy, prediction.energy) + noiseFloor);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Re-predict using phase differences between frequencies
|
|
452
|
+
for (int b = 0; b < bands; ++b) {
|
|
453
|
+
// Find maximum-energy channel and calculate that
|
|
454
|
+
int maxChannel = 0;
|
|
455
|
+
Sample maxEnergy = predictionsForChannel(0)[b].energy;
|
|
456
|
+
for (int c = 1; c < channels; ++c) {
|
|
457
|
+
Sample e = predictionsForChannel(c)[b].energy;
|
|
458
|
+
if (e > maxEnergy) {
|
|
459
|
+
maxChannel = c;
|
|
460
|
+
maxEnergy = e;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
auto *predictions = predictionsForChannel(maxChannel);
|
|
465
|
+
auto &prediction = predictions[b];
|
|
466
|
+
auto *bins = bandsForChannel(maxChannel);
|
|
467
|
+
auto &outputBin = bins[b];
|
|
468
|
+
|
|
469
|
+
Complex phase = 0;
|
|
470
|
+
auto mapPoint = outputMap[b];
|
|
471
|
+
|
|
472
|
+
// Upwards vertical steps
|
|
473
|
+
if (b > 0) {
|
|
474
|
+
Sample binTimeFactor = randomTimeFactor ? timeFactorDist(randomEngine) : timeFactor;
|
|
475
|
+
Complex downInput = getFractional<&Band::input>(maxChannel, mapPoint.inputBin - binTimeFactor);
|
|
476
|
+
Complex shortVerticalTwist = signalsmith::perf::mul<true>(prediction.input, downInput);
|
|
477
|
+
|
|
478
|
+
auto &downBin = bins[b - 1];
|
|
479
|
+
phase += signalsmith::perf::mul(downBin.output, shortVerticalTwist);
|
|
480
|
+
|
|
481
|
+
if (b >= longVerticalStep) {
|
|
482
|
+
Complex longDownInput = getFractional<&Band::input>(maxChannel, mapPoint.inputBin - longVerticalStep*binTimeFactor);
|
|
483
|
+
Complex longVerticalTwist = signalsmith::perf::mul<true>(prediction.input, longDownInput);
|
|
484
|
+
|
|
485
|
+
auto &longDownBin = bins[b - longVerticalStep];
|
|
486
|
+
phase += signalsmith::perf::mul(longDownBin.output, longVerticalTwist);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// Downwards vertical steps
|
|
490
|
+
if (b < bands - 1) {
|
|
491
|
+
auto &upPrediction = predictions[b + 1];
|
|
492
|
+
auto &upMapPoint = outputMap[b + 1];
|
|
493
|
+
|
|
494
|
+
Sample binTimeFactor = randomTimeFactor ? timeFactorDist(randomEngine) : timeFactor;
|
|
495
|
+
Complex downInput = getFractional<&Band::input>(maxChannel, upMapPoint.inputBin - binTimeFactor);
|
|
496
|
+
Complex shortVerticalTwist = signalsmith::perf::mul<true>(upPrediction.input, downInput);
|
|
497
|
+
|
|
498
|
+
auto &upBin = bins[b + 1];
|
|
499
|
+
phase += signalsmith::perf::mul<true>(upBin.output, shortVerticalTwist);
|
|
500
|
+
|
|
501
|
+
if (b < bands - longVerticalStep) {
|
|
502
|
+
auto &longUpPrediction = predictions[b + longVerticalStep];
|
|
503
|
+
auto &longUpMapPoint = outputMap[b + longVerticalStep];
|
|
504
|
+
|
|
505
|
+
Complex longDownInput = getFractional<&Band::input>(maxChannel, longUpMapPoint.inputBin - longVerticalStep*binTimeFactor);
|
|
506
|
+
Complex longVerticalTwist = signalsmith::perf::mul<true>(longUpPrediction.input, longDownInput);
|
|
507
|
+
|
|
508
|
+
auto &longUpBin = bins[b + longVerticalStep];
|
|
509
|
+
phase += signalsmith::perf::mul<true>(longUpBin.output, longVerticalTwist);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
outputBin.output = prediction.makeOutput(phase);
|
|
514
|
+
|
|
515
|
+
// All other bins are locked in phase
|
|
516
|
+
for (int c = 0; c < channels; ++c) {
|
|
517
|
+
if (c != maxChannel) {
|
|
518
|
+
auto &channelBin = bandsForChannel(c)[b];
|
|
519
|
+
auto &channelPrediction = predictionsForChannel(c)[b];
|
|
520
|
+
|
|
521
|
+
Complex channelTwist = signalsmith::perf::mul<true>(channelPrediction.input, prediction.input);
|
|
522
|
+
Complex channelPhase = signalsmith::perf::mul(outputBin.output, channelTwist);
|
|
523
|
+
channelBin.output = channelPrediction.makeOutput(channelPhase);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (newSpectrum) {
|
|
529
|
+
for (auto &bin : channelBands) {
|
|
530
|
+
bin.prevInput = bin.input;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Produces smoothed energy across all channels
|
|
536
|
+
void smoothEnergy(Sample smoothingBins) {
|
|
537
|
+
Sample smoothingSlew = 1/(1 + smoothingBins*Sample(0.5));
|
|
538
|
+
for (auto &e : energy) e = 0;
|
|
539
|
+
for (int c = 0; c < channels; ++c) {
|
|
540
|
+
Band *bins = bandsForChannel(c);
|
|
541
|
+
for (int b = 0; b < bands; ++b) {
|
|
542
|
+
Sample e = std::norm(bins[b].input);
|
|
543
|
+
bins[b].inputEnergy = e; // Used for interpolating prediction energy
|
|
544
|
+
energy[b] += e;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
for (int b = 0; b < bands; ++b) {
|
|
548
|
+
smoothedEnergy[b] = energy[b];
|
|
549
|
+
}
|
|
550
|
+
Sample e = 0;
|
|
551
|
+
for (int repeat = 0; repeat < 2; ++repeat) {
|
|
552
|
+
for (int b = bands - 1; b >= 0; --b) {
|
|
553
|
+
e += (smoothedEnergy[b] - e)*smoothingSlew;
|
|
554
|
+
smoothedEnergy[b] = e;
|
|
555
|
+
}
|
|
556
|
+
for (int b = 0; b < bands; ++b) {
|
|
557
|
+
e += (smoothedEnergy[b] - e)*smoothingSlew;
|
|
558
|
+
smoothedEnergy[b] = e;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
Sample mapFreq(Sample freq) const {
|
|
564
|
+
if (customFreqMap) return customFreqMap(freq);
|
|
565
|
+
if (freq > freqTonalityLimit) {
|
|
566
|
+
Sample diff = freq - freqTonalityLimit;
|
|
567
|
+
return freqTonalityLimit*freqMultiplier + diff;
|
|
568
|
+
}
|
|
569
|
+
return freq*freqMultiplier;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Identifies spectral peaks using energy across all channels
|
|
573
|
+
void findPeaks(Sample smoothingBins) {
|
|
574
|
+
smoothEnergy(smoothingBins);
|
|
575
|
+
|
|
576
|
+
peaks.resize(0);
|
|
577
|
+
|
|
578
|
+
int start = 0;
|
|
579
|
+
while (start < bands) {
|
|
580
|
+
if (energy[start] > smoothedEnergy[start]) {
|
|
581
|
+
int end = start;
|
|
582
|
+
Sample bandSum = 0, energySum = 0;
|
|
583
|
+
while (end < bands && energy[end] > smoothedEnergy[end]) {
|
|
584
|
+
bandSum += end*energy[end];
|
|
585
|
+
energySum += energy[end];
|
|
586
|
+
++end;
|
|
587
|
+
}
|
|
588
|
+
Sample avgBand = bandSum/energySum;
|
|
589
|
+
Sample avgFreq = bandToFreq(avgBand);
|
|
590
|
+
peaks.emplace_back(Peak{avgBand, freqToBand(mapFreq(avgFreq))});
|
|
591
|
+
|
|
592
|
+
start = end;
|
|
593
|
+
}
|
|
594
|
+
++start;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
void updateOutputMap() {
|
|
599
|
+
if (peaks.empty()) {
|
|
600
|
+
for (int b = 0; b < bands; ++b) {
|
|
601
|
+
outputMap[b] = {Sample(b), 1};
|
|
602
|
+
}
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
Sample bottomOffset = peaks[0].input - peaks[0].output;
|
|
606
|
+
for (int b = 0; b < std::min<int>(bands, std::ceil(peaks[0].output)); ++b) {
|
|
607
|
+
outputMap[b] = {b + bottomOffset, 1};
|
|
608
|
+
}
|
|
609
|
+
// Interpolate between points
|
|
610
|
+
for (size_t p = 1; p < peaks.size(); ++p) {
|
|
611
|
+
const Peak &prev = peaks[p - 1], &next = peaks[p];
|
|
612
|
+
Sample rangeScale = 1/(next.output - prev.output);
|
|
613
|
+
Sample outOffset = prev.input - prev.output;
|
|
614
|
+
Sample outScale = next.input - next.output - prev.input + prev.output;
|
|
615
|
+
Sample gradScale = outScale*rangeScale;
|
|
616
|
+
int startBin = std::max<int>(0, std::ceil(prev.output));
|
|
617
|
+
int endBin = std::min<int>(bands, std::ceil(next.output));
|
|
618
|
+
for (int b = startBin; b < endBin; ++b) {
|
|
619
|
+
Sample r = (b - prev.output)*rangeScale;
|
|
620
|
+
Sample h = r*r*(3 - 2*r);
|
|
621
|
+
Sample outB = b + outOffset + h*outScale;
|
|
622
|
+
|
|
623
|
+
Sample gradH = 6*r*(1 - r);
|
|
624
|
+
Sample gradB = 1 + gradH*gradScale;
|
|
625
|
+
|
|
626
|
+
outputMap[b] = {outB, gradB};
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
Sample topOffset = peaks.back().input - peaks.back().output;
|
|
630
|
+
for (int b = std::max<int>(0, peaks.back().output); b < bands; ++b) {
|
|
631
|
+
outputMap[b] = {b + topOffset, 1};
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
}} // namespace
|
|
637
|
+
#endif // include guard
|
package/ios/core/AudioPlayer.m
CHANGED
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
[self setupAndInitAudioSession];
|
|
14
14
|
[self setupAndInitNotificationHandlers];
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
self.sampleRate = [self.audioSession sampleRate];
|
|
18
17
|
|
|
19
18
|
_format = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:self.sampleRate channels:2];
|
|
@@ -139,7 +138,8 @@
|
|
|
139
138
|
|
|
140
139
|
[self.audioSession setCategory:AVAudioSessionCategoryPlayback
|
|
141
140
|
mode:AVAudioSessionModeDefault
|
|
142
|
-
options:AVAudioSessionCategoryOptionDuckOthers|AVAudioSessionCategoryOptionAllowBluetooth|
|
|
141
|
+
options:AVAudioSessionCategoryOptionDuckOthers | AVAudioSessionCategoryOptionAllowBluetooth |
|
|
142
|
+
AVAudioSessionCategoryOptionAllowAirPlay
|
|
143
143
|
error:&error];
|
|
144
144
|
|
|
145
145
|
if (error != nil) {
|
|
@@ -9,6 +9,7 @@ import AudioBufferSourceNode from "./AudioBufferSourceNode.js";
|
|
|
9
9
|
import AudioBuffer from "./AudioBuffer.js";
|
|
10
10
|
import PeriodicWave from "./PeriodicWave.js";
|
|
11
11
|
import AnalyserNode from "./AnalyserNode.js";
|
|
12
|
+
import StretcherNode from "./StretcherNode.js";
|
|
12
13
|
import { InvalidAccessError, NotSupportedError } from "../errors/index.js";
|
|
13
14
|
export default class BaseAudioContext {
|
|
14
15
|
constructor(context) {
|
|
@@ -59,6 +60,9 @@ export default class BaseAudioContext {
|
|
|
59
60
|
createAnalyser() {
|
|
60
61
|
return new AnalyserNode(this, this.context.createAnalyser());
|
|
61
62
|
}
|
|
63
|
+
createStretcher() {
|
|
64
|
+
return new StretcherNode(this, this.context.createStretcher());
|
|
65
|
+
}
|
|
62
66
|
async decodeAudioDataSource(sourcePath) {
|
|
63
67
|
// Remove the file:// prefix if it exists
|
|
64
68
|
if (sourcePath.startsWith('file://')) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["AudioDestinationNode","OscillatorNode","GainNode","StereoPannerNode","BiquadFilterNode","AudioBufferSourceNode","AudioBuffer","PeriodicWave","AnalyserNode","InvalidAccessError","NotSupportedError","BaseAudioContext","constructor","context","destination","sampleRate","currentTime","state","createOscillator","createGain","createStereoPanner","createBiquadFilter","createBufferSource","createBuffer","numOfChannels","length","createPeriodicWave","real","imag","constraints","disableNormalization","createAnalyser","decodeAudioDataSource","sourcePath","startsWith","replace"],"sourceRoot":"../../../src","sources":["core/BaseAudioContext.ts"],"mappings":";;AAEA,OAAOA,oBAAoB,MAAM,2BAAwB;AACzD,OAAOC,cAAc,MAAM,qBAAkB;AAC7C,OAAOC,QAAQ,MAAM,eAAY;AACjC,OAAOC,gBAAgB,MAAM,uBAAoB;AACjD,OAAOC,gBAAgB,MAAM,uBAAoB;AACjD,OAAOC,qBAAqB,MAAM,4BAAyB;AAC3D,OAAOC,WAAW,MAAM,kBAAe;AACvC,OAAOC,YAAY,MAAM,mBAAgB;AACzC,OAAOC,YAAY,MAAM,mBAAgB;AACzC,SAASC,kBAAkB,EAAEC,iBAAiB,QAAQ,oBAAW;AAEjE,eAAe,MAAMC,gBAAgB,CAAC;EAKpCC,WAAWA,CAACC,OAA0B,EAAE;IACtC,IAAI,CAACA,OAAO,GAAGA,OAAO;IACtB,IAAI,CAACC,WAAW,GAAG,
|
|
1
|
+
{"version":3,"names":["AudioDestinationNode","OscillatorNode","GainNode","StereoPannerNode","BiquadFilterNode","AudioBufferSourceNode","AudioBuffer","PeriodicWave","AnalyserNode","StretcherNode","InvalidAccessError","NotSupportedError","BaseAudioContext","constructor","context","destination","sampleRate","currentTime","state","createOscillator","createGain","createStereoPanner","createBiquadFilter","createBufferSource","createBuffer","numOfChannels","length","createPeriodicWave","real","imag","constraints","disableNormalization","createAnalyser","createStretcher","decodeAudioDataSource","sourcePath","startsWith","replace"],"sourceRoot":"../../../src","sources":["core/BaseAudioContext.ts"],"mappings":"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","ignoreList":[]}
|