Add reflected pre-roll to .outputSeek()

This commit is contained in:
Geraint 2025-08-11 16:37:44 +01:00
parent 2724daacaf
commit 90d6c686eb
2 changed files with 31 additions and 26 deletions

View File

@ -7,7 +7,7 @@ include(FetchContent)
FetchContent_Declare( FetchContent_Declare(
signalsmith-linear signalsmith-linear
GIT_REPOSITORY https://github.com/Signalsmith-Audio/linear.git GIT_REPOSITORY https://github.com/Signalsmith-Audio/linear.git
GIT_TAG 0.2.2 GIT_TAG 0.2.3
GIT_SHALLOW ON GIT_SHALLOW ON
) )
FetchContent_MakeAvailable(signalsmith-linear) FetchContent_MakeAvailable(signalsmith-linear)

View File

@ -89,7 +89,8 @@ struct SignalsmithStretch {
blockProcess = {}; blockProcess = {};
formantMetric.resize(bands + 2); formantMetric.resize(bands + 2);
tmpBuffer.resize(std::max(outputLatency()*channels, blockSamples + intervalSamples)); tmpProcessBuffer.resize(blockSamples + intervalSamples);
tmpPreRollBuffer.resize(outputLatency()*channels);
} }
// For querying the existing config // For querying the existing config
int blockSamples() const { int blockSamples() const {
@ -137,11 +138,11 @@ struct SignalsmithStretch {
// You should ideally feed it `seekLength()` frames of input, unless it's directly after a `.reset()` (in which case `.outputSeek()` might be a better choice) // You should ideally feed it `seekLength()` frames of input, unless it's directly after a `.reset()` (in which case `.outputSeek()` might be a better choice)
template<class Inputs> template<class Inputs>
void seek(Inputs &&inputs, int inputSamples, double playbackRate) { void seek(Inputs &&inputs, int inputSamples, double playbackRate) {
tmpBuffer.resize(0); tmpProcessBuffer.resize(0);
tmpBuffer.resize(stft.blockSamples() + stft.defaultInterval()); tmpProcessBuffer.resize(stft.blockSamples() + stft.defaultInterval());
int startIndex = std::max<int>(0, inputSamples - int(tmpBuffer.size())); // start position in input int startIndex = std::max<int>(0, inputSamples - int(tmpProcessBuffer.size())); // start position in input
int padStart = int(tmpBuffer.size() + startIndex) - inputSamples; // start position in tmpBuffer int padStart = int(tmpProcessBuffer.size() + startIndex) - inputSamples; // start position in tmpProcessBuffer
Sample totalEnergy = 0; Sample totalEnergy = 0;
for (int c = 0; c < channels; ++c) { for (int c = 0; c < channels; ++c) {
@ -149,12 +150,12 @@ struct SignalsmithStretch {
for (int i = startIndex; i < inputSamples; ++i) { for (int i = startIndex; i < inputSamples; ++i) {
Sample s = inputChannel[i]; Sample s = inputChannel[i];
totalEnergy += s*s; totalEnergy += s*s;
tmpBuffer[i - startIndex + padStart] = s; tmpProcessBuffer[i - startIndex + padStart] = s;
} }
stft.writeInput(c, tmpBuffer.size(), tmpBuffer.data()); stft.writeInput(c, tmpProcessBuffer.size(), tmpProcessBuffer.data());
} }
stft.moveInput(tmpBuffer.size()); stft.moveInput(tmpProcessBuffer.size());
if (totalEnergy >= noiseFloor) { if (totalEnergy >= noiseFloor) {
silenceCounter = 0; silenceCounter = 0;
silenceFirst = true; silenceFirst = true;
@ -180,20 +181,26 @@ struct SignalsmithStretch {
int seekSamples = inputLength - surplusInput; int seekSamples = inputLength - surplusInput;
seek(inputs, seekSamples, playbackRate); seek(inputs, seekSamples, playbackRate);
tmpBuffer.resize(outputLatency()*channels); tmpPreRollBuffer.resize(outputLatency()*channels);
struct PreRollOutput { struct BufferOutput {
Sample *samples; Sample *samples;
int length; int length;
Sample * operator[](int c) { Sample * operator[](int c) {
return samples + c*length; return samples + c*length;
} }
} preRollOutput{tmpBuffer.data(), outputLatency()}; } preRollOutput{tmpPreRollBuffer.data(), outputLatency()};
// Use the surplus input to produce pre-roll output // Use the surplus input to produce pre-roll output
OffsetIO<Inputs> offsetInput{inputs, seekSamples}; OffsetIO<Inputs> offsetInput{inputs, seekSamples};
process(offsetInput, surplusInput, preRollOutput, outputLatency()); process(offsetInput, surplusInput, preRollOutput, preRollOutput.length);
// TODO: put the thing down, flip it and reverse it
// put the thing down, flip it and reverse it
for (auto &v : tmpPreRollBuffer) v = -v;
for (int c = 0; c < channels; ++c) {
std::reverse(preRollOutput[c], preRollOutput[c] + preRollOutput.length);
stft.addOutput(c, preRollOutput.length, preRollOutput[c]);
}
} }
int outputSeekLength(Sample playbackRate) const { int outputSeekLength(Sample playbackRate) const {
return inputLatency() + playbackRate*outputLatency(); return inputLatency() + playbackRate*outputLatency();
@ -208,14 +215,14 @@ struct SignalsmithStretch {
auto copyInput = [&](int toIndex){ auto copyInput = [&](int toIndex){
int length = std::min<int>(int(stft.blockSamples() + stft.defaultInterval()), toIndex - prevCopiedInput); int length = std::min<int>(int(stft.blockSamples() + stft.defaultInterval()), toIndex - prevCopiedInput);
tmpBuffer.resize(length); tmpProcessBuffer.resize(length);
int offset = toIndex - length; int offset = toIndex - length;
for (int c = 0; c < channels; ++c) { for (int c = 0; c < channels; ++c) {
auto &&inputBuffer = inputs[c]; auto &&inputBuffer = inputs[c];
for (int i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
tmpBuffer[i] = inputBuffer[i + offset]; tmpProcessBuffer[i] = inputBuffer[i + offset];
} }
stft.writeInput(c, length, tmpBuffer.data()); stft.writeInput(c, length, tmpProcessBuffer.data());
} }
stft.moveInput(length); stft.moveInput(length);
prevCopiedInput = toIndex; prevCopiedInput = toIndex;
@ -433,19 +440,17 @@ struct SignalsmithStretch {
if (outputBlock > 0) process(zeros, outputBlock*playbackRate, outputs, outputBlock); if (outputBlock > 0) process(zeros, outputBlock*playbackRate, outputs, outputBlock);
int tailSamples = outputSamples - outputBlock; // at most one interval int tailSamples = outputSamples - outputBlock; // at most one interval
int reflectSamples = std::min<int>(tailSamples, outputSamples - tailSamples); tmpProcessBuffer.resize(tailSamples);
stft.finishOutput(1); stft.finishOutput(1);
for (int c = 0; c < channels; ++c) { for (int c = 0; c < channels; ++c) {
tmpBuffer.resize(tailSamples); stft.readOutput(c, tailSamples, tmpProcessBuffer.data());
stft.readOutput(c, tailSamples, tmpBuffer.data());
auto &&outputChannel = outputs[c]; auto &&outputChannel = outputs[c];
for (int i = 0; i < tailSamples; ++i) { for (int i = 0; i < tailSamples; ++i) {
outputChannel[outputBlock + i] = tmpBuffer[i]; outputChannel[outputBlock + i] = tmpProcessBuffer[i];
} }
tmpBuffer.resize(reflectSamples); stft.readOutput(c, tailSamples, tailSamples, tmpProcessBuffer.data());
stft.readOutput(c, reflectSamples, tailSamples, tmpBuffer.data()); for (int i = 0; i < tailSamples; ++i) {
for (int i = 0; i < reflectSamples; ++i) { outputChannel[outputBlock + tailSamples - 1 - i] -= tmpProcessBuffer[i];
outputChannel[outputBlock + tailSamples - 1 - i] -= tmpBuffer[i];
} }
} }
stft.reset(0.1f); stft.reset(0.1f);
@ -516,7 +521,7 @@ private:
typename STFT::Input stashedInput; typename STFT::Input stashedInput;
typename STFT::Output stashedOutput; typename STFT::Output stashedOutput;
std::vector<Sample> tmpBuffer; std::vector<Sample> tmpProcessBuffer, tmpPreRollBuffer;
int channels = 0, bands = 0; int channels = 0, bands = 0;
int prevInputOffset = -1; int prevInputOffset = -1;