206 lines
5.6 KiB
C++
206 lines
5.6 KiB
C++
/*******************************************************************************
|
|
* Copyright 2009-2025 Jörg Müller
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
******************************************************************************/
|
|
|
|
#include "fx/TimeStretchPitchScaleReader.h"
|
|
|
|
#include <cstring>
|
|
|
|
#include "Exception.h"
|
|
#include "IReader.h"
|
|
|
|
#include "util/Buffer.h"
|
|
|
|
using namespace RubberBand;
|
|
|
|
AUD_NAMESPACE_BEGIN
|
|
|
|
void TimeStretchPitchScaleReader::reset()
|
|
{
|
|
auto startPad{m_stretcher->getPreferredStartPad()};
|
|
|
|
m_samplesToDrop = m_stretcher->getStartDelay();
|
|
|
|
m_deinterleaved[0].assureSize(startPad * sizeof(sample_t));
|
|
std::memset(m_deinterleaved[0].getBuffer(), 0, startPad * sizeof(sample_t));
|
|
|
|
for(auto& channel : m_channelData)
|
|
channel = m_deinterleaved[0].getBuffer();
|
|
|
|
m_stretcher->process(m_channelData.data(), startPad, m_finishedReader);
|
|
}
|
|
|
|
TimeStretchPitchScaleReader::TimeStretchPitchScaleReader(std::shared_ptr<IReader> reader, double timeRatio, double pitchScale, StretcherQuality quality, bool preserveFormant) :
|
|
EffectReader(reader), m_position(0), m_finishedReader(false), m_deinterleaved(reader->getSpecs().channels), m_channelData(reader->getSpecs().channels)
|
|
{
|
|
if (pitchScale < 1.0 / 256.0 || pitchScale > 256.0)
|
|
AUD_THROW(StateException, "The pitch scale must be between 1/256 and 256");
|
|
|
|
if (timeRatio < 1.0 / 256.0 || timeRatio > 256.0)
|
|
AUD_THROW(StateException, "The time-stretch ratio must be between 1/256 and 256");
|
|
|
|
RubberBandStretcher::Options options = RubberBandStretcher::OptionProcessRealTime | RubberBandStretcher::OptionEngineFiner | RubberBandStretcher::OptionChannelsTogether;
|
|
|
|
switch(quality)
|
|
{
|
|
case StretcherQuality::HIGH:
|
|
options |= RubberBandStretcher::OptionPitchHighQuality;
|
|
break;
|
|
case StretcherQuality::FAST:
|
|
options |= RubberBandStretcher::OptionPitchHighSpeed;
|
|
options |= RubberBandStretcher::OptionWindowShort;
|
|
break;
|
|
case StretcherQuality::CONSISTENT:
|
|
options |= RubberBandStretcher::OptionPitchHighConsistency;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
options |= preserveFormant ? RubberBandStretcher::OptionFormantPreserved : RubberBandStretcher::OptionFormantShifted;
|
|
m_stretcher = std::make_unique<RubberBandStretcher>(m_reader->getSpecs().rate, m_reader->getSpecs().channels, options, timeRatio, pitchScale);
|
|
|
|
reset();
|
|
}
|
|
|
|
void TimeStretchPitchScaleReader::read(int& length, bool& eos, sample_t* buffer)
|
|
{
|
|
if(length == 0)
|
|
return;
|
|
|
|
int samplesize = AUD_SAMPLE_SIZE(m_reader->getSpecs());
|
|
int channels = m_reader->getSpecs().channels;
|
|
int samplesRead = 0;
|
|
|
|
eos = false;
|
|
while(samplesRead < length)
|
|
{
|
|
int len = m_stretcher->getSamplesRequired();
|
|
if(!m_finishedReader && len != 0)
|
|
{
|
|
m_buffer.assureSize(len * samplesize);
|
|
sample_t* buf = m_buffer.getBuffer();
|
|
|
|
m_reader->read(len, m_finishedReader, buf);
|
|
|
|
// Deinterleave the input reader buffer for processing
|
|
for(int channel = 0; channel < channels; channel++)
|
|
{
|
|
m_deinterleaved[channel].assureSize(len * sizeof(sample_t));
|
|
sample_t* channelBuf = m_deinterleaved[channel].getBuffer();
|
|
|
|
for(int i = 0; i < len; i++)
|
|
{
|
|
channelBuf[i] = buf[i * channels + channel];
|
|
}
|
|
|
|
m_channelData[channel] = channelBuf;
|
|
}
|
|
|
|
m_stretcher->process(m_channelData.data(), len, m_finishedReader);
|
|
}
|
|
|
|
int available = m_stretcher->available();
|
|
if(available == -1)
|
|
{
|
|
eos = true;
|
|
break;
|
|
}
|
|
|
|
if(available == 0)
|
|
continue;
|
|
|
|
available = std::min(m_samplesToDrop ? m_samplesToDrop : length - samplesRead, available);
|
|
|
|
for(int channel = 0; channel < channels; channel++)
|
|
{
|
|
m_deinterleaved[channel].assureSize(available * sizeof(sample_t));
|
|
m_channelData[channel] = m_deinterleaved[channel].getBuffer();
|
|
}
|
|
|
|
m_stretcher->retrieve(m_channelData.data(), available);
|
|
|
|
if(m_samplesToDrop)
|
|
{
|
|
m_samplesToDrop -= available;
|
|
}
|
|
else
|
|
{
|
|
// Interleave the retrieved data into the buffer
|
|
for(int channel = 0; channel < channels; channel++)
|
|
{
|
|
sample_t* outputBuf = m_deinterleaved[channel].getBuffer();
|
|
for(int i = 0; i < available; i++)
|
|
{
|
|
buffer[(samplesRead + i) * channels + channel] = outputBuf[i];
|
|
}
|
|
}
|
|
|
|
samplesRead += available;
|
|
}
|
|
}
|
|
|
|
length = samplesRead;
|
|
m_position += length;
|
|
eos = m_stretcher->available() == -1;
|
|
}
|
|
|
|
double TimeStretchPitchScaleReader::getTimeRatio() const
|
|
{
|
|
return m_stretcher->getTimeRatio();
|
|
}
|
|
|
|
void TimeStretchPitchScaleReader::setTimeRatio(double timeRatio)
|
|
{
|
|
if(timeRatio >= 1.0 / 256.0 && timeRatio <= 256.0)
|
|
{
|
|
m_stretcher->setTimeRatio(timeRatio);
|
|
}
|
|
}
|
|
|
|
double TimeStretchPitchScaleReader::getPitchScale() const
|
|
{
|
|
return m_stretcher->getPitchScale();
|
|
}
|
|
|
|
void TimeStretchPitchScaleReader::setPitchScale(double pitchScale)
|
|
{
|
|
if(pitchScale >= 1.0 / 256.0 && pitchScale <= 256.0)
|
|
{
|
|
m_stretcher->setPitchScale(pitchScale);
|
|
}
|
|
}
|
|
|
|
void TimeStretchPitchScaleReader::seek(int position)
|
|
{
|
|
m_reader->seek(int(position / getTimeRatio()));
|
|
m_finishedReader = false;
|
|
m_stretcher->reset();
|
|
reset();
|
|
m_position = position;
|
|
}
|
|
|
|
int TimeStretchPitchScaleReader::getLength() const
|
|
{
|
|
return m_reader->getLength() * getTimeRatio();
|
|
}
|
|
|
|
int TimeStretchPitchScaleReader::getPosition() const
|
|
{
|
|
return m_position;
|
|
}
|
|
|
|
AUD_NAMESPACE_END
|