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