You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
538 lines
16 KiB
538 lines
16 KiB
////////////////////////////////////////////////////////////////////////////// |
|
/// |
|
/// 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 |
|
/// |
|
//////////////////////////////////////////////////////////////////////////////// |
|
// |
|
// 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 <assert.h> |
|
#include <stdlib.h> |
|
#include <memory.h> |
|
#include <math.h> |
|
#include <stdio.h> |
|
|
|
#include "SoundTouch.h" |
|
#include "TDStretch.h" |
|
#include "RateTransposer.h" |
|
#include "cpu_detect.h" |
|
|
|
using namespace soundtouch; |
|
|
|
/// test if two floating point numbers are equal |
|
#define TEST_FLOAT_EQUAL(a, b) (fabs(a - b) < 1e-10) |
|
|
|
|
|
/// Print library version string for autoconf |
|
extern "C" void soundtouch_ac_test() |
|
{ |
|
printf("SoundTouch Version: %s\n",SOUNDTOUCH_VERSION); |
|
} |
|
|
|
|
|
SoundTouch::SoundTouch() |
|
{ |
|
// Initialize rate transposer and tempo changer instances |
|
|
|
pRateTransposer = new RateTransposer(); |
|
pTDStretch = TDStretch::newInstance(); |
|
|
|
setOutPipe(pTDStretch); |
|
|
|
rate = tempo = 0; |
|
|
|
virtualPitch = |
|
virtualRate = |
|
virtualTempo = 1.0; |
|
|
|
calcEffectiveRateAndTempo(); |
|
|
|
samplesExpectedOut = 0; |
|
samplesOutput = 0; |
|
|
|
channels = 0; |
|
bSrateSet = false; |
|
} |
|
|
|
|
|
SoundTouch::~SoundTouch() |
|
{ |
|
delete pRateTransposer; |
|
delete pTDStretch; |
|
} |
|
|
|
|
|
/// Get SoundTouch library version string |
|
const char *SoundTouch::getVersionString() |
|
{ |
|
static const char *_version = SOUNDTOUCH_VERSION; |
|
|
|
return _version; |
|
} |
|
|
|
|
|
/// Get SoundTouch library version Id |
|
uint SoundTouch::getVersionId() |
|
{ |
|
return SOUNDTOUCH_VERSION_ID; |
|
} |
|
|
|
|
|
// Sets the number of channels, 1 = mono, 2 = stereo |
|
void SoundTouch::setChannels(uint numChannels) |
|
{ |
|
if (!verifyNumberOfChannels(numChannels)) return; |
|
|
|
channels = numChannels; |
|
pRateTransposer->setChannels((int)numChannels); |
|
pTDStretch->setChannels((int)numChannels); |
|
} |
|
|
|
|
|
// Sets new rate control value. Normal rate = 1.0, smaller values |
|
// represent slower rate, larger faster rates. |
|
void SoundTouch::setRate(double newRate) |
|
{ |
|
virtualRate = newRate; |
|
calcEffectiveRateAndTempo(); |
|
} |
|
|
|
|
|
// Sets new rate control value as a difference in percents compared |
|
// to the original rate (-50 .. +100 %) |
|
void SoundTouch::setRateChange(double newRate) |
|
{ |
|
virtualRate = 1.0 + 0.01 * newRate; |
|
calcEffectiveRateAndTempo(); |
|
} |
|
|
|
|
|
// Sets new tempo control value. Normal tempo = 1.0, smaller values |
|
// represent slower tempo, larger faster tempo. |
|
void SoundTouch::setTempo(double newTempo) |
|
{ |
|
virtualTempo = newTempo; |
|
calcEffectiveRateAndTempo(); |
|
} |
|
|
|
|
|
// Sets new tempo control value as a difference in percents compared |
|
// to the original tempo (-50 .. +100 %) |
|
void SoundTouch::setTempoChange(double newTempo) |
|
{ |
|
virtualTempo = 1.0 + 0.01 * newTempo; |
|
calcEffectiveRateAndTempo(); |
|
} |
|
|
|
|
|
// Sets new pitch control value. Original pitch = 1.0, smaller values |
|
// represent lower pitches, larger values higher pitch. |
|
void SoundTouch::setPitch(double newPitch) |
|
{ |
|
virtualPitch = newPitch; |
|
calcEffectiveRateAndTempo(); |
|
} |
|
|
|
|
|
// Sets pitch change in octaves compared to the original pitch |
|
// (-1.00 .. +1.00) |
|
void SoundTouch::setPitchOctaves(double newPitch) |
|
{ |
|
virtualPitch = exp(0.69314718056 * newPitch); |
|
calcEffectiveRateAndTempo(); |
|
} |
|
|
|
|
|
// Sets pitch change in semi-tones compared to the original pitch |
|
// (-12 .. +12) |
|
void SoundTouch::setPitchSemiTones(int newPitch) |
|
{ |
|
setPitchOctaves((double)newPitch / 12.0); |
|
} |
|
|
|
|
|
void SoundTouch::setPitchSemiTones(double newPitch) |
|
{ |
|
setPitchOctaves(newPitch / 12.0); |
|
} |
|
|
|
|
|
// Calculates 'effective' rate and tempo values from the |
|
// nominal control values. |
|
void SoundTouch::calcEffectiveRateAndTempo() |
|
{ |
|
double oldTempo = tempo; |
|
double oldRate = rate; |
|
|
|
tempo = virtualTempo / virtualPitch; |
|
rate = virtualPitch * virtualRate; |
|
|
|
if (!TEST_FLOAT_EQUAL(rate,oldRate)) pRateTransposer->setRate(rate); |
|
if (!TEST_FLOAT_EQUAL(tempo, oldTempo)) pTDStretch->setTempo(tempo); |
|
|
|
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER |
|
if (rate <= 1.0f) |
|
{ |
|
if (output != pTDStretch) |
|
{ |
|
FIFOSamplePipe *tempoOut; |
|
|
|
assert(output == pRateTransposer); |
|
// move samples in the current output buffer to the output of pTDStretch |
|
tempoOut = pTDStretch->getOutput(); |
|
tempoOut->moveSamples(*output); |
|
// move samples in pitch transposer's store buffer to tempo changer's input |
|
// deprecated : pTDStretch->moveSamples(*pRateTransposer->getStore()); |
|
|
|
output = pTDStretch; |
|
} |
|
} |
|
else |
|
#endif |
|
{ |
|
if (output != pRateTransposer) |
|
{ |
|
FIFOSamplePipe *transOut; |
|
|
|
assert(output == pTDStretch); |
|
// move samples in the current output buffer to the output of pRateTransposer |
|
transOut = pRateTransposer->getOutput(); |
|
transOut->moveSamples(*output); |
|
// move samples in tempo changer's input to pitch transposer's input |
|
pRateTransposer->moveSamples(*pTDStretch->getInput()); |
|
|
|
output = pRateTransposer; |
|
} |
|
} |
|
} |
|
|
|
|
|
// Sets sample rate. |
|
void SoundTouch::setSampleRate(uint srate) |
|
{ |
|
// set sample rate, leave other tempo changer parameters as they are. |
|
pTDStretch->setParameters((int)srate); |
|
bSrateSet = true; |
|
} |
|
|
|
|
|
// Adds 'numSamples' pcs of samples from the 'samples' memory position into |
|
// the input of the object. |
|
void SoundTouch::putSamples(const SAMPLETYPE *samples, uint nSamples) |
|
{ |
|
if (bSrateSet == false) |
|
{ |
|
ST_THROW_RT_ERROR("SoundTouch : Sample rate not defined"); |
|
} |
|
else if (channels == 0) |
|
{ |
|
ST_THROW_RT_ERROR("SoundTouch : Number of channels not defined"); |
|
} |
|
|
|
// accumulate how many samples are expected out from processing, given the current |
|
// processing setting |
|
samplesExpectedOut += (double)nSamples / ((double)rate * (double)tempo); |
|
|
|
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER |
|
if (rate <= 1.0f) |
|
{ |
|
// transpose the rate down, output the transposed sound to tempo changer buffer |
|
assert(output == pTDStretch); |
|
pRateTransposer->putSamples(samples, nSamples); |
|
pTDStretch->moveSamples(*pRateTransposer); |
|
} |
|
else |
|
#endif |
|
{ |
|
// evaluate the tempo changer, then transpose the rate up, |
|
assert(output == pRateTransposer); |
|
pTDStretch->putSamples(samples, nSamples); |
|
pRateTransposer->moveSamples(*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() |
|
{ |
|
int i; |
|
int numStillExpected; |
|
SAMPLETYPE *buff = new SAMPLETYPE[128 * channels]; |
|
|
|
// how many samples are still expected to output |
|
numStillExpected = (int)((long)(samplesExpectedOut + 0.5) - samplesOutput); |
|
if (numStillExpected < 0) numStillExpected = 0; |
|
|
|
memset(buff, 0, 128 * channels * sizeof(SAMPLETYPE)); |
|
// "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 |
|
// 24ksamples in any case) |
|
for (i = 0; (numStillExpected > (int)numSamples()) && (i < 200); i ++) |
|
{ |
|
putSamples(buff, 128); |
|
} |
|
|
|
adjustAmountOfSamples(numStillExpected); |
|
|
|
delete[] buff; |
|
|
|
// Clear input buffers |
|
pTDStretch->clearInput(); |
|
// yet leave the 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(int settingId, int value) |
|
{ |
|
int 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 |
|
pRateTransposer->enableAAFilter((value != 0) ? true : false); |
|
return true; |
|
|
|
case SETTING_AA_FILTER_LENGTH : |
|
// sets anti-alias filter length |
|
pRateTransposer->getAAFilter()->setLength(value); |
|
return true; |
|
|
|
case SETTING_USE_QUICKSEEK : |
|
// enables / disables tempo routine quick seeking algorithm |
|
pTDStretch->enableQuickSeek((value != 0) ? true : false); |
|
return true; |
|
|
|
case SETTING_SEQUENCE_MS: |
|
// change time-stretch sequence duration parameter |
|
pTDStretch->setParameters(sampleRate, value, seekWindowMs, overlapMs); |
|
return true; |
|
|
|
case SETTING_SEEKWINDOW_MS: |
|
// change time-stretch seek window length parameter |
|
pTDStretch->setParameters(sampleRate, sequenceMs, value, overlapMs); |
|
return true; |
|
|
|
case SETTING_OVERLAP_MS: |
|
// change time-stretch overlap length parameter |
|
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. |
|
int SoundTouch::getSetting(int settingId) const |
|
{ |
|
int temp; |
|
|
|
switch (settingId) |
|
{ |
|
case SETTING_USE_AA_FILTER : |
|
return (uint)pRateTransposer->isAAFilterEnabled(); |
|
|
|
case SETTING_AA_FILTER_LENGTH : |
|
return pRateTransposer->getAAFilter()->getLength(); |
|
|
|
case SETTING_USE_QUICKSEEK : |
|
return (uint)pTDStretch->isQuickSeekEnabled(); |
|
|
|
case SETTING_SEQUENCE_MS: |
|
pTDStretch->getParameters(nullptr, &temp, nullptr, nullptr); |
|
return temp; |
|
|
|
case SETTING_SEEKWINDOW_MS: |
|
pTDStretch->getParameters(nullptr, nullptr, &temp, nullptr); |
|
return temp; |
|
|
|
case SETTING_OVERLAP_MS: |
|
pTDStretch->getParameters(nullptr, nullptr, nullptr, &temp); |
|
return temp; |
|
|
|
case SETTING_NOMINAL_INPUT_SEQUENCE : |
|
{ |
|
int size = pTDStretch->getInputSampleReq(); |
|
|
|
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER |
|
if (rate <= 1.0) |
|
{ |
|
// transposing done before timestretch, which impacts latency |
|
return (int)(size * rate + 0.5); |
|
} |
|
#endif |
|
return size; |
|
} |
|
|
|
case SETTING_NOMINAL_OUTPUT_SEQUENCE : |
|
{ |
|
int size = pTDStretch->getOutputBatchSize(); |
|
|
|
if (rate > 1.0) |
|
{ |
|
// transposing done after timestretch, which impacts latency |
|
return (int)(size / rate + 0.5); |
|
} |
|
return size; |
|
} |
|
|
|
case SETTING_INITIAL_LATENCY: |
|
{ |
|
double latency = pTDStretch->getLatency(); |
|
int latency_tr = pRateTransposer->getLatency(); |
|
|
|
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER |
|
if (rate <= 1.0) |
|
{ |
|
// transposing done before timestretch, which impacts latency |
|
latency = (latency + latency_tr) * rate; |
|
} |
|
else |
|
#endif |
|
{ |
|
latency += (double)latency_tr / rate; |
|
} |
|
|
|
return (int)(latency + 0.5); |
|
} |
|
|
|
default : |
|
return 0; |
|
} |
|
} |
|
|
|
|
|
// Clears all the samples in the object's output and internal processing |
|
// buffers. |
|
void SoundTouch::clear() |
|
{ |
|
samplesExpectedOut = 0; |
|
samplesOutput = 0; |
|
pRateTransposer->clear(); |
|
pTDStretch->clear(); |
|
} |
|
|
|
|
|
/// Returns number of samples currently unprocessed. |
|
uint SoundTouch::numUnprocessedSamples() const |
|
{ |
|
FIFOSamplePipe * psp; |
|
if (pTDStretch) |
|
{ |
|
psp = pTDStretch->getInput(); |
|
if (psp) |
|
{ |
|
return psp->numSamples(); |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
|
|
/// Output samples from beginning of the sample buffer. Copies requested samples to |
|
/// output buffer and removes them from the sample buffer. If there are less than |
|
/// 'numsample' samples in the buffer, returns all that available. |
|
/// |
|
/// \return Number of samples returned. |
|
uint SoundTouch::receiveSamples(SAMPLETYPE *output, uint maxSamples) |
|
{ |
|
uint ret = FIFOProcessor::receiveSamples(output, maxSamples); |
|
samplesOutput += (long)ret; |
|
return ret; |
|
} |
|
|
|
|
|
/// Adjusts book-keeping so that given number of samples are removed from beginning of the |
|
/// sample buffer without copying them anywhere. |
|
/// |
|
/// Used to reduce the number of samples in the buffer when accessing the sample buffer directly |
|
/// with 'ptrBegin' function. |
|
uint SoundTouch::receiveSamples(uint maxSamples) |
|
{ |
|
uint ret = FIFOProcessor::receiveSamples(maxSamples); |
|
samplesOutput += (long)ret; |
|
return ret; |
|
} |
|
|
|
|
|
/// Get ratio between input and output audio durations, useful for calculating |
|
/// processed output duration: if you'll process a stream of N samples, then |
|
/// you can expect to get out N * getInputOutputSampleRatio() samples. |
|
double SoundTouch::getInputOutputSampleRatio() |
|
{ |
|
return 1.0 / (tempo * rate); |
|
}
|
|
|