Bug Fix: Significantly rework Rubber Band DSP
This should be perfectly safe to use in all situations now. It may have been unstable due to mishandling return values, or not supporting requesting more sample data from the library without feeding in more input first. Also, still signaling the End of Stream flag on chunk reading should be correct, as downstream processors only react to it when the buffer runs empty. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
parent
0dee6e05ab
commit
660d2b25be
1 changed files with 69 additions and 61 deletions
|
@ -22,13 +22,18 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
|
||||||
|
|
||||||
RubberBandState ts;
|
RubberBandState ts;
|
||||||
RubberBandOptions tslastoptions, tsnewoptions;
|
RubberBandOptions tslastoptions, tsnewoptions;
|
||||||
size_t blockSize, toDrop, samplesBuffered, tschannels;
|
size_t tschannels;
|
||||||
|
ssize_t blockSize, toDrop, samplesBuffered;
|
||||||
BOOL tsapplynewoptions;
|
BOOL tsapplynewoptions;
|
||||||
BOOL tsrestartengine;
|
BOOL tsrestartengine;
|
||||||
double tempo, pitch;
|
double tempo, pitch;
|
||||||
double lastTempo, lastPitch;
|
double lastTempo, lastPitch;
|
||||||
double stretchIn, stretchOut;
|
double stretchIn, stretchOut;
|
||||||
|
|
||||||
|
double streamTimestamp;
|
||||||
|
double streamTimeRatio;
|
||||||
|
BOOL isHDCD;
|
||||||
|
|
||||||
BOOL stopping, paused;
|
BOOL stopping, paused;
|
||||||
BOOL processEntered;
|
BOOL processEntered;
|
||||||
|
|
||||||
|
@ -226,7 +231,7 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
|
||||||
RubberBandOptions options = [self getRubberbandOptions];
|
RubberBandOptions options = [self getRubberbandOptions];
|
||||||
tslastoptions = options;
|
tslastoptions = options;
|
||||||
tschannels = inputFormat.mChannelsPerFrame;
|
tschannels = inputFormat.mChannelsPerFrame;
|
||||||
ts = rubberband_new(inputFormat.mSampleRate, inputFormat.mChannelsPerFrame, options, 1.0 / tempo, pitch);
|
ts = rubberband_new(inputFormat.mSampleRate, (int)tschannels, options, 1.0 / tempo, pitch);
|
||||||
if(!ts)
|
if(!ts)
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
|
@ -241,13 +246,13 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
|
||||||
rsPtrs[i] = &rsInBuffer[4096 * i];
|
rsPtrs[i] = &rsInBuffer[4096 * i];
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t toPad = rubberband_get_preferred_start_pad(ts);
|
ssize_t toPad = rubberband_get_preferred_start_pad(ts);
|
||||||
if(toPad > 0) {
|
if(toPad > 0) {
|
||||||
for(size_t i = 0; i < inputFormat.mChannelsPerFrame; ++i) {
|
for(size_t i = 0; i < tschannels; ++i) {
|
||||||
memset(rsPtrs[i], 0, 4096 * sizeof(float));
|
memset(rsPtrs[i], 0, 4096 * sizeof(float));
|
||||||
}
|
}
|
||||||
while(toPad > 0) {
|
while(toPad > 0) {
|
||||||
size_t p = toPad;
|
ssize_t p = toPad;
|
||||||
if(p > blockSize) p = blockSize;
|
if(p > blockSize) p = blockSize;
|
||||||
rubberband_process(ts, (const float * const *)rsPtrs, (int)p, false);
|
rubberband_process(ts, (const float * const *)rsPtrs, (int)p, false);
|
||||||
toPad -= p;
|
toPad -= p;
|
||||||
|
@ -417,19 +422,23 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
|
||||||
return [self readChunk:4096];
|
return [self readChunk:4096];
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t samplesToProcess = rubberband_get_samples_required(ts);
|
ssize_t samplesToProcess = rubberband_get_samples_required(ts);
|
||||||
if(samplesToProcess > blockSize)
|
if(samplesToProcess > blockSize)
|
||||||
samplesToProcess = blockSize;
|
samplesToProcess = blockSize;
|
||||||
|
|
||||||
|
int channels = (int)(inputFormat.mChannelsPerFrame);
|
||||||
|
|
||||||
|
if(samplesToProcess > 0) {
|
||||||
AudioChunk *chunk = [self readAndMergeChunksAsFloat32:samplesToProcess];
|
AudioChunk *chunk = [self readAndMergeChunksAsFloat32:samplesToProcess];
|
||||||
if(!chunk || ![chunk frameCount]) {
|
if(!chunk || ![chunk frameCount]) {
|
||||||
processEntered = NO;
|
processEntered = NO;
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
double streamTimestamp = [chunk streamTimestamp];
|
streamTimestamp = [chunk streamTimestamp];
|
||||||
|
streamTimeRatio = [chunk streamTimeRatio];
|
||||||
|
isHDCD = [chunk isHDCD];
|
||||||
|
|
||||||
int channels = (int)(inputFormat.mChannelsPerFrame);
|
|
||||||
size_t frameCount = [chunk frameCount];
|
size_t frameCount = [chunk frameCount];
|
||||||
NSData *sampleData = [chunk removeSamples:frameCount];
|
NSData *sampleData = [chunk removeSamples:frameCount];
|
||||||
|
|
||||||
|
@ -439,29 +448,28 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
|
||||||
|
|
||||||
stretchIn += [chunk duration] / tempo;
|
stretchIn += [chunk duration] / tempo;
|
||||||
|
|
||||||
bool endOfStream = [[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES;
|
endOfStream = [[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES;
|
||||||
|
|
||||||
int len = (int)frameCount;
|
int len = (int)frameCount;
|
||||||
|
|
||||||
rubberband_process(ts, (const float * const *)rsPtrs, len, endOfStream);
|
rubberband_process(ts, (const float * const *)rsPtrs, len, endOfStream);
|
||||||
|
}
|
||||||
|
|
||||||
ssize_t samplesAvailable;
|
ssize_t samplesAvailable;
|
||||||
if(!stopping && (samplesAvailable = rubberband_available(ts)) > 0) {
|
while(!stopping && (samplesAvailable = rubberband_available(ts)) > 0) {
|
||||||
while(!stopping && samplesAvailable > 0) {
|
|
||||||
if(toDrop > 0) {
|
if(toDrop > 0) {
|
||||||
size_t blockDrop = toDrop;
|
ssize_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;
|
ssize_t maxAvailable = 65536 - samplesBuffered;
|
||||||
size_t samplesOut = samplesAvailable;
|
ssize_t samplesOut = samplesAvailable;
|
||||||
if(samplesOut > maxAvailable) {
|
if(samplesOut > maxAvailable) {
|
||||||
samplesOut = maxAvailable;
|
samplesOut = maxAvailable;
|
||||||
if(!samplesOut) {
|
if(samplesOut <= 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,34 +479,34 @@ static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext;
|
||||||
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;
|
||||||
samplesAvailable -= samplesOut;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(endOfStream) {
|
if(endOfStream) {
|
||||||
self->endOfStream = YES;
|
if(samplesBuffered > 0) {
|
||||||
if(samplesBuffered) {
|
ssize_t delta = (stretchIn - stretchOut) * inputFormat.mSampleRate;
|
||||||
double outputDuration = (double)(samplesBuffered) / inputFormat.mSampleRate;
|
if(delta > 0 && samplesBuffered > delta) {
|
||||||
if(outputDuration + stretchOut > stretchIn) {
|
|
||||||
// Seems that Rubber Band over-flushes when it hits end of stream
|
// Seems that Rubber Band over-flushes when it hits end of stream
|
||||||
samplesBuffered = (size_t)((stretchIn - stretchOut) * inputFormat.mSampleRate);
|
// Also somehow, this was being miscalculated before and ending up negative
|
||||||
|
samplesBuffered = delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioChunk *outputChunk = nil;
|
AudioChunk *outputChunk = nil;
|
||||||
if(samplesBuffered) {
|
if(samplesBuffered > 0) {
|
||||||
outputChunk = [[AudioChunk alloc] init];
|
outputChunk = [[AudioChunk alloc] init];
|
||||||
[outputChunk setFormat:inputFormat];
|
[outputChunk setFormat:inputFormat];
|
||||||
if(inputChannelConfig) {
|
if(inputChannelConfig) {
|
||||||
[outputChunk setChannelConfig:inputChannelConfig];
|
[outputChunk setChannelConfig:inputChannelConfig];
|
||||||
}
|
}
|
||||||
if([chunk isHDCD]) [outputChunk setHDCD];
|
if(isHDCD) [outputChunk setHDCD];
|
||||||
[outputChunk setStreamTimestamp:streamTimestamp];
|
[outputChunk setStreamTimestamp:streamTimestamp];
|
||||||
[outputChunk setStreamTimeRatio:[chunk streamTimeRatio] * tempo];
|
[outputChunk setStreamTimeRatio:streamTimeRatio * tempo];
|
||||||
[outputChunk assignSamples:&rsOutBuffer[0] frameCount:samplesBuffered];
|
[outputChunk assignSamples:&rsOutBuffer[0] frameCount:samplesBuffered];
|
||||||
samplesBuffered = 0;
|
samplesBuffered = 0;
|
||||||
stretchOut += [outputChunk duration];
|
double chunkDuration = [outputChunk duration];
|
||||||
|
stretchOut += chunkDuration;
|
||||||
|
streamTimestamp += chunkDuration * [outputChunk streamTimeRatio];
|
||||||
}
|
}
|
||||||
|
|
||||||
processEntered = NO;
|
processEntered = NO;
|
||||||
|
|
Loading…
Reference in a new issue