Files
librubberband/src/faster/R2Stretcher.cpp

1363 lines
42 KiB
C++
Raw Normal View History

2007-11-06 21:41:16 +00:00
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
/*
2012-09-09 16:57:42 +01:00
Rubber Band Library
2007-11-06 21:41:16 +00:00
An audio time-stretching and pitch-shifting library.
2024-03-07 15:19:37 +00:00
Copyright 2007-2024 Particular Programs Ltd.
2012-09-09 16:57:42 +01:00
2007-11-06 21:41:16 +00:00
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version. See the file
COPYING included with this distribution for more information.
2012-09-09 16:57:42 +01:00
Alternatively, if you have a valid commercial licence for the
Rubber Band Library obtained by agreement with the copyright
holders, you may redistribute and/or modify it under the terms
described in that licence.
If you wish to distribute code using the Rubber Band Library
under terms other than those of the GNU General Public License,
you must obtain a valid commercial licence before doing so.
2007-11-06 21:41:16 +00:00
*/
#include "R2Stretcher.h"
#include "PercussiveAudioCurve.h"
#include "HighFrequencyAudioCurve.h"
#include "SilentAudioCurve.h"
#include "CompoundAudioCurve.h"
2007-11-06 21:41:16 +00:00
#include "StretcherChannelData.h"
2022-05-23 17:59:40 +01:00
#include "../common/StretchCalculator.h"
#include "../common/Resampler.h"
#include "../common/Profiler.h"
#include "../common/sysutils.h"
#include "../common/mathmisc.h"
2007-11-06 21:41:16 +00:00
#include <cassert>
#include <cmath>
#include <set>
#include <map>
2015-06-29 14:50:33 +01:00
#include <algorithm>
2007-11-06 21:41:16 +00:00
using std::vector;
using std::map;
using std::set;
using std::max;
using std::min;
namespace RubberBand {
const size_t
R2Stretcher::m_defaultIncrement = 256;
const size_t
R2Stretcher::m_defaultFftSize = 2048;
R2Stretcher::R2Stretcher(size_t sampleRate,
size_t channels,
RubberBandStretcher::Options options,
double initialTimeRatio,
2022-06-21 16:06:16 +01:00
double initialPitchScale,
Log log) :
2008-06-09 20:46:37 +00:00
m_sampleRate(sampleRate),
2007-11-06 21:41:16 +00:00
m_channels(channels),
m_timeRatio(initialTimeRatio),
m_pitchScale(initialPitchScale),
m_fftSize(m_defaultFftSize),
m_aWindowSize(m_defaultFftSize),
m_sWindowSize(m_defaultFftSize),
m_increment(m_defaultIncrement),
m_outbufSize(m_defaultFftSize * 2),
m_maxProcessSize(m_defaultFftSize),
2007-11-06 21:41:16 +00:00
m_expectedInputDuration(0),
2012-09-09 16:57:42 +01:00
#ifndef NO_THREADING
2007-11-06 21:41:16 +00:00
m_threaded(false),
2012-09-09 16:57:42 +01:00
#endif
2007-11-06 21:41:16 +00:00
m_realtime(false),
m_options(options),
2022-06-21 16:06:16 +01:00
m_log(log),
2007-11-06 21:41:16 +00:00
m_mode(JustCreated),
m_awindow(0),
m_afilter(0),
m_swindow(0),
2007-11-06 21:41:16 +00:00
m_studyFFT(0),
2012-09-09 16:57:42 +01:00
#ifndef NO_THREADING
m_spaceAvailable("space"),
2012-09-09 16:57:42 +01:00
#endif
2007-11-06 21:41:16 +00:00
m_inputDuration(0),
m_detectorType(CompoundAudioCurve::CompoundDetector),
m_silentHistory(0),
2007-11-06 21:41:16 +00:00
m_lastProcessOutputIncrements(16),
m_lastProcessPhaseResetDf(16),
m_emergencyScavenger(10, 4),
m_phaseResetAudioCurve(0),
m_silentAudioCurve(0),
2007-11-06 21:41:16 +00:00
m_stretchCalculator(0),
m_freq0(600),
m_freq1(1200),
m_freq2(12000),
m_baseFftSize(m_defaultFftSize)
2007-11-06 21:41:16 +00:00
{
Profiler profiler("R2Stretcher::R2Stretcher");
2022-06-22 13:42:58 +01:00
m_log.log(1, "R2Stretcher::R2Stretcher: rate, options",
m_sampleRate, options);
m_log.log(1, "R2Stretcher::R2Stretcher: initial time ratio and pitch scale",
m_timeRatio, m_pitchScale);
// Window size will vary according to the audio sample rate, but
// we don't let it drop below the 48k default
2008-06-09 20:46:37 +00:00
m_rateMultiple = float(m_sampleRate) / 48000.f;
// if (m_rateMultiple < 1.f) m_rateMultiple = 1.f;
m_baseFftSize = roundUp(int(m_defaultFftSize * m_rateMultiple));
2007-11-06 21:41:16 +00:00
if ((options & RubberBandStretcher::OptionWindowShort) ||
(options & RubberBandStretcher::OptionWindowLong)) {
if ((options & RubberBandStretcher::OptionWindowShort) &&
(options & RubberBandStretcher::OptionWindowLong)) {
m_log.log(0, "R2Stretcher::R2Stretcher: Cannot specify OptionWindowLong and OptionWindowShort together; falling back to OptionWindowStandard");
} else if (options & RubberBandStretcher::OptionWindowShort) {
m_baseFftSize = m_baseFftSize / 2;
m_log.log(1, "setting baseFftSize", m_baseFftSize);
} else if (options & RubberBandStretcher::OptionWindowLong) {
m_baseFftSize = m_baseFftSize * 2;
m_log.log(1, "setting baseFftSize", m_baseFftSize);
2007-11-06 21:41:16 +00:00
}
m_fftSize = m_baseFftSize;
m_aWindowSize = m_baseFftSize;
m_sWindowSize = m_baseFftSize;
m_outbufSize = m_sWindowSize * 2;
m_maxProcessSize = m_aWindowSize;
2007-11-06 21:41:16 +00:00
}
if (m_options & RubberBandStretcher::OptionProcessRealTime) {
2007-11-06 21:41:16 +00:00
m_realtime = true;
}
2012-09-09 16:57:42 +01:00
#ifndef NO_THREADING
2007-11-06 21:41:16 +00:00
if (m_channels > 1) {
2007-11-26 11:50:29 +00:00
m_threaded = true;
if (m_realtime) {
m_threaded = false;
} else if (m_options & RubberBandStretcher::OptionThreadingNever) {
2007-11-26 11:50:29 +00:00
m_threaded = false;
} else if (!(m_options & RubberBandStretcher::OptionThreadingAlways) &&
2007-11-26 11:50:29 +00:00
!system_is_multiprocessor()) {
m_threaded = false;
}
2007-11-06 21:41:16 +00:00
2022-06-21 20:26:25 +01:00
if (m_threaded) {
m_log.log(1, "Going multithreaded...");
2007-11-06 21:41:16 +00:00
}
}
2012-09-09 16:57:42 +01:00
#endif
2007-11-06 21:41:16 +00:00
configure();
}
R2Stretcher::~R2Stretcher()
2007-11-06 21:41:16 +00:00
{
2012-09-09 16:57:42 +01:00
#ifndef NO_THREADING
2007-11-06 21:41:16 +00:00
if (m_threaded) {
MutexLocker locker(&m_threadSetMutex);
for (set<ProcessThread *>::iterator i = m_threadSet.begin();
i != m_threadSet.end(); ++i) {
m_log.log(1, "RubberBandStretcher::~RubberBandStretcher: joining for channel", (*i)->channel());
(*i)->abandon();
2007-11-06 21:41:16 +00:00
(*i)->wait();
delete *i;
}
}
2012-09-09 16:57:42 +01:00
#endif
2007-11-06 21:41:16 +00:00
for (size_t c = 0; c < m_channels; ++c) {
delete m_channelData[c];
}
delete m_phaseResetAudioCurve;
delete m_silentAudioCurve;
2007-11-06 21:41:16 +00:00
delete m_stretchCalculator;
delete m_studyFFT;
for (map<size_t, Window<float> *>::iterator i = m_windows.begin();
i != m_windows.end(); ++i) {
delete i->second;
}
for (map<size_t, SincWindow<float> *>::iterator i = m_sincs.begin();
i != m_sincs.end(); ++i) {
delete i->second;
}
2007-11-06 21:41:16 +00:00
}
void
R2Stretcher::reset()
2007-11-06 21:41:16 +00:00
{
2012-09-09 16:57:42 +01:00
#ifndef NO_THREADING
if (m_threaded) {
m_threadSetMutex.lock();
for (set<ProcessThread *>::iterator i = m_threadSet.begin();
i != m_threadSet.end(); ++i) {
m_log.log(1, "RubberBandStretcher::~RubberBandStretcher: joining for channel", (*i)->channel());
(*i)->abandon();
(*i)->wait();
delete *i;
}
m_threadSet.clear();
}
2012-09-09 16:57:42 +01:00
#endif
2007-11-06 21:41:16 +00:00
m_emergencyScavenger.scavenge();
if (m_stretchCalculator) {
m_stretchCalculator->reset();
}
2007-11-06 21:41:16 +00:00
for (size_t c = 0; c < m_channels; ++c) {
m_channelData[c]->reset();
2007-11-06 21:41:16 +00:00
}
2007-11-06 21:41:16 +00:00
m_mode = JustCreated;
if (m_phaseResetAudioCurve) m_phaseResetAudioCurve->reset();
if (m_silentAudioCurve) m_silentAudioCurve->reset();
2023-03-23 17:26:11 +00:00
m_expectedInputDuration = 0;
m_maxProcessSize = 0;
2007-11-06 21:41:16 +00:00
m_inputDuration = 0;
m_silentHistory = 0;
2007-11-06 21:41:16 +00:00
2012-09-09 16:57:42 +01:00
#ifndef NO_THREADING
2007-11-06 21:41:16 +00:00
if (m_threaded) m_threadSetMutex.unlock();
2012-09-09 16:57:42 +01:00
#endif
reconfigure();
2007-11-06 21:41:16 +00:00
}
void
R2Stretcher::setTimeRatio(double ratio)
2007-11-06 21:41:16 +00:00
{
if (!m_realtime) {
if (m_mode == Studying || m_mode == Processing) {
m_log.log(0, "R2Stretcher::setTimeRatio: Cannot set ratio while studying or processing in non-RT mode");
2007-11-06 21:41:16 +00:00
return;
}
}
if (ratio == m_timeRatio) return;
m_timeRatio = ratio;
reconfigure();
}
void
R2Stretcher::setPitchScale(double fs)
2007-11-06 21:41:16 +00:00
{
if (!m_realtime) {
if (m_mode == Studying || m_mode == Processing) {
m_log.log(0, "R2Stretcher::setPitchScale: Cannot set ratio while studying or processing in non-RT mode");
2007-11-06 21:41:16 +00:00
return;
}
}
if (fs == m_pitchScale) return;
2008-07-04 19:31:23 +00:00
2008-07-04 14:19:32 +00:00
bool was1 = (m_pitchScale == 1.f);
bool rbs = resampleBeforeStretching();
2007-11-06 21:41:16 +00:00
m_pitchScale = fs;
reconfigure();
2008-07-04 14:19:32 +00:00
if (!(m_options & RubberBandStretcher::OptionPitchHighConsistency) &&
2008-07-04 14:19:32 +00:00
(was1 || resampleBeforeStretching() != rbs) &&
m_pitchScale != 1.f) {
2008-07-04 19:31:23 +00:00
2008-07-04 14:19:32 +00:00
// resampling mode has changed
for (int c = 0; c < int(m_channels); ++c) {
2008-07-04 14:19:32 +00:00
if (m_channelData[c]->resampler) {
m_channelData[c]->resampler->reset();
}
}
}
2007-11-06 21:41:16 +00:00
}
double
R2Stretcher::getTimeRatio() const
2007-11-06 21:41:16 +00:00
{
return m_timeRatio;
}
double
R2Stretcher::getPitchScale() const
2007-11-06 21:41:16 +00:00
{
return m_pitchScale;
}
void
R2Stretcher::setExpectedInputDuration(size_t samples)
2007-11-06 21:41:16 +00:00
{
if (samples == m_expectedInputDuration) return;
m_expectedInputDuration = samples;
reconfigure();
}
void
R2Stretcher::setMaxProcessSize(size_t samples)
2007-11-06 21:41:16 +00:00
{
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;
2007-11-06 21:41:16 +00:00
reconfigure();
}
2023-07-25 13:11:21 +01:00
size_t
R2Stretcher::getProcessSizeLimit() const
{
return 524288;
}
void
2022-08-04 16:58:00 +01:00
R2Stretcher::setKeyFrameMap(const std::map<size_t, size_t> &mapping)
{
if (m_realtime) {
m_log.log(0, "R2Stretcher::setKeyFrameMap: Cannot specify key frame map in RT mode");
return;
}
if (m_mode == Processing) {
m_log.log(0, "R2Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun");
return;
}
if (m_stretchCalculator) {
m_stretchCalculator->setKeyFrameMap(mapping);
}
}
2007-11-06 21:41:16 +00:00
float
R2Stretcher::getFrequencyCutoff(int n) const
2007-11-06 21:41:16 +00:00
{
switch (n) {
case 0: return m_freq0;
case 1: return m_freq1;
case 2: return m_freq2;
}
return 0.f;
}
void
R2Stretcher::setFrequencyCutoff(int n, float f)
2007-11-06 21:41:16 +00:00
{
switch (n) {
case 0: m_freq0 = f; break;
case 1: m_freq1 = f; break;
case 2: m_freq2 = f; break;
}
}
double
R2Stretcher::getEffectiveRatio() const
2007-11-06 21:41:16 +00:00
{
// Returns the ratio that the internal time stretcher needs to
// achieve, not the resulting duration ratio of the output (which
// is simply m_timeRatio).
// A frequency shift is achieved using an additional time shift,
// followed by resampling back to the original time shift to
// change the pitch. Note that the resulting frequency change is
// fixed, as it is effected by the resampler -- in contrast to
// time shifting, which is variable aiming to place the majority
// of the stretch or squash in low-interest regions of audio.
return m_timeRatio * m_pitchScale;
}
void
R2Stretcher::calculateSizes()
2007-11-06 21:41:16 +00:00
{
size_t inputIncrement = m_defaultIncrement;
size_t windowSize = m_baseFftSize;
2007-11-06 21:41:16 +00:00
size_t outputIncrement;
if (m_pitchScale <= 0.0) {
// This special case is likelier than one might hope, because
// of naive initialisations in programs that set it from a
// variable
m_log.log(0, "WARNING: Pitch scale must be greater than zero! Resetting it to default, no pitch shift will happen", m_pitchScale);
m_pitchScale = 1.0;
}
if (m_timeRatio <= 0.0) {
// Likewise
m_log.log(0, "WARNING: Time ratio must be greater than zero! Resetting it to default, no time stretch will happen", m_timeRatio);
m_timeRatio = 1.0;
}
if (m_pitchScale != m_pitchScale || m_timeRatio != m_timeRatio ||
m_pitchScale == m_pitchScale/2.0 || m_timeRatio == m_timeRatio/2.0) {
m_log.log(0, "WARNING: NaN or Inf presented for time ratio or pitch scale! Resetting it to default, no time stretch will happen", m_timeRatio, m_pitchScale);
m_timeRatio = 1.0;
m_pitchScale = 1.0;
}
2007-11-06 21:41:16 +00:00
double r = getEffectiveRatio();
if (m_realtime) {
if (r < 1) {
bool rsb = (m_pitchScale < 1.0 && !resampleBeforeStretching());
float windowIncrRatio = 4.5;
if (r == 1.0) windowIncrRatio = 4;
else if (rsb) windowIncrRatio = 4.5;
else windowIncrRatio = 6;
inputIncrement = int(windowSize / windowIncrRatio);
2007-11-06 21:41:16 +00:00
outputIncrement = int(floor(inputIncrement * r));
// Very long stretch or very low pitch shift
if (outputIncrement < m_defaultIncrement / 4) {
if (outputIncrement < 1) outputIncrement = 1;
while (outputIncrement < m_defaultIncrement / 4 &&
windowSize < m_baseFftSize * 4) {
outputIncrement *= 2;
inputIncrement = lrint(ceil(outputIncrement / r));
windowSize = roundUp(lrint(ceil(inputIncrement * windowIncrRatio)));
}
2007-11-06 21:41:16 +00:00
}
2007-11-06 21:41:16 +00:00
} else {
bool rsb = (m_pitchScale > 1.0 && resampleBeforeStretching());
float windowIncrRatio = 4.5;
if (r == 1.0) windowIncrRatio = 4;
else if (rsb) windowIncrRatio = 4.5;
else windowIncrRatio = 8;
outputIncrement = int(windowSize / windowIncrRatio);
inputIncrement = int(outputIncrement / r);
while (outputIncrement > 1024 * m_rateMultiple &&
inputIncrement > 1) {
outputIncrement /= 2;
inputIncrement = int(outputIncrement / r);
}
while (inputIncrement < 1) {
outputIncrement *= 2;
inputIncrement = int(outputIncrement / r);
}
size_t minwin = roundUp(lrint(outputIncrement * windowIncrRatio));
if (windowSize < minwin) windowSize = minwin;
if (rsb) {
2022-06-22 11:33:36 +01:00
size_t oldWindowSize = windowSize;
size_t newWindowSize = roundUp(lrint(windowSize / m_pitchScale));
if (newWindowSize < 512) newWindowSize = 512;
size_t div = windowSize / newWindowSize;
if (inputIncrement > div && outputIncrement > div) {
inputIncrement /= div;
outputIncrement /= div;
windowSize /= div;
}
2022-06-22 11:33:36 +01:00
m_log.log(2, "adjusting window size from/to", oldWindowSize, windowSize);
m_log.log(2, "input and output increments", inputIncrement, outputIncrement);
2007-11-06 21:41:16 +00:00
}
}
} else {
if (r < 1) {
inputIncrement = windowSize / 4;
2007-11-06 21:41:16 +00:00
while (inputIncrement >= 512) inputIncrement /= 2;
outputIncrement = int(floor(inputIncrement * r));
if (outputIncrement < 1) {
outputIncrement = 1;
inputIncrement = roundUp(lrint(ceil(outputIncrement / r)));
windowSize = inputIncrement * 4;
2007-11-06 21:41:16 +00:00
}
} else {
outputIncrement = windowSize / 6;
2007-11-06 21:41:16 +00:00
inputIncrement = int(outputIncrement / r);
while (outputIncrement > 1024 && inputIncrement > 1) {
outputIncrement /= 2;
inputIncrement = int(outputIncrement / r);
}
while (inputIncrement < 1) {
outputIncrement *= 2;
inputIncrement = int(outputIncrement / r);
}
windowSize = std::max(windowSize, roundUp(outputIncrement * 6));
if (r > 5) while (windowSize < 8192) windowSize *= 2;
2007-11-06 21:41:16 +00:00
}
}
2007-11-06 21:41:16 +00:00
if (m_expectedInputDuration > 0) {
while (inputIncrement * 4 > m_expectedInputDuration &&
inputIncrement > 1) {
inputIncrement /= 2;
}
}
// m_fftSize can be almost anything, but it can't be greater than
// 4 * m_baseFftSize unless ratio is less than 1/1024.
2007-11-06 21:41:16 +00:00
m_fftSize = windowSize;
if (m_options & RubberBandStretcher::OptionSmoothingOn) {
m_aWindowSize = windowSize * 2;
m_sWindowSize = windowSize * 2;
} else {
m_aWindowSize = windowSize;
m_sWindowSize = windowSize;
}
2007-11-06 21:41:16 +00:00
m_increment = inputIncrement;
// When squashing, the greatest theoretically possible output
// increment is the input increment. When stretching adaptively
// the sky's the limit in principle, but we expect
// StretchCalculator to restrict itself to using no more than
// twice the basic output increment (i.e. input increment times
// ratio) for any chunk.
2007-11-06 21:41:16 +00:00
m_log.log(1, "calculateSizes: time ratio and pitch scale", m_timeRatio, m_pitchScale);
m_log.log(1, "effective ratio", getEffectiveRatio());
m_log.log(1, "analysis and synthesis window sizes", m_aWindowSize, m_sWindowSize);
m_log.log(1, "fft size", m_fftSize);
m_log.log(1, "input increment and mean output increment", m_increment, m_increment * getEffectiveRatio());
2007-11-06 21:41:16 +00:00
if (std::max(m_aWindowSize, m_sWindowSize) > m_maxProcessSize) {
m_maxProcessSize = std::max(m_aWindowSize, m_sWindowSize);
2007-11-06 21:41:16 +00:00
}
m_outbufSize =
size_t
(ceil(max
(m_maxProcessSize / m_pitchScale,
m_maxProcessSize * 2 * (m_timeRatio > 1.f ? m_timeRatio : 1.f))));
2007-11-06 21:41:16 +00:00
if (m_realtime) {
// This headroom is so as to try to avoid reallocation when
// the pitch scale changes
m_outbufSize = m_outbufSize * 16;
} else {
2012-09-09 16:57:42 +01:00
#ifndef NO_THREADING
2007-11-06 21:41:16 +00:00
if (m_threaded) {
// This headroom is to permit the processing threads to
// run ahead of the buffer output drainage; the exact
// amount of headroom is a question of tuning rather than
// results
m_outbufSize = m_outbufSize * 16;
}
2012-09-09 16:57:42 +01:00
#endif
2007-11-06 21:41:16 +00:00
}
2022-06-22 11:33:36 +01:00
m_log.log(1, "calculateSizes: outbuf size", m_outbufSize);
2007-11-06 21:41:16 +00:00
}
void
R2Stretcher::configure()
2007-11-06 21:41:16 +00:00
{
2022-06-22 11:33:36 +01:00
if (m_realtime) {
m_log.log(1, "configure, realtime: pitch scale and channels",
m_pitchScale, m_channels);
} else {
m_log.log(1, "configure, offline: pitch scale and channels",
m_pitchScale, m_channels);
}
size_t prevFftSize = m_fftSize;
size_t prevAWindowSize = m_aWindowSize;
size_t prevSWindowSize = m_sWindowSize;
2007-11-06 21:41:16 +00:00
size_t prevOutbufSize = m_outbufSize;
if (m_windows.empty()) {
prevFftSize = 0;
prevAWindowSize = 0;
prevSWindowSize = 0;
2007-11-06 21:41:16 +00:00
prevOutbufSize = 0;
}
calculateSizes();
bool fftSizeChanged = (prevFftSize != m_fftSize);
bool windowSizeChanged = ((prevAWindowSize != m_aWindowSize) ||
(prevSWindowSize != m_sWindowSize));
2007-11-06 21:41:16 +00:00
bool outbufSizeChanged = (prevOutbufSize != m_outbufSize);
// This function may be called at any time in non-RT mode, after a
// parameter has changed. It shouldn't be legal to call it after
// processing has already begun.
// This function is only called once (on construction) in RT
// mode. After that reconfigure() does the work in a hopefully
// RT-safe way.
set<size_t> windowSizes;
2007-11-06 21:41:16 +00:00
if (m_realtime) {
windowSizes.insert(m_baseFftSize);
windowSizes.insert(m_baseFftSize / 2);
windowSizes.insert(m_baseFftSize * 2);
// windowSizes.insert(m_baseFftSize * 4);
2007-11-06 21:41:16 +00:00
}
windowSizes.insert(m_fftSize);
windowSizes.insert(m_aWindowSize);
windowSizes.insert(m_sWindowSize);
2007-11-06 21:41:16 +00:00
if (windowSizeChanged) {
2007-11-06 21:41:16 +00:00
for (set<size_t>::const_iterator i = windowSizes.begin();
i != windowSizes.end(); ++i) {
2007-11-06 21:41:16 +00:00
if (m_windows.find(*i) == m_windows.end()) {
2022-05-18 17:51:20 +01:00
m_windows[*i] = new Window<float>(HannWindow, *i);
2007-11-06 21:41:16 +00:00
}
if (m_sincs.find(*i) == m_sincs.end()) {
m_sincs[*i] = new SincWindow<float>(*i, *i);
}
2007-11-06 21:41:16 +00:00
}
m_awindow = m_windows[m_aWindowSize];
m_afilter = m_sincs[m_aWindowSize];
m_swindow = m_windows[m_sWindowSize];
2007-11-06 21:41:16 +00:00
2022-06-22 11:33:36 +01:00
m_log.log(1, "analysis and synthesis window areas",
m_awindow->getArea(), m_swindow->getArea());
2007-11-06 21:41:16 +00:00
}
if (windowSizeChanged || outbufSizeChanged) {
2007-11-06 21:41:16 +00:00
for (size_t c = 0; c < m_channelData.size(); ++c) {
delete m_channelData[c];
}
m_channelData.clear();
for (size_t c = 0; c < m_channels; ++c) {
m_channelData.push_back
(new ChannelData(windowSizes,
std::max(m_aWindowSize, m_sWindowSize),
m_fftSize,
m_outbufSize));
2007-11-06 21:41:16 +00:00
}
}
if (!m_realtime && fftSizeChanged) {
2007-11-06 21:41:16 +00:00
delete m_studyFFT;
m_studyFFT = new FFT(m_fftSize);
2007-11-06 21:41:16 +00:00
m_studyFFT->initFloat();
}
2008-07-01 20:49:24 +00:00
if (m_pitchScale != 1.0 ||
(m_options & RubberBandStretcher::OptionPitchHighConsistency) ||
2008-07-01 20:49:24 +00:00
m_realtime) {
2007-11-06 21:41:16 +00:00
for (size_t c = 0; c < m_channels; ++c) {
if (m_channelData[c]->resampler) continue;
Resampler::Parameters params;
params.quality = Resampler::FastestTolerable;
if (m_realtime) {
params.dynamism = Resampler::RatioOftenChanging;
params.ratioChange = Resampler::SmoothRatioChange;
} else {
// ratio can't be changed in offline mode
params.dynamism = Resampler::RatioMostlyFixed;
params.ratioChange = Resampler::SuddenRatioChange;
}
params.maxBufferSize = 4096 * 16;
2022-06-22 13:42:58 +01:00
int myLevel = m_log.getDebugLevel();
params.debugLevel = (myLevel > 0 ? myLevel-1 : 0);
m_channelData[c]->resampler = new Resampler(params, 1);
2007-11-06 21:41:16 +00:00
// rbs is the amount of buffer space we think we'll need
// for resampling; but allocate a sensible amount in case
// the pitch scale changes during use
size_t rbs =
2007-11-06 21:41:16 +00:00
lrintf(ceil((m_increment * m_timeRatio * 2) / m_pitchScale));
if (rbs < m_increment * 16) rbs = m_increment * 16;
if (rbs < m_aWindowSize * 2) rbs = m_aWindowSize * 2;
m_channelData[c]->setResampleBufSize(rbs);
2007-11-06 21:41:16 +00:00
}
}
delete m_phaseResetAudioCurve;
m_phaseResetAudioCurve = new CompoundAudioCurve
(CompoundAudioCurve::Parameters(m_sampleRate, m_fftSize));
m_phaseResetAudioCurve->setType(m_detectorType);
2007-11-06 21:41:16 +00:00
delete m_silentAudioCurve;
m_silentAudioCurve = new SilentAudioCurve
(SilentAudioCurve::Parameters(m_sampleRate, m_fftSize));
2007-11-06 21:41:16 +00:00
delete m_stretchCalculator;
m_stretchCalculator = new StretchCalculator
2008-06-09 20:46:37 +00:00
(m_sampleRate, m_increment,
2022-06-21 16:06:16 +01:00
!(m_options & RubberBandStretcher::OptionTransientsSmooth),
m_log);
2007-11-06 21:41:16 +00:00
2022-06-22 13:42:58 +01:00
m_stretchCalculator->setDebugLevel(m_log.getDebugLevel());
2007-11-06 21:41:16 +00:00
m_inputDuration = 0;
// Prepare the inbufs with half a chunk of emptiness. The centre
// point of the first processing chunk for the onset detector
2007-11-06 21:41:16 +00:00
// should be the first sample of the audio, and we continue until
// we can no longer centre a chunk within the input audio. The
// number of onset detector chunks will be the number of audio
2007-11-06 21:41:16 +00:00
// samples input, divided by the input increment, plus one.
// In real-time mode, we don't do this prefill -- it's better to
// start with a swoosh than introduce more latency, and we don't
// want gaps when the ratio changes.
if (!m_realtime) {
2022-06-22 11:33:36 +01:00
m_log.log(1, "offline mode: prefilling with", m_aWindowSize/2);
2007-11-06 21:41:16 +00:00
for (size_t c = 0; c < m_channels; ++c) {
m_channelData[c]->reset();
m_channelData[c]->inbuf->zero(m_aWindowSize/2);
2007-11-06 21:41:16 +00:00
}
2022-06-22 11:33:36 +01:00
} else {
m_log.log(1, "realtime mode: no prefill");
}
2007-11-06 21:41:16 +00:00
}
void
R2Stretcher::reconfigure()
2007-11-06 21:41:16 +00:00
{
if (!m_realtime) {
if (m_mode == Studying) {
// stop and calculate the stretch curve so far, then reset
// the df vectors
calculateStretch();
m_phaseResetDf.clear();
m_silence.clear();
2007-11-06 21:41:16 +00:00
m_inputDuration = 0;
}
configure();
}
size_t prevFftSize = m_fftSize;
size_t prevAWindowSize = m_aWindowSize;
size_t prevSWindowSize = m_sWindowSize;
2007-11-06 21:41:16 +00:00
size_t prevOutbufSize = m_outbufSize;
calculateSizes();
bool somethingChanged = false;
2007-11-06 21:41:16 +00:00
// There are various allocations in this function, but they should
// never happen in normal use -- they just recover from the case
// where not all of the things we need were correctly created when
// we first configured (for whatever reason). This is intended to
// be "effectively" realtime safe. The same goes for
// ChannelData::setOutbufSize and setSizes.
2007-11-06 21:41:16 +00:00
if (m_aWindowSize != prevAWindowSize ||
m_sWindowSize != prevSWindowSize) {
2007-11-06 21:41:16 +00:00
if (m_windows.find(m_aWindowSize) == m_windows.end()) {
2022-06-22 11:33:36 +01:00
m_log.log(0, "WARNING: reconfigure(): window allocation required in realtime mode, size", m_aWindowSize);
m_windows[m_aWindowSize] = new Window<float>
2022-05-18 17:51:20 +01:00
(HannWindow, m_aWindowSize);
m_sincs[m_aWindowSize] = new SincWindow<float>
(m_aWindowSize, m_aWindowSize);
2007-11-06 21:41:16 +00:00
}
if (m_windows.find(m_sWindowSize) == m_windows.end()) {
2022-06-22 11:33:36 +01:00
m_log.log(0, "WARNING: reconfigure(): window allocation required in realtime mode, size", m_sWindowSize);
m_windows[m_sWindowSize] = new Window<float>
2022-05-18 17:51:20 +01:00
(HannWindow, m_sWindowSize);
m_sincs[m_sWindowSize] = new SincWindow<float>
(m_sWindowSize, m_sWindowSize);
}
m_awindow = m_windows[m_aWindowSize];
m_afilter = m_sincs[m_aWindowSize];
m_swindow = m_windows[m_sWindowSize];
2007-11-06 21:41:16 +00:00
for (size_t c = 0; c < m_channels; ++c) {
m_channelData[c]->setSizes(std::max(m_aWindowSize, m_sWindowSize),
m_fftSize);
2007-11-06 21:41:16 +00:00
}
somethingChanged = true;
2007-11-06 21:41:16 +00:00
}
if (m_outbufSize != prevOutbufSize) {
for (size_t c = 0; c < m_channels; ++c) {
m_channelData[c]->setOutbufSize(m_outbufSize);
}
somethingChanged = true;
2007-11-06 21:41:16 +00:00
}
if (m_pitchScale != 1.0) {
for (size_t c = 0; c < m_channels; ++c) {
if (m_channelData[c]->resampler) continue;
2022-06-22 11:33:36 +01:00
m_log.log(0, "WARNING: reconfigure(): resampler construction required in RT mode");
2007-11-06 21:41:16 +00:00
Resampler::Parameters params;
params.quality = Resampler::FastestTolerable;
params.dynamism = Resampler::RatioOftenChanging;
2021-05-11 17:20:12 +01:00
params.ratioChange = Resampler::SmoothRatioChange;
params.maxBufferSize = m_sWindowSize;
2022-06-22 13:42:58 +01:00
int myLevel = m_log.getDebugLevel();
params.debugLevel = (myLevel > 0 ? myLevel-1 : 0);
m_channelData[c]->resampler = new Resampler(params, 1);
2007-11-06 21:41:16 +00:00
size_t rbs =
lrintf(ceil((m_increment * m_timeRatio * 2) / m_pitchScale));
if (rbs < m_increment * 16) rbs = m_increment * 16;
m_channelData[c]->setResampleBufSize(rbs);
somethingChanged = true;
2007-11-06 21:41:16 +00:00
}
}
if (m_fftSize != prevFftSize) {
m_phaseResetAudioCurve->setFftSize(m_fftSize);
m_silentAudioCurve->setFftSize(m_fftSize);
somethingChanged = true;
}
2022-06-22 11:33:36 +01:00
if (somethingChanged) {
m_log.log(1, "reconfigure: at least one parameter changed");
} else {
m_log.log(1, "reconfigure: nothing changed");
2007-11-06 21:41:16 +00:00
}
}
size_t
R2Stretcher::getPreferredStartPad() const
{
if (!m_realtime) return 0;
size_t pad = m_aWindowSize / 2;
if (resampleBeforeStretching()) {
return size_t(ceil(pad * m_pitchScale));
} else {
return pad;
}
}
size_t
R2Stretcher::getStartDelay() const
2007-11-06 21:41:16 +00:00
{
if (!m_realtime) return 0;
size_t pad = m_aWindowSize / 2;
if (resampleBeforeStretching()) {
return pad;
} else {
return size_t(ceil(pad / m_pitchScale));
}
2007-11-06 21:41:16 +00:00
}
void
R2Stretcher::setTransientsOption(RubberBandStretcher::Options options)
2007-11-06 21:41:16 +00:00
{
if (!m_realtime) {
2022-06-22 11:33:36 +01:00
m_log.log(0, "R2Stretcher::setTransientsOption: Not permissible in non-realtime mode");
return;
}
int mask = (RubberBandStretcher::OptionTransientsMixed |
RubberBandStretcher::OptionTransientsSmooth |
RubberBandStretcher::OptionTransientsCrisp);
m_options &= ~mask;
options &= mask;
m_options |= options;
m_stretchCalculator->setUseHardPeaks
(!(m_options & RubberBandStretcher::OptionTransientsSmooth));
2007-11-06 21:41:16 +00:00
}
void
R2Stretcher::setDetectorOption(RubberBandStretcher::Options options)
{
if (!m_realtime) {
2022-06-22 11:33:36 +01:00
m_log.log(0, "R2Stretcher::setDetectorOption: Not permissible in non-realtime mode");
return;
}
int mask = (RubberBandStretcher::OptionDetectorPercussive |
RubberBandStretcher::OptionDetectorCompound |
RubberBandStretcher::OptionDetectorSoft);
m_options &= ~mask;
options &= mask;
m_options |= options;
CompoundAudioCurve::Type dt = CompoundAudioCurve::CompoundDetector;
if (m_options & RubberBandStretcher::OptionDetectorPercussive) {
dt = CompoundAudioCurve::PercussiveDetector;
} else if (m_options & RubberBandStretcher::OptionDetectorSoft) {
dt = CompoundAudioCurve::SoftDetector;
}
if (dt == m_detectorType) return;
m_detectorType = dt;
if (m_phaseResetAudioCurve) {
m_phaseResetAudioCurve->setType(m_detectorType);
}
}
2007-11-06 21:41:16 +00:00
void
R2Stretcher::setPhaseOption(RubberBandStretcher::Options options)
2007-11-06 21:41:16 +00:00
{
int mask = (RubberBandStretcher::OptionPhaseLaminar |
RubberBandStretcher::OptionPhaseIndependent);
m_options &= ~mask;
options &= mask;
m_options |= options;
}
void
R2Stretcher::setFormantOption(RubberBandStretcher::Options options)
{
int mask = (RubberBandStretcher::OptionFormantShifted |
RubberBandStretcher::OptionFormantPreserved);
m_options &= ~mask;
options &= mask;
m_options |= options;
}
void
R2Stretcher::setPitchOption(RubberBandStretcher::Options options)
{
if (!m_realtime) {
2022-06-22 11:33:36 +01:00
m_log.log(0, "R2Stretcher::setPitchOption: Pitch option is not used in non-RT mode");
return;
}
RubberBandStretcher::Options prior = m_options;
int mask = (RubberBandStretcher::OptionPitchHighQuality |
RubberBandStretcher::OptionPitchHighSpeed |
RubberBandStretcher::OptionPitchHighConsistency);
m_options &= ~mask;
options &= mask;
m_options |= options;
if (prior != m_options) reconfigure();
2007-11-06 21:41:16 +00:00
}
void
R2Stretcher::study(const float *const *input, size_t samples, bool final)
2007-11-06 21:41:16 +00:00
{
Profiler profiler("R2Stretcher::study");
2007-11-06 21:41:16 +00:00
if (m_realtime) {
2022-06-22 11:33:36 +01:00
m_log.log(0, "R2Stretcher::study: Not meaningful in realtime mode");
2007-11-06 21:41:16 +00:00
return;
}
if (m_mode == Processing || m_mode == Finished) {
2022-06-22 11:33:36 +01:00
m_log.log(0, "R2Stretcher::study: Cannot study after processing");
2007-11-06 21:41:16 +00:00
return;
}
m_mode = Studying;
size_t consumed = 0;
ChannelData &cd = *m_channelData[0];
RingBuffer<float> &inbuf = *cd.inbuf;
const float *mixdown;
float *mdalloc = 0;
if (m_channels > 1 || final) {
// mix down into a single channel for analysis
mdalloc = new float[samples];
for (size_t i = 0; i < samples; ++i) {
if (i < samples) {
mdalloc[i] = input[0][i];
} else {
mdalloc[i] = 0.f;
}
}
for (size_t c = 1; c < m_channels; ++c) {
for (size_t i = 0; i < samples; ++i) {
mdalloc[i] += input[c][i];
}
}
for (size_t i = 0; i < samples; ++i) {
mdalloc[i] /= m_channels;
}
mixdown = mdalloc;
} else {
mixdown = input[0];
}
while (consumed < samples) {
size_t writable = inbuf.getWriteSpace();
writable = min(writable, samples - consumed);
if (writable == 0) {
// warn
2022-06-22 11:33:36 +01:00
m_log.log(0, "WARNING: writable == 0: consumed, samples",
consumed, samples);
2007-11-06 21:41:16 +00:00
} else {
inbuf.write(mixdown + consumed, writable);
consumed += writable;
}
while ((inbuf.getReadSpace() >= int(m_aWindowSize)) ||
(final && (inbuf.getReadSpace() >= int(m_aWindowSize/2)))) {
2007-11-06 21:41:16 +00:00
// We know we have at least m_aWindowSize samples
// available in m_inbuf. We need to peek m_aWindowSize of
// them for processing, and then skip m_increment to
// advance the read pointer.
2007-11-06 21:41:16 +00:00
// cd.accumulator is not otherwise used during studying,
// so we can use it as a temporary buffer here
size_t ready = inbuf.getReadSpace();
assert(final || ready >= m_aWindowSize);
inbuf.peek(cd.accumulator, std::min(ready, m_aWindowSize));
if (m_aWindowSize == m_fftSize) {
// We don't need the fftshift for studying, as we're
// only interested in magnitude.
m_awindow->cut(cd.accumulator);
} else {
// If we need to fold (i.e. if the window size is
// greater than the fft size so we are doing a
// time-aliased presum fft) or zero-pad, then we might
// as well use our standard function for it. This
// means we retain the m_afilter cut if folding as well,
// which is good for consistency with real-time mode.
// We get fftshift as well, which we don't want, but
// the penalty is nominal.
// Note that we can't do this in-place. Pity
2007-11-06 21:41:16 +00:00
float *tmp = (float *)alloca
(std::max(m_fftSize, m_aWindowSize) * sizeof(float));
2007-11-06 21:41:16 +00:00
if (m_aWindowSize > m_fftSize) {
m_afilter->cut(cd.accumulator);
}
cutShiftAndFold(tmp, m_fftSize, cd.accumulator, m_awindow);
v_copy(cd.accumulator, tmp, m_fftSize);
}
2007-11-06 21:41:16 +00:00
m_studyFFT->forwardMagnitude(cd.accumulator, cd.fltbuf);
float df = m_phaseResetAudioCurve->processFloat(cd.fltbuf, m_increment);
m_phaseResetDf.push_back(df);
2007-11-06 21:41:16 +00:00
// cout << m_phaseResetDf.size() << " [" << final << "] -> " << df << " \t: ";
2007-11-06 21:41:16 +00:00
df = m_silentAudioCurve->processFloat(cd.fltbuf, m_increment);
bool silent = (df > 0.f);
2022-06-22 11:33:36 +01:00
if (silent) {
m_log.log(2, "silence at", m_inputDuration);
2008-07-02 21:28:23 +00:00
}
m_silence.push_back(silent);
2007-11-06 21:41:16 +00:00
// cout << df << endl;
// We have augmented the input by m_aWindowSize/2 so that
// the first chunk is centred on the first audio sample.
// We want to ensure that m_inputDuration contains the
// exact input duration without including this extra bit.
// We just add up all the increments here, and deduct the
// extra afterwards.
2007-11-06 21:41:16 +00:00
m_inputDuration += m_increment;
inbuf.skip(m_increment);
}
}
if (final) {
int rs = inbuf.getReadSpace();
m_inputDuration += rs;
if (m_inputDuration > m_aWindowSize/2) { // deducting the extra
m_inputDuration -= m_aWindowSize/2;
2007-11-06 21:41:16 +00:00
}
}
2014-10-16 21:36:39 +01:00
if (m_channels > 1 || final) delete[] mdalloc;
2007-11-06 21:41:16 +00:00
}
vector<int>
R2Stretcher::getOutputIncrements() const
2007-11-06 21:41:16 +00:00
{
if (!m_realtime) {
return m_outputIncrements;
} else {
vector<int> increments;
while (m_lastProcessOutputIncrements.getReadSpace() > 0) {
increments.push_back(m_lastProcessOutputIncrements.readOne());
}
return increments;
}
}
vector<float>
R2Stretcher::getPhaseResetCurve() const
2007-11-06 21:41:16 +00:00
{
if (!m_realtime) {
return m_phaseResetDf;
2007-11-06 21:41:16 +00:00
} else {
vector<float> df;
while (m_lastProcessPhaseResetDf.getReadSpace() > 0) {
df.push_back(m_lastProcessPhaseResetDf.readOne());
2007-11-06 21:41:16 +00:00
}
return df;
}
}
vector<int>
R2Stretcher::getExactTimePoints() const
{
std::vector<int> points;
if (!m_realtime) {
std::vector<StretchCalculator::Peak> peaks =
m_stretchCalculator->getLastCalculatedPeaks();
for (size_t i = 0; i < peaks.size(); ++i) {
points.push_back(peaks[i].chunk);
}
}
return points;
}
2007-11-06 21:41:16 +00:00
void
R2Stretcher::calculateStretch()
2007-11-06 21:41:16 +00:00
{
Profiler profiler("R2Stretcher::calculateStretch");
size_t inputDuration = m_inputDuration;
if (!m_realtime && m_expectedInputDuration > 0) {
if (m_expectedInputDuration != inputDuration) {
2022-06-22 11:33:36 +01:00
m_log.log(0, "WARNING: Actual study() duration differs from duration set by setExpectedInputDuration - using the latter for calculation", m_inputDuration, m_expectedInputDuration);
inputDuration = m_expectedInputDuration;
}
}
2007-11-06 21:41:16 +00:00
std::vector<int> increments = m_stretchCalculator->calculate
(getEffectiveRatio(),
inputDuration,
m_phaseResetDf);
2007-11-06 21:41:16 +00:00
int history = 0;
for (size_t i = 0; i < increments.size(); ++i) {
if (i >= m_silence.size()) break;
if (m_silence[i]) ++history;
else history = 0;
if (history >= int(m_aWindowSize / m_increment) && increments[i] >= 0) {
increments[i] = -increments[i];
2022-06-22 11:33:36 +01:00
m_log.log(2, "phase reset on silence: silent history", history);
}
}
2007-11-06 21:41:16 +00:00
if (m_outputIncrements.empty()) m_outputIncrements = increments;
else {
for (size_t i = 0; i < increments.size(); ++i) {
m_outputIncrements.push_back(increments[i]);
}
}
return;
}
void
R2Stretcher::setDebugLevel(int level)
2007-11-06 21:41:16 +00:00
{
2022-06-22 13:42:58 +01:00
m_log.setDebugLevel(level);
if (m_stretchCalculator) {
m_stretchCalculator->setDebugLevel(level);
}
2007-11-06 21:41:16 +00:00
}
size_t
R2Stretcher::getSamplesRequired() const
2007-11-06 21:41:16 +00:00
{
Profiler profiler("R2Stretcher::getSamplesRequired");
2007-11-06 21:41:16 +00:00
size_t reqd = 0;
for (size_t c = 0; c < m_channels; ++c) {
size_t reqdHere = 0;
ChannelData &cd = *m_channelData[c];
RingBuffer<float> &inbuf = *cd.inbuf;
RingBuffer<float> &outbuf = *cd.outbuf;
2007-11-06 21:41:16 +00:00
size_t rs = inbuf.getReadSpace();
size_t ws = outbuf.getReadSpace();
2022-06-22 11:33:36 +01:00
m_log.log(3, "getSamplesRequired: ws and rs ", ws, rs);
// We should never return zero in non-threaded modes if
// available() would also return zero, i.e. if ws == 0. If we
// do that, nothing will ever happen again! We need to demand
// at least one increment (i.e. a nominal amount) to feed the
// engine.
if (ws == 0 && reqd == 0) reqd = m_increment;
2007-11-06 21:41:16 +00:00
2007-12-10 22:20:07 +00:00
// See notes in testInbufReadSpace
2007-11-06 21:41:16 +00:00
if (rs < m_aWindowSize && !cd.draining) {
2007-11-06 21:41:16 +00:00
if (cd.inputSize == -1) {
reqdHere = m_aWindowSize - rs;
2007-11-06 21:41:16 +00:00
if (reqdHere > reqd) reqd = reqdHere;
continue;
}
if (rs == 0) {
reqdHere = m_aWindowSize;
2007-11-06 21:41:16 +00:00
if (reqdHere > reqd) reqd = reqdHere;
continue;
}
}
}
if (resampleBeforeStretching() && m_pitchScale > 1.0) {
reqd = size_t(ceil(double(reqd) * m_pitchScale));
}
2007-11-06 21:41:16 +00:00
return reqd;
}
void
R2Stretcher::process(const float *const *input, size_t samples, bool final)
2007-11-06 21:41:16 +00:00
{
Profiler profiler("R2Stretcher::process");
2023-06-08 09:40:11 +01:00
m_log.log(3, "process entering, samples and final", samples, final);
2007-11-06 21:41:16 +00:00
if (m_mode == Finished) {
2022-06-22 11:33:36 +01:00
m_log.log(0, "R2Stretcher::process: Cannot process again after final chunk");
2007-11-06 21:41:16 +00:00
return;
}
if (m_mode == JustCreated || m_mode == Studying) {
if (m_mode == Studying) {
2007-11-06 21:41:16 +00:00
calculateStretch();
if (!m_realtime) {
// See note in configure() above. Of course, we should
// never enter Studying unless we are non-RT anyway
2022-06-22 11:33:36 +01:00
m_log.log(1, "offline mode: prefilling with", m_aWindowSize/2);
for (size_t c = 0; c < m_channels; ++c) {
m_channelData[c]->reset();
m_channelData[c]->inbuf->zero(m_aWindowSize/2);
}
}
2007-11-06 21:41:16 +00:00
}
2012-09-09 16:57:42 +01:00
#ifndef NO_THREADING
2007-11-06 21:41:16 +00:00
if (m_threaded) {
MutexLocker locker(&m_threadSetMutex);
for (size_t c = 0; c < m_channels; ++c) {
ProcessThread *thread = new ProcessThread(this, c);
m_threadSet.insert(thread);
thread->start();
}
2022-06-22 11:33:36 +01:00
m_log.log(1, "created threads", m_channels);
2007-11-06 21:41:16 +00:00
}
2012-09-09 16:57:42 +01:00
#endif
2007-11-06 21:41:16 +00:00
m_mode = Processing;
}
bool allConsumed = false;
size_t *consumed = (size_t *)alloca(m_channels * sizeof(size_t));
2007-11-06 21:41:16 +00:00
for (size_t c = 0; c < m_channels; ++c) {
consumed[c] = 0;
}
while (!allConsumed) {
// In a threaded mode, our "consumed" counters only indicate
// the number of samples that have been taken into the input
// ring buffers waiting to be processed by the process thread.
// In non-threaded mode, "consumed" counts the number that
// have actually been processed.
allConsumed = true;
2007-11-06 21:41:16 +00:00
for (size_t c = 0; c < m_channels; ++c) {
consumed[c] += consumeChannel(c,
input,
consumed[c],
samples - consumed[c],
final);
if (c == 0) {
2023-06-08 09:40:11 +01:00
m_log.log(3, "consumed channel 0, consumed and samples now", consumed[c], samples);
}
2007-11-06 21:41:16 +00:00
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);
}
2007-11-06 21:41:16 +00:00
m_channelData[c]->inputSize = m_channelData[c]->inCount;
}
}
2011-12-09 18:18:32 +00:00
if (
2012-09-09 16:57:42 +01:00
#ifndef NO_THREADING
2011-12-09 18:18:32 +00:00
!m_threaded &&
2012-09-09 16:57:42 +01:00
#endif
2011-12-09 18:18:32 +00:00
!m_realtime) {
2007-11-26 11:00:47 +00:00
bool any = false, last = false;
processChunks(c, any, last);
2007-11-06 21:41:16 +00:00
}
}
if (m_realtime) {
// When running in real time, we need to process both
// channels in step because we will need to use the sum of
// their frequency domain representations as the input to
// the realtime onset detector
processOneChunk();
}
2012-09-09 16:57:42 +01:00
#ifndef NO_THREADING
2007-11-06 21:41:16 +00:00
if (m_threaded) {
for (ThreadSet::iterator i = m_threadSet.begin();
i != m_threadSet.end(); ++i) {
(*i)->signalDataAvailable();
}
m_spaceAvailable.lock();
2007-11-06 21:41:16 +00:00
if (!allConsumed) {
m_spaceAvailable.wait(500);
}
m_spaceAvailable.unlock();
2007-11-06 21:41:16 +00:00
}
2012-09-09 16:57:42 +01:00
#endif
2008-07-01 20:49:24 +00:00
2023-06-08 09:40:11 +01:00
m_log.log(3, "process looping");
}
2008-07-01 20:49:24 +00:00
2023-06-08 09:40:11 +01:00
m_log.log(3, "process returning");
2007-11-06 21:41:16 +00:00
if (final) m_mode = Finished;
}
}