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,88 +422,91 @@ 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;
|
||||||
|
|
||||||
AudioChunk *chunk = [self readAndMergeChunksAsFloat32:samplesToProcess];
|
|
||||||
if(!chunk || ![chunk frameCount]) {
|
|
||||||
processEntered = NO;
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
double streamTimestamp = [chunk streamTimestamp];
|
|
||||||
|
|
||||||
int channels = (int)(inputFormat.mChannelsPerFrame);
|
int channels = (int)(inputFormat.mChannelsPerFrame);
|
||||||
size_t frameCount = [chunk frameCount];
|
|
||||||
NSData *sampleData = [chunk removeSamples:frameCount];
|
|
||||||
|
|
||||||
for (size_t i = 0; i < channels; ++i) {
|
if(samplesToProcess > 0) {
|
||||||
cblas_scopy((int)frameCount, ((const float *)[sampleData bytes]) + i, channels, rsPtrs[i], 1);
|
AudioChunk *chunk = [self readAndMergeChunksAsFloat32:samplesToProcess];
|
||||||
|
if(!chunk || ![chunk frameCount]) {
|
||||||
|
processEntered = NO;
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
streamTimestamp = [chunk streamTimestamp];
|
||||||
|
streamTimeRatio = [chunk streamTimeRatio];
|
||||||
|
isHDCD = [chunk isHDCD];
|
||||||
|
|
||||||
|
size_t frameCount = [chunk frameCount];
|
||||||
|
NSData *sampleData = [chunk removeSamples:frameCount];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < channels; ++i) {
|
||||||
|
cblas_scopy((int)frameCount, ((const float *)[sampleData bytes]) + i, channels, rsPtrs[i], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
stretchIn += [chunk duration] / tempo;
|
||||||
|
|
||||||
|
endOfStream = [[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES;
|
||||||
|
|
||||||
|
int len = (int)frameCount;
|
||||||
|
|
||||||
|
rubberband_process(ts, (const float * const *)rsPtrs, len, endOfStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
stretchIn += [chunk duration] / tempo;
|
|
||||||
|
|
||||||
bool endOfStream = [[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES;
|
|
||||||
|
|
||||||
int len = (int)frameCount;
|
|
||||||
|
|
||||||
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) {
|
ssize_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;
|
continue;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
ssize_t maxAvailable = 65536 - samplesBuffered;
|
||||||
|
ssize_t samplesOut = samplesAvailable;
|
||||||
|
if(samplesOut > maxAvailable) {
|
||||||
|
samplesOut = maxAvailable;
|
||||||
|
if(samplesOut <= 0) {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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