Add .exact() method to process entire buffers, including folding extra output back in
This commit is contained in:
parent
343581bb52
commit
c5987267e6
26
cmd/Makefile
26
cmd/Makefile
@ -11,19 +11,19 @@ out/stretch: main.cpp ../signalsmith-stretch.h util/*.h util/*.hxx
|
|||||||
# Uses input files from: https://signalsmith-audio.co.uk/code/stretch/inputs.zip
|
# Uses input files from: https://signalsmith-audio.co.uk/code/stretch/inputs.zip
|
||||||
examples: out/stretch
|
examples: out/stretch
|
||||||
mkdir -p out/examples
|
mkdir -p out/examples
|
||||||
inputs/run-all.sh out/examples/u2- out/stretch --semitones=2
|
inputs/run-all.sh out/examples/u2- out/stretch --semitones=2 --exact
|
||||||
inputs/run-all.sh out/examples/d2- out/stretch --semitones=-2
|
inputs/run-all.sh out/examples/d2- out/stretch --semitones=-2 --exact
|
||||||
inputs/run-all.sh out/examples/u4- out/stretch --semitones=4
|
inputs/run-all.sh out/examples/u4- out/stretch --semitones=4 --exact
|
||||||
inputs/run-all.sh out/examples/d4- out/stretch --semitones=-4
|
inputs/run-all.sh out/examples/d4- out/stretch --semitones=-4 --exact
|
||||||
inputs/run-all.sh out/examples/u8- out/stretch --semitones=8
|
inputs/run-all.sh out/examples/u8- out/stretch --semitones=8 --exact
|
||||||
inputs/run-all.sh out/examples/d8- out/stretch --semitones=-8
|
inputs/run-all.sh out/examples/d8- out/stretch --semitones=-8 --exact
|
||||||
inputs/run-all.sh out/examples/u16- out/stretch --semitones=16
|
inputs/run-all.sh out/examples/u16- out/stretch --semitones=16 --exact
|
||||||
inputs/run-all.sh out/examples/d16- out/stretch --semitones=-16
|
inputs/run-all.sh out/examples/d16- out/stretch --semitones=-16 --exact
|
||||||
inputs/run-all.sh out/examples/t_8- out/stretch --time=0.8
|
inputs/run-all.sh out/examples/t_8- out/stretch --time=0.8 --exact
|
||||||
inputs/run-all.sh out/examples/t1_2- out/stretch --time=1.2
|
inputs/run-all.sh out/examples/t1_2- out/stretch --time=1.2 --exact
|
||||||
inputs/run-all.sh out/examples/t1_5- out/stretch --time=1.5
|
inputs/run-all.sh out/examples/t1_5- out/stretch --time=1.5 --exact
|
||||||
inputs/run-all.sh out/examples/t2- out/stretch --time=2
|
inputs/run-all.sh out/examples/t2- out/stretch --time=2 --exact
|
||||||
inputs/run-all.sh out/examples/t4- out/stretch --time=4
|
inputs/run-all.sh out/examples/t4- out/stretch --time=4 --exact
|
||||||
|
|
||||||
TEST_WAV ?= "inputs/voice.wav"
|
TEST_WAV ?= "inputs/voice.wav"
|
||||||
|
|
||||||
|
|||||||
26
cmd/main.cpp
26
cmd/main.cpp
@ -123,6 +123,14 @@ int main(int argc, char* argv[]) {
|
|||||||
std::cout << "\tallocated " << (initMemory.allocBytes/1000) << "kB, freed " << (initMemory.freeBytes/1000) << "kB\n";
|
std::cout << "\tallocated " << (initMemory.allocBytes/1000) << "kB, freed " << (initMemory.freeBytes/1000) << "kB\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signalsmith::MemoryTracker processMemory;
|
||||||
|
|
||||||
|
if (exactLength) {
|
||||||
|
outWav.samples.resize(outputLength*outWav.channels);
|
||||||
|
stopwatch.start();
|
||||||
|
processMemory = {};
|
||||||
|
stretch.exact(inWav, int(inputLength), outWav, int(outputLength));
|
||||||
|
} else {
|
||||||
// pad the input at the end, since we'll be reading slightly ahead
|
// pad the input at the end, since we'll be reading slightly ahead
|
||||||
size_t paddedInputLength = inputLength + stretch.inputLatency();
|
size_t paddedInputLength = inputLength + stretch.inputLatency();
|
||||||
inWav.samples.resize(paddedInputLength*inWav.channels);
|
inWav.samples.resize(paddedInputLength*inWav.channels);
|
||||||
@ -131,18 +139,18 @@ int main(int argc, char* argv[]) {
|
|||||||
int paddedOutputLength = outputLength + tailSamples;
|
int paddedOutputLength = outputLength + tailSamples;
|
||||||
outWav.samples.resize(paddedOutputLength*outWav.channels);
|
outWav.samples.resize(paddedOutputLength*outWav.channels);
|
||||||
|
|
||||||
signalsmith::MemoryTracker processMemory;
|
|
||||||
|
|
||||||
stopwatch.start();
|
stopwatch.start();
|
||||||
// The simplest way to deal with input latency (when have access to the audio buffer) is to always be slightly ahead in the input
|
// The simplest way to deal with input latency (when have access to the audio buffer) is to always be slightly ahead in the input
|
||||||
stretch.seek(inWav, stretch.inputLatency(), 1/time);
|
stretch.seek(inWav, stretch.inputLatency(), 1/time);
|
||||||
inWav.offset += stretch.inputLatency();
|
inWav.offset += stretch.inputLatency();
|
||||||
// Process it all in one call, although it works just the same if we split into smaller blocks
|
// Process it all in one call, although it works just the same if we split into smaller blocks
|
||||||
|
processMemory = {};
|
||||||
stretch.process(inWav, int(inputLength), outWav, int(outputLength));
|
stretch.process(inWav, int(inputLength), outWav, int(outputLength));
|
||||||
// Read the last bit of output without giving it any more input
|
// Read the last bit of output without giving it any more input
|
||||||
outWav.offset += outputLength;
|
outWav.offset += outputLength;
|
||||||
stretch.flush(outWav, tailSamples);
|
stretch.flush(outWav, tailSamples);
|
||||||
outWav.offset -= outputLength;
|
outWav.offset -= outputLength;
|
||||||
|
}
|
||||||
|
|
||||||
double processSeconds = stopwatch.lap();
|
double processSeconds = stopwatch.lap();
|
||||||
double processRate = (inWav.length()/inWav.sampleRate)/processSeconds;
|
double processRate = (inWav.length()/inWav.sampleRate)/processSeconds;
|
||||||
@ -154,20 +162,6 @@ int main(int argc, char* argv[]) {
|
|||||||
if (processMemory) args.errorExit("allocated during process()");
|
if (processMemory) args.errorExit("allocated during process()");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exactLength) {
|
|
||||||
// The start has some extra output - we could just trim it, but we might as well fold it back into the output
|
|
||||||
for (size_t c = 0; c < outWav.channels; ++c) {
|
|
||||||
for (int i = 0; i < stretch.outputLatency(); ++i) {
|
|
||||||
double trimmed = outWav[stretch.outputLatency() - 1 - i][c];
|
|
||||||
outWav[stretch.outputLatency() + i][c] -= trimmed; // reversed in time and negated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Skips the output
|
|
||||||
outWav.offset += stretch.outputLatency();
|
|
||||||
|
|
||||||
// the `.flush()` call already handled foldback stuff at the end (since we asked for a shorter `tailSamples`)
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef PROFILE_PLOT_CHUNKS
|
#ifdef PROFILE_PLOT_CHUNKS
|
||||||
signalsmith::plot::Figure figure;
|
signalsmith::plot::Figure figure;
|
||||||
auto &plot = figure(0, 0).plot(400, 150);
|
auto &plot = figure(0, 0).plot(400, 150);
|
||||||
|
|||||||
@ -402,6 +402,69 @@ struct SignalsmithStretch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class Inputs, class Outputs>
|
||||||
|
bool exact(Inputs &&inputs, int inputSamples, Outputs &&outputs, int outputSamples) {
|
||||||
|
if (outputSamples < outputLatency()*2) return false; // too short for this
|
||||||
|
|
||||||
|
struct ZeroPaddedInput {
|
||||||
|
Inputs &inputs;
|
||||||
|
int offset, length;
|
||||||
|
|
||||||
|
struct Channel {
|
||||||
|
ZeroPaddedInput &zpi;
|
||||||
|
int channel;
|
||||||
|
|
||||||
|
Sample operator[](int i) {
|
||||||
|
if (zpi.offset + i < zpi.length) return zpi.inputs[channel][zpi.offset + i];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Channel operator[](int c){
|
||||||
|
return {*this, c};
|
||||||
|
}
|
||||||
|
} zpi{inputs, inputLatency(), inputSamples};
|
||||||
|
seek(inputs, inputLatency(), Sample(inputSamples)/outputSamples); // start positioned on the centre of the input
|
||||||
|
process(zpi, inputSamples, outputs, outputSamples);
|
||||||
|
|
||||||
|
// Fold the first bit of the input back onto itself
|
||||||
|
for (int c = 0; c < channels; ++c) {
|
||||||
|
auto &&channel = outputs[c];
|
||||||
|
for (int i = 0; i < std::min<int>(outputSamples - outputLatency(), outputLatency()); ++i) {
|
||||||
|
channel[i + outputLatency()] -= channel[outputLatency() - 1 - i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Shuffle everything along to compensate for output latency
|
||||||
|
for (int c = 0; c < channels; ++c) {
|
||||||
|
auto &&channel = outputs[c];
|
||||||
|
for (int i = 0; i < outputSamples - outputLatency(); ++i) {
|
||||||
|
channel[i] = channel[i + outputLatency()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OffsetOutput {
|
||||||
|
Outputs &outputs;
|
||||||
|
int offset;
|
||||||
|
|
||||||
|
struct Channel {
|
||||||
|
OffsetOutput &oo;
|
||||||
|
int channel;
|
||||||
|
|
||||||
|
decltype(outputs[0][0]) operator[](int i) {
|
||||||
|
return oo.outputs[channel][oo.offset + i];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Channel operator[](int c){
|
||||||
|
return {*this, c};
|
||||||
|
}
|
||||||
|
} oo{outputs, outputSamples - outputLatency()};
|
||||||
|
// Get the final chunk - extra output is already folded back as part of this
|
||||||
|
flush(oo, outputLatency());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _splitComputation = false;
|
bool _splitComputation = false;
|
||||||
struct {
|
struct {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user