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 08b991a7c3
commit 108186450f

View file

@ -23,6 +23,7 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
BOOL tsrestartengine;
double tempo, pitch;
double lastTempo, lastPitch;
double stretchIn, stretchOut;
BOOL stopping, paused;
BOOL processEntered;
@ -246,6 +247,9 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
tsapplynewoptions = NO;
tsrestartengine = NO;
stretchIn = 0.0;
stretchOut = 0.0;
return YES;
}
@ -390,6 +394,8 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
return nil;
}
stretchIn += [chunk duration] / tempo;
bool endOfStream = [[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES;
size_t frameCount = [chunk frameCount];
@ -406,32 +412,43 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
rubberband_process(ts, (const float * const *)rsPtrs, len, endOfStream);
size_t samplesAvailable;
while(!stopping && (samplesAvailable = rubberband_available(ts)) > 0) {
if(toDrop > 0) {
size_t blockDrop = toDrop;
if(blockDrop > samplesAvailable) blockDrop = samplesAvailable;
if(blockDrop > blockSize) blockDrop = blockSize;
rubberband_retrieve(ts, (float * const *)rsPtrs, (int)blockDrop);
toDrop -= blockDrop;
continue;
}
size_t maxAvailable = 65536 - samplesBuffered;
if(samplesAvailable > maxAvailable) {
samplesAvailable = maxAvailable;
if(!samplesAvailable) {
break;
if(!stopping && (samplesAvailable = rubberband_available(ts)) > 0) {
while(!stopping && samplesAvailable > 0) {
if(toDrop > 0) {
size_t blockDrop = toDrop;
if(blockDrop > samplesAvailable) blockDrop = samplesAvailable;
if(blockDrop > blockSize) blockDrop = blockSize;
rubberband_retrieve(ts, (float * const *)rsPtrs, (int)blockDrop);
toDrop -= blockDrop;
samplesAvailable -= blockDrop;
continue;
}
size_t maxAvailable = 65536 - samplesBuffered;
size_t samplesOut = samplesAvailable;
if(samplesOut > maxAvailable) {
samplesOut = maxAvailable;
if(!samplesOut) {
break;
}
}
if(samplesOut > blockSize) samplesOut = blockSize;
rubberband_retrieve(ts, (float * const *)rsPtrs, (int)samplesOut);
for(size_t i = 0; i < channels; ++i) {
cblas_scopy((int)samplesOut, rsPtrs[i], 1, &rsOutBuffer[samplesBuffered * channels + i], channels);
}
samplesBuffered += samplesOut;
samplesAvailable -= samplesOut;
}
size_t samplesOut = samplesAvailable;
if(samplesOut > blockSize) samplesOut = blockSize;
rubberband_retrieve(ts, (float * const *)rsPtrs, (int)samplesOut);
for(size_t i = 0; i < channels; ++i) {
cblas_scopy((int)samplesOut, rsPtrs[i], 1, &rsOutBuffer[samplesBuffered * channels + i], channels);
}
samplesBuffered += samplesOut;
if(endOfStream && samplesOut < 4096) {
self->endOfStream = YES;
break;
}
if(endOfStream) {
self->endOfStream = YES;
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];
samplesBuffered = 0;
stretchOut += [outputChunk duration];
}
processEntered = NO;