From b1cd0913e2f4e79ee5e78d99748fcd1377693081 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 1 Jun 2023 14:09:39 +0100 Subject: [PATCH] Significant further work on internal buffer sizes --- rubberband/RubberBandStretcher.h | 7 ++ src/finer/R3Stretcher.cpp | 171 ++++++++++++++++++------------- src/finer/R3Stretcher.h | 6 +- src/test/TestStretcher.cpp | 7 +- 4 files changed, 116 insertions(+), 75 deletions(-) diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index 12610ce..2227bee 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -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 diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index 6979f90..735eba0 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -637,32 +637,58 @@ R3Stretcher::getSamplesRequired() const } void -R3Stretcher::setMaxProcessSize(size_t n) +R3Stretcher::setMaxProcessSize(size_t requested) { - size_t oldInSize = m_channelData[0]->inbuf->getSize(); - size_t newInSize = getWindowSourceSize() + n; - - size_t oldOutSize = m_channelData[0]->outbuf->getSize(); - size_t newOutSize = newInSize; - - if (newInSize > oldInSize) { - m_log.log(1, "setMaxProcessSize: resizing inbuf from and to", oldInSize, newInSize); - for (int c = 0; c < m_parameters.channels; ++c) { - m_channelData[c]->inbuf = std::unique_ptr> - (m_channelData[c]->inbuf->resized(newInSize)); - } + 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 at inbuf, newInSize <= oldInSize", newInSize, oldInSize); + n = int(requested); } - if (newOutSize > oldOutSize) { - m_log.log(1, "setMaxProcessSize: resizing outbuf from and to", oldOutSize, newOutSize); - for (int c = 0; c < m_parameters.channels; ++c) { - m_channelData[c]->outbuf = std::unique_ptr> - (m_channelData[c]->outbuf->resized(newOutSize)); - } - } else { - m_log.log(1, "setMaxProcessSize: nothing to be done at outbuf, newOutSize <= oldOutSize", newOutSize, oldOutSize); + 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>(newBuf); + } +} + +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>(newBuf); } } @@ -676,6 +702,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) { @@ -714,8 +747,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 @@ -731,35 +768,22 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) 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(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(false); - 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>(newBuf); - } - continue; } + ensureInbuf(remaining); + ws = m_channelData[0]->inbuf->getWriteSpace(); if (resamplingBefore) { @@ -775,10 +799,11 @@ 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 >= int(samples)); + bool finalHop = (final && inputIx + resampleInput >= n); int resampleOutput = m_resampler->resample (m_channelAssembly.resampled.data(), @@ -789,13 +814,16 @@ R3Stretcher::process(const float *const *input, size_t samples, bool 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 { @@ -806,13 +834,19 @@ 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(final && inputIx >= int(samples)); + consume(final && inputIx >= n); } if (final) { @@ -961,9 +995,7 @@ R3Stretcher::consume(bool final) Profiler profiler("R3Stretcher::consume/loop"); int readSpace = cd0->inbuf->getReadSpace(); - int writeSpace = cd0->outbuf->getWriteSpace(); - - m_log.log(2, "consume: read space and write space", readSpace, writeSpace); + m_log.log(2, "consume: read space", readSpace); if (readSpace < getWindowSourceSize()) { if (final) { @@ -980,19 +1012,8 @@ R3Stretcher::consume(bool final) break; } } - - while (writeSpace < outhop) { - m_log.log(0, "R3Stretcher::process: WARNING: Forced to increase output buffer size. Using smaller process blocks or an artificially larger value for setMaxProcessSize may avoid this"); - size_t oldSize = m_channelData[0]->outbuf->getSize(); - size_t 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]->outbuf->resized(newSize); - m_channelData[c]->outbuf = - std::unique_ptr>(newBuf); - } - writeSpace = m_channelData[0]->outbuf->getWriteSpace(); - } + + ensureOutbuf(outhop); // Analysis @@ -1086,12 +1107,19 @@ R3Stretcher::consume(bool final) 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; @@ -1101,7 +1129,10 @@ R3Stretcher::consume(bool final) 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; diff --git a/src/finer/R3Stretcher.h b/src/finer/R3Stretcher.h index 7cdd74b..0f97a28 100644 --- a/src/finer/R3Stretcher.h +++ b/src/finer/R3Stretcher.h @@ -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 @@ -361,6 +363,8 @@ protected: void prepareInput(const float *const *input, int ix, int n); 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); diff --git a/src/test/TestStretcher.cpp b/src/test/TestStretcher.cpp index a2568c7..1817af7 100644 --- a/src/test/TestStretcher.cpp +++ b/src/test/TestStretcher.cpp @@ -680,8 +680,7 @@ BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_finer_stretch) sinusoid_realtime(RubberBandStretcher::OptionEngineFiner | RubberBandStretcher::OptionProcessRealTime, 2.0, 1.0, - 80000, - true); + 80000); } BOOST_AUTO_TEST_CASE(sinusoid_realtime_long_blocksize_finer_shift) @@ -1394,8 +1393,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; } }