* Initial import
This commit is contained in:
978
src/StretcherImpl.cpp
Normal file
978
src/StretcherImpl.cpp
Normal file
@@ -0,0 +1,978 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007 Chris Cannam.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include "StretcherImpl.h"
|
||||
#include "PercussiveAudioCurve.h"
|
||||
#include "HighFrequencyAudioCurve.h"
|
||||
#include "ConstantAudioCurve.h"
|
||||
#include "StretchCalculator.h"
|
||||
#include "StretcherChannelData.h"
|
||||
#include "Resampler.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using std::vector;
|
||||
using std::map;
|
||||
using std::set;
|
||||
using std::max;
|
||||
using std::min;
|
||||
|
||||
namespace RubberBand {
|
||||
|
||||
static const size_t defaultIncrement = 256;
|
||||
static const size_t defaultBlockSize = 2048;
|
||||
|
||||
RubberBandStretcher::Impl::Impl(RubberBandStretcher *stretcher,
|
||||
size_t sampleRate,
|
||||
size_t channels,
|
||||
Options options,
|
||||
double initialTimeRatio,
|
||||
double initialPitchScale) :
|
||||
m_stretcher(stretcher),
|
||||
m_channels(channels),
|
||||
m_timeRatio(initialTimeRatio),
|
||||
m_pitchScale(initialPitchScale),
|
||||
m_blockSize(defaultBlockSize),
|
||||
m_outbufSize(defaultBlockSize * 2),
|
||||
m_increment(defaultIncrement),
|
||||
m_maxProcessBlockSize(defaultBlockSize),
|
||||
m_expectedInputDuration(0),
|
||||
m_threaded(false),
|
||||
m_realtime(false),
|
||||
m_options(options),
|
||||
m_debugLevel(1),
|
||||
m_mode(JustCreated),
|
||||
m_window(0),
|
||||
m_studyFFT(0),
|
||||
m_inputDuration(0),
|
||||
m_lastProcessOutputIncrements(16),
|
||||
m_lastProcessLockDf(16),
|
||||
m_lockAudioCurve(0),
|
||||
m_stretchAudioCurve(0),
|
||||
m_stretchCalculator(0),
|
||||
m_freq0(600),
|
||||
m_freq1(1200),
|
||||
m_freq2(12000),
|
||||
m_baseBlockSize(defaultBlockSize)
|
||||
{
|
||||
cerr << "RubberBandStretcher::Impl::Impl: options = " << options << endl;
|
||||
|
||||
if ((options & OptionWindowShort) || (options & OptionWindowLong)) {
|
||||
if ((options & OptionWindowShort) && (options & OptionWindowLong)) {
|
||||
cerr << "RubberBandStretcher::Impl::Impl: Cannot specify OptionWindowLong and OptionWindowShort together; falling back to OptionWindowStandard" << endl;
|
||||
} else if (options & OptionWindowShort) {
|
||||
m_baseBlockSize = defaultBlockSize / 2;
|
||||
cerr << "setting baseBlockSize to " << m_baseBlockSize << endl;
|
||||
} else if (options & OptionWindowLong) {
|
||||
m_baseBlockSize = defaultBlockSize * 2;
|
||||
cerr << "setting baseBlockSize to " << m_baseBlockSize << endl;
|
||||
}
|
||||
m_blockSize = m_baseBlockSize;
|
||||
m_outbufSize = m_baseBlockSize * 2;
|
||||
m_maxProcessBlockSize = m_baseBlockSize;
|
||||
}
|
||||
|
||||
if (m_options & OptionProcessRealTime) {
|
||||
|
||||
m_realtime = true;
|
||||
|
||||
if (!(m_options & OptionStretchPrecise)) {
|
||||
cerr << "RubberBandStretcher::Impl::Impl: Real-time mode: enabling OptionStretchPrecise" << endl;
|
||||
m_options |= OptionStretchPrecise;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_channels > 1) {
|
||||
|
||||
if (!m_realtime &&
|
||||
!(m_options & OptionThreadingNone) &&
|
||||
Thread::threadingAvailable() &&
|
||||
system_is_multiprocessor()) {
|
||||
|
||||
m_threaded = true;
|
||||
|
||||
if (m_debugLevel > 0) {
|
||||
cerr << "Going multithreaded..." << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configure();
|
||||
}
|
||||
|
||||
RubberBandStretcher::Impl::~Impl()
|
||||
{
|
||||
if (m_threaded) {
|
||||
MutexLocker locker(&m_threadSetMutex);
|
||||
for (set<ProcessThread *>::iterator i = m_threadSet.begin();
|
||||
i != m_threadSet.end(); ++i) {
|
||||
if (m_debugLevel > 0) {
|
||||
cerr << "RubberBandStretcher::~RubberBandStretcher: joining (channel " << *i << ")" << endl;
|
||||
}
|
||||
(*i)->wait();
|
||||
delete *i;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t c = 0; c < m_channels; ++c) {
|
||||
delete m_channelData[c];
|
||||
}
|
||||
|
||||
delete m_lockAudioCurve;
|
||||
delete m_stretchAudioCurve;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::reset()
|
||||
{
|
||||
//!!! does not do the right thing in threaded mode
|
||||
|
||||
if (m_threaded) m_threadSetMutex.lock();
|
||||
|
||||
for (size_t c = 0; c < m_channels; ++c) {
|
||||
delete m_channelData[c];
|
||||
m_channelData[c] = new ChannelData(m_blockSize, m_outbufSize);
|
||||
}
|
||||
m_mode = JustCreated;
|
||||
if (m_lockAudioCurve) m_lockAudioCurve->reset();
|
||||
if (m_stretchAudioCurve) m_stretchAudioCurve->reset();
|
||||
m_inputDuration = 0;
|
||||
|
||||
if (m_threaded) m_threadSetMutex.unlock();
|
||||
// m_done = false;
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::setTimeRatio(double ratio)
|
||||
{
|
||||
if (!m_realtime) {
|
||||
if (m_mode == Studying || m_mode == Processing) {
|
||||
cerr << "RubberBandStretcher::Impl::setTimeRatio: Cannot set ratio while studying or processing in non-RT mode" << endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ratio == m_timeRatio) return;
|
||||
m_timeRatio = ratio;
|
||||
|
||||
reconfigure();
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::setPitchScale(double fs)
|
||||
{
|
||||
if (!m_realtime) {
|
||||
if (m_mode == Studying || m_mode == Processing) {
|
||||
cerr << "RubberBandStretcher::Impl::setPitchScale: Cannot set ratio while studying or processing in non-RT mode" << endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (fs == m_pitchScale) return;
|
||||
m_pitchScale = fs;
|
||||
|
||||
reconfigure();
|
||||
}
|
||||
|
||||
double
|
||||
RubberBandStretcher::Impl::getTimeRatio() const
|
||||
{
|
||||
return m_timeRatio;
|
||||
}
|
||||
|
||||
double
|
||||
RubberBandStretcher::Impl::getPitchScale() const
|
||||
{
|
||||
return m_pitchScale;
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::setExpectedInputDuration(size_t samples)
|
||||
{
|
||||
if (samples == m_expectedInputDuration) return;
|
||||
m_expectedInputDuration = samples;
|
||||
|
||||
reconfigure();
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::setMaxProcessBlockSize(size_t samples)
|
||||
{
|
||||
if (samples <= m_maxProcessBlockSize) return;
|
||||
m_maxProcessBlockSize = samples;
|
||||
|
||||
reconfigure();
|
||||
}
|
||||
|
||||
float
|
||||
RubberBandStretcher::Impl::getFrequencyCutoff(int n) const
|
||||
{
|
||||
switch (n) {
|
||||
case 0: return m_freq0;
|
||||
case 1: return m_freq1;
|
||||
case 2: return m_freq2;
|
||||
}
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::setFrequencyCutoff(int n, float f)
|
||||
{
|
||||
switch (n) {
|
||||
case 0: m_freq0 = f; break;
|
||||
case 1: m_freq1 = f; break;
|
||||
case 2: m_freq2 = f; break;
|
||||
}
|
||||
}
|
||||
|
||||
double
|
||||
RubberBandStretcher::Impl::getEffectiveRatio() const
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
size_t
|
||||
RubberBandStretcher::Impl::roundUp(size_t value)
|
||||
{
|
||||
if (!(value & (value - 1))) return value;
|
||||
int bits = 0;
|
||||
while (value) { ++bits; value >>= 1; }
|
||||
value = 1 << bits;
|
||||
return value;
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::calculateSizes()
|
||||
{
|
||||
size_t inputIncrement = defaultIncrement;
|
||||
size_t blockSize = m_baseBlockSize;
|
||||
size_t outputIncrement;
|
||||
|
||||
double r = getEffectiveRatio();
|
||||
|
||||
if (m_realtime) {
|
||||
|
||||
// use a fixed input increment
|
||||
|
||||
inputIncrement = defaultIncrement;
|
||||
|
||||
if (r < 1) {
|
||||
outputIncrement = int(floor(inputIncrement * r));
|
||||
if (outputIncrement < 1) {
|
||||
outputIncrement = 1;
|
||||
inputIncrement = roundUp(lrint(ceil(outputIncrement / r)));
|
||||
blockSize = inputIncrement * 4;
|
||||
}
|
||||
} else {
|
||||
outputIncrement = int(ceil(inputIncrement * r));
|
||||
while (outputIncrement > 1024 && inputIncrement > 1) {
|
||||
inputIncrement /= 2;
|
||||
outputIncrement = lrint(ceil(inputIncrement * r));
|
||||
}
|
||||
blockSize = std::max(blockSize, roundUp(outputIncrement * 4.5));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// use a variable increment
|
||||
|
||||
if (r < 1) {
|
||||
inputIncrement = blockSize / 4;
|
||||
while (inputIncrement >= 512) inputIncrement /= 2;
|
||||
outputIncrement = int(floor(inputIncrement * r));
|
||||
if (outputIncrement < 1) {
|
||||
outputIncrement = 1;
|
||||
inputIncrement = roundUp(lrint(ceil(outputIncrement / r)));
|
||||
blockSize = inputIncrement * 4;
|
||||
}
|
||||
} else {
|
||||
outputIncrement = blockSize / 6;
|
||||
inputIncrement = int(outputIncrement / r);
|
||||
while (outputIncrement > 1024 && inputIncrement > 1) {
|
||||
outputIncrement /= 2;
|
||||
inputIncrement = int(outputIncrement / r);
|
||||
}
|
||||
blockSize = std::max(blockSize, roundUp(outputIncrement * 6));
|
||||
}
|
||||
}
|
||||
|
||||
if (m_expectedInputDuration > 0) {
|
||||
while (inputIncrement * 4 > m_expectedInputDuration &&
|
||||
inputIncrement > 1) {
|
||||
inputIncrement /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
// blockSize can be almost anything, but it can't be greater than
|
||||
// 4 * defaultBlockSize unless ratio is less than 1/1024.
|
||||
|
||||
m_blockSize = blockSize;
|
||||
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 block.
|
||||
|
||||
if (m_debugLevel > 0) {
|
||||
cerr << "configure: effective ratio = " << getEffectiveRatio() << endl;
|
||||
cerr << "configure: block size = " << m_blockSize << ", increment = " << m_increment << " (approx output increment = " << int(lrint(m_increment * getEffectiveRatio())) << ")" << endl;
|
||||
}
|
||||
|
||||
static size_t maxBlockSize = 0;
|
||||
|
||||
if (m_blockSize > maxBlockSize) {
|
||||
//!!!
|
||||
cerr << "configure: NOTE: max block size so far increased from "
|
||||
<< maxBlockSize << " to " << m_blockSize << endl;
|
||||
maxBlockSize = m_blockSize;
|
||||
}
|
||||
|
||||
if (m_blockSize > m_maxProcessBlockSize) {
|
||||
m_maxProcessBlockSize = m_blockSize;
|
||||
}
|
||||
|
||||
m_outbufSize = max
|
||||
(size_t(ceil(m_maxProcessBlockSize / m_pitchScale)),
|
||||
m_blockSize);
|
||||
|
||||
if (m_realtime) {
|
||||
// This headroom is so as to try to avoid reallocation when
|
||||
// the pitch scale changes
|
||||
m_outbufSize = m_outbufSize * 16;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//!!! for very long stretches (e.g. x5), this is necessary; for
|
||||
//even longer ones (e.g. x10), even more of an outbuf is
|
||||
//necessary. clearly something wrong in our calculations... or do
|
||||
//we just need to ensure client calls setMaxProcessBlockSize?
|
||||
if (!m_realtime && !m_threaded) {
|
||||
//!!! m_outbufSize = m_outbufSize * 2;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::configure()
|
||||
{
|
||||
size_t prevBlockSize = m_blockSize;
|
||||
size_t prevOutbufSize = m_outbufSize;
|
||||
if (m_windows.empty()) {
|
||||
prevBlockSize = 0;
|
||||
prevOutbufSize = 0;
|
||||
}
|
||||
|
||||
calculateSizes();
|
||||
|
||||
bool blockSizeChanged = (prevBlockSize != m_blockSize);
|
||||
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> blockSizes;
|
||||
if (m_realtime) {
|
||||
blockSizes.insert(m_baseBlockSize);
|
||||
blockSizes.insert(m_baseBlockSize * 2);
|
||||
blockSizes.insert(m_baseBlockSize * 4);
|
||||
}
|
||||
blockSizes.insert(m_blockSize);
|
||||
|
||||
if (blockSizeChanged) {
|
||||
|
||||
for (set<size_t>::const_iterator i = blockSizes.begin();
|
||||
i != blockSizes.end(); ++i) {
|
||||
if (m_windows.find(*i) == m_windows.end()) {
|
||||
m_windows[*i] = new Window<float>(HanningWindow, *i);
|
||||
}
|
||||
}
|
||||
m_window = m_windows[m_blockSize];
|
||||
|
||||
if (m_debugLevel > 0) {
|
||||
cerr << "Window area: " << m_window->getArea() << "; synthesis window area: " << m_window->getArea() << endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (blockSizeChanged || outbufSizeChanged) {
|
||||
|
||||
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(blockSizes, m_blockSize, m_outbufSize));
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_realtime && blockSizeChanged) {
|
||||
delete m_studyFFT;
|
||||
m_studyFFT = new FFT(m_blockSize);
|
||||
m_studyFFT->initFloat();
|
||||
}
|
||||
|
||||
if (m_pitchScale != 1.0 || m_realtime) {
|
||||
|
||||
for (size_t c = 0; c < m_channels; ++c) {
|
||||
|
||||
if (m_channelData[c]->resampler) continue;
|
||||
|
||||
m_channelData[c]->resampler =
|
||||
new Resampler(Resampler::FastestTolerable, 1, 4096 * 16);
|
||||
|
||||
m_channelData[c]->resamplebufSize =
|
||||
lrintf(ceil((m_increment * m_timeRatio * 2) / m_pitchScale));
|
||||
m_channelData[c]->resamplebuf =
|
||||
new float[m_channelData[c]->resamplebufSize];
|
||||
}
|
||||
}
|
||||
|
||||
delete m_lockAudioCurve;
|
||||
m_lockAudioCurve = new PercussiveAudioCurve(m_stretcher->m_sampleRate,
|
||||
m_blockSize);
|
||||
|
||||
// stretchAudioCurve unused in RT mode; lockAudioCurve and
|
||||
// stretchCalculator however are used in all modes
|
||||
|
||||
if (!m_realtime) {
|
||||
delete m_stretchAudioCurve;
|
||||
if (!(m_options & OptionStretchPrecise)) {
|
||||
//!!! probably adaptively-whitened spectral difference curve
|
||||
//would be better
|
||||
m_stretchAudioCurve = new HighFrequencyAudioCurve
|
||||
(m_stretcher->m_sampleRate, m_blockSize);
|
||||
} else {
|
||||
m_stretchAudioCurve = new ConstantAudioCurve
|
||||
(m_stretcher->m_sampleRate, m_blockSize);
|
||||
}
|
||||
}
|
||||
|
||||
delete m_stretchCalculator;
|
||||
m_stretchCalculator = new StretchCalculator
|
||||
(m_stretcher->m_sampleRate, m_increment,
|
||||
!(m_options & OptionTransientsSmooth));
|
||||
|
||||
m_stretchCalculator->setDebugLevel(m_debugLevel);
|
||||
m_inputDuration = 0;
|
||||
|
||||
// Prepare the inbufs with half a block of emptiness. The centre
|
||||
// point of the first processing block for the onset detector
|
||||
// should be the first sample of the audio, and we continue until
|
||||
// we can no longer centre a block within the input audio. The
|
||||
// number of onset detector blocks will be the number of audio
|
||||
// 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) {
|
||||
for (size_t c = 0; c < m_channels; ++c) {
|
||||
m_channelData[c]->reset();
|
||||
m_channelData[c]->inbuf->zero(m_blockSize/2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//!!! separated out from configure() for the moment so we can look at
|
||||
// the logic of it
|
||||
void
|
||||
RubberBandStretcher::Impl::reconfigure()
|
||||
{
|
||||
if (!m_realtime) {
|
||||
if (m_mode == Studying) {
|
||||
// stop and calculate the stretch curve so far, then reset
|
||||
// the df vectors
|
||||
calculateStretch();
|
||||
m_lockDf.clear();
|
||||
m_stretchDf.clear();
|
||||
m_inputDuration = 0;
|
||||
}
|
||||
configure();
|
||||
}
|
||||
|
||||
size_t prevBlockSize = m_blockSize;
|
||||
size_t prevOutbufSize = m_outbufSize;
|
||||
|
||||
calculateSizes();
|
||||
|
||||
// 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 setBlockSize.
|
||||
|
||||
if (m_blockSize != prevBlockSize) {
|
||||
|
||||
if (m_windows.find(m_blockSize) == m_windows.end()) {
|
||||
std::cerr << "WARNING: reconfigure(): window allocation (size " << m_blockSize << ") required in RT mode" << std::endl;
|
||||
m_windows[m_blockSize] = new Window<float>(HanningWindow, m_blockSize);
|
||||
}
|
||||
m_window = m_windows[m_blockSize];
|
||||
|
||||
for (size_t c = 0; c < m_channels; ++c) {
|
||||
m_channelData[c]->setBlockSize(m_blockSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_outbufSize != prevOutbufSize) {
|
||||
for (size_t c = 0; c < m_channels; ++c) {
|
||||
m_channelData[c]->setOutbufSize(m_outbufSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pitchScale != 1.0) {
|
||||
for (size_t c = 0; c < m_channels; ++c) {
|
||||
|
||||
if (m_channelData[c]->resampler) continue;
|
||||
|
||||
std::cerr << "WARNING: reconfigure(): resampler construction required in RT mode" << std::endl;
|
||||
|
||||
m_channelData[c]->resampler =
|
||||
new Resampler(Resampler::FastestTolerable, 1, m_blockSize);
|
||||
|
||||
m_channelData[c]->resamplebufSize =
|
||||
lrintf(ceil((m_increment * m_timeRatio * 2) / m_pitchScale));
|
||||
m_channelData[c]->resamplebuf =
|
||||
new float[m_channelData[c]->resamplebufSize];
|
||||
}
|
||||
}
|
||||
|
||||
if (m_blockSize != prevBlockSize) {
|
||||
m_lockAudioCurve->setBlockSize(m_blockSize);
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
RubberBandStretcher::Impl::getLatency() const
|
||||
{
|
||||
if (!m_realtime) return 0;
|
||||
return int((m_blockSize/2) / m_pitchScale + 1);
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::setTransientsOption(Options options)
|
||||
{
|
||||
if (options & OptionTransientsSmooth) {
|
||||
m_options |= OptionTransientsSmooth;
|
||||
} else {
|
||||
m_options &= ~OptionTransientsSmooth;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::setPhaseOption(Options options)
|
||||
{
|
||||
if (options & OptionPhaseIndependent) {
|
||||
m_options |= OptionPhaseIndependent;
|
||||
} else {
|
||||
m_options &= ~OptionPhaseIndependent;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::study(const float *const *input, size_t samples, bool final)
|
||||
{
|
||||
if (m_realtime) {
|
||||
if (m_debugLevel > 1) {
|
||||
cerr << "RubberBandStretcher::Impl::study: Not meaningful in realtime mode" << endl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_mode == Processing || m_mode == Finished) {
|
||||
cerr << "RubberBandStretcher::Impl::study: Cannot study after processing" << endl;
|
||||
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
|
||||
cerr << "WARNING: writable == 0 (consumed = " << consumed << ", samples = " << samples << ")" << endl;
|
||||
} else {
|
||||
inbuf.write(mixdown + consumed, writable);
|
||||
consumed += writable;
|
||||
}
|
||||
|
||||
while ((inbuf.getReadSpace() >= m_blockSize) ||
|
||||
(final && (inbuf.getReadSpace() >= m_blockSize/2))) {
|
||||
|
||||
//!!! inconsistency throughout -- we are using "blocksize",
|
||||
// but "chunk" instead of "block"
|
||||
|
||||
// We know we have at least m_blockSize samples available
|
||||
// in m_inbuf. We need to peek m_blockSize of them for
|
||||
// processing, and then skip m_increment to advance the
|
||||
// read pointer.
|
||||
|
||||
// cd.accumulator is not otherwise used during studying,
|
||||
// so we can use it as a temporary buffer here
|
||||
|
||||
size_t got = inbuf.peek(cd.accumulator, m_blockSize);
|
||||
assert(final || got == m_blockSize);
|
||||
|
||||
m_window->cut(cd.accumulator);
|
||||
|
||||
// We don't need the fftshift for studying, as we're only
|
||||
// interested in magnitude
|
||||
|
||||
m_studyFFT->forwardMagnitude(cd.accumulator, cd.fltbuf);
|
||||
|
||||
float df = m_lockAudioCurve->process(cd.fltbuf, m_increment);
|
||||
m_lockDf.push_back(df);
|
||||
|
||||
// cout << m_lockDf.size() << " [" << final << "] -> " << df << " \t: ";
|
||||
|
||||
df = m_stretchAudioCurve->process(cd.fltbuf, m_increment);
|
||||
m_stretchDf.push_back(df);
|
||||
|
||||
// cout << df << endl;
|
||||
|
||||
// We have augmented the input by m_blockSize/2 so
|
||||
// that the first block 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.
|
||||
|
||||
m_inputDuration += m_increment;
|
||||
// cerr << "incr input duration by increment: " << m_increment << " -> " << m_inputDuration << endl;
|
||||
inbuf.skip(m_increment);
|
||||
}
|
||||
}
|
||||
|
||||
if (final) {
|
||||
int rs = inbuf.getReadSpace();
|
||||
m_inputDuration += rs;
|
||||
// cerr << "incr input duration by read space: " << rs << " -> " << m_inputDuration << endl;
|
||||
|
||||
if (m_inputDuration > m_blockSize/2) { // deducting the extra
|
||||
m_inputDuration -= m_blockSize/2;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_channels > 1) delete[] mdalloc;
|
||||
}
|
||||
|
||||
vector<int>
|
||||
RubberBandStretcher::Impl::getOutputIncrements() const
|
||||
{
|
||||
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>
|
||||
RubberBandStretcher::Impl::getLockCurve() const
|
||||
{
|
||||
if (!m_realtime) {
|
||||
return m_lockDf;
|
||||
} else {
|
||||
vector<float> df;
|
||||
while (m_lastProcessLockDf.getReadSpace() > 0) {
|
||||
df.push_back(m_lastProcessLockDf.readOne());
|
||||
}
|
||||
return df;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::calculateStretch()
|
||||
{
|
||||
std::vector<int> increments = m_stretchCalculator->calculate
|
||||
(getEffectiveRatio(),
|
||||
m_inputDuration,
|
||||
m_lockDf,
|
||||
m_stretchDf);
|
||||
|
||||
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
|
||||
RubberBandStretcher::Impl::setDebugLevel(int level)
|
||||
{
|
||||
m_debugLevel = level;
|
||||
if (m_stretchCalculator) m_stretchCalculator->setDebugLevel(level);
|
||||
}
|
||||
|
||||
size_t
|
||||
RubberBandStretcher::Impl::getSamplesRequired() const
|
||||
{
|
||||
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;
|
||||
|
||||
size_t rs = inbuf.getReadSpace();
|
||||
|
||||
// See notes in testInbufReadSpace below
|
||||
|
||||
if (rs < m_blockSize && !cd.draining) {
|
||||
|
||||
if (cd.inputSize == -1) {
|
||||
reqdHere = m_blockSize - rs;
|
||||
if (reqdHere > reqd) reqd = reqdHere;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rs == 0) {
|
||||
reqdHere = m_blockSize;
|
||||
if (reqdHere > reqd) reqd = reqdHere;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reqd;
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::process(const float *const *input, size_t samples, bool final)
|
||||
{
|
||||
if (m_mode == Finished) {
|
||||
cerr << "RubberBandStretcher::Impl::process: Cannot process again after final block" << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_mode == JustCreated || m_mode == Studying) {
|
||||
|
||||
//!!! m_studying isn't the right test for "is this the first
|
||||
//time we've processed?"
|
||||
|
||||
if (m_mode == Studying) {
|
||||
calculateStretch();
|
||||
}
|
||||
|
||||
for (size_t c = 0; c < m_channels; ++c) {
|
||||
m_channelData[c]->reset();
|
||||
m_channelData[c]->inbuf->zero(m_blockSize/2);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (m_debugLevel > 0) {
|
||||
cerr << m_channels << " threads created" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
m_mode = Processing;
|
||||
}
|
||||
|
||||
bool allConsumed = false;
|
||||
|
||||
map<size_t, size_t> consumed;
|
||||
for (size_t c = 0; c < m_channels; ++c) {
|
||||
consumed[c] = 0;
|
||||
}
|
||||
|
||||
while (!allConsumed) {
|
||||
|
||||
// cerr << "process looping" << endl;
|
||||
|
||||
//#ifndef NO_THREADING
|
||||
// if (m_threaded) {
|
||||
// pthread_mutex_lock(&m_inputProcessedMutex);
|
||||
// }
|
||||
//#endif
|
||||
|
||||
// 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;
|
||||
for (size_t c = 0; c < m_channels; ++c) {
|
||||
consumed[c] += consumeChannel(c,
|
||||
input[c] + consumed[c],
|
||||
samples - consumed[c]);
|
||||
if (consumed[c] < samples) {
|
||||
allConsumed = false;
|
||||
// cerr << "process: waiting on input consumption for channel " << c << endl;
|
||||
} else {
|
||||
if (final) {
|
||||
m_channelData[c]->inputSize = m_channelData[c]->inCount;
|
||||
}
|
||||
// cerr << "process: happy with channel " << c << endl;
|
||||
}
|
||||
if (!m_threaded && !m_realtime) {
|
||||
processChunks(c);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (m_threaded) {
|
||||
m_dataAvailable.signal();
|
||||
m_spaceAvailable.lock();
|
||||
if (!allConsumed) {
|
||||
m_spaceAvailable.wait(500);
|
||||
}
|
||||
/*
|
||||
} else {
|
||||
if (!allConsumed) {
|
||||
cerr << "RubberBandStretcher::Impl::process: ERROR: Too much data provided to process() call -- either call setMaxProcessBlockSize() beforehand, or provide only getSamplesRequired() frames at a time" << endl;
|
||||
for (size_t c = 0; c < m_channels; ++c) {
|
||||
cerr << "channel " << c << ": " << samples << " provided, " << consumed[c] << " consumed" << endl;
|
||||
}
|
||||
// break;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
// cerr << "process returning" << endl;
|
||||
|
||||
if (final) m_mode = Finished;
|
||||
}
|
||||
|
||||
size_t
|
||||
RubberBandStretcher::Impl::consumeChannel(size_t c, const float *input, size_t samples)
|
||||
{
|
||||
size_t consumed = 0;
|
||||
|
||||
ChannelData &cd = *m_channelData[c];
|
||||
RingBuffer<float> &inbuf = *cd.inbuf;
|
||||
|
||||
while (consumed < samples) {
|
||||
|
||||
size_t writable = inbuf.getWriteSpace();
|
||||
|
||||
// cerr << "channel " << c << ": samples remaining = " << samples - consumed << ", writable space = " << writable << endl;
|
||||
|
||||
writable = min(writable, samples - consumed);
|
||||
|
||||
if (writable == 0) {
|
||||
// warn
|
||||
// cerr << "WARNING: writable == 0 for ch " << c << " (consumed = " << consumed << ", samples = " << samples << ")" << endl;
|
||||
return consumed;
|
||||
} else {
|
||||
inbuf.write(input + consumed, writable);
|
||||
consumed += writable;
|
||||
cd.inCount += writable;
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user