Bug Fix: Restructure Rubber Band gapless handler

Change how samples are accounted for by the filter.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
Christopher Snowhill 2025-03-04 00:40:49 -08:00
parent bce00aff2e
commit 019bdd7a36

View file

@ -28,7 +28,8 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
BOOL tsrestartengine; BOOL tsrestartengine;
double tempo, pitch; double tempo, pitch;
double lastTempo, lastPitch; double lastTempo, lastPitch;
double stretchIn, stretchOut; double countIn;
uint64_t countOut;
double streamTimestamp; double streamTimestamp;
double streamTimeRatio; double streamTimeRatio;
@ -265,8 +266,8 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
tsrestartengine = NO; tsrestartengine = NO;
flushed = NO; flushed = NO;
stretchIn = 0.0; countIn = 0.0;
stretchOut = 0.0; countOut = 0;
return YES; return YES;
} }
@ -443,9 +444,9 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
streamTimeRatio = [chunk streamTimeRatio]; streamTimeRatio = [chunk streamTimeRatio];
isHDCD = [chunk isHDCD]; isHDCD = [chunk isHDCD];
stretchIn += [chunk duration] / tempo;
size_t frameCount = [chunk frameCount]; size_t frameCount = [chunk frameCount];
countIn += ((double)frameCount) / tempo;
NSData *sampleData = [chunk removeSamples:frameCount]; NSData *sampleData = [chunk removeSamples:frameCount];
for (size_t i = 0; i < channels; ++i) { for (size_t i = 0; i < channels; ++i) {
@ -487,11 +488,10 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
if(flushed) { if(flushed) {
if(samplesBuffered > 0) { if(samplesBuffered > 0) {
ssize_t delta = (stretchIn - stretchOut) * inputFormat.mSampleRate; ssize_t ideal = (ssize_t)floor(countIn + 0.5);
if(delta > 0 && samplesBuffered > delta) { if(countOut + samplesBuffered > ideal) {
// Seems that Rubber Band over-flushes when it hits end of stream // Rubber Band does not account for flushing duration in real time mode
// Also somehow, this was being miscalculated before and ending up negative samplesBuffered = ideal - countOut;
samplesBuffered = delta;
} }
} }
} }
@ -507,9 +507,9 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
[outputChunk setStreamTimestamp:streamTimestamp]; [outputChunk setStreamTimestamp:streamTimestamp];
[outputChunk setStreamTimeRatio:streamTimeRatio * tempo]; [outputChunk setStreamTimeRatio:streamTimeRatio * tempo];
[outputChunk assignSamples:&rsOutBuffer[0] frameCount:samplesBuffered]; [outputChunk assignSamples:&rsOutBuffer[0] frameCount:samplesBuffered];
countOut += samplesBuffered;
samplesBuffered = 0; samplesBuffered = 0;
double chunkDuration = [outputChunk duration]; double chunkDuration = [outputChunk duration];
stretchOut += chunkDuration;
streamTimestamp += chunkDuration * [outputChunk streamTimeRatio]; streamTimestamp += chunkDuration * [outputChunk streamTimeRatio];
} }
@ -525,9 +525,11 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
// how much audio will be lopped off at the end of the process. // how much audio will be lopped off at the end of the process.
// //
// Tested once, this tends to be close to zero when actually called. // Tested once, this tends to be close to zero when actually called.
rbBuffered = stretchIn - stretchOut; rbBuffered = countIn - (double)(countOut);
if(rbBuffered < 0.0) { if(rbBuffered < 0) {
rbBuffered = 0.0; rbBuffered = 0.0;
} else {
rbBuffered /= inputFormat.mSampleRate;
} }
} }
return [buffer listDuration] + rbBuffered; return [buffer listDuration] + rbBuffered;