From c9507ff7f14948c9de8fc940727804ff62ebcaae Mon Sep 17 00:00:00 2001 From: oparviai Date: Sun, 26 Jul 2015 14:45:48 +0000 Subject: [PATCH] - Improved SoundTouch::flush() so that it produces exactly accurate number of output samples. - Changed 'float' variables into 'double' for more precise calculation of input-vs-output samples. --- include/SoundTouch.h | 48 +++++++--- source/SoundTouch/InterpolateCubic.h | 2 +- source/SoundTouch/InterpolateLinear.cpp | 13 +-- source/SoundTouch/InterpolateLinear.h | 4 +- source/SoundTouch/InterpolateShannon.h | 2 +- source/SoundTouch/RateTransposer.cpp | 12 +-- source/SoundTouch/RateTransposer.h | 6 +- source/SoundTouch/SoundTouch.cpp | 116 ++++++++++++++---------- source/SoundTouch/TDStretch.cpp | 4 +- source/SoundTouch/TDStretch.h | 8 +- 10 files changed, 131 insertions(+), 84 deletions(-) diff --git a/include/SoundTouch.h b/include/SoundTouch.h index 081c37e..aad41d6 100644 --- a/include/SoundTouch.h +++ b/include/SoundTouch.h @@ -151,17 +151,24 @@ private: class TDStretch *pTDStretch; /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. - float virtualRate; + double virtualRate; /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. - float virtualTempo; + double virtualTempo; /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. - float virtualPitch; + double virtualPitch; /// Flag: Has sample rate been set? bool bSrateSet; + /// Accumulator for how many samples in total will be expected as output vs. samples put in, + /// considering current processing settings. + double samplesExpectedOut; + + /// Accumulator for how many samples in total have been read out from the processing so far + long samplesOutput; + /// Calculates effective rate & tempo valuescfrom 'virtualRate', 'virtualTempo' and /// 'virtualPitch' parameters. void calcEffectiveRateAndTempo(); @@ -171,10 +178,10 @@ protected : uint channels; /// Effective 'rate' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch' - float rate; + double rate; /// Effective 'tempo' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch' - float tempo; + double tempo; public: SoundTouch(); @@ -188,32 +195,32 @@ public: /// Sets new rate control value. Normal rate = 1.0, smaller values /// represent slower rate, larger faster rates. - void setRate(float newRate); + void setRate(double newRate); /// Sets new tempo control value. Normal tempo = 1.0, smaller values /// represent slower tempo, larger faster tempo. - void setTempo(float newTempo); + void setTempo(double newTempo); /// Sets new rate control value as a difference in percents compared /// to the original rate (-50 .. +100 %) - void setRateChange(float newRate); + void setRateChange(double newRate); /// Sets new tempo control value as a difference in percents compared /// to the original tempo (-50 .. +100 %) - void setTempoChange(float newTempo); + void setTempoChange(double newTempo); /// Sets new pitch control value. Original pitch = 1.0, smaller values /// represent lower pitches, larger values higher pitch. - void setPitch(float newPitch); + void setPitch(double newPitch); /// Sets pitch change in octaves compared to the original pitch /// (-1.00 .. +1.00) - void setPitchOctaves(float newPitch); + void setPitchOctaves(double newPitch); /// Sets pitch change in semi-tones compared to the original pitch /// (-12 .. +12) void setPitchSemiTones(int newPitch); - void setPitchSemiTones(float newPitch); + void setPitchSemiTones(double newPitch); /// Sets the number of channels, 1 = mono, 2 = stereo void setChannels(uint numChannels); @@ -240,6 +247,23 @@ public: ///< contains data for both channels. ); + /// 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. + virtual uint receiveSamples(SAMPLETYPE *output, ///< Buffer where to copy output samples. + uint maxSamples ///< How many samples to receive at max. + ); + + /// 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. + virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe. + ); + /// Clears all the samples in the object's output and internal processing /// buffers. virtual void clear(); diff --git a/source/SoundTouch/InterpolateCubic.h b/source/SoundTouch/InterpolateCubic.h index 0ce10f7..4776321 100644 --- a/source/SoundTouch/InterpolateCubic.h +++ b/source/SoundTouch/InterpolateCubic.h @@ -56,7 +56,7 @@ protected: const SAMPLETYPE *src, int &srcSamples); - float fract; + double fract; public: InterpolateCubic(); diff --git a/source/SoundTouch/InterpolateLinear.cpp b/source/SoundTouch/InterpolateLinear.cpp index 2f21266..c2e6f75 100644 --- a/source/SoundTouch/InterpolateLinear.cpp +++ b/source/SoundTouch/InterpolateLinear.cpp @@ -170,9 +170,9 @@ int InterpolateLinearInteger::transposeMulti(SAMPLETYPE *dest, const SAMPLETYPE // Sets new target iRate. Normal iRate = 1.0, smaller values represent slower // iRate, larger faster iRates. -void InterpolateLinearInteger::setRate(float newRate) +void InterpolateLinearInteger::setRate(double newRate) { - iRate = (int)(newRate * SCALE + 0.5f); + iRate = (int)(newRate * SCALE + 0.5); TransposerBase::setRate(newRate); } @@ -190,7 +190,7 @@ InterpolateLinearFloat::InterpolateLinearFloat() : TransposerBase() // Notice: use local function calling syntax for sake of clarity, // to indicate the fact that C++ constructor can't call virtual functions. resetRegisters(); - setRate(1.0f); + setRate(1.0); } @@ -275,12 +275,13 @@ int InterpolateLinearFloat::transposeMulti(SAMPLETYPE *dest, const SAMPLETYPE *s i = 0; while (srcCount < srcSampleEnd) { - float temp, vol1; + float temp, vol1, fract_float; - vol1 = (1.0f- fract); + vol1 = (float)(1.0 - fract); + fract_float = (float)fract; for (int c = 0; c < numChannels; c ++) { - temp = vol1 * src[c] + fract * src[c + numChannels]; + temp = vol1 * src[c] + fract_float * src[c + numChannels]; *dest = (SAMPLETYPE)temp; dest ++; } diff --git a/source/SoundTouch/InterpolateLinear.h b/source/SoundTouch/InterpolateLinear.h index f7d06b7..457a692 100644 --- a/source/SoundTouch/InterpolateLinear.h +++ b/source/SoundTouch/InterpolateLinear.h @@ -63,7 +63,7 @@ public: /// Sets new target rate. Normal rate = 1.0, smaller values represent slower /// rate, larger faster rates. - virtual void setRate(float newRate); + virtual void setRate(double newRate); }; @@ -71,7 +71,7 @@ public: class InterpolateLinearFloat : public TransposerBase { protected: - float fract; + double fract; virtual void resetRegisters(); diff --git a/source/SoundTouch/InterpolateShannon.h b/source/SoundTouch/InterpolateShannon.h index 8ae1a1f..646cfbc 100644 --- a/source/SoundTouch/InterpolateShannon.h +++ b/source/SoundTouch/InterpolateShannon.h @@ -61,7 +61,7 @@ protected: const SAMPLETYPE *src, int &srcSamples); - float fract; + double fract; public: InterpolateShannon(); diff --git a/source/SoundTouch/RateTransposer.cpp b/source/SoundTouch/RateTransposer.cpp index 80b5b6e..33d19a1 100644 --- a/source/SoundTouch/RateTransposer.cpp +++ b/source/SoundTouch/RateTransposer.cpp @@ -97,20 +97,20 @@ AAFilter *RateTransposer::getAAFilter() // Sets new target iRate. Normal iRate = 1.0, smaller values represent slower // iRate, larger faster iRates. -void RateTransposer::setRate(float newRate) +void RateTransposer::setRate(double newRate) { double fCutoff; pTransposer->setRate(newRate); // design a new anti-alias filter - if (newRate > 1.0f) + if (newRate > 1.0) { - fCutoff = 0.5f / newRate; + fCutoff = 0.5 / newRate; } else { - fCutoff = 0.5f * newRate; + fCutoff = 0.5 * newRate; } pAAFilter->setCutoffFreq(fCutoff); } @@ -225,7 +225,7 @@ void TransposerBase::setAlgorithm(TransposerBase::ALGORITHM a) int TransposerBase::transpose(FIFOSampleBuffer &dest, FIFOSampleBuffer &src) { int numSrcSamples = src.numSamples(); - int sizeDemand = (int)((float)numSrcSamples / rate) + 8; + int sizeDemand = (int)((double)numSrcSamples / rate) + 8; int numOutput; SAMPLETYPE *psrc = src.ptrBegin(); SAMPLETYPE *pdest = dest.ptrEnd(sizeDemand); @@ -270,7 +270,7 @@ void TransposerBase::setChannels(int channels) } -void TransposerBase::setRate(float newRate) +void TransposerBase::setRate(double newRate) { rate = newRate; } diff --git a/source/SoundTouch/RateTransposer.h b/source/SoundTouch/RateTransposer.h index 701a7c4..b122d4f 100644 --- a/source/SoundTouch/RateTransposer.h +++ b/source/SoundTouch/RateTransposer.h @@ -81,14 +81,14 @@ protected: static ALGORITHM algorithm; public: - float rate; + double rate; int numChannels; TransposerBase(); virtual ~TransposerBase(); virtual int transpose(FIFOSampleBuffer &dest, FIFOSampleBuffer &src); - virtual void setRate(float newRate); + virtual void setRate(double newRate); virtual void setChannels(int channels); // static factory function @@ -158,7 +158,7 @@ public: /// Sets new target rate. Normal rate = 1.0, smaller values represent slower /// rate, larger faster rates. - virtual void setRate(float newRate); + virtual void setRate(double newRate); /// Sets the number of channels, 1 = mono, 2 = stereo void setChannels(int channels); diff --git a/source/SoundTouch/SoundTouch.cpp b/source/SoundTouch/SoundTouch.cpp index bbc46d7..f18d80a 100644 --- a/source/SoundTouch/SoundTouch.cpp +++ b/source/SoundTouch/SoundTouch.cpp @@ -110,6 +110,9 @@ SoundTouch::SoundTouch() calcEffectiveRateAndTempo(); + samplesExpectedOut = 0; + samplesOutput = 0; + channels = 0; bSrateSet = false; } @@ -157,7 +160,7 @@ void SoundTouch::setChannels(uint numChannels) // Sets new rate control value. Normal rate = 1.0, smaller values // represent slower rate, larger faster rates. -void SoundTouch::setRate(float newRate) +void SoundTouch::setRate(double newRate) { virtualRate = newRate; calcEffectiveRateAndTempo(); @@ -167,9 +170,9 @@ void SoundTouch::setRate(float newRate) // Sets new rate control value as a difference in percents compared // to the original rate (-50 .. +100 %) -void SoundTouch::setRateChange(float newRate) +void SoundTouch::setRateChange(double newRate) { - virtualRate = 1.0f + 0.01f * newRate; + virtualRate = 1.0 + 0.01 * newRate; calcEffectiveRateAndTempo(); } @@ -177,7 +180,7 @@ void SoundTouch::setRateChange(float newRate) // Sets new tempo control value. Normal tempo = 1.0, smaller values // represent slower tempo, larger faster tempo. -void SoundTouch::setTempo(float newTempo) +void SoundTouch::setTempo(double newTempo) { virtualTempo = newTempo; calcEffectiveRateAndTempo(); @@ -187,9 +190,9 @@ void SoundTouch::setTempo(float newTempo) // Sets new tempo control value as a difference in percents compared // to the original tempo (-50 .. +100 %) -void SoundTouch::setTempoChange(float newTempo) +void SoundTouch::setTempoChange(double newTempo) { - virtualTempo = 1.0f + 0.01f * newTempo; + virtualTempo = 1.0 + 0.01 * newTempo; calcEffectiveRateAndTempo(); } @@ -197,7 +200,7 @@ void SoundTouch::setTempoChange(float newTempo) // Sets new pitch control value. Original pitch = 1.0, smaller values // represent lower pitches, larger values higher pitch. -void SoundTouch::setPitch(float newPitch) +void SoundTouch::setPitch(double newPitch) { virtualPitch = newPitch; calcEffectiveRateAndTempo(); @@ -207,9 +210,9 @@ void SoundTouch::setPitch(float newPitch) // Sets pitch change in octaves compared to the original pitch // (-1.00 .. +1.00) -void SoundTouch::setPitchOctaves(float newPitch) +void SoundTouch::setPitchOctaves(double newPitch) { - virtualPitch = (float)exp(0.69314718056f * newPitch); + virtualPitch = exp(0.69314718056 * newPitch); calcEffectiveRateAndTempo(); } @@ -219,14 +222,14 @@ void SoundTouch::setPitchOctaves(float newPitch) // (-12 .. +12) void SoundTouch::setPitchSemiTones(int newPitch) { - setPitchOctaves((float)newPitch / 12.0f); + setPitchOctaves((double)newPitch / 12.0); } -void SoundTouch::setPitchSemiTones(float newPitch) +void SoundTouch::setPitchSemiTones(double newPitch) { - setPitchOctaves(newPitch / 12.0f); + setPitchOctaves(newPitch / 12.0); } @@ -234,14 +237,14 @@ void SoundTouch::setPitchSemiTones(float newPitch) // nominal control values. void SoundTouch::calcEffectiveRateAndTempo() { - float oldTempo = tempo; - float oldRate = rate; + double oldTempo = tempo; + double oldRate = rate; - tempo = virtualTempo / virtualPitch; - rate = virtualPitch * virtualRate; + tempo = virtualTempo / virtualPitch; + rate = virtualPitch * virtualRate; if (!TEST_FLOAT_EQUAL(rate,oldRate)) pRateTransposer->setRate(rate); - if (!TEST_FLOAT_EQUAL(tempo, oldTempo)) pTDStretch->setTempo(tempo); + if (!TEST_FLOAT_EQUAL(tempo, oldTempo)) pTDStretch->setTempo(tempo); #ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER if (rate <= 1.0f) @@ -317,8 +320,13 @@ void SoundTouch::putSamples(const SAMPLETYPE *samples, uint nSamples) pTDStretch->putSamples(samples, nSamples); } */ + + // 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 - else if (rate <= 1.0f) + if (rate <= 1.0f) { // transpose the rate down, output the transposed sound to tempo changer buffer assert(output == pTDStretch); @@ -346,44 +354,30 @@ void SoundTouch::putSamples(const SAMPLETYPE *samples, uint nSamples) void SoundTouch::flush() { int i; - int nUnprocessed; - int nOut; - SAMPLETYPE *buff = new SAMPLETYPE[64 * channels]; - - // check how many samples still await processing, and scale - // that by tempo & rate to get expected output sample count - nUnprocessed = numUnprocessedSamples(); - nUnprocessed = (int)((double)nUnprocessed / (tempo * rate) + 0.5); + int numStillExpected; + SAMPLETYPE *buff = new SAMPLETYPE[128 * channels]; - nOut = numSamples(); // ready samples currently in buffer ... - nOut += nUnprocessed; // ... and how many we expect there to be in the end - - memset(buff, 0, 64 * channels * sizeof(SAMPLETYPE)); + // how many samples are still expected to output + numStillExpected = (int)((long)(samplesExpectedOut + 0.5) - samplesOutput); + + 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 - // 8ksamples in any case) - for (i = 0; i < 128; i ++) - { - putSamples(buff, 64); - if ((int)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 - adjustAmountOfSamples(nOut); + // 24ksamples in any case) + for (i = 0; (numStillExpected > (int)numSamples()) && (i < 200); i ++) + { + putSamples(buff, 128); + } - // finish - break; - } - } + adjustAmountOfSamples(numStillExpected); delete[] buff; - // Clear working buffers - pRateTransposer->clear(); + // Clear input buffers + // pRateTransposer->clearInput(); pTDStretch->clearInput(); - // yet leave the 'tempoChanger' output intouched as that's where the + // yet leave the output intouched as that's where the // flushed samples are! } @@ -482,6 +476,7 @@ int SoundTouch::getSetting(int settingId) const // buffers. void SoundTouch::clear() { + samplesExpectedOut = 0; pRateTransposer->clear(); pTDStretch->clear(); } @@ -502,3 +497,30 @@ uint SoundTouch::numUnprocessedSamples() const } 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; +} diff --git a/source/SoundTouch/TDStretch.cpp b/source/SoundTouch/TDStretch.cpp index fabf54b..9ad27aa 100644 --- a/source/SoundTouch/TDStretch.cpp +++ b/source/SoundTouch/TDStretch.cpp @@ -459,7 +459,7 @@ void TDStretch::calcSeqParameters() // Sets new target tempo. Normal tempo = 'SCALE', smaller values represent slower // tempo, larger faster tempo. -void TDStretch::setTempo(float newTempo) +void TDStretch::setTempo(double newTempo) { int intskip; @@ -470,7 +470,7 @@ void TDStretch::setTempo(float newTempo) // Calculate ideal skip length (according to tempo value) nominalSkip = tempo * (seekWindowLength - overlapLength); - intskip = (int)(nominalSkip + 0.5f); + intskip = (int)(nominalSkip + 0.5); // Calculate how many samples are needed in the 'inputBuffer' to // process another batch of samples diff --git a/source/SoundTouch/TDStretch.h b/source/SoundTouch/TDStretch.h index 18849b2..6400f05 100644 --- a/source/SoundTouch/TDStretch.h +++ b/source/SoundTouch/TDStretch.h @@ -112,7 +112,7 @@ class TDStretch : public FIFOProcessor protected: int channels; int sampleReq; - float tempo; + double tempo; SAMPLETYPE *pMidBuffer; SAMPLETYPE *pMidBufferUnaligned; @@ -121,8 +121,8 @@ protected: int seekWindowLength; int overlapDividerBits; int slopingDivider; - float nominalSkip; - float skipFract; + double nominalSkip; + double skipFract; FIFOSampleBuffer outputBuffer; FIFOSampleBuffer inputBuffer; bool bQuickSeek; @@ -182,7 +182,7 @@ public: /// Sets new target tempo. Normal tempo = 'SCALE', smaller values represent slower /// tempo, larger faster tempo. - void setTempo(float newTempo); + void setTempo(double newTempo); /// Returns nonzero if there aren't any samples available for outputting. virtual void clear();