Merge from branch process-size

This commit is contained in:
Chris Cannam
2023-06-08 09:48:16 +01:00
6 changed files with 319 additions and 109 deletions

View File

@@ -803,6 +803,13 @@ public:
* study() (to which you may pass any number of samples at a time,
* and from which there is no output).
*
* Despite the existence of this call and its use of a size_t
* argument, there is an internal limit to the maximum process
* buffer size that can be requested. This is currently 524288 (or
* 2^19). The Rubber Band API is essentially block-based and is
* not designed to process an entire signal within a single
* process cycle.
*
* Note that the value of "samples" refers to the number of audio
* sample frames, which may be multi-channel, not the number of
* individual samples. (For example, one second of stereo audio

View File

@@ -307,7 +307,10 @@ R2Stretcher::setExpectedInputDuration(size_t samples)
void
R2Stretcher::setMaxProcessSize(size_t samples)
{
m_log.log(2, "R2Stretcher::setMaxProcessSize", samples);
if (samples <= m_maxProcessSize) return;
m_log.log(2, "R2Stretcher::setMaxProcessSize: increasing from, to", m_maxProcessSize, samples);
m_maxProcessSize = samples;
reconfigure();
@@ -1232,6 +1235,8 @@ R2Stretcher::process(const float *const *input, size_t samples, bool final)
{
Profiler profiler("R2Stretcher::process");
m_log.log(3, "process entering, samples and final", samples, final);
if (m_mode == Finished) {
m_log.log(0, "R2Stretcher::process: Cannot process again after final chunk");
return;
@@ -1294,10 +1299,16 @@ R2Stretcher::process(const float *const *input, size_t samples, bool final)
consumed[c],
samples - consumed[c],
final);
if (c == 0) {
m_log.log(3, "consumed channel 0, consumed and samples now", consumed[c], samples);
}
if (consumed[c] < samples) {
allConsumed = false;
} else {
if (final) {
if (c == 0) {
m_log.log(2, "final is true, setting input size", m_channelData[c]->inCount);
}
m_channelData[c]->inputSize = m_channelData[c]->inCount;
}
}
@@ -1332,10 +1343,10 @@ R2Stretcher::process(const float *const *input, size_t samples, bool final)
}
#endif
m_log.log(2, "process looping");
m_log.log(3, "process looping");
}
m_log.log(2, "process returning");
m_log.log(3, "process returning");
if (final) m_mode = Finished;
}

View File

@@ -175,8 +175,10 @@ R2Stretcher::consumeChannel(size_t c,
Profiler profiler2("R2Stretcher::resample");
toWrite = int(ceil(samples / m_pitchScale));
bool shortened = false;
if (writable < toWrite) {
samples = int(floor(writable * m_pitchScale));
shortened = true;
if (samples == 0) return 0;
}
@@ -213,7 +215,7 @@ R2Stretcher::consumeChannel(size_t c,
&input,
samples,
1.0 / m_pitchScale,
final);
final && !shortened);
#if defined(STRETCHER_IMPL_RESAMPLER_MUTEX_REQUIRED)
if (m_threaded) {
@@ -224,6 +226,7 @@ R2Stretcher::consumeChannel(size_t c,
if (writable < toWrite) {
if (resampling) {
m_log.log(1, "consumeChannel: resampler produced too much output, cannot use", toWrite, writable);
return 0;
}
toWrite = writable;
@@ -323,6 +326,8 @@ R2Stretcher::processOneChunk()
{
Profiler profiler("R2Stretcher::processOneChunk");
m_log.log(3, "R2Stretcher::processOneChunk");
// Process a single chunk for all channels, provided there is
// enough data on each channel for at least one chunk. This is
// able to calculate increments as it goes along.
@@ -335,6 +340,7 @@ R2Stretcher::processOneChunk()
return false;
}
ChannelData &cd = *m_channelData[c];
m_log.log(3, "read space and draining", cd.inbuf->getReadSpace(), cd.draining);
if (!cd.draining) {
size_t ready = cd.inbuf->getReadSpace();
assert(ready >= m_aWindowSize || cd.inputSize >= 0);
@@ -356,6 +362,7 @@ R2Stretcher::processOneChunk()
m_channelData[c]->chunkCount++;
}
m_log.log(3, "R2Stretcher::processOneChunk returning", last);
return last;
}
@@ -395,7 +402,9 @@ R2Stretcher::testInbufReadSpace(size_t c)
m_log.log(2, "read space = 0, giving up");
return false;
} else if (rs < m_aWindowSize/2) {
m_log.log(2, "setting draining true with read space", rs);
m_log.log(2, "setting draining true with read space and window size", rs, m_aWindowSize);
m_log.log(2, "outbuf read space is", cd.outbuf->getReadSpace());
m_log.log(2, "accumulator fill is", cd.accumulatorFill);
cd.draining = true;
}
}
@@ -454,6 +463,11 @@ R2Stretcher::processChunkForChannel(size_t c,
if (cd.draining) {
m_log.log(2, "draining: accumulator fill and shift increment", cd.accumulatorFill, shiftIncrement);
m_log.log(2, "outbuf read space is", cd.outbuf->getReadSpace());
if (cd.accumulatorFill == 0) {
m_log.log(2, "draining: accumulator empty");
return true;
}
if (shiftIncrement == 0) {
m_log.log(0, "WARNING: draining: shiftIncrement == 0, can't handle that in this context: setting to", m_increment);
shiftIncrement = m_increment;
@@ -494,6 +508,7 @@ R2Stretcher::processChunkForChannel(size_t c,
}
writeChunk(c, shiftIncrement, last);
m_log.log(3, "processChunkForChannel: accumulatorFill now; returning", cd.accumulatorFill, last);
return last;
}
@@ -1069,7 +1084,7 @@ R2Stretcher::writeChunk(size_t channel, size_t shiftIncrement, bool last)
si,
1.0 / m_pitchScale,
last);
#if defined(STRETCHER_IMPL_RESAMPLER_MUTEX_REQUIRED)
if (m_threaded) {
m_resamplerMutex.unlock();
@@ -1095,10 +1110,12 @@ R2Stretcher::writeChunk(size_t channel, size_t shiftIncrement, bool last)
} else {
cd.accumulatorFill = 0;
if (cd.draining) {
m_log.log(2, "processChunks: setting outputComplete to true");
m_log.log(2, "writeChunk: setting outputComplete to true");
cd.outputComplete = true;
}
}
m_log.log(3, "writeChunk: accumulatorFill now", cd.accumulatorFill);
}
void
@@ -1143,6 +1160,8 @@ R2Stretcher::writeOutput(RingBuffer<float> &to,
}
outCount += written;
m_log.log(3, "written and new outCount", written, outCount);
return;
}
@@ -1168,6 +1187,8 @@ R2Stretcher::available() const
{
Profiler profiler("R2Stretcher::available");
m_log.log(3, "R2Stretcher::available");
#ifndef NO_THREADING
if (m_threaded) {
MutexLocker locker(&m_threadSetMutex);
@@ -1180,14 +1201,24 @@ R2Stretcher::available() const
#ifndef NO_THREADING
if (!m_threaded) {
#endif
for (size_t c = 0; c < m_channels; ++c) {
if (m_channelData[c]->inputSize >= 0) {
if (m_channelData[c]->inbuf->getReadSpace() > 0) {
m_log.log(2, "calling processChunks from available, channel" , c);
//!!! do we ever actually do this? if so, this method should not be const
// ^^^ yes, we do sometimes -- e.g. when fed a very short file
bool any = false, last = false;
((R2Stretcher *)this)->processChunks(c, any, last);
if (m_channelData[0]->inputSize >= 0) {
//!!! do we ever actually do this? if so, this method should not be const
// ^^^ yes, we do sometimes -- e.g. when fed a very short file
if (m_realtime) {
while (m_channelData[0]->inbuf->getReadSpace() > 0 ||
m_channelData[0]->draining) {
m_log.log(2, "calling processOneChunk from available");
if (((R2Stretcher *)this)->processOneChunk()) {
break;
}
}
} else {
for (size_t c = 0; c < m_channels; ++c) {
if (m_channelData[c]->inbuf->getReadSpace() > 0) {
m_log.log(2, "calling processChunks from available, channel" , c);
bool any = false, last = false;
((R2Stretcher *)this)->processChunks(c, any, last);
}
}
}
}
@@ -1208,11 +1239,23 @@ R2Stretcher::available() const
if (m_channelData[i]->resampler) haveResamplers = true;
}
if (min == 0 && consumed) return -1;
if (m_pitchScale == 1.0) return min;
if (min == 0 && consumed) {
m_log.log(2, "R2Stretcher::available: end of stream");
return -1;
}
if (m_pitchScale == 1.0) {
m_log.log(3, "R2Stretcher::available (not shifting): returning", min);
return min;
}
if (haveResamplers) return min; // resampling has already happened
return int(floor(min / m_pitchScale));
int rv;
if (haveResamplers) {
rv = min; // resampling has already happened
} else {
rv = int(floor(min / m_pitchScale));
}
m_log.log(3, "R2Stretcher::available (shifting): returning", rv);
return rv;
}
size_t
@@ -1220,6 +1263,8 @@ R2Stretcher::retrieve(float *const *output, size_t samples) const
{
Profiler profiler("R2Stretcher::retrieve");
m_log.log(3, "R2Stretcher::retrieve", samples);
size_t got = samples;
for (size_t c = 0; c < m_channels; ++c) {
@@ -1244,6 +1289,8 @@ R2Stretcher::retrieve(float *const *output, size_t samples) const
}
}
m_log.log(3, "R2Stretcher::retrieve returning", got);
return got;
}

View File

@@ -105,16 +105,19 @@ R3Stretcher::initialise()
int inRingBufferSize = getWindowSourceSize() * 16;
int outRingBufferSize = getWindowSourceSize() * 16;
int hopBufferSize =
2 * std::max(m_limits.maxInhop, m_limits.maxPreferredOuthop);
m_channelData.clear();
for (int c = 0; c < m_parameters.channels; ++c) {
m_channelData.push_back(std::make_shared<ChannelData>
(segmenterParameters,
classifierParameters,
m_guideConfiguration.longestFftSize,
getWindowSourceSize(),
inRingBufferSize,
outRingBufferSize));
outRingBufferSize,
hopBufferSize));
for (int b = 0; b < m_guideConfiguration.fftBandLimitCount; ++b) {
const auto &band = m_guideConfiguration.fftBandLimits[b];
int fftSize = band.fftSize;
@@ -634,19 +637,61 @@ R3Stretcher::getSamplesRequired() const
}
void
R3Stretcher::setMaxProcessSize(size_t n)
R3Stretcher::setMaxProcessSize(size_t requested)
{
size_t oldSize = m_channelData[0]->inbuf->getSize();
size_t newSize = getWindowSourceSize() + n;
if (newSize > oldSize) {
m_log.log(1, "setMaxProcessSize: resizing from and to", oldSize, newSize);
for (int c = 0; c < m_parameters.channels; ++c) {
m_channelData[c]->inbuf = std::unique_ptr<RingBuffer<float>>
(m_channelData[c]->inbuf->resized(newSize));
}
m_log.log(2, "R3Stretcher::setMaxProcessSize", requested);
int n = m_limits.overallMaxProcessSize;
if (requested > size_t(n)) {
m_log.log(0, "R3Stretcher::setMaxProcessSize: request exceeds overall limit", requested, n);
} else {
m_log.log(1, "setMaxProcessSize: nothing to be done, newSize <= oldSize", newSize, oldSize);
n = int(requested);
}
ensureInbuf(n * 2, false);
ensureOutbuf(n * 8, false);
}
void
R3Stretcher::ensureInbuf(int required, bool warn)
{
int ws = m_channelData[0]->inbuf->getWriteSpace();
if (required < ws) {
return;
}
if (warn) {
m_log.log(0, "R3Stretcher::ensureInbuf: WARNING: Forced to increase input buffer size. Either setMaxProcessSize was not properly called, process is being called repeatedly without retrieve, or an internal error has led to an incorrect resampler output calculation. Samples to write and space available", required, ws);
}
size_t oldSize = m_channelData[0]->inbuf->getSize();
size_t newSize = oldSize - ws + required;
if (newSize < oldSize * 2) newSize = oldSize * 2;
m_log.log(warn ? 0 : 2, "R3Stretcher::ensureInbuf: old and new sizes", oldSize, newSize);
for (int c = 0; c < m_parameters.channels; ++c) {
auto newBuf = m_channelData[c]->inbuf->resized(newSize);
m_channelData[c]->inbuf = std::unique_ptr<RingBuffer<float>>(newBuf);
// mixdown is used for mid-side mixing as well as the single
// hop output mix, so it needs to be enough to match the inbuf
m_channelData[c]->mixdown.resize(newSize, 0.f);
}
}
void
R3Stretcher::ensureOutbuf(int required, bool warn)
{
int ws = m_channelData[0]->outbuf->getWriteSpace();
if (required < ws) {
return;
}
if (warn) {
m_log.log(0, "R3Stretcher::ensureOutbuf: WARNING: Forced to increase output buffer size. Using smaller process blocks or an artificially larger value for setMaxProcessSize may avoid this. Samples to write and space available", required, ws);
}
size_t oldSize = m_channelData[0]->outbuf->getSize();
size_t newSize = oldSize - ws + required;
if (newSize < oldSize * 2) newSize = oldSize * 2;
m_log.log(warn ? 0 : 2, "R3Stretcher::ensureOutbuf: old and new sizes", oldSize, newSize);
for (int c = 0; c < m_parameters.channels; ++c) {
auto newBuf = m_channelData[c]->outbuf->resized(newSize);
m_channelData[c]->outbuf = std::unique_ptr<RingBuffer<float>>(newBuf);
}
}
@@ -660,6 +705,13 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final)
return;
}
int n = m_limits.overallMaxProcessSize;
if (samples > size_t(n)) {
m_log.log(0, "R3Stretcher::process: request exceeds overall limit", samples, n);
} else {
n = int(samples);
}
if (!isRealTime()) {
if (m_mode == ProcessMode::Studying) {
@@ -698,8 +750,12 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final)
// when the ratio changes.
int pad = getWindowSourceSize() / 2;
m_log.log(1, "offline mode: prefilling with", pad);
ensureInbuf(pad);
for (int c = 0; c < m_parameters.channels; ++c) {
m_channelData[c]->inbuf->zero(pad);
int zeroed = m_channelData[c]->inbuf->zero(pad);
if (zeroed != pad) {
m_log.log(0, "R3Stretcher: WARNING: too few padding samples written", zeroed, pad);
}
}
// NB by the time we skip this later we may have resampled
@@ -709,52 +765,28 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final)
}
}
if (final) {
// We don't distinguish between Finished and "draining, but
// haven't yet delivered all the samples" because the
// distinction is meaningless internally - it only affects
// whether available() finds any samples in the buffer
m_log.log(1, "final is set, entering Finished mode");
m_mode = ProcessMode::Finished;
} else {
m_mode = ProcessMode::Processing;
}
bool resamplingBefore = false;
areWeResampling(&resamplingBefore, nullptr);
int channels = m_parameters.channels;
int inputIx = 0;
if (samples == 0 && final) {
if (n == 0 && final) {
m_log.log(2, "process: no samples but final specified, consuming");
consume();
consume(true);
} else while (inputIx < int(samples)) {
} else while (inputIx < n) {
int remaining = n - inputIx;
int remaining = int(samples) - inputIx;
int ws = m_channelData[0]->inbuf->getWriteSpace();
if (ws == 0) {
consume();
ws = m_channelData[0]->inbuf->getWriteSpace();
}
if (ws == 0) {
m_log.log(0, "R3Stretcher::process: WARNING: Forced to increase input buffer size. Either setMaxProcessSize was not properly called, process is being called repeatedly without retrieve, or an internal error has led to an incorrect resampler output calculation. Samples to write", remaining);
size_t oldSize = m_channelData[0]->inbuf->getSize();
size_t newSize = oldSize + remaining;
if (newSize < oldSize * 2) newSize = oldSize * 2;
m_log.log(0, "R3Stretcher::process: old and new sizes", oldSize, newSize);
for (int c = 0; c < m_parameters.channels; ++c) {
auto newBuf = m_channelData[c]->inbuf->resized(newSize);
m_channelData[c]->inbuf =
std::unique_ptr<RingBuffer<float>>(newBuf);
}
continue;
consume(false);
}
ensureInbuf(remaining);
ws = m_channelData[0]->inbuf->getWriteSpace();
if (resamplingBefore) {
@@ -770,24 +802,31 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final)
int resampleInput = std::min(remaining, maxResampleInput);
if (resampleInput == 0) resampleInput = 1;
m_log.log(2, "R3Stretcher::process: resamplingBefore is true, resampleInput and maxResampleOutput", resampleInput, maxResampleOutput);
prepareInput(input, inputIx, resampleInput);
bool finalHop = (final && inputIx + resampleInput >= n);
int resampleOutput = m_resampler->resample
(m_channelAssembly.resampled.data(),
maxResampleOutput,
m_channelAssembly.input.data(),
resampleInput,
1.0 / m_pitchScale,
final);
finalHop);
inputIx += resampleInput;
m_log.log(2, "process: resamplingBefore is true, writing to inbuf from resampled data, former read space and samples being added", m_channelData[0]->inbuf->getReadSpace(), resampleOutput);
for (int c = 0; c < m_parameters.channels; ++c) {
m_channelData[c]->inbuf->write
int written = m_channelData[c]->inbuf->write
(m_channelData.at(c)->resampled.data(),
resampleOutput);
if (written != resampleOutput) {
m_log.log(0, "R3Stretcher: WARNING: too few samples written to input buffer from resampler", written, resampleOutput);
}
}
} else {
@@ -798,13 +837,30 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final)
prepareInput(input, inputIx, toWrite);
for (int c = 0; c < m_parameters.channels; ++c) {
m_channelData[c]->inbuf->write
int written = m_channelData[c]->inbuf->write
(m_channelAssembly.input[c], toWrite);
if (written != toWrite) {
m_log.log(0, "R3Stretcher: WARNING: too few samples written to input buffer", written, toWrite);
}
}
inputIx += toWrite;
}
consume();
consume(final && inputIx >= n);
}
if (final) {
// We don't distinguish between Finished and "draining, but
// haven't yet delivered all the samples" because the
// distinction is meaningless internally - it only affects
// whether available() finds any samples in the buffer
m_log.log(1, "final is set, entering Finished mode");
m_mode = ProcessMode::Finished;
} else {
m_mode = ProcessMode::Processing;
}
}
@@ -826,6 +882,8 @@ R3Stretcher::retrieve(float *const *output, size_t samples) const
int got = samples;
m_log.log(2, "retrieve: requested, outbuf has", samples, m_channelData[0]->outbuf->getReadSpace());
for (int c = 0; c < m_parameters.channels; ++c) {
int gotHere = m_channelData[c]->outbuf->read(output[c], got);
if (gotHere < got) {
@@ -847,6 +905,8 @@ R3Stretcher::retrieve(float *const *output, size_t samples) const
}
}
m_log.log(2, "retrieve: returning, outbuf now has", got, m_channelData[0]->outbuf->getReadSpace());
return got;
}
@@ -856,6 +916,11 @@ R3Stretcher::prepareInput(const float *const *input, int ix, int n)
if (useMidSide()) {
auto &c0 = m_channelData.at(0)->mixdown;
auto &c1 = m_channelData.at(1)->mixdown;
int bufsize = c0.size();
if (n > bufsize) {
m_log.log(0, "R3Stretcher::prepareInput: WARNING: called with size greater than mixdown buffer length", n, bufsize);
n = bufsize;
}
for (int i = 0; i < n; ++i) {
float l = input[0][i + ix];
float r = input[1][i + ix];
@@ -874,7 +939,7 @@ R3Stretcher::prepareInput(const float *const *input, int ix, int n)
}
void
R3Stretcher::consume()
R3Stretcher::consume(bool final)
{
Profiler profiler("R3Stretcher::consume");
@@ -927,23 +992,21 @@ R3Stretcher::consume()
auto &cd0 = m_channelData.at(0);
m_log.log(2, "consume: write space and outhop", cd0->outbuf->getWriteSpace(), outhop);
while (cd0->outbuf->getWriteSpace() >= outhop) {
// NB our ChannelData, ScaleData, and ChannelScaleData maps
// contain shared_ptrs; whenever we retain one of them in a
// variable, we do so by reference to avoid copying the shared_ptr
// (as that is not realtime safe). Same goes for the map iterators
while (true) {
Profiler profiler("R3Stretcher::consume/loop");
// NB our ChannelData, ScaleData, and ChannelScaleData maps
// contain shared_ptrs; whenever we retain one of them in a
// variable, we do so by reference to avoid copying the
// shared_ptr (as that is not realtime safe). Same goes for
// the map iterators
int readSpace = cd0->inbuf->getReadSpace();
m_log.log(2, "consume: read space and window source size", readSpace, getWindowSourceSize());
m_log.log(2, "consume: read space", readSpace);
if (readSpace < getWindowSourceSize()) {
if (m_mode == ProcessMode::Finished) {
if (final) {
if (readSpace == 0) {
int fill = cd0->scales.at(longest)->accumulatorFill;
if (fill == 0) {
@@ -958,6 +1021,8 @@ R3Stretcher::consume()
}
}
ensureOutbuf(outhop);
// Analysis
for (int c = 0; c < channels; ++c) {
@@ -1009,9 +1074,9 @@ R3Stretcher::consume()
m_channelAssembly.resampled[c] = cd->resampled.data();
}
bool final = (m_mode == ProcessMode::Finished &&
readSpace < inhop &&
cd0->scales.at(longest)->accumulatorFill <= outhop);
bool finalHop = (final &&
readSpace < inhop &&
cd0->scales.at(longest)->accumulatorFill <= outhop);
resampledCount = m_resampler->resample
(m_channelAssembly.resampled.data(),
@@ -1019,7 +1084,7 @@ R3Stretcher::consume()
m_channelAssembly.mixdown.data(),
outhop,
1.0 / m_pitchScale,
final);
finalHop);
}
// Emit
@@ -1041,8 +1106,8 @@ R3Stretcher::consume()
int advanceCount = inhop;
if (advanceCount > readSpace) {
// This should happen only when draining (Finished)
if (m_mode != ProcessMode::Finished) {
// This should happen only when draining
if (!final) {
m_log.log(0, "R3Stretcher: WARNING: readSpace < inhop when processing is not yet finished", readSpace, inhop);
}
advanceCount = readSpace;
@@ -1050,12 +1115,19 @@ R3Stretcher::consume()
for (int c = 0; c < channels; ++c) {
auto &cd = m_channelData.at(c);
int written = 0;
if (resamplingAfter) {
cd->outbuf->write(cd->resampled.data(), writeCount);
written = cd->outbuf->write(cd->resampled.data(), writeCount);
} else {
cd->outbuf->write(cd->mixdown.data(), writeCount);
written = cd->outbuf->write(cd->mixdown.data(), writeCount);
}
if (written != writeCount) {
m_log.log(0, "R3Stretcher: WARNING: too few samples written to output buffer", written, writeCount);
}
int skipped = cd->inbuf->skip(advanceCount);
if (skipped != advanceCount) {
m_log.log(0, "R3Stretcher: WARNING: too few samples advanced", skipped, advanceCount);
}
cd->inbuf->skip(advanceCount);
}
m_consumedInputDuration += advanceCount;
@@ -1065,7 +1137,10 @@ R3Stretcher::consume()
int rs = cd0->outbuf->getReadSpace();
int toSkip = std::min(m_startSkip, rs);
for (int c = 0; c < channels; ++c) {
m_channelData.at(c)->outbuf->skip(toSkip);
int skipped = m_channelData.at(c)->outbuf->skip(toSkip);
if (skipped != toSkip) {
m_log.log(0, "R3Stretcher: WARNING: too few samples skipped at output", skipped, toSkip);
}
}
m_startSkip -= toSkip;
m_totalOutputDuration = rs - toSkip;
@@ -1074,6 +1149,8 @@ R3Stretcher::consume()
m_prevInhop = inhop;
m_prevOuthop = outhop;
}
m_log.log(2, "consume: write space reduced to", cd0->outbuf->getWriteSpace());
}
void

View File

@@ -110,13 +110,15 @@ protected:
int minInhop;
int maxInhopWithReadahead;
int maxInhop;
int overallMaxProcessSize;
Limits(RubberBandStretcher::Options options, double rate) :
// commented values are results when rate = 44100 or 48000
minPreferredOuthop(roundUpDiv(rate, 512)), // 128
maxPreferredOuthop(roundUpDiv(rate, 128)), // 512
minInhop(1),
maxInhopWithReadahead(roundUpDiv(rate, 64)), // 1024
maxInhop(roundUpDiv(rate, 32)) { // 2048
maxInhop(roundUpDiv(rate, 32)), // 2048
overallMaxProcessSize(524288) {
if (options & RubberBandStretcher::OptionWindowShort) {
// See note in calculateHop
minPreferredOuthop = roundUpDiv(rate, 256); // 256
@@ -228,10 +230,10 @@ protected:
std::unique_ptr<FormantData> formant;
ChannelData(BinSegmenter::Parameters segmenterParameters,
BinClassifier::Parameters classifierParameters,
int /*!!! longestFftSize */,
int windowSourceSize,
int inRingBufferSize,
int outRingBufferSize) :
int outRingBufferSize,
int hopBufferSize) :
scales(),
windowSource(windowSourceSize, 0.0),
readahead(segmenterParameters.fftSize),
@@ -244,7 +246,7 @@ protected:
segmenter(new BinSegmenter(segmenterParameters)),
segmentation(), prevSegmentation(), nextSegmentation(),
mixdown(inRingBufferSize, 0.f),
resampled(outRingBufferSize, 0.f),
resampled(hopBufferSize, 0.f),
inbuf(new RingBuffer<float>(inRingBufferSize)),
outbuf(new RingBuffer<float>(outRingBufferSize)),
formant(new FormantData(segmenterParameters.fftSize)) { }
@@ -359,8 +361,10 @@ protected:
void initialise();
void prepareInput(const float *const *input, int ix, int n);
void consume();
void consume(bool final);
void createResampler();
void ensureInbuf(int, bool warn = true);
void ensureOutbuf(int, bool warn = true);
void calculateHop();
void updateRatioFromMap();
void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop);

View File

@@ -233,6 +233,7 @@ static vector<float> process_realtime(RubberBandStretcher &stretcher,
const vector<float> &in,
int nOut,
int bs,
bool roundUpProcessSize,
bool printDebug)
{
int n = in.size();
@@ -270,9 +271,20 @@ static vector<float> process_realtime(RubberBandStretcher &stretcher,
} else if (available == 0) { // need to provide more input
int required = stretcher.getSamplesRequired();
BOOST_TEST(required > 0); // because available == 0
int toProcess = std::min(required, n - inOffset);
int toProcess = required;
if (roundUpProcessSize) {
// We sometimes want to explicitly test passing
// large blocks to process, longer than
// getSamplesRequired indicates
toProcess = std::max(required, bs);
}
bool final = false;
if (toProcess >= n - inOffset) {
toProcess = n - inOffset;
final = true;
}
const float *const source = in.data() + inOffset;
bool final = (toProcess < required);
// cerr << "toProcess = " << toProcess << ", inOffset = " << inOffset << ", n = " << n << ", required = " << required << ", outOffset = " << outOffset << ", obtained = " << obtained << ", bs = " << bs << ", final = " << final << endl;
stretcher.process(&source, toProcess, final);
inOffset += toProcess;
BOOST_TEST(stretcher.available() > 0);
@@ -311,6 +323,7 @@ static void sinusoid_realtime(RubberBandStretcher::Options options,
double timeRatio,
double pitchScale,
int bs = 512,
bool roundUpProcessSize = false,
bool printDebug = false)
{
int n = (timeRatio < 1.0 ? 80000 : 40000);
@@ -324,11 +337,12 @@ static void sinusoid_realtime(RubberBandStretcher::Options options,
// expected place
RubberBandStretcher stretcher(rate, 1, options, timeRatio, pitchScale);
stretcher.setMaxProcessSize(bs);
if (printDebug) {
stretcher.setDebugLevel(2);
}
stretcher.setMaxProcessSize(bs);
// The input signal is a fixed frequency sinusoid that steps up in
// amplitude every 1/10 of the total duration - from 0.1 at the
@@ -342,7 +356,8 @@ static void sinusoid_realtime(RubberBandStretcher::Options options,
in[i] = sample;
}
vector<float> out = process_realtime(stretcher, in, nOut, bs, printDebug);
vector<float> out = process_realtime(stretcher, in, nOut, bs,
roundUpProcessSize, printDebug);
// Step through the output signal in chunk of 1/20 of its duration
// (i.e. a rather arbitrary two per expected 0.1 increment in
@@ -665,16 +680,64 @@ BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_faster)
{
sinusoid_realtime(RubberBandStretcher::OptionEngineFaster |
RubberBandStretcher::OptionProcessRealTime,
8.0, 1.5,
80000);
4.0, 0.5,
80000, true);
}
BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_faster_stretch)
{
sinusoid_realtime(RubberBandStretcher::OptionEngineFaster |
RubberBandStretcher::OptionProcessRealTime,
2.0, 1.0,
80000, true);
}
BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_faster_shrink)
{
sinusoid_realtime(RubberBandStretcher::OptionEngineFaster |
RubberBandStretcher::OptionProcessRealTime,
0.8, 1.0,
80000, true);
}
BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_faster_higher)
{
sinusoid_realtime(RubberBandStretcher::OptionEngineFaster |
RubberBandStretcher::OptionProcessRealTime,
1.0, 2.0,
80000, true);
}
BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_faster_lower)
{
sinusoid_realtime(RubberBandStretcher::OptionEngineFaster |
RubberBandStretcher::OptionProcessRealTime,
1.0, 0.5,
80000, true);
}
BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_finer)
{
sinusoid_realtime(RubberBandStretcher::OptionEngineFiner |
RubberBandStretcher::OptionProcessRealTime,
8.0, 1.5,
80000);
4.0, 0.5,
80000, true);
}
BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_finer_stretch)
{
sinusoid_realtime(RubberBandStretcher::OptionEngineFiner |
RubberBandStretcher::OptionProcessRealTime,
2.0, 1.0,
80000, true);
}
BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_finer_shift)
{
sinusoid_realtime(RubberBandStretcher::OptionEngineFiner |
RubberBandStretcher::OptionProcessRealTime,
1.0, 0.5,
80000, true);
}
BOOST_AUTO_TEST_CASE(impulses_2x_offline_faster)
@@ -912,7 +975,8 @@ static void impulses_realtime(RubberBandStretcher::Options options,
in[9900] = 1.f;
in[9901] = -1.f;
vector<float> out = process_realtime(stretcher, in, nOut, bs, printDebug);
vector<float> out = process_realtime(stretcher, in, nOut, bs,
false, printDebug);
int peak0 = -1, peak1 = -1, peak2 = -1;
float max;
@@ -1379,8 +1443,8 @@ static void with_resets(RubberBandStretcher::Options options,
for (int i = 0; i < nActual; ++i) {
BOOST_TEST(out[i] == outRef[i]);
if (out[i] != outRef[i]) {
std::cerr << "Failure at index " << i << " in run "
<< run << std::endl;
std::cerr << "Failure at index " << i << " of "
<< nActual << " in run " << run << std::endl;
break;
}
}