//////////////////////////////////////////////////////////////////////////////
///
/// SoundTouch - main class for tempo/pitch/rate adjusting routines.
///
/// Notes:
/// - Initialize the SoundTouch object instance by setting up the sound stream
/// parameters with functions 'setSampleRate' and 'setChannels', then set
/// desired tempo/pitch/rate settings with the corresponding functions.
///
/// - The SoundTouch class behaves like a first-in-first-out pipeline: The
/// samples that are to be processed are fed into one of the pipe by calling
/// function 'putSamples', while the ready processed samples can be read
/// from the other end of the pipeline with function 'receiveSamples'.
///
/// - The SoundTouch processing classes require certain sized 'batches' of
/// samples in order to process the sound. For this reason the classes buffer
/// incoming samples until there are enough of samples available for
/// processing, then they carry out the processing step and consequently
/// make the processed samples available for outputting.
///
/// - For the above reason, the processing routines introduce a certain
/// 'latency' between the input and output, so that the samples input to
/// SoundTouch may not be immediately available in the output, and neither
/// the amount of outputtable samples may not immediately be in direct
/// relationship with the amount of previously input samples.
///
/// - The tempo/pitch/rate control parameters can be altered during processing.
/// Please notice though that they aren't currently protected by semaphores,
/// so in multi-thread application external semaphore protection may be
/// required.
///
/// - This class utilizes classes 'TDStretch' for tempo change (without modifying
/// pitch) and 'RateTransposer' for changing the playback rate (that is, both
/// tempo and pitch in the same ratio) of the sound. The third available control
/// 'pitch' (change pitch but maintain tempo) is produced by a combination of
/// combining the two other controls.
///
/// Author : Copyright (c) Olli Parviainen
/// Author e-mail : oparviai 'at' iki.fi
/// SoundTouch WWW: http://www.surina.net/soundtouch
///
////////////////////////////////////////////////////////////////////////////////
//
// Last changed : $Date: 2012-06-13 16:29:53 -0300 (qua, 13 jun 2012) $
// File revision : $Revision: 4 $
//
// $Id: SoundTouch.cpp 143 2012-06-13 19:29:53Z oparviai $
//
////////////////////////////////////////////////////////////////////////////////
//
// License :
//
// SoundTouch audio processing library
// Copyright (c) Olli Parviainen
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
////////////////////////////////////////////////////////////////////////////////
#include "XSFCommon.h"
#include <stdexcept>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include "SoundTouch.h"
#include "TDStretch.h"
#include "RateTransposer.h"
#include "cpu_detect.h"
using namespace soundtouch;
SoundTouch::SoundTouch()
{
// Initialize rate transposer and tempo changer instances
this->pRateTransposer.reset(RateTransposer::newInstance());
this->pTDStretch.reset(TDStretch::newInstance());
this->setOutPipe(this->pTDStretch.get());
this->rate = this->tempo = 0;
this->virtualPitch = this->virtualRate = this->virtualTempo = 1.0;
this->calcEffectiveRateAndTempo();
this->channels = 0;
this->bSrateSet = false;
}
SoundTouch::~SoundTouch()
{
}
// Sets the number of channels, 1 = mono, 2 = stereo
void SoundTouch::setChannels(uint32_t numChannels)
{
if (numChannels != 1 && numChannels != 2)
throw std::runtime_error("Illegal number of channels");
this->channels = numChannels;
this->pRateTransposer->setChannels(static_cast<int32_t>(numChannels));
this->pTDStretch->setChannels(static_cast<int32_t>(numChannels));
}
// Sets new rate control value. Normal rate = 1.0, smaller values
// represent slower rate, larger faster rates.
void SoundTouch::setRate(float newRate)
{
this->virtualRate = newRate;
this->calcEffectiveRateAndTempo();
}
// Sets new rate control value as a difference in percents compared
// to the original rate (-50 .. +100 %)
void SoundTouch::setRateChange(float newRate)
{
this->virtualRate = 1.0f + 0.01f * newRate;
this->calcEffectiveRateAndTempo();
}
// Sets new tempo control value. Normal tempo = 1.0, smaller values
// represent slower tempo, larger faster tempo.
void SoundTouch::setTempo(float newTempo)
{
this->virtualTempo = newTempo;
this->calcEffectiveRateAndTempo();
}
// Sets new tempo control value as a difference in percents compared
// to the original tempo (-50 .. +100 %)
void SoundTouch::setTempoChange(float newTempo)
{
this->virtualTempo = 1.0f + 0.01f * newTempo;
this->calcEffectiveRateAndTempo();
}
// Sets new pitch control value. Original pitch = 1.0, smaller values
// represent lower pitches, larger values higher pitch.
void SoundTouch::setPitch(float newPitch)
{
this->virtualPitch = newPitch;
this->calcEffectiveRateAndTempo();
}
// Sets pitch change in octaves compared to the original pitch
// (-1.00 .. +1.00)
void SoundTouch::setPitchOctaves(float newPitch)
{
this->virtualPitch = std::exp(0.69314718056f * newPitch);
this->calcEffectiveRateAndTempo();
}
// Sets pitch change in semi-tones compared to the original pitch
// (-12 .. +12)
void SoundTouch::setPitchSemiTones(int newPitch)
{
this->setPitchOctaves(newPitch / 12.0f);
}
void SoundTouch::setPitchSemiTones(float newPitch)
{
this->setPitchOctaves(newPitch / 12.0f);
}
// Calculates 'effective' rate and tempo values from the
// nominal control values.
void SoundTouch::calcEffectiveRateAndTempo()
{
float oldTempo = this->tempo;
float oldRate = this->rate;
this->tempo = this->virtualTempo / this->virtualPitch;
this->rate = this->virtualPitch * this->virtualRate;
if (!fEqual(this->rate, oldRate))
this->pRateTransposer->setRate(this->rate);
if (!fEqual(this->tempo, oldTempo))
this->pTDStretch->setTempo(this->tempo);
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER
if (this->rate <= 1.0f)
{
if (this->output != this->pTDStretch.get())
{
FIFOSamplePipe *tempoOut;
assert(this->output == this->pRateTransposer.get());
// move samples in the current output buffer to the output of pTDStretch
tempoOut = this->pTDStretch->getOutput();
tempoOut->moveSamples(*this->output);
// move samples in pitch transposer's store buffer to tempo changer's input
this->pTDStretch->moveSamples(*this->pRateTransposer->getStore());
this->output = pTDStretch.get();
}
}
else
#endif
{
if (this->output != this->pRateTransposer.get())
{
assert(this->output == this->pTDStretch.get());
// move samples in the current output buffer to the output of pRateTransposer
FIFOSamplePipe *transOut = this->pRateTransposer->getOutput();
transOut->moveSamples(*this->output);
// move samples in tempo changer's input to pitch transposer's input
this->pRateTransposer->moveSamples(*this->pTDStretch->getInput());
this->output = this->pRateTransposer.get();
}
}
}
// Sets sample rate.
void SoundTouch::setSampleRate(uint32_t srate)
{
this->bSrateSet = true;
// set sample rate, leave other tempo changer parameters as they are.
this->pTDStretch->setParameters(static_cast<int32_t>(srate));
}
// Adds 'numSamples' pcs of samples from the 'samples' memory position into
// the input of the object.
void SoundTouch::putSamples(const SAMPLETYPE *samples, uint32_t nSamples)
{
if (!this->bSrateSet)
throw std::runtime_error("SoundTouch : Sample rate not defined");
else if (!this->channels)
throw std::runtime_error("SoundTouch : Number of channels not defined");
// Transpose the rate of the new samples if necessary
/* Bypass the nominal setting - can introduce a click in sound when tempo/pitch control crosses the nominal value...
if (this->rate == 1.0f)
{
// The rate value is same as the original, simply evaluate the tempo changer.
assert(this->output == this->pTDStretch.get());
if (!this->pRateTransposer->isEmpty())
{
// yet flush the last samples in the pitch transposer buffer
// (may happen if 'rate' changes from a non-zero value to zero)
this->pTDStretch->moveSamples(*this->pRateTransposer);
}
this->pTDStretch->putSamples(samples, nSamples);
}*/
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER
else if (this->rate <= 1.0f)
{
// transpose the rate down, output the transposed sound to tempo changer buffer
assert(this->output == this->pTDStretch.get());
this->pRateTransposer->putSamples(samples, nSamples);
this->pTDStretch->moveSamples(*this->pRateTransposer);
}
else
#endif
{
assert(this->rate > 1.0f);
// evaluate the tempo changer, then transpose the rate up,
assert(this->output == this->pRateTransposer.get());
this->pTDStretch->putSamples(samples, nSamples);
this->pRateTransposer->moveSamples(*this->pTDStretch);
}
}
// Flushes the last samples from the processing pipeline to the output.
// Clears also the internal processing buffers.
//
// Note: This function is meant for extracting the last samples of a sound
// stream. This function may introduce additional blank samples in the end
// of the sound stream, and thus it's not recommended to call this function
// in the middle of a sound stream.
void SoundTouch::flush()
{
// check how many samples still await processing, and scale
// that by tempo & rate to get expected output sample count
int32_t nUnprocessed = this->numUnprocessedSamples();
nUnprocessed = static_cast<int32_t>(nUnprocessed / (tempo * rate) + 0.5);
int32_t nOut = this->numSamples(); // ready samples currently in buffer ...
nOut += nUnprocessed; // ... and how many we expect there to be in the end
// "Push" the last active samples out from the processing pipeline by
// feeding blank samples into the processing pipeline until new,
// processed samples appear in the output (not however, more than
// 8ksamples in any case)
SAMPLETYPE buff[128] = { 0 };
for (int i = 0; i < 128; ++i)
{
this->putSamples(buff, 64);
if (static_cast<int32_t>(numSamples()) >= nOut)
{
// Enough new samples have appeared into the output!
// As samples come from processing with bigger chunks, now truncate it
// back to maximum "nOut" samples to improve duration accuracy
this->adjustAmountOfSamples(nOut);
// finish
break;
}
}
// Clear working buffers
this->pRateTransposer->clear();
this->pTDStretch->clearInput();
// yet leave the 'tempoChanger' output intouched as that's where the
// flushed samples are!
}
// Changes a setting controlling the processing system behaviour. See the
// 'SETTING_...' defines for available setting ID's.
bool SoundTouch::setSetting(int32_t settingId, int32_t value)
{
int32_t sampleRate, sequenceMs, seekWindowMs, overlapMs;
// read current tdstretch routine parameters
pTDStretch->getParameters(&sampleRate, &sequenceMs, &seekWindowMs, &overlapMs);
switch (settingId)
{
case SETTING_USE_AA_FILTER:
// enables / disabless anti-alias filter
this->pRateTransposer->enableAAFilter(!!value);
return true;
case SETTING_AA_FILTER_LENGTH:
// sets anti-alias filter length
this->pRateTransposer->getAAFilter()->setLength(value);
return true;
case SETTING_USE_QUICKSEEK:
// enables / disables tempo routine quick seeking algorithm
this->pTDStretch->enableQuickSeek(!!value);
return true;
case SETTING_SEQUENCE_MS:
// change time-stretch sequence duration parameter
this->pTDStretch->setParameters(sampleRate, value, seekWindowMs, overlapMs);
return true;
case SETTING_SEEKWINDOW_MS:
// change time-stretch seek window length parameter
this->pTDStretch->setParameters(sampleRate, sequenceMs, value, overlapMs);
return true;
case SETTING_OVERLAP_MS:
// change time-stretch overlap length parameter
this->pTDStretch->setParameters(sampleRate, sequenceMs, seekWindowMs, value);
return true;
default:
return false;
}
}
// Reads a setting controlling the processing system behaviour. See the
// 'SETTING_...' defines for available setting ID's.
//
// Returns the setting value.
int32_t SoundTouch::getSetting(int32_t settingId) const
{
int32_t temp;
switch (settingId)
{
case SETTING_USE_AA_FILTER:
return this->pRateTransposer->isAAFilterEnabled();
case SETTING_AA_FILTER_LENGTH:
return this->pRateTransposer->getAAFilter()->getLength();
case SETTING_USE_QUICKSEEK:
return this->pTDStretch->isQuickSeekEnabled();
case SETTING_SEQUENCE_MS:
this->pTDStretch->getParameters(nullptr, &temp, nullptr, nullptr);
return temp;
case SETTING_SEEKWINDOW_MS:
this->pTDStretch->getParameters(nullptr, nullptr, &temp, nullptr);
return temp;
case SETTING_OVERLAP_MS:
this->pTDStretch->getParameters(nullptr, nullptr, nullptr, &temp);
return temp;
case SETTING_NOMINAL_INPUT_SEQUENCE:
return this->pTDStretch->getInputSampleReq();
case SETTING_NOMINAL_OUTPUT_SEQUENCE:
return this->pTDStretch->getOutputBatchSize();
default:
return 0;
}
}
// Clears all the samples in the object's output and internal processing
// buffers.
void SoundTouch::clear()
{
this->pRateTransposer->clear();
this->pTDStretch->clear();
}
/// Returns number of samples currently unprocessed.
uint32_t SoundTouch::numUnprocessedSamples() const
{
if (this->pTDStretch.get())
{
FIFOSamplePipe *psp = this->pTDStretch->getInput();
if (psp)
return psp->numSamples();
}
return 0;
}