Rubber Band: Handle end of stream flushing better

The end of stream flushing should only request remaining samples once,
as should the rest of the process. The problem with the Rubber Band code
in this case is that it will wrap the remaining samples pointer after it
has been flushed, and emit a really huge number.

Also, add code to try to equalize the samples output with the samples
input, relative to the tempo stretching, as Rubber Band seems to flush
entirely too much data at end of stream, which can create noticeable
gaps in the output. This solves that as well.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
Christopher Snowhill 2025-02-11 18:09:04 -08:00
parent cb8d873b5b
commit cddfc3d1db

View file

@ -23,6 +23,7 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
BOOL tsrestartengine; BOOL tsrestartengine;
double tempo, pitch; double tempo, pitch;
double lastTempo, lastPitch; double lastTempo, lastPitch;
double stretchIn, stretchOut;
BOOL stopping, paused; BOOL stopping, paused;
BOOL processEntered; BOOL processEntered;
@ -246,6 +247,9 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
tsapplynewoptions = NO; tsapplynewoptions = NO;
tsrestartengine = NO; tsrestartengine = NO;
stretchIn = 0.0;
stretchOut = 0.0;
return YES; return YES;
} }
@ -390,6 +394,8 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
return nil; return nil;
} }
stretchIn += [chunk duration] / tempo;
bool endOfStream = [[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES; bool endOfStream = [[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES;
size_t frameCount = [chunk frameCount]; size_t frameCount = [chunk frameCount];
@ -406,32 +412,43 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
rubberband_process(ts, (const float * const *)rsPtrs, len, endOfStream); rubberband_process(ts, (const float * const *)rsPtrs, len, endOfStream);
size_t samplesAvailable; size_t samplesAvailable;
while(!stopping && (samplesAvailable = rubberband_available(ts)) > 0) { if(!stopping && (samplesAvailable = rubberband_available(ts)) > 0) {
while(!stopping && samplesAvailable > 0) {
if(toDrop > 0) { if(toDrop > 0) {
size_t blockDrop = toDrop; size_t blockDrop = toDrop;
if(blockDrop > samplesAvailable) blockDrop = samplesAvailable; if(blockDrop > samplesAvailable) blockDrop = samplesAvailable;
if(blockDrop > blockSize) blockDrop = blockSize; if(blockDrop > blockSize) blockDrop = blockSize;
rubberband_retrieve(ts, (float * const *)rsPtrs, (int)blockDrop); rubberband_retrieve(ts, (float * const *)rsPtrs, (int)blockDrop);
toDrop -= blockDrop; toDrop -= blockDrop;
samplesAvailable -= blockDrop;
continue; continue;
} }
size_t maxAvailable = 65536 - samplesBuffered; size_t maxAvailable = 65536 - samplesBuffered;
if(samplesAvailable > maxAvailable) { size_t samplesOut = samplesAvailable;
samplesAvailable = maxAvailable; if(samplesOut > maxAvailable) {
if(!samplesAvailable) { samplesOut = maxAvailable;
if(!samplesOut) {
break; break;
} }
} }
size_t samplesOut = samplesAvailable;
if(samplesOut > blockSize) samplesOut = blockSize; if(samplesOut > blockSize) samplesOut = blockSize;
rubberband_retrieve(ts, (float * const *)rsPtrs, (int)samplesOut); rubberband_retrieve(ts, (float * const *)rsPtrs, (int)samplesOut);
for(size_t i = 0; i < channels; ++i) { for(size_t i = 0; i < channels; ++i) {
cblas_scopy((int)samplesOut, rsPtrs[i], 1, &rsOutBuffer[samplesBuffered * channels + i], channels); cblas_scopy((int)samplesOut, rsPtrs[i], 1, &rsOutBuffer[samplesBuffered * channels + i], channels);
} }
samplesBuffered += samplesOut; samplesBuffered += samplesOut;
if(endOfStream && samplesOut < 4096) { samplesAvailable -= samplesOut;
}
}
if(endOfStream) {
self->endOfStream = YES; self->endOfStream = YES;
break; if(samplesBuffered) {
double outputDuration = (double)(samplesBuffered) / inputFormat.mSampleRate;
if(outputDuration + stretchOut > stretchIn) {
// Seems that Rubber Band over-flushes when it hits end of stream
samplesBuffered = (size_t)((stretchIn - stretchOut) * inputFormat.mSampleRate);
}
} }
} }
@ -444,6 +461,7 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
} }
[outputChunk assignSamples:rsOutBuffer frameCount:samplesBuffered]; [outputChunk assignSamples:rsOutBuffer frameCount:samplesBuffered];
samplesBuffered = 0; samplesBuffered = 0;
stretchOut += [outputChunk duration];
} }
processEntered = NO; processEntered = NO;