Merge from branch process-size
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user