From 63b38be97c12e6b6f0ec39008f6b0dd06dbcf3f0 Mon Sep 17 00:00:00 2001 From: skidau Date: Wed, 9 Jan 2013 22:57:32 +1100 Subject: [PATCH] Added audio time stretching by using the SoundTouch library. --- CMakeLists.txt | 11 + Externals/SoundTouch/AAFilter.cpp | 184 ++++ Externals/SoundTouch/AAFilter.h | 91 ++ Externals/SoundTouch/BPMDetect.cpp | 370 ++++++++ Externals/SoundTouch/BPMDetect.h | 164 ++++ Externals/SoundTouch/CMakeLists.txt | 15 + Externals/SoundTouch/FIFOSampleBuffer.cpp | 274 ++++++ Externals/SoundTouch/FIFOSampleBuffer.h | 178 ++++ Externals/SoundTouch/FIFOSamplePipe.h | 234 +++++ Externals/SoundTouch/FIRFilter.cpp | 259 ++++++ Externals/SoundTouch/FIRFilter.h | 145 ++++ Externals/SoundTouch/PeakFinder.cpp | 276 ++++++ Externals/SoundTouch/PeakFinder.h | 97 +++ Externals/SoundTouch/RateTransposer.cpp | 626 ++++++++++++++ Externals/SoundTouch/RateTransposer.h | 159 ++++ Externals/SoundTouch/STTypes.h | 189 ++++ Externals/SoundTouch/SoundTouch.cpp | 501 +++++++++++ Externals/SoundTouch/SoundTouch.h | 277 ++++++ Externals/SoundTouch/SoundTouch.vcxproj | 352 ++++++++ .../SoundTouch/SoundTouch.vcxproj.filters | 60 ++ Externals/SoundTouch/TDStretch.cpp | 808 ++++++++++++++++++ Externals/SoundTouch/TDStretch.h | 268 ++++++ Externals/SoundTouch/cpu_detect.h | 62 ++ Externals/SoundTouch/cpu_detect_x86.cpp | 137 +++ Externals/SoundTouch/mmx_optimized.cpp | 317 +++++++ Externals/SoundTouch/sse_optimized.cpp | 361 ++++++++ Source/Core/AudioCommon/AudioCommon.vcxproj | 24 +- Source/Core/AudioCommon/Src/Mixer.h | 5 + Source/Core/AudioCommon/Src/OpenALStream.cpp | 79 +- Source/Core/AudioCommon/Src/OpenALStream.h | 12 +- Source/Core/Core/Src/Core.cpp | 9 +- Source/Dolphin_2010.sln | 14 + 32 files changed, 6520 insertions(+), 38 deletions(-) create mode 100644 Externals/SoundTouch/AAFilter.cpp create mode 100644 Externals/SoundTouch/AAFilter.h create mode 100644 Externals/SoundTouch/BPMDetect.cpp create mode 100644 Externals/SoundTouch/BPMDetect.h create mode 100644 Externals/SoundTouch/CMakeLists.txt create mode 100644 Externals/SoundTouch/FIFOSampleBuffer.cpp create mode 100644 Externals/SoundTouch/FIFOSampleBuffer.h create mode 100644 Externals/SoundTouch/FIFOSamplePipe.h create mode 100644 Externals/SoundTouch/FIRFilter.cpp create mode 100644 Externals/SoundTouch/FIRFilter.h create mode 100644 Externals/SoundTouch/PeakFinder.cpp create mode 100644 Externals/SoundTouch/PeakFinder.h create mode 100644 Externals/SoundTouch/RateTransposer.cpp create mode 100644 Externals/SoundTouch/RateTransposer.h create mode 100644 Externals/SoundTouch/STTypes.h create mode 100644 Externals/SoundTouch/SoundTouch.cpp create mode 100644 Externals/SoundTouch/SoundTouch.h create mode 100644 Externals/SoundTouch/SoundTouch.vcxproj create mode 100644 Externals/SoundTouch/SoundTouch.vcxproj.filters create mode 100644 Externals/SoundTouch/TDStretch.cpp create mode 100644 Externals/SoundTouch/TDStretch.h create mode 100644 Externals/SoundTouch/cpu_detect.h create mode 100644 Externals/SoundTouch/cpu_detect_x86.cpp create mode 100644 Externals/SoundTouch/mmx_optimized.cpp create mode 100644 Externals/SoundTouch/sse_optimized.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b025de11ef..f286cb3502 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -452,6 +452,17 @@ else() include_directories(Externals/SOIL) endif() +if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + check_lib(SoundTouch SoundTouch SoundTouch.h QUIET) +endif() +if(SOUNDTOUCH_FOUND) + message("Using shared SoundTouch") +else() + message("Using static SoundTouch from Externals") + add_subdirectory(Externals/SoundTouch) + include_directories(Externals/SoundTouch) +endif() + # If zlib has already been found on a previous run of cmake don't check again # as the check seems to take a long time. if(NOT ZLIB_FOUND) diff --git a/Externals/SoundTouch/AAFilter.cpp b/Externals/SoundTouch/AAFilter.cpp new file mode 100644 index 0000000000..f099bced93 --- /dev/null +++ b/Externals/SoundTouch/AAFilter.cpp @@ -0,0 +1,184 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// FIR low-pass (anti-alias) filter with filter coefficient design routine and +/// MMX optimization. +/// +/// Anti-alias filter is used to prevent folding of high frequencies when +/// transposing the sample rate with interpolation. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2009-01-11 13:34:24 +0200 (Sun, 11 Jan 2009) $ +// File revision : $Revision: 4 $ +// +// $Id: AAFilter.cpp 45 2009-01-11 11:34:24Z 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 +#include +#include +#include +#include "AAFilter.h" +#include "FIRFilter.h" + +using namespace soundtouch; + +#define PI 3.141592655357989 +#define TWOPI (2 * PI) + +/***************************************************************************** + * + * Implementation of the class 'AAFilter' + * + *****************************************************************************/ + +AAFilter::AAFilter(uint len) +{ + pFIR = FIRFilter::newInstance(); + cutoffFreq = 0.5; + setLength(len); +} + + + +AAFilter::~AAFilter() +{ + delete pFIR; +} + + + +// Sets new anti-alias filter cut-off edge frequency, scaled to +// sampling frequency (nyquist frequency = 0.5). +// The filter will cut frequencies higher than the given frequency. +void AAFilter::setCutoffFreq(double newCutoffFreq) +{ + cutoffFreq = newCutoffFreq; + calculateCoeffs(); +} + + + +// Sets number of FIR filter taps +void AAFilter::setLength(uint newLength) +{ + length = newLength; + calculateCoeffs(); +} + + + +// Calculates coefficients for a low-pass FIR filter using Hamming window +void AAFilter::calculateCoeffs() +{ + uint i; + double cntTemp, temp, tempCoeff,h, w; + double fc2, wc; + double scaleCoeff, sum; + double *work; + SAMPLETYPE *coeffs; + + assert(length >= 2); + assert(length % 4 == 0); + assert(cutoffFreq >= 0); + assert(cutoffFreq <= 0.5); + + work = new double[length]; + coeffs = new SAMPLETYPE[length]; + + fc2 = 2.0 * cutoffFreq; + wc = PI * fc2; + tempCoeff = TWOPI / (double)length; + + sum = 0; + for (i = 0; i < length; i ++) + { + cntTemp = (double)i - (double)(length / 2); + + temp = cntTemp * wc; + if (temp != 0) + { + h = fc2 * sin(temp) / temp; // sinc function + } + else + { + h = 1.0; + } + w = 0.54 + 0.46 * cos(tempCoeff * cntTemp); // hamming window + + temp = w * h; + work[i] = temp; + + // calc net sum of coefficients + sum += temp; + } + + // ensure the sum of coefficients is larger than zero + assert(sum > 0); + + // ensure we've really designed a lowpass filter... + assert(work[length/2] > 0); + assert(work[length/2 + 1] > -1e-6); + assert(work[length/2 - 1] > -1e-6); + + // Calculate a scaling coefficient in such a way that the result can be + // divided by 16384 + scaleCoeff = 16384.0f / sum; + + for (i = 0; i < length; i ++) + { + // scale & round to nearest integer + temp = work[i] * scaleCoeff; + temp += (temp >= 0) ? 0.5 : -0.5; + // ensure no overfloods + assert(temp >= -32768 && temp <= 32767); + coeffs[i] = (SAMPLETYPE)temp; + } + + // Set coefficients. Use divide factor 14 => divide result by 2^14 = 16384 + pFIR->setCoefficients(coeffs, length, 14); + + delete[] work; + delete[] coeffs; +} + + +// Applies the filter to the given sequence of samples. +// Note : The amount of outputted samples is by value of 'filter length' +// smaller than the amount of input samples. +uint AAFilter::evaluate(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples, uint numChannels) const +{ + return pFIR->evaluate(dest, src, numSamples, numChannels); +} + + +uint AAFilter::getLength() const +{ + return pFIR->getLength(); +} diff --git a/Externals/SoundTouch/AAFilter.h b/Externals/SoundTouch/AAFilter.h new file mode 100644 index 0000000000..d0997570d3 --- /dev/null +++ b/Externals/SoundTouch/AAFilter.h @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo +/// while maintaining the original pitch by using a time domain WSOLA-like method +/// with several performance-increasing tweaks. +/// +/// Anti-alias filter is used to prevent folding of high frequencies when +/// transposing the sample rate with interpolation. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2008-02-10 18:26:55 +0200 (Sun, 10 Feb 2008) $ +// File revision : $Revision: 4 $ +// +// $Id: AAFilter.h 11 2008-02-10 16:26:55Z 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef AAFilter_H +#define AAFilter_H + +#include "STTypes.h" + +namespace soundtouch +{ + +class AAFilter +{ +protected: + class FIRFilter *pFIR; + + /// Low-pass filter cut-off frequency, negative = invalid + double cutoffFreq; + + /// num of filter taps + uint length; + + /// Calculate the FIR coefficients realizing the given cutoff-frequency + void calculateCoeffs(); +public: + AAFilter(uint length); + + ~AAFilter(); + + /// Sets new anti-alias filter cut-off edge frequency, scaled to sampling + /// frequency (nyquist frequency = 0.5). The filter will cut off the + /// frequencies than that. + void setCutoffFreq(double newCutoffFreq); + + /// Sets number of FIR filter taps, i.e. ~filter complexity + void setLength(uint newLength); + + uint getLength() const; + + /// Applies the filter to the given sequence of samples. + /// Note : The amount of outputted samples is by value of 'filter length' + /// smaller than the amount of input samples. + uint evaluate(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples, + uint numChannels) const; +}; + +} + +#endif diff --git a/Externals/SoundTouch/BPMDetect.cpp b/Externals/SoundTouch/BPMDetect.cpp new file mode 100644 index 0000000000..a48cbd97c3 --- /dev/null +++ b/Externals/SoundTouch/BPMDetect.cpp @@ -0,0 +1,370 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Beats-per-minute (BPM) detection routine. +/// +/// The beat detection algorithm works as follows: +/// - Use function 'inputSamples' to input a chunks of samples to the class for +/// analysis. It's a good idea to enter a large sound file or stream in smallish +/// chunks of around few kilosamples in order not to extinguish too much RAM memory. +/// - Inputted sound data is decimated to approx 500 Hz to reduce calculation burden, +/// which is basically ok as low (bass) frequencies mostly determine the beat rate. +/// Simple averaging is used for anti-alias filtering because the resulting signal +/// quality isn't of that high importance. +/// - Decimated sound data is enveloped, i.e. the amplitude shape is detected by +/// taking absolute value that's smoothed by sliding average. Signal levels that +/// are below a couple of times the general RMS amplitude level are cut away to +/// leave only notable peaks there. +/// - Repeating sound patterns (e.g. beats) are detected by calculating short-term +/// autocorrelation function of the enveloped signal. +/// - After whole sound data file has been analyzed as above, the bpm level is +/// detected by function 'getBpm' that finds the highest peak of the autocorrelation +/// function, calculates it's precise location and converts this reading to bpm's. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-08-30 22:45:25 +0300 (Thu, 30 Aug 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: BPMDetect.cpp 149 2012-08-30 19:45:25Z 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 +#include +#include +#include +#include "FIFOSampleBuffer.h" +#include "PeakFinder.h" +#include "BPMDetect.h" + +using namespace soundtouch; + +#define INPUT_BLOCK_SAMPLES 2048 +#define DECIMATED_BLOCK_SAMPLES 256 + +/// decay constant for calculating RMS volume sliding average approximation +/// (time constant is about 10 sec) +const float avgdecay = 0.99986f; + +/// Normalization coefficient for calculating RMS sliding average approximation. +const float avgnorm = (1 - avgdecay); + + +//////////////////////////////////////////////////////////////////////////////// + +// Enable following define to create bpm analysis file: + +// #define _CREATE_BPM_DEBUG_FILE + +#ifdef _CREATE_BPM_DEBUG_FILE + + #define DEBUGFILE_NAME "c:\\temp\\soundtouch-bpm-debug.txt" + + static void _SaveDebugData(const float *data, int minpos, int maxpos, double coeff) + { + FILE *fptr = fopen(DEBUGFILE_NAME, "wt"); + int i; + + if (fptr) + { + printf("\n\nWriting BPM debug data into file " DEBUGFILE_NAME "\n\n"); + for (i = minpos; i < maxpos; i ++) + { + fprintf(fptr, "%d\t%.1lf\t%f\n", i, coeff / (double)i, data[i]); + } + fclose(fptr); + } + } +#else + #define _SaveDebugData(a,b,c,d) +#endif + +//////////////////////////////////////////////////////////////////////////////// + + +BPMDetect::BPMDetect(int numChannels, int aSampleRate) +{ + this->sampleRate = aSampleRate; + this->channels = numChannels; + + decimateSum = 0; + decimateCount = 0; + + envelopeAccu = 0; + + // Initialize RMS volume accumulator to RMS level of 1500 (out of 32768) that's + // safe initial RMS signal level value for song data. This value is then adapted + // to the actual level during processing. +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + // integer samples + RMSVolumeAccu = (1500 * 1500) / avgnorm; +#else + // float samples, scaled to range [-1..+1[ + RMSVolumeAccu = (0.045f * 0.045f) / avgnorm; +#endif + + // choose decimation factor so that result is approx. 1000 Hz + decimateBy = sampleRate / 1000; + assert(decimateBy > 0); + assert(INPUT_BLOCK_SAMPLES < decimateBy * DECIMATED_BLOCK_SAMPLES); + + // Calculate window length & starting item according to desired min & max bpms + windowLen = (60 * sampleRate) / (decimateBy * MIN_BPM); + windowStart = (60 * sampleRate) / (decimateBy * MAX_BPM); + + assert(windowLen > windowStart); + + // allocate new working objects + xcorr = new float[windowLen]; + memset(xcorr, 0, windowLen * sizeof(float)); + + // allocate processing buffer + buffer = new FIFOSampleBuffer(); + // we do processing in mono mode + buffer->setChannels(1); + buffer->clear(); +} + + + +BPMDetect::~BPMDetect() +{ + delete[] xcorr; + delete buffer; +} + + + +/// convert to mono, low-pass filter & decimate to about 500 Hz. +/// return number of outputted samples. +/// +/// Decimation is used to remove the unnecessary frequencies and thus to reduce +/// the amount of data needed to be processed as calculating autocorrelation +/// function is a very-very heavy operation. +/// +/// Anti-alias filtering is done simply by averaging the samples. This is really a +/// poor-man's anti-alias filtering, but it's not so critical in this kind of application +/// (it'd also be difficult to design a high-quality filter with steep cut-off at very +/// narrow band) +int BPMDetect::decimate(SAMPLETYPE *dest, const SAMPLETYPE *src, int numsamples) +{ + int count, outcount; + LONG_SAMPLETYPE out; + + assert(channels > 0); + assert(decimateBy > 0); + outcount = 0; + for (count = 0; count < numsamples; count ++) + { + int j; + + // convert to mono and accumulate + for (j = 0; j < channels; j ++) + { + decimateSum += src[j]; + } + src += j; + + decimateCount ++; + if (decimateCount >= decimateBy) + { + // Store every Nth sample only + out = (LONG_SAMPLETYPE)(decimateSum / (decimateBy * channels)); + decimateSum = 0; + decimateCount = 0; +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + // check ranges for sure (shouldn't actually be necessary) + if (out > 32767) + { + out = 32767; + } + else if (out < -32768) + { + out = -32768; + } +#endif // SOUNDTOUCH_INTEGER_SAMPLES + dest[outcount] = (SAMPLETYPE)out; + outcount ++; + } + } + return outcount; +} + + + +// Calculates autocorrelation function of the sample history buffer +void BPMDetect::updateXCorr(int process_samples) +{ + int offs; + SAMPLETYPE *pBuffer; + + assert(buffer->numSamples() >= (uint)(process_samples + windowLen)); + + pBuffer = buffer->ptrBegin(); + for (offs = windowStart; offs < windowLen; offs ++) + { + LONG_SAMPLETYPE sum; + int i; + + sum = 0; + for (i = 0; i < process_samples; i ++) + { + sum += pBuffer[i] * pBuffer[i + offs]; // scaling the sub-result shouldn't be necessary + } +// xcorr[offs] *= xcorr_decay; // decay 'xcorr' here with suitable coefficients + // if it's desired that the system adapts automatically to + // various bpms, e.g. in processing continouos music stream. + // The 'xcorr_decay' should be a value that's smaller than but + // close to one, and should also depend on 'process_samples' value. + + xcorr[offs] += (float)sum; + } +} + + +// Calculates envelope of the sample data +void BPMDetect::calcEnvelope(SAMPLETYPE *samples, int numsamples) +{ + const static double decay = 0.7f; // decay constant for smoothing the envelope + const static double norm = (1 - decay); + + int i; + LONG_SAMPLETYPE out; + double val; + + for (i = 0; i < numsamples; i ++) + { + // calc average RMS volume + RMSVolumeAccu *= avgdecay; + val = (float)fabs((float)samples[i]); + RMSVolumeAccu += val * val; + + // cut amplitudes that are below cutoff ~2 times RMS volume + // (we're interested in peak values, not the silent moments) + if (val < 0.5 * sqrt(RMSVolumeAccu * avgnorm)) + { + val = 0; + } + + // smooth amplitude envelope + envelopeAccu *= decay; + envelopeAccu += val; + out = (LONG_SAMPLETYPE)(envelopeAccu * norm); + +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + // cut peaks (shouldn't be necessary though) + if (out > 32767) out = 32767; +#endif // SOUNDTOUCH_INTEGER_SAMPLES + samples[i] = (SAMPLETYPE)out; + } +} + + + +void BPMDetect::inputSamples(const SAMPLETYPE *samples, int numSamples) +{ + SAMPLETYPE decimated[DECIMATED_BLOCK_SAMPLES]; + + // iterate so that max INPUT_BLOCK_SAMPLES processed per iteration + while (numSamples > 0) + { + int block; + int decSamples; + + block = (numSamples > INPUT_BLOCK_SAMPLES) ? INPUT_BLOCK_SAMPLES : numSamples; + + // decimate. note that converts to mono at the same time + decSamples = decimate(decimated, samples, block); + samples += block * channels; + numSamples -= block; + + // envelope new samples and add them to buffer + calcEnvelope(decimated, decSamples); + buffer->putSamples(decimated, decSamples); + } + + // when the buffer has enought samples for processing... + if ((int)buffer->numSamples() > windowLen) + { + int processLength; + + // how many samples are processed + processLength = (int)buffer->numSamples() - windowLen; + + // ... calculate autocorrelations for oldest samples... + updateXCorr(processLength); + // ... and remove them from the buffer + buffer->receiveSamples(processLength); + } +} + + + +void BPMDetect::removeBias() +{ + int i; + float minval = 1e12f; // arbitrary large number + + for (i = windowStart; i < windowLen; i ++) + { + if (xcorr[i] < minval) + { + minval = xcorr[i]; + } + } + + for (i = windowStart; i < windowLen; i ++) + { + xcorr[i] -= minval; + } +} + + +float BPMDetect::getBpm() +{ + double peakPos; + double coeff; + PeakFinder peakFinder; + + coeff = 60.0 * ((double)sampleRate / (double)decimateBy); + + // save bpm debug analysis data if debug data enabled + _SaveDebugData(xcorr, windowStart, windowLen, coeff); + + // remove bias from xcorr data + removeBias(); + + // find peak position + peakPos = peakFinder.detectPeak(xcorr, windowStart, windowLen); + + assert(decimateBy != 0); + if (peakPos < 1e-9) return 0.0; // detection failed. + + // calculate BPM + return (float) (coeff / peakPos); +} diff --git a/Externals/SoundTouch/BPMDetect.h b/Externals/SoundTouch/BPMDetect.h new file mode 100644 index 0000000000..72489894bd --- /dev/null +++ b/Externals/SoundTouch/BPMDetect.h @@ -0,0 +1,164 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Beats-per-minute (BPM) detection routine. +/// +/// The beat detection algorithm works as follows: +/// - Use function 'inputSamples' to input a chunks of samples to the class for +/// analysis. It's a good idea to enter a large sound file or stream in smallish +/// chunks of around few kilosamples in order not to extinguish too much RAM memory. +/// - Input sound data is decimated to approx 500 Hz to reduce calculation burden, +/// which is basically ok as low (bass) frequencies mostly determine the beat rate. +/// Simple averaging is used for anti-alias filtering because the resulting signal +/// quality isn't of that high importance. +/// - Decimated sound data is enveloped, i.e. the amplitude shape is detected by +/// taking absolute value that's smoothed by sliding average. Signal levels that +/// are below a couple of times the general RMS amplitude level are cut away to +/// leave only notable peaks there. +/// - Repeating sound patterns (e.g. beats) are detected by calculating short-term +/// autocorrelation function of the enveloped signal. +/// - After whole sound data file has been analyzed as above, the bpm level is +/// detected by function 'getBpm' that finds the highest peak of the autocorrelation +/// function, calculates it's precise location and converts this reading to bpm's. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-08-30 22:53:44 +0300 (Thu, 30 Aug 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: BPMDetect.h 150 2012-08-30 19:53:44Z 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _BPMDetect_H_ +#define _BPMDetect_H_ + +#include "STTypes.h" +#include "FIFOSampleBuffer.h" + +namespace soundtouch +{ + +/// Minimum allowed BPM rate. Used to restrict accepted result above a reasonable limit. +#define MIN_BPM 29 + +/// Maximum allowed BPM rate. Used to restrict accepted result below a reasonable limit. +#define MAX_BPM 200 + + +/// Class for calculating BPM rate for audio data. +class BPMDetect +{ +protected: + /// Auto-correlation accumulator bins. + float *xcorr; + + /// Amplitude envelope sliding average approximation level accumulator + double envelopeAccu; + + /// RMS volume sliding average approximation level accumulator + double RMSVolumeAccu; + + /// Sample average counter. + int decimateCount; + + /// Sample average accumulator for FIFO-like decimation. + soundtouch::LONG_SAMPLETYPE decimateSum; + + /// Decimate sound by this coefficient to reach approx. 500 Hz. + int decimateBy; + + /// Auto-correlation window length + int windowLen; + + /// Number of channels (1 = mono, 2 = stereo) + int channels; + + /// sample rate + int sampleRate; + + /// Beginning of auto-correlation window: Autocorrelation isn't being updated for + /// the first these many correlation bins. + int windowStart; + + /// FIFO-buffer for decimated processing samples. + soundtouch::FIFOSampleBuffer *buffer; + + /// Updates auto-correlation function for given number of decimated samples that + /// are read from the internal 'buffer' pipe (samples aren't removed from the pipe + /// though). + void updateXCorr(int process_samples /// How many samples are processed. + ); + + /// Decimates samples to approx. 500 Hz. + /// + /// \return Number of output samples. + int decimate(soundtouch::SAMPLETYPE *dest, ///< Destination buffer + const soundtouch::SAMPLETYPE *src, ///< Source sample buffer + int numsamples ///< Number of source samples. + ); + + /// Calculates amplitude envelope for the buffer of samples. + /// Result is output to 'samples'. + void calcEnvelope(soundtouch::SAMPLETYPE *samples, ///< Pointer to input/output data buffer + int numsamples ///< Number of samples in buffer + ); + + /// remove constant bias from xcorr data + void removeBias(); + +public: + /// Constructor. + BPMDetect(int numChannels, ///< Number of channels in sample data. + int sampleRate ///< Sample rate in Hz. + ); + + /// Destructor. + virtual ~BPMDetect(); + + /// Inputs a block of samples for analyzing: Envelopes the samples and then + /// updates the autocorrelation estimation. When whole song data has been input + /// in smaller blocks using this function, read the resulting bpm with 'getBpm' + /// function. + /// + /// Notice that data in 'samples' array can be disrupted in processing. + void inputSamples(const soundtouch::SAMPLETYPE *samples, ///< Pointer to input/working data buffer + int numSamples ///< Number of samples in buffer + ); + + + /// Analyzes the results and returns the BPM rate. Use this function to read result + /// after whole song data has been input to the class by consecutive calls of + /// 'inputSamples' function. + /// + /// \return Beats-per-minute rate, or zero if detection failed. + float getBpm(); +}; + +} + +#endif // _BPMDetect_H_ diff --git a/Externals/SoundTouch/CMakeLists.txt b/Externals/SoundTouch/CMakeLists.txt new file mode 100644 index 0000000000..a09aac1946 --- /dev/null +++ b/Externals/SoundTouch/CMakeLists.txt @@ -0,0 +1,15 @@ +set(SRCS + AAFilter.cpp + BPMDetect.cpp + cpu_detect_x86.cpp + FIFOSampleBuffer.cpp + FIRFilter.cpp + mmx_optimized.cpp + PeakFinder.cpp + RateTransposer.cpp + SoundTouch.cpp + sse_optimized.cpp + TDStretch.cpp + ) + +add_library(SoundTouch STATIC ${SRCS}) diff --git a/Externals/SoundTouch/FIFOSampleBuffer.cpp b/Externals/SoundTouch/FIFOSampleBuffer.cpp new file mode 100644 index 0000000000..7f088b80be --- /dev/null +++ b/Externals/SoundTouch/FIFOSampleBuffer.cpp @@ -0,0 +1,274 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// A buffer class for temporarily storaging sound samples, operates as a +/// first-in-first-out pipe. +/// +/// Samples are added to the end of the sample buffer with the 'putSamples' +/// function, and are received from the beginning of the buffer by calling +/// the 'receiveSamples' function. The class automatically removes the +/// outputted samples from the buffer, as well as grows the buffer size +/// whenever necessary. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-11-08 20:53:01 +0200 (Thu, 08 Nov 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: FIFOSampleBuffer.cpp 160 2012-11-08 18:53:01Z 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 +#include +#include +#include + +#include "FIFOSampleBuffer.h" + +using namespace soundtouch; + +// Constructor +FIFOSampleBuffer::FIFOSampleBuffer(int numChannels) +{ + assert(numChannels > 0); + sizeInBytes = 0; // reasonable initial value + buffer = NULL; + bufferUnaligned = NULL; + samplesInBuffer = 0; + bufferPos = 0; + channels = (uint)numChannels; + ensureCapacity(32); // allocate initial capacity +} + + +// destructor +FIFOSampleBuffer::~FIFOSampleBuffer() +{ + delete[] bufferUnaligned; + bufferUnaligned = NULL; + buffer = NULL; +} + + +// Sets number of channels, 1 = mono, 2 = stereo +void FIFOSampleBuffer::setChannels(int numChannels) +{ + uint usedBytes; + + assert(numChannels > 0); + usedBytes = channels * samplesInBuffer; + channels = (uint)numChannels; + samplesInBuffer = usedBytes / channels; +} + + +// if output location pointer 'bufferPos' isn't zero, 'rewinds' the buffer and +// zeroes this pointer by copying samples from the 'bufferPos' pointer +// location on to the beginning of the buffer. +void FIFOSampleBuffer::rewind() +{ + if (buffer && bufferPos) + { + memmove(buffer, ptrBegin(), sizeof(SAMPLETYPE) * channels * samplesInBuffer); + bufferPos = 0; + } +} + + +// Adds 'numSamples' pcs of samples from the 'samples' memory position to +// the sample buffer. +void FIFOSampleBuffer::putSamples(const SAMPLETYPE *samples, uint nSamples) +{ + memcpy(ptrEnd(nSamples), samples, sizeof(SAMPLETYPE) * nSamples * channels); + samplesInBuffer += nSamples; +} + + +// Increases the number of samples in the buffer without copying any actual +// samples. +// +// This function is used to update the number of samples in the sample buffer +// when accessing the buffer directly with 'ptrEnd' function. Please be +// careful though! +void FIFOSampleBuffer::putSamples(uint nSamples) +{ + uint req; + + req = samplesInBuffer + nSamples; + ensureCapacity(req); + samplesInBuffer += nSamples; +} + + +// Returns a pointer to the end of the used part of the sample buffer (i.e. +// where the new samples are to be inserted). This function may be used for +// inserting new samples into the sample buffer directly. Please be careful! +// +// Parameter 'slackCapacity' tells the function how much free capacity (in +// terms of samples) there _at least_ should be, in order to the caller to +// succesfully insert all the required samples to the buffer. When necessary, +// the function grows the buffer size to comply with this requirement. +// +// When using this function as means for inserting new samples, also remember +// to increase the sample count afterwards, by calling the +// 'putSamples(numSamples)' function. +SAMPLETYPE *FIFOSampleBuffer::ptrEnd(uint slackCapacity) +{ + ensureCapacity(samplesInBuffer + slackCapacity); + return buffer + samplesInBuffer * channels; +} + + +// Returns a pointer to the beginning of the currently non-outputted samples. +// This function is provided for accessing the output samples directly. +// Please be careful! +// +// When using this function to output samples, also remember to 'remove' the +// outputted samples from the buffer by calling the +// 'receiveSamples(numSamples)' function +SAMPLETYPE *FIFOSampleBuffer::ptrBegin() +{ + assert(buffer); + return buffer + bufferPos * channels; +} + + +// Ensures that the buffer has enought capacity, i.e. space for _at least_ +// 'capacityRequirement' number of samples. The buffer is grown in steps of +// 4 kilobytes to eliminate the need for frequently growing up the buffer, +// as well as to round the buffer size up to the virtual memory page size. +void FIFOSampleBuffer::ensureCapacity(uint capacityRequirement) +{ + SAMPLETYPE *tempUnaligned, *temp; + + if (capacityRequirement > getCapacity()) + { + // enlarge the buffer in 4kbyte steps (round up to next 4k boundary) + sizeInBytes = (capacityRequirement * channels * sizeof(SAMPLETYPE) + 4095) & (uint)-4096; + assert(sizeInBytes % 2 == 0); + tempUnaligned = new SAMPLETYPE[sizeInBytes / sizeof(SAMPLETYPE) + 16 / sizeof(SAMPLETYPE)]; + if (tempUnaligned == NULL) + { + ST_THROW_RT_ERROR("Couldn't allocate memory!\n"); + } + // Align the buffer to begin at 16byte cache line boundary for optimal performance + temp = (SAMPLETYPE *)SOUNDTOUCH_ALIGN_POINTER_16(tempUnaligned); + if (samplesInBuffer) + { + memcpy(temp, ptrBegin(), samplesInBuffer * channels * sizeof(SAMPLETYPE)); + } + delete[] bufferUnaligned; + buffer = temp; + bufferUnaligned = tempUnaligned; + bufferPos = 0; + } + else + { + // simply rewind the buffer (if necessary) + rewind(); + } +} + + +// Returns the current buffer capacity in terms of samples +uint FIFOSampleBuffer::getCapacity() const +{ + return sizeInBytes / (channels * sizeof(SAMPLETYPE)); +} + + +// Returns the number of samples currently in the buffer +uint FIFOSampleBuffer::numSamples() const +{ + return samplesInBuffer; +} + + +// Output samples from beginning of the sample buffer. Copies demanded number +// of samples to output and removes them from the sample buffer. If there +// are less than 'numsample' samples in the buffer, returns all available. +// +// Returns number of samples copied. +uint FIFOSampleBuffer::receiveSamples(SAMPLETYPE *output, uint maxSamples) +{ + uint num; + + num = (maxSamples > samplesInBuffer) ? samplesInBuffer : maxSamples; + + memcpy(output, ptrBegin(), channels * sizeof(SAMPLETYPE) * num); + return receiveSamples(num); +} + + +// Removes samples from the beginning of the sample buffer without copying them +// anywhere. Used to reduce the number of samples in the buffer, when accessing +// the sample buffer with the 'ptrBegin' function. +uint FIFOSampleBuffer::receiveSamples(uint maxSamples) +{ + if (maxSamples >= samplesInBuffer) + { + uint temp; + + temp = samplesInBuffer; + samplesInBuffer = 0; + return temp; + } + + samplesInBuffer -= maxSamples; + bufferPos += maxSamples; + + return maxSamples; +} + + +// Returns nonzero if the sample buffer is empty +int FIFOSampleBuffer::isEmpty() const +{ + return (samplesInBuffer == 0) ? 1 : 0; +} + + +// Clears the sample buffer +void FIFOSampleBuffer::clear() +{ + samplesInBuffer = 0; + bufferPos = 0; +} + + +/// allow trimming (downwards) amount of samples in pipeline. +/// Returns adjusted amount of samples +uint FIFOSampleBuffer::adjustAmountOfSamples(uint numSamples) +{ + if (numSamples < samplesInBuffer) + { + samplesInBuffer = numSamples; + } + return samplesInBuffer; +} + diff --git a/Externals/SoundTouch/FIFOSampleBuffer.h b/Externals/SoundTouch/FIFOSampleBuffer.h new file mode 100644 index 0000000000..3789b4d307 --- /dev/null +++ b/Externals/SoundTouch/FIFOSampleBuffer.h @@ -0,0 +1,178 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// A buffer class for temporarily storaging sound samples, operates as a +/// first-in-first-out pipe. +/// +/// Samples are added to the end of the sample buffer with the 'putSamples' +/// function, and are received from the beginning of the buffer by calling +/// the 'receiveSamples' function. The class automatically removes the +/// output samples from the buffer as well as grows the storage size +/// whenever necessary. +/// +/// 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 22:29:53 +0300 (Wed, 13 Jun 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: FIFOSampleBuffer.h 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FIFOSampleBuffer_H +#define FIFOSampleBuffer_H + +#include "FIFOSamplePipe.h" + +namespace soundtouch +{ + +/// Sample buffer working in FIFO (first-in-first-out) principle. The class takes +/// care of storage size adjustment and data moving during input/output operations. +/// +/// Notice that in case of stereo audio, one sample is considered to consist of +/// both channel data. +class FIFOSampleBuffer : public FIFOSamplePipe +{ +private: + /// Sample buffer. + SAMPLETYPE *buffer; + + // Raw unaligned buffer memory. 'buffer' is made aligned by pointing it to first + // 16-byte aligned location of this buffer + SAMPLETYPE *bufferUnaligned; + + /// Sample buffer size in bytes + uint sizeInBytes; + + /// How many samples are currently in buffer. + uint samplesInBuffer; + + /// Channels, 1=mono, 2=stereo. + uint channels; + + /// Current position pointer to the buffer. This pointer is increased when samples are + /// removed from the pipe so that it's necessary to actually rewind buffer (move data) + /// only new data when is put to the pipe. + uint bufferPos; + + /// Rewind the buffer by moving data from position pointed by 'bufferPos' to real + /// beginning of the buffer. + void rewind(); + + /// Ensures that the buffer has capacity for at least this many samples. + void ensureCapacity(uint capacityRequirement); + + /// Returns current capacity. + uint getCapacity() const; + +public: + + /// Constructor + FIFOSampleBuffer(int numChannels = 2 ///< Number of channels, 1=mono, 2=stereo. + ///< Default is stereo. + ); + + /// destructor + ~FIFOSampleBuffer(); + + /// Returns a pointer to the beginning of the output samples. + /// This function is provided for accessing the output samples directly. + /// Please be careful for not to corrupt the book-keeping! + /// + /// When using this function to output samples, also remember to 'remove' the + /// output samples from the buffer by calling the + /// 'receiveSamples(numSamples)' function + virtual SAMPLETYPE *ptrBegin(); + + /// Returns a pointer to the end of the used part of the sample buffer (i.e. + /// where the new samples are to be inserted). This function may be used for + /// inserting new samples into the sample buffer directly. Please be careful + /// not corrupt the book-keeping! + /// + /// When using this function as means for inserting new samples, also remember + /// to increase the sample count afterwards, by calling the + /// 'putSamples(numSamples)' function. + SAMPLETYPE *ptrEnd( + uint slackCapacity ///< How much free capacity (in samples) there _at least_ + ///< should be so that the caller can succesfully insert the + ///< desired samples to the buffer. If necessary, the function + ///< grows the buffer size to comply with this requirement. + ); + + /// Adds 'numSamples' pcs of samples from the 'samples' memory position to + /// the sample buffer. + virtual void putSamples(const SAMPLETYPE *samples, ///< Pointer to samples. + uint numSamples ///< Number of samples to insert. + ); + + /// Adjusts the book-keeping to increase number of samples in the buffer without + /// copying any actual samples. + /// + /// This function is used to update the number of samples in the sample buffer + /// when accessing the buffer directly with 'ptrEnd' function. Please be + /// careful though! + virtual void putSamples(uint numSamples ///< Number of samples been inserted. + ); + + /// 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. + ); + + /// Returns number of samples currently available. + virtual uint numSamples() const; + + /// Sets number of channels, 1 = mono, 2 = stereo. + void setChannels(int numChannels); + + /// Returns nonzero if there aren't any samples available for outputting. + virtual int isEmpty() const; + + /// Clears all the samples. + virtual void clear(); + + /// allow trimming (downwards) amount of samples in pipeline. + /// Returns adjusted amount of samples + uint adjustAmountOfSamples(uint numSamples); +}; + +} + +#endif diff --git a/Externals/SoundTouch/FIFOSamplePipe.h b/Externals/SoundTouch/FIFOSamplePipe.h new file mode 100644 index 0000000000..f26c57b0b2 --- /dev/null +++ b/Externals/SoundTouch/FIFOSamplePipe.h @@ -0,0 +1,234 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// 'FIFOSamplePipe' : An abstract base class for classes that manipulate sound +/// samples by operating like a first-in-first-out pipe: New samples are fed +/// into one end of the pipe with the 'putSamples' function, and the processed +/// samples are received from the other end with the 'receiveSamples' function. +/// +/// 'FIFOProcessor' : A base class for classes the do signal processing with +/// the samples while operating like a first-in-first-out pipe. When samples +/// are input with the 'putSamples' function, the class processes them +/// and moves the processed samples to the given 'output' pipe object, which +/// may be either another processing stage, or a fifo sample buffer object. +/// +/// 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 22:29:53 +0300 (Wed, 13 Jun 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: FIFOSamplePipe.h 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FIFOSamplePipe_H +#define FIFOSamplePipe_H + +#include +#include +#include "STTypes.h" + +namespace soundtouch +{ + +/// Abstract base class for FIFO (first-in-first-out) sample processing classes. +class FIFOSamplePipe +{ +public: + // virtual default destructor + virtual ~FIFOSamplePipe() {} + + + /// Returns a pointer to the beginning of the output samples. + /// This function is provided for accessing the output samples directly. + /// Please be careful for not to corrupt the book-keeping! + /// + /// When using this function to output samples, also remember to 'remove' the + /// output samples from the buffer by calling the + /// 'receiveSamples(numSamples)' function + virtual SAMPLETYPE *ptrBegin() = 0; + + /// Adds 'numSamples' pcs of samples from the 'samples' memory position to + /// the sample buffer. + virtual void putSamples(const SAMPLETYPE *samples, ///< Pointer to samples. + uint numSamples ///< Number of samples to insert. + ) = 0; + + + // Moves samples from the 'other' pipe instance to this instance. + void moveSamples(FIFOSamplePipe &other ///< Other pipe instance where from the receive the data. + ) + { + int oNumSamples = other.numSamples(); + + putSamples(other.ptrBegin(), oNumSamples); + other.receiveSamples(oNumSamples); + }; + + /// 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. + ) = 0; + + /// 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. + ) = 0; + + /// Returns number of samples currently available. + virtual uint numSamples() const = 0; + + // Returns nonzero if there aren't any samples available for outputting. + virtual int isEmpty() const = 0; + + /// Clears all the samples. + virtual void clear() = 0; + + /// allow trimming (downwards) amount of samples in pipeline. + /// Returns adjusted amount of samples + virtual uint adjustAmountOfSamples(uint numSamples) = 0; + +}; + + + +/// Base-class for sound processing routines working in FIFO principle. With this base +/// class it's easy to implement sound processing stages that can be chained together, +/// so that samples that are fed into beginning of the pipe automatically go through +/// all the processing stages. +/// +/// When samples are input to this class, they're first processed and then put to +/// the FIFO pipe that's defined as output of this class. This output pipe can be +/// either other processing stage or a FIFO sample buffer. +class FIFOProcessor :public FIFOSamplePipe +{ +protected: + /// Internal pipe where processed samples are put. + FIFOSamplePipe *output; + + /// Sets output pipe. + void setOutPipe(FIFOSamplePipe *pOutput) + { + assert(output == NULL); + assert(pOutput != NULL); + output = pOutput; + } + + + /// Constructor. Doesn't define output pipe; it has to be set be + /// 'setOutPipe' function. + FIFOProcessor() + { + output = NULL; + } + + + /// Constructor. Configures output pipe. + FIFOProcessor(FIFOSamplePipe *pOutput ///< Output pipe. + ) + { + output = pOutput; + } + + + /// Destructor. + virtual ~FIFOProcessor() + { + } + + + /// Returns a pointer to the beginning of the output samples. + /// This function is provided for accessing the output samples directly. + /// Please be careful for not to corrupt the book-keeping! + /// + /// When using this function to output samples, also remember to 'remove' the + /// output samples from the buffer by calling the + /// 'receiveSamples(numSamples)' function + virtual SAMPLETYPE *ptrBegin() + { + return output->ptrBegin(); + } + +public: + + /// 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 *outBuffer, ///< Buffer where to copy output samples. + uint maxSamples ///< How many samples to receive at max. + ) + { + return output->receiveSamples(outBuffer, maxSamples); + } + + + /// 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. + ) + { + return output->receiveSamples(maxSamples); + } + + + /// Returns number of samples currently available. + virtual uint numSamples() const + { + return output->numSamples(); + } + + + /// Returns nonzero if there aren't any samples available for outputting. + virtual int isEmpty() const + { + return output->isEmpty(); + } + + /// allow trimming (downwards) amount of samples in pipeline. + /// Returns adjusted amount of samples + virtual uint adjustAmountOfSamples(uint numSamples) + { + return output->adjustAmountOfSamples(numSamples); + } + +}; + +} + +#endif diff --git a/Externals/SoundTouch/FIRFilter.cpp b/Externals/SoundTouch/FIRFilter.cpp new file mode 100644 index 0000000000..1570516b77 --- /dev/null +++ b/Externals/SoundTouch/FIRFilter.cpp @@ -0,0 +1,259 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// General FIR digital filter routines with MMX optimization. +/// +/// Note : MMX optimized functions reside in a separate, platform-specific file, +/// e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2011-09-02 21:56:11 +0300 (Fri, 02 Sep 2011) $ +// File revision : $Revision: 4 $ +// +// $Id: FIRFilter.cpp 131 2011-09-02 18:56:11Z 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 +#include +#include +#include +#include "FIRFilter.h" +#include "cpu_detect.h" + +using namespace soundtouch; + +/***************************************************************************** + * + * Implementation of the class 'FIRFilter' + * + *****************************************************************************/ + +FIRFilter::FIRFilter() +{ + resultDivFactor = 0; + resultDivider = 0; + length = 0; + lengthDiv8 = 0; + filterCoeffs = NULL; +} + + +FIRFilter::~FIRFilter() +{ + delete[] filterCoeffs; +} + +// Usual C-version of the filter routine for stereo sound +uint FIRFilter::evaluateFilterStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples) const +{ + uint i, j, end; + LONG_SAMPLETYPE suml, sumr; +#ifdef SOUNDTOUCH_FLOAT_SAMPLES + // when using floating point samples, use a scaler instead of a divider + // because division is much slower operation than multiplying. + double dScaler = 1.0 / (double)resultDivider; +#endif + + assert(length != 0); + assert(src != NULL); + assert(dest != NULL); + assert(filterCoeffs != NULL); + + end = 2 * (numSamples - length); + + for (j = 0; j < end; j += 2) + { + const SAMPLETYPE *ptr; + + suml = sumr = 0; + ptr = src + j; + + for (i = 0; i < length; i += 4) + { + // loop is unrolled by factor of 4 here for efficiency + suml += ptr[2 * i + 0] * filterCoeffs[i + 0] + + ptr[2 * i + 2] * filterCoeffs[i + 1] + + ptr[2 * i + 4] * filterCoeffs[i + 2] + + ptr[2 * i + 6] * filterCoeffs[i + 3]; + sumr += ptr[2 * i + 1] * filterCoeffs[i + 0] + + ptr[2 * i + 3] * filterCoeffs[i + 1] + + ptr[2 * i + 5] * filterCoeffs[i + 2] + + ptr[2 * i + 7] * filterCoeffs[i + 3]; + } + +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + suml >>= resultDivFactor; + sumr >>= resultDivFactor; + // saturate to 16 bit integer limits + suml = (suml < -32768) ? -32768 : (suml > 32767) ? 32767 : suml; + // saturate to 16 bit integer limits + sumr = (sumr < -32768) ? -32768 : (sumr > 32767) ? 32767 : sumr; +#else + suml *= dScaler; + sumr *= dScaler; +#endif // SOUNDTOUCH_INTEGER_SAMPLES + dest[j] = (SAMPLETYPE)suml; + dest[j + 1] = (SAMPLETYPE)sumr; + } + return numSamples - length; +} + + + + +// Usual C-version of the filter routine for mono sound +uint FIRFilter::evaluateFilterMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples) const +{ + uint i, j, end; + LONG_SAMPLETYPE sum; +#ifdef SOUNDTOUCH_FLOAT_SAMPLES + // when using floating point samples, use a scaler instead of a divider + // because division is much slower operation than multiplying. + double dScaler = 1.0 / (double)resultDivider; +#endif + + + assert(length != 0); + + end = numSamples - length; + for (j = 0; j < end; j ++) + { + sum = 0; + for (i = 0; i < length; i += 4) + { + // loop is unrolled by factor of 4 here for efficiency + sum += src[i + 0] * filterCoeffs[i + 0] + + src[i + 1] * filterCoeffs[i + 1] + + src[i + 2] * filterCoeffs[i + 2] + + src[i + 3] * filterCoeffs[i + 3]; + } +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + sum >>= resultDivFactor; + // saturate to 16 bit integer limits + sum = (sum < -32768) ? -32768 : (sum > 32767) ? 32767 : sum; +#else + sum *= dScaler; +#endif // SOUNDTOUCH_INTEGER_SAMPLES + dest[j] = (SAMPLETYPE)sum; + src ++; + } + return end; +} + + +// Set filter coeffiecients and length. +// +// Throws an exception if filter length isn't divisible by 8 +void FIRFilter::setCoefficients(const SAMPLETYPE *coeffs, uint newLength, uint uResultDivFactor) +{ + assert(newLength > 0); + if (newLength % 8) ST_THROW_RT_ERROR("FIR filter length not divisible by 8"); + + lengthDiv8 = newLength / 8; + length = lengthDiv8 * 8; + assert(length == newLength); + + resultDivFactor = uResultDivFactor; + resultDivider = (SAMPLETYPE)::pow(2.0, (int)resultDivFactor); + + delete[] filterCoeffs; + filterCoeffs = new SAMPLETYPE[length]; + memcpy(filterCoeffs, coeffs, length * sizeof(SAMPLETYPE)); +} + + +uint FIRFilter::getLength() const +{ + return length; +} + + + +// Applies the filter to the given sequence of samples. +// +// Note : The amount of outputted samples is by value of 'filter_length' +// smaller than the amount of input samples. +uint FIRFilter::evaluate(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples, uint numChannels) const +{ + assert(numChannels == 1 || numChannels == 2); + + assert(length > 0); + assert(lengthDiv8 * 8 == length); + if (numSamples < length) return 0; + if (numChannels == 2) + { + return evaluateFilterStereo(dest, src, numSamples); + } else { + return evaluateFilterMono(dest, src, numSamples); + } +} + + + +// Operator 'new' is overloaded so that it automatically creates a suitable instance +// depending on if we've a MMX-capable CPU available or not. +void * FIRFilter::operator new(size_t s) +{ + // Notice! don't use "new FIRFilter" directly, use "newInstance" to create a new instance instead! + ST_THROW_RT_ERROR("Error in FIRFilter::new: Don't use 'new FIRFilter', use 'newInstance' member instead!"); + return newInstance(); +} + + +FIRFilter * FIRFilter::newInstance() +{ + uint uExtensions; + + uExtensions = detectCPUextensions(); + + // Check if MMX/SSE instruction set extensions supported by CPU + +#ifdef SOUNDTOUCH_ALLOW_MMX + // MMX routines available only with integer sample types + if (uExtensions & SUPPORT_MMX) + { + return ::new FIRFilterMMX; + } + else +#endif // SOUNDTOUCH_ALLOW_MMX + +#ifdef SOUNDTOUCH_ALLOW_SSE + if (uExtensions & SUPPORT_SSE) + { + // SSE support + return ::new FIRFilterSSE; + } + else +#endif // SOUNDTOUCH_ALLOW_SSE + + { + // ISA optimizations not supported, use plain C version + return ::new FIRFilter; + } +} diff --git a/Externals/SoundTouch/FIRFilter.h b/Externals/SoundTouch/FIRFilter.h new file mode 100644 index 0000000000..e1563094af --- /dev/null +++ b/Externals/SoundTouch/FIRFilter.h @@ -0,0 +1,145 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// General FIR digital filter routines with MMX optimization. +/// +/// Note : MMX optimized functions reside in a separate, platform-specific file, +/// e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2011-02-13 21:13:57 +0200 (Sun, 13 Feb 2011) $ +// File revision : $Revision: 4 $ +// +// $Id: FIRFilter.h 104 2011-02-13 19:13:57Z 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FIRFilter_H +#define FIRFilter_H + +#include +#include "STTypes.h" + +namespace soundtouch +{ + +class FIRFilter +{ +protected: + // Number of FIR filter taps + uint length; + // Number of FIR filter taps divided by 8 + uint lengthDiv8; + + // Result divider factor in 2^k format + uint resultDivFactor; + + // Result divider value. + SAMPLETYPE resultDivider; + + // Memory for filter coefficients + SAMPLETYPE *filterCoeffs; + + virtual uint evaluateFilterStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) const; + virtual uint evaluateFilterMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) const; + +public: + FIRFilter(); + virtual ~FIRFilter(); + + /// Operator 'new' is overloaded so that it automatically creates a suitable instance + /// depending on if we've a MMX-capable CPU available or not. + static void * operator new(size_t s); + + static FIRFilter *newInstance(); + + /// Applies the filter to the given sequence of samples. + /// Note : The amount of outputted samples is by value of 'filter_length' + /// smaller than the amount of input samples. + /// + /// \return Number of samples copied to 'dest'. + uint evaluate(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples, + uint numChannels) const; + + uint getLength() const; + + virtual void setCoefficients(const SAMPLETYPE *coeffs, + uint newLength, + uint uResultDivFactor); +}; + + +// Optional subclasses that implement CPU-specific optimizations: + +#ifdef SOUNDTOUCH_ALLOW_MMX + +/// Class that implements MMX optimized functions exclusive for 16bit integer samples type. + class FIRFilterMMX : public FIRFilter + { + protected: + short *filterCoeffsUnalign; + short *filterCoeffsAlign; + + virtual uint evaluateFilterStereo(short *dest, const short *src, uint numSamples) const; + public: + FIRFilterMMX(); + ~FIRFilterMMX(); + + virtual void setCoefficients(const short *coeffs, uint newLength, uint uResultDivFactor); + }; + +#endif // SOUNDTOUCH_ALLOW_MMX + + +#ifdef SOUNDTOUCH_ALLOW_SSE + /// Class that implements SSE optimized functions exclusive for floating point samples type. + class FIRFilterSSE : public FIRFilter + { + protected: + float *filterCoeffsUnalign; + float *filterCoeffsAlign; + + virtual uint evaluateFilterStereo(float *dest, const float *src, uint numSamples) const; + public: + FIRFilterSSE(); + ~FIRFilterSSE(); + + virtual void setCoefficients(const float *coeffs, uint newLength, uint uResultDivFactor); + }; + +#endif // SOUNDTOUCH_ALLOW_SSE + +} + +#endif // FIRFilter_H diff --git a/Externals/SoundTouch/PeakFinder.cpp b/Externals/SoundTouch/PeakFinder.cpp new file mode 100644 index 0000000000..47ed27cb50 --- /dev/null +++ b/Externals/SoundTouch/PeakFinder.cpp @@ -0,0 +1,276 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Peak detection routine. +/// +/// The routine detects highest value on an array of values and calculates the +/// precise peak location as a mass-center of the 'hump' around the peak value. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-12-28 21:52:47 +0200 (Fri, 28 Dec 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: PeakFinder.cpp 164 2012-12-28 19:52:47Z 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 +#include + +#include "PeakFinder.h" + +using namespace soundtouch; + +#define max(x, y) (((x) > (y)) ? (x) : (y)) + + +PeakFinder::PeakFinder() +{ + minPos = maxPos = 0; +} + + +// Finds real 'top' of a peak hump from neighnourhood of the given 'peakpos'. +int PeakFinder::findTop(const float *data, int peakpos) const +{ + int i; + int start, end; + float refvalue; + + refvalue = data[peakpos]; + + // seek within ±10 points + start = peakpos - 10; + if (start < minPos) start = minPos; + end = peakpos + 10; + if (end > maxPos) end = maxPos; + + for (i = start; i <= end; i ++) + { + if (data[i] > refvalue) + { + peakpos = i; + refvalue = data[i]; + } + } + + // failure if max value is at edges of seek range => it's not peak, it's at slope. + if ((peakpos == start) || (peakpos == end)) return 0; + + return peakpos; +} + + +// Finds 'ground level' of a peak hump by starting from 'peakpos' and proceeding +// to direction defined by 'direction' until next 'hump' after minimum value will +// begin +int PeakFinder::findGround(const float *data, int peakpos, int direction) const +{ + int lowpos; + int pos; + int climb_count; + float refvalue; + float delta; + + climb_count = 0; + refvalue = data[peakpos]; + lowpos = peakpos; + + pos = peakpos; + + while ((pos > minPos+1) && (pos < maxPos-1)) + { + int prevpos; + + prevpos = pos; + pos += direction; + + // calculate derivate + delta = data[pos] - data[prevpos]; + if (delta <= 0) + { + // going downhill, ok + if (climb_count) + { + climb_count --; // decrease climb count + } + + // check if new minimum found + if (data[pos] < refvalue) + { + // new minimum found + lowpos = pos; + refvalue = data[pos]; + } + } + else + { + // going uphill, increase climbing counter + climb_count ++; + if (climb_count > 5) break; // we've been climbing too long => it's next uphill => quit + } + } + return lowpos; +} + + +// Find offset where the value crosses the given level, when starting from 'peakpos' and +// proceeds to direction defined in 'direction' +int PeakFinder::findCrossingLevel(const float *data, float level, int peakpos, int direction) const +{ + float peaklevel; + int pos; + + peaklevel = data[peakpos]; + assert(peaklevel >= level); + pos = peakpos; + while ((pos >= minPos) && (pos < maxPos)) + { + if (data[pos + direction] < level) return pos; // crossing found + pos += direction; + } + return -1; // not found +} + + +// Calculates the center of mass location of 'data' array items between 'firstPos' and 'lastPos' +double PeakFinder::calcMassCenter(const float *data, int firstPos, int lastPos) const +{ + int i; + float sum; + float wsum; + + sum = 0; + wsum = 0; + for (i = firstPos; i <= lastPos; i ++) + { + sum += (float)i * data[i]; + wsum += data[i]; + } + + if (wsum < 1e-6) return 0; + return sum / wsum; +} + + + +/// get exact center of peak near given position by calculating local mass of center +double PeakFinder::getPeakCenter(const float *data, int peakpos) const +{ + float peakLevel; // peak level + int crosspos1, crosspos2; // position where the peak 'hump' crosses cutting level + float cutLevel; // cutting value + float groundLevel; // ground level of the peak + int gp1, gp2; // bottom positions of the peak 'hump' + + // find ground positions. + gp1 = findGround(data, peakpos, -1); + gp2 = findGround(data, peakpos, 1); + + groundLevel = 0.5f * (data[gp1] + data[gp2]); + peakLevel = data[peakpos]; + + // calculate 70%-level of the peak + cutLevel = 0.70f * peakLevel + 0.30f * groundLevel; + // find mid-level crossings + crosspos1 = findCrossingLevel(data, cutLevel, peakpos, -1); + crosspos2 = findCrossingLevel(data, cutLevel, peakpos, 1); + + if ((crosspos1 < 0) || (crosspos2 < 0)) return 0; // no crossing, no peak.. + + // calculate mass center of the peak surroundings + return calcMassCenter(data, crosspos1, crosspos2); +} + + + +double PeakFinder::detectPeak(const float *data, int aminPos, int amaxPos) +{ + + int i; + int peakpos; // position of peak level + double highPeak, peak; + + this->minPos = aminPos; + this->maxPos = amaxPos; + + // find absolute peak + peakpos = minPos; + peak = data[minPos]; + for (i = minPos + 1; i < maxPos; i ++) + { + if (data[i] > peak) + { + peak = data[i]; + peakpos = i; + } + } + + // Calculate exact location of the highest peak mass center + highPeak = getPeakCenter(data, peakpos); + peak = highPeak; + + // Now check if the highest peak were in fact harmonic of the true base beat peak + // - sometimes the highest peak can be Nth harmonic of the true base peak yet + // just a slightly higher than the true base + + for (i = 3; i < 10; i ++) + { + double peaktmp, harmonic; + int i1,i2; + + harmonic = (double)i * 0.5; + peakpos = (int)(highPeak / harmonic + 0.5f); + if (peakpos < minPos) break; + peakpos = findTop(data, peakpos); // seek true local maximum index + if (peakpos == 0) continue; // no local max here + + // calculate mass-center of possible harmonic peak + peaktmp = getPeakCenter(data, peakpos); + + // accept harmonic peak if + // (a) it is found + // (b) is within ±4% of the expected harmonic interval + // (c) has at least half x-corr value of the max. peak + + double diff = harmonic * peaktmp / highPeak; + if ((diff < 0.96) || (diff > 1.04)) continue; // peak too afar from expected + + // now compare to highest detected peak + i1 = (int)(highPeak + 0.5); + i2 = (int)(peaktmp + 0.5); + if (data[i2] >= 0.4*data[i1]) + { + // The harmonic is at least half as high primary peak, + // thus use the harmonic peak instead + peak = peaktmp; + } + } + + return peak; +} diff --git a/Externals/SoundTouch/PeakFinder.h b/Externals/SoundTouch/PeakFinder.h new file mode 100644 index 0000000000..d170b1c58b --- /dev/null +++ b/Externals/SoundTouch/PeakFinder.h @@ -0,0 +1,97 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// The routine detects highest value on an array of values and calculates the +/// precise peak location as a mass-center of the 'hump' around the peak value. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2011-12-30 22:33:46 +0200 (Fri, 30 Dec 2011) $ +// File revision : $Revision: 4 $ +// +// $Id: PeakFinder.h 132 2011-12-30 20:33:46Z 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _PeakFinder_H_ +#define _PeakFinder_H_ + +namespace soundtouch +{ + +class PeakFinder +{ +protected: + /// Min, max allowed peak positions within the data vector + int minPos, maxPos; + + /// Calculates the mass center between given vector items. + double calcMassCenter(const float *data, ///< Data vector. + int firstPos, ///< Index of first vector item beloging to the peak. + int lastPos ///< Index of last vector item beloging to the peak. + ) const; + + /// Finds the data vector index where the monotoniously decreasing signal crosses the + /// given level. + int findCrossingLevel(const float *data, ///< Data vector. + float level, ///< Goal crossing level. + int peakpos, ///< Peak position index within the data vector. + int direction /// Direction where to proceed from the peak: 1 = right, -1 = left. + ) const; + + // Finds real 'top' of a peak hump from neighnourhood of the given 'peakpos'. + int findTop(const float *data, int peakpos) const; + + + /// Finds the 'ground' level, i.e. smallest level between two neighbouring peaks, to right- + /// or left-hand side of the given peak position. + int findGround(const float *data, /// Data vector. + int peakpos, /// Peak position index within the data vector. + int direction /// Direction where to proceed from the peak: 1 = right, -1 = left. + ) const; + + /// get exact center of peak near given position by calculating local mass of center + double getPeakCenter(const float *data, int peakpos) const; + +public: + /// Constructor. + PeakFinder(); + + /// Detect exact peak position of the data vector by finding the largest peak 'hump' + /// and calculating the mass-center location of the peak hump. + /// + /// \return The location of the largest base harmonic peak hump. + double detectPeak(const float *data, /// Data vector to be analyzed. The data vector has + /// to be at least 'maxPos' items long. + int minPos, ///< Min allowed peak location within the vector data. + int maxPos ///< Max allowed peak location within the vector data. + ); +}; + +} + +#endif // _PeakFinder_H_ diff --git a/Externals/SoundTouch/RateTransposer.cpp b/Externals/SoundTouch/RateTransposer.cpp new file mode 100644 index 0000000000..3ad8d93e5c --- /dev/null +++ b/Externals/SoundTouch/RateTransposer.cpp @@ -0,0 +1,626 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sample rate transposer. Changes sample rate by using linear interpolation +/// together with anti-alias filtering (first order interpolation with anti- +/// alias filtering should be quite adequate for this application) +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2011-09-02 21:56:11 +0300 (Fri, 02 Sep 2011) $ +// File revision : $Revision: 4 $ +// +// $Id: RateTransposer.cpp 131 2011-09-02 18:56:11Z 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 +#include +#include +#include +#include "RateTransposer.h" +#include "AAFilter.h" + +using namespace soundtouch; + + +/// A linear samplerate transposer class that uses integer arithmetics. +/// for the transposing. +class RateTransposerInteger : public RateTransposer +{ +protected: + int iSlopeCount; + int iRate; + SAMPLETYPE sPrevSampleL, sPrevSampleR; + + virtual void resetRegisters(); + + virtual uint transposeStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + virtual uint transposeMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + +public: + RateTransposerInteger(); + virtual ~RateTransposerInteger(); + + /// Sets new target rate. Normal rate = 1.0, smaller values represent slower + /// rate, larger faster rates. + virtual void setRate(float newRate); + +}; + + +/// A linear samplerate transposer class that uses floating point arithmetics +/// for the transposing. +class RateTransposerFloat : public RateTransposer +{ +protected: + float fSlopeCount; + SAMPLETYPE sPrevSampleL, sPrevSampleR; + + virtual void resetRegisters(); + + virtual uint transposeStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + virtual uint transposeMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + +public: + RateTransposerFloat(); + virtual ~RateTransposerFloat(); +}; + + + + +// Operator 'new' is overloaded so that it automatically creates a suitable instance +// depending on if we've a MMX/SSE/etc-capable CPU available or not. +void * RateTransposer::operator new(size_t s) +{ + ST_THROW_RT_ERROR("Error in RateTransoser::new: don't use \"new TDStretch\" directly, use \"newInstance\" to create a new instance instead!"); + return newInstance(); +} + + +RateTransposer *RateTransposer::newInstance() +{ +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + return ::new RateTransposerInteger; +#else + return ::new RateTransposerFloat; +#endif +} + + +// Constructor +RateTransposer::RateTransposer() : FIFOProcessor(&outputBuffer) +{ + numChannels = 2; + bUseAAFilter = TRUE; + fRate = 0; + + // Instantiates the anti-alias filter with default tap length + // of 32 + pAAFilter = new AAFilter(32); +} + + + +RateTransposer::~RateTransposer() +{ + delete pAAFilter; +} + + + +/// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable +void RateTransposer::enableAAFilter(BOOL newMode) +{ + bUseAAFilter = newMode; +} + + +/// Returns nonzero if anti-alias filter is enabled. +BOOL RateTransposer::isAAFilterEnabled() const +{ + return bUseAAFilter; +} + + +AAFilter *RateTransposer::getAAFilter() +{ + return pAAFilter; +} + + + +// Sets new target iRate. Normal iRate = 1.0, smaller values represent slower +// iRate, larger faster iRates. +void RateTransposer::setRate(float newRate) +{ + double fCutoff; + + fRate = newRate; + + // design a new anti-alias filter + if (newRate > 1.0f) + { + fCutoff = 0.5f / newRate; + } + else + { + fCutoff = 0.5f * newRate; + } + pAAFilter->setCutoffFreq(fCutoff); +} + + +// Outputs as many samples of the 'outputBuffer' as possible, and if there's +// any room left, outputs also as many of the incoming samples as possible. +// The goal is to drive the outputBuffer empty. +// +// It's allowed for 'output' and 'input' parameters to point to the same +// memory position. +/* +void RateTransposer::flushStoreBuffer() +{ + if (storeBuffer.isEmpty()) return; + + outputBuffer.moveSamples(storeBuffer); +} +*/ + + +// Adds 'nSamples' pcs of samples from the 'samples' memory position into +// the input of the object. +void RateTransposer::putSamples(const SAMPLETYPE *samples, uint nSamples) +{ + processSamples(samples, nSamples); +} + + + +// Transposes up the sample rate, causing the observed playback 'rate' of the +// sound to decrease +void RateTransposer::upsample(const SAMPLETYPE *src, uint nSamples) +{ + uint count, sizeTemp, num; + + // If the parameter 'uRate' value is smaller than 'SCALE', first transpose + // the samples and then apply the anti-alias filter to remove aliasing. + + // First check that there's enough room in 'storeBuffer' + // (+16 is to reserve some slack in the destination buffer) + sizeTemp = (uint)((float)nSamples / fRate + 16.0f); + + // Transpose the samples, store the result into the end of "storeBuffer" + count = transpose(storeBuffer.ptrEnd(sizeTemp), src, nSamples); + storeBuffer.putSamples(count); + + // Apply the anti-alias filter to samples in "store output", output the + // result to "dest" + num = storeBuffer.numSamples(); + count = pAAFilter->evaluate(outputBuffer.ptrEnd(num), + storeBuffer.ptrBegin(), num, (uint)numChannels); + outputBuffer.putSamples(count); + + // Remove the processed samples from "storeBuffer" + storeBuffer.receiveSamples(count); +} + + +// Transposes down the sample rate, causing the observed playback 'rate' of the +// sound to increase +void RateTransposer::downsample(const SAMPLETYPE *src, uint nSamples) +{ + uint count, sizeTemp; + + // If the parameter 'uRate' value is larger than 'SCALE', first apply the + // anti-alias filter to remove high frequencies (prevent them from folding + // over the lover frequencies), then transpose. + + // Add the new samples to the end of the storeBuffer + storeBuffer.putSamples(src, nSamples); + + // Anti-alias filter the samples to prevent folding and output the filtered + // data to tempBuffer. Note : because of the FIR filter length, the + // filtering routine takes in 'filter_length' more samples than it outputs. + assert(tempBuffer.isEmpty()); + sizeTemp = storeBuffer.numSamples(); + + count = pAAFilter->evaluate(tempBuffer.ptrEnd(sizeTemp), + storeBuffer.ptrBegin(), sizeTemp, (uint)numChannels); + + if (count == 0) return; + + // Remove the filtered samples from 'storeBuffer' + storeBuffer.receiveSamples(count); + + // Transpose the samples (+16 is to reserve some slack in the destination buffer) + sizeTemp = (uint)((float)nSamples / fRate + 16.0f); + count = transpose(outputBuffer.ptrEnd(sizeTemp), tempBuffer.ptrBegin(), count); + outputBuffer.putSamples(count); +} + + +// Transposes sample rate by applying anti-alias filter to prevent folding. +// Returns amount of samples returned in the "dest" buffer. +// The maximum amount of samples that can be returned at a time is set by +// the 'set_returnBuffer_size' function. +void RateTransposer::processSamples(const SAMPLETYPE *src, uint nSamples) +{ + uint count; + uint sizeReq; + + if (nSamples == 0) return; + assert(pAAFilter); + + // If anti-alias filter is turned off, simply transpose without applying + // the filter + if (bUseAAFilter == FALSE) + { + sizeReq = (uint)((float)nSamples / fRate + 1.0f); + count = transpose(outputBuffer.ptrEnd(sizeReq), src, nSamples); + outputBuffer.putSamples(count); + return; + } + + // Transpose with anti-alias filter + if (fRate < 1.0f) + { + upsample(src, nSamples); + } + else + { + downsample(src, nSamples); + } +} + + +// Transposes the sample rate of the given samples using linear interpolation. +// Returns the number of samples returned in the "dest" buffer +inline uint RateTransposer::transpose(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + if (numChannels == 2) + { + return transposeStereo(dest, src, nSamples); + } + else + { + return transposeMono(dest, src, nSamples); + } +} + + +// Sets the number of channels, 1 = mono, 2 = stereo +void RateTransposer::setChannels(int nChannels) +{ + assert(nChannels > 0); + if (numChannels == nChannels) return; + + assert(nChannels == 1 || nChannels == 2); + numChannels = nChannels; + + storeBuffer.setChannels(numChannels); + tempBuffer.setChannels(numChannels); + outputBuffer.setChannels(numChannels); + + // Inits the linear interpolation registers + resetRegisters(); +} + + +// Clears all the samples in the object +void RateTransposer::clear() +{ + outputBuffer.clear(); + storeBuffer.clear(); +} + + +// Returns nonzero if there aren't any samples available for outputting. +int RateTransposer::isEmpty() const +{ + int res; + + res = FIFOProcessor::isEmpty(); + if (res == 0) return 0; + return storeBuffer.isEmpty(); +} + + +////////////////////////////////////////////////////////////////////////////// +// +// RateTransposerInteger - integer arithmetic implementation +// + +/// fixed-point interpolation routine precision +#define SCALE 65536 + +// Constructor +RateTransposerInteger::RateTransposerInteger() : RateTransposer() +{ + // Notice: use local function calling syntax for sake of clarity, + // to indicate the fact that C++ constructor can't call virtual functions. + RateTransposerInteger::resetRegisters(); + RateTransposerInteger::setRate(1.0f); +} + + +RateTransposerInteger::~RateTransposerInteger() +{ +} + + +void RateTransposerInteger::resetRegisters() +{ + iSlopeCount = 0; + sPrevSampleL = + sPrevSampleR = 0; +} + + + +// Transposes the sample rate of the given samples using linear interpolation. +// 'Mono' version of the routine. Returns the number of samples returned in +// the "dest" buffer +uint RateTransposerInteger::transposeMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + unsigned int i, used; + LONG_SAMPLETYPE temp, vol1; + + if (nSamples == 0) return 0; // no samples, no work + + used = 0; + i = 0; + + // Process the last sample saved from the previous call first... + while (iSlopeCount <= SCALE) + { + vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); + temp = vol1 * sPrevSampleL + iSlopeCount * src[0]; + dest[i] = (SAMPLETYPE)(temp / SCALE); + i++; + iSlopeCount += iRate; + } + // now always (iSlopeCount > SCALE) + iSlopeCount -= SCALE; + + while (1) + { + while (iSlopeCount > SCALE) + { + iSlopeCount -= SCALE; + used ++; + if (used >= nSamples - 1) goto end; + } + vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); + temp = src[used] * vol1 + iSlopeCount * src[used + 1]; + dest[i] = (SAMPLETYPE)(temp / SCALE); + + i++; + iSlopeCount += iRate; + } +end: + // Store the last sample for the next round + sPrevSampleL = src[nSamples - 1]; + + return i; +} + + +// Transposes the sample rate of the given samples using linear interpolation. +// 'Stereo' version of the routine. Returns the number of samples returned in +// the "dest" buffer +uint RateTransposerInteger::transposeStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + unsigned int srcPos, i, used; + LONG_SAMPLETYPE temp, vol1; + + if (nSamples == 0) return 0; // no samples, no work + + used = 0; + i = 0; + + // Process the last sample saved from the sPrevSampleLious call first... + while (iSlopeCount <= SCALE) + { + vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); + temp = vol1 * sPrevSampleL + iSlopeCount * src[0]; + dest[2 * i] = (SAMPLETYPE)(temp / SCALE); + temp = vol1 * sPrevSampleR + iSlopeCount * src[1]; + dest[2 * i + 1] = (SAMPLETYPE)(temp / SCALE); + i++; + iSlopeCount += iRate; + } + // now always (iSlopeCount > SCALE) + iSlopeCount -= SCALE; + + while (1) + { + while (iSlopeCount > SCALE) + { + iSlopeCount -= SCALE; + used ++; + if (used >= nSamples - 1) goto end; + } + srcPos = 2 * used; + vol1 = (LONG_SAMPLETYPE)(SCALE - iSlopeCount); + temp = src[srcPos] * vol1 + iSlopeCount * src[srcPos + 2]; + dest[2 * i] = (SAMPLETYPE)(temp / SCALE); + temp = src[srcPos + 1] * vol1 + iSlopeCount * src[srcPos + 3]; + dest[2 * i + 1] = (SAMPLETYPE)(temp / SCALE); + + i++; + iSlopeCount += iRate; + } +end: + // Store the last sample for the next round + sPrevSampleL = src[2 * nSamples - 2]; + sPrevSampleR = src[2 * nSamples - 1]; + + return i; +} + + +// Sets new target iRate. Normal iRate = 1.0, smaller values represent slower +// iRate, larger faster iRates. +void RateTransposerInteger::setRate(float newRate) +{ + iRate = (int)(newRate * SCALE + 0.5f); + RateTransposer::setRate(newRate); +} + + +////////////////////////////////////////////////////////////////////////////// +// +// RateTransposerFloat - floating point arithmetic implementation +// +////////////////////////////////////////////////////////////////////////////// + +// Constructor +RateTransposerFloat::RateTransposerFloat() : RateTransposer() +{ + // Notice: use local function calling syntax for sake of clarity, + // to indicate the fact that C++ constructor can't call virtual functions. + RateTransposerFloat::resetRegisters(); + RateTransposerFloat::setRate(1.0f); +} + + +RateTransposerFloat::~RateTransposerFloat() +{ +} + + +void RateTransposerFloat::resetRegisters() +{ + fSlopeCount = 0; + sPrevSampleL = + sPrevSampleR = 0; +} + + + +// Transposes the sample rate of the given samples using linear interpolation. +// 'Mono' version of the routine. Returns the number of samples returned in +// the "dest" buffer +uint RateTransposerFloat::transposeMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + unsigned int i, used; + + used = 0; + i = 0; + + // Process the last sample saved from the previous call first... + while (fSlopeCount <= 1.0f) + { + dest[i] = (SAMPLETYPE)((1.0f - fSlopeCount) * sPrevSampleL + fSlopeCount * src[0]); + i++; + fSlopeCount += fRate; + } + fSlopeCount -= 1.0f; + + if (nSamples > 1) + { + while (1) + { + while (fSlopeCount > 1.0f) + { + fSlopeCount -= 1.0f; + used ++; + if (used >= nSamples - 1) goto end; + } + dest[i] = (SAMPLETYPE)((1.0f - fSlopeCount) * src[used] + fSlopeCount * src[used + 1]); + i++; + fSlopeCount += fRate; + } + } +end: + // Store the last sample for the next round + sPrevSampleL = src[nSamples - 1]; + + return i; +} + + +// Transposes the sample rate of the given samples using linear interpolation. +// 'Mono' version of the routine. Returns the number of samples returned in +// the "dest" buffer +uint RateTransposerFloat::transposeStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint nSamples) +{ + unsigned int srcPos, i, used; + + if (nSamples == 0) return 0; // no samples, no work + + used = 0; + i = 0; + + // Process the last sample saved from the sPrevSampleLious call first... + while (fSlopeCount <= 1.0f) + { + dest[2 * i] = (SAMPLETYPE)((1.0f - fSlopeCount) * sPrevSampleL + fSlopeCount * src[0]); + dest[2 * i + 1] = (SAMPLETYPE)((1.0f - fSlopeCount) * sPrevSampleR + fSlopeCount * src[1]); + i++; + fSlopeCount += fRate; + } + // now always (iSlopeCount > 1.0f) + fSlopeCount -= 1.0f; + + if (nSamples > 1) + { + while (1) + { + while (fSlopeCount > 1.0f) + { + fSlopeCount -= 1.0f; + used ++; + if (used >= nSamples - 1) goto end; + } + srcPos = 2 * used; + + dest[2 * i] = (SAMPLETYPE)((1.0f - fSlopeCount) * src[srcPos] + + fSlopeCount * src[srcPos + 2]); + dest[2 * i + 1] = (SAMPLETYPE)((1.0f - fSlopeCount) * src[srcPos + 1] + + fSlopeCount * src[srcPos + 3]); + + i++; + fSlopeCount += fRate; + } + } +end: + // Store the last sample for the next round + sPrevSampleL = src[2 * nSamples - 2]; + sPrevSampleR = src[2 * nSamples - 1]; + + return i; +} diff --git a/Externals/SoundTouch/RateTransposer.h b/Externals/SoundTouch/RateTransposer.h new file mode 100644 index 0000000000..48f7bed5c2 --- /dev/null +++ b/Externals/SoundTouch/RateTransposer.h @@ -0,0 +1,159 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sample rate transposer. Changes sample rate by using linear interpolation +/// together with anti-alias filtering (first order interpolation with anti- +/// alias filtering should be quite adequate for this application). +/// +/// Use either of the derived classes of 'RateTransposerInteger' or +/// 'RateTransposerFloat' for corresponding integer/floating point tranposing +/// algorithm implementation. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2009-02-21 18:00:14 +0200 (Sat, 21 Feb 2009) $ +// File revision : $Revision: 4 $ +// +// $Id: RateTransposer.h 63 2009-02-21 16:00:14Z 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef RateTransposer_H +#define RateTransposer_H + +#include +#include "AAFilter.h" +#include "FIFOSamplePipe.h" +#include "FIFOSampleBuffer.h" + +#include "STTypes.h" + +namespace soundtouch +{ + +/// A common linear samplerate transposer class. +/// +/// Note: Use function "RateTransposer::newInstance()" to create a new class +/// instance instead of the "new" operator; that function automatically +/// chooses a correct implementation depending on if integer or floating +/// arithmetics are to be used. +class RateTransposer : public FIFOProcessor +{ +protected: + /// Anti-alias filter object + AAFilter *pAAFilter; + + float fRate; + + int numChannels; + + /// Buffer for collecting samples to feed the anti-alias filter between + /// two batches + FIFOSampleBuffer storeBuffer; + + /// Buffer for keeping samples between transposing & anti-alias filter + FIFOSampleBuffer tempBuffer; + + /// Output sample buffer + FIFOSampleBuffer outputBuffer; + + BOOL bUseAAFilter; + + virtual void resetRegisters() = 0; + + virtual uint transposeStereo(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) = 0; + virtual uint transposeMono(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples) = 0; + inline uint transpose(SAMPLETYPE *dest, + const SAMPLETYPE *src, + uint numSamples); + + void downsample(const SAMPLETYPE *src, + uint numSamples); + void upsample(const SAMPLETYPE *src, + uint numSamples); + + /// Transposes sample rate by applying anti-alias filter to prevent folding. + /// Returns amount of samples returned in the "dest" buffer. + /// The maximum amount of samples that can be returned at a time is set by + /// the 'set_returnBuffer_size' function. + void processSamples(const SAMPLETYPE *src, + uint numSamples); + + +public: + RateTransposer(); + virtual ~RateTransposer(); + + /// Operator 'new' is overloaded so that it automatically creates a suitable instance + /// depending on if we're to use integer or floating point arithmetics. + static void *operator new(size_t s); + + /// Use this function instead of "new" operator to create a new instance of this class. + /// This function automatically chooses a correct implementation, depending on if + /// integer ot floating point arithmetics are to be used. + static RateTransposer *newInstance(); + + /// Returns the output buffer object + FIFOSamplePipe *getOutput() { return &outputBuffer; }; + + /// Returns the store buffer object + FIFOSamplePipe *getStore() { return &storeBuffer; }; + + /// Return anti-alias filter object + AAFilter *getAAFilter(); + + /// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable + void enableAAFilter(BOOL newMode); + + /// Returns nonzero if anti-alias filter is enabled. + BOOL isAAFilterEnabled() const; + + /// Sets new target rate. Normal rate = 1.0, smaller values represent slower + /// rate, larger faster rates. + virtual void setRate(float newRate); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(int channels); + + /// Adds 'numSamples' pcs of samples from the 'samples' memory position into + /// the input of the object. + void putSamples(const SAMPLETYPE *samples, uint numSamples); + + /// Clears all the samples in the object + void clear(); + + /// Returns nonzero if there aren't any samples available for outputting. + int isEmpty() const; +}; + +} + +#endif diff --git a/Externals/SoundTouch/STTypes.h b/Externals/SoundTouch/STTypes.h new file mode 100644 index 0000000000..24828f3295 --- /dev/null +++ b/Externals/SoundTouch/STTypes.h @@ -0,0 +1,189 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Common type definitions for SoundTouch audio processing library. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-12-28 16:53:56 +0200 (Fri, 28 Dec 2012) $ +// File revision : $Revision: 3 $ +// +// $Id: STTypes.h 162 2012-12-28 14:53:56Z 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef STTypes_H +#define STTypes_H + +typedef unsigned int uint; +typedef unsigned long ulong; + +// Patch for MinGW: on Win64 long is 32-bit +#ifdef _WIN64 + typedef unsigned long long ulongptr; +#else + typedef ulong ulongptr; +#endif + + +// Helper macro for aligning pointer up to next 16-byte boundary +#define SOUNDTOUCH_ALIGN_POINTER_16(x) ( ( (ulongptr)(x) + 15 ) & ~(ulongptr)15 ) + + +#if (defined(__GNUC__) && !defined(ANDROID)) + // In GCC, include soundtouch_config.h made by config scritps. + // Skip this in Android compilation that uses GCC but without configure scripts. + #include "soundtouch_config.h" +#endif + +#ifndef _WINDEF_ + // if these aren't defined already by Windows headers, define now + + typedef int BOOL; + + #define FALSE 0 + #define TRUE 1 + +#endif // _WINDEF_ + + +namespace soundtouch +{ + /// Activate these undef's to overrule the possible sampletype + /// setting inherited from some other header file: + #undef SOUNDTOUCH_INTEGER_SAMPLES + #undef SOUNDTOUCH_FLOAT_SAMPLES + + #if (defined(__SOFTFP__)) + // For Android compilation: Force use of Integer samples in case that + // compilation uses soft-floating point emulation - soft-fp is way too slow + #undef SOUNDTOUCH_FLOAT_SAMPLES + #define SOUNDTOUCH_INTEGER_SAMPLES 1 + #endif + + #if !(SOUNDTOUCH_INTEGER_SAMPLES || SOUNDTOUCH_FLOAT_SAMPLES) + + /// Choose either 32bit floating point or 16bit integer sampletype + /// by choosing one of the following defines, unless this selection + /// has already been done in some other file. + //// + /// Notes: + /// - In Windows environment, choose the sample format with the + /// following defines. + /// - In GNU environment, the floating point samples are used by + /// default, but integer samples can be chosen by giving the + /// following switch to the configure script: + /// ./configure --enable-integer-samples + /// However, if you still prefer to select the sample format here + /// also in GNU environment, then please #undef the INTEGER_SAMPLE + /// and FLOAT_SAMPLE defines first as in comments above. + #define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples + //#define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples + + #endif + + #if (_M_IX86 || __i386__ || __x86_64__ || _M_X64) + /// Define this to allow X86-specific assembler/intrinsic optimizations. + /// Notice that library contains also usual C++ versions of each of these + /// these routines, so if you're having difficulties getting the optimized + /// routines compiled for whatever reason, you may disable these optimizations + /// to make the library compile. + + #define SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS 1 + + /// In GNU environment, allow the user to override this setting by + /// giving the following switch to the configure script: + /// ./configure --disable-x86-optimizations + /// ./configure --enable-x86-optimizations=no + #ifdef SOUNDTOUCH_DISABLE_X86_OPTIMIZATIONS + #undef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS + #endif + #else + /// Always disable optimizations when not using a x86 systems. + #undef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS + + #endif + + // If defined, allows the SIMD-optimized routines to take minor shortcuts + // for improved performance. Undefine to require faithfully similar SIMD + // calculations as in normal C implementation. + #define SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION 1 + + + #ifdef SOUNDTOUCH_INTEGER_SAMPLES + // 16bit integer sample type + typedef short SAMPLETYPE; + // data type for sample accumulation: Use 32bit integer to prevent overflows + typedef long LONG_SAMPLETYPE; + + #ifdef SOUNDTOUCH_FLOAT_SAMPLES + // check that only one sample type is defined + #error "conflicting sample types defined" + #endif // SOUNDTOUCH_FLOAT_SAMPLES + + #ifdef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS + // Allow MMX optimizations +#ifndef _M_X64 + #define SOUNDTOUCH_ALLOW_MMX 1 +#endif + #endif + + #else + + // floating point samples + typedef float SAMPLETYPE; + // data type for sample accumulation: Use double to utilize full precision. + typedef double LONG_SAMPLETYPE; + + #ifdef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS + // Allow SSE optimizations + #define SOUNDTOUCH_ALLOW_SSE 1 + #endif + + #endif // SOUNDTOUCH_INTEGER_SAMPLES + +}; + +// define ST_NO_EXCEPTION_HANDLING switch to disable throwing std exceptions: +#define ST_NO_EXCEPTION_HANDLING 1 +#ifdef ST_NO_EXCEPTION_HANDLING + // Exceptions disabled. Throw asserts instead if enabled. + #include + #define ST_THROW_RT_ERROR(x) {assert((const char *)x);} +#else + // use c++ standard exceptions + #include + #define ST_THROW_RT_ERROR(x) {throw std::runtime_error(x);} +#endif + +// When this #define is active, eliminates a clicking sound when the "rate" or "pitch" +// parameter setting crosses from value <1 to >=1 or vice versa during processing. +// Default is off as such crossover is untypical case and involves a slight sound +// quality compromise. +//#define SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER 1 + +#endif diff --git a/Externals/SoundTouch/SoundTouch.cpp b/Externals/SoundTouch/SoundTouch.cpp new file mode 100644 index 0000000000..f9160ed56d --- /dev/null +++ b/Externals/SoundTouch/SoundTouch.cpp @@ -0,0 +1,501 @@ +////////////////////////////////////////////////////////////////////////////// +/// +/// 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 22:29:53 +0300 (Wed, 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 +#include +#include +#include +#include + +#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 = RateTransposer::newInstance(); + pTDStretch = TDStretch::newInstance(); + + setOutPipe(pTDStretch); + + rate = tempo = 0; + + virtualPitch = + virtualRate = + virtualTempo = 1.0; + + calcEffectiveRateAndTempo(); + + 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 (numChannels != 1 && numChannels != 2) + { + ST_THROW_RT_ERROR("Illegal number of channels"); + } + 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(float newRate) +{ + virtualRate = newRate; + calcEffectiveRateAndTempo(); +} + + + +// Sets new rate control value as a difference in percents compared +// to the original rate (-50 .. +100 %) +void SoundTouch::setRateChange(float newRate) +{ + virtualRate = 1.0f + 0.01f * newRate; + calcEffectiveRateAndTempo(); +} + + + +// Sets new tempo control value. Normal tempo = 1.0, smaller values +// represent slower tempo, larger faster tempo. +void SoundTouch::setTempo(float newTempo) +{ + virtualTempo = newTempo; + calcEffectiveRateAndTempo(); +} + + + +// Sets new tempo control value as a difference in percents compared +// to the original tempo (-50 .. +100 %) +void SoundTouch::setTempoChange(float newTempo) +{ + virtualTempo = 1.0f + 0.01f * newTempo; + calcEffectiveRateAndTempo(); +} + + + +// Sets new pitch control value. Original pitch = 1.0, smaller values +// represent lower pitches, larger values higher pitch. +void SoundTouch::setPitch(float newPitch) +{ + virtualPitch = newPitch; + calcEffectiveRateAndTempo(); +} + + + +// Sets pitch change in octaves compared to the original pitch +// (-1.00 .. +1.00) +void SoundTouch::setPitchOctaves(float newPitch) +{ + virtualPitch = (float)exp(0.69314718056f * newPitch); + calcEffectiveRateAndTempo(); +} + + + +// Sets pitch change in semi-tones compared to the original pitch +// (-12 .. +12) +void SoundTouch::setPitchSemiTones(int newPitch) +{ + setPitchOctaves((float)newPitch / 12.0f); +} + + + +void SoundTouch::setPitchSemiTones(float newPitch) +{ + setPitchOctaves(newPitch / 12.0f); +} + + +// Calculates 'effective' rate and tempo values from the +// nominal control values. +void SoundTouch::calcEffectiveRateAndTempo() +{ + float oldTempo = tempo; + float 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 + 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) +{ + bSrateSet = TRUE; + // set sample rate, leave other tempo changer parameters as they are. + pTDStretch->setParameters((int)srate); +} + + +// 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"); + } + + // 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 (rate == 1.0f) + { + // The rate value is same as the original, simply evaluate the tempo changer. + assert(output == pTDStretch); + if (pRateTransposer->isEmpty() == 0) + { + // yet flush the last samples in the pitch transposer buffer + // (may happen if 'rate' changes from a non-zero value to zero) + pTDStretch->moveSamples(*pRateTransposer); + } + pTDStretch->putSamples(samples, nSamples); + } + */ +#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER + else 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 nUnprocessed; + int nOut; + SAMPLETYPE buff[64*2]; // note: allocate 2*64 to cater 64 sample frames of stereo sound + + // 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); + + 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)); + // "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); + + // finish + break; + } + } + + // Clear working buffers + pRateTransposer->clear(); + 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(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(NULL, &temp, NULL, NULL); + return temp; + + case SETTING_SEEKWINDOW_MS: + pTDStretch->getParameters(NULL, NULL, &temp, NULL); + return temp; + + case SETTING_OVERLAP_MS: + pTDStretch->getParameters(NULL, NULL, NULL, &temp); + return temp; + + case SETTING_NOMINAL_INPUT_SEQUENCE : + return pTDStretch->getInputSampleReq(); + + case SETTING_NOMINAL_OUTPUT_SEQUENCE : + return pTDStretch->getOutputBatchSize(); + + default : + return 0; + } +} + + +// Clears all the samples in the object's output and internal processing +// buffers. +void SoundTouch::clear() +{ + 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; +} diff --git a/Externals/SoundTouch/SoundTouch.h b/Externals/SoundTouch/SoundTouch.h new file mode 100644 index 0000000000..c6af895811 --- /dev/null +++ b/Externals/SoundTouch/SoundTouch.h @@ -0,0 +1,277 @@ +////////////////////////////////////////////////////////////////////////////// +/// +/// 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-12-28 21:32:59 +0200 (Fri, 28 Dec 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: SoundTouch.h 163 2012-12-28 19:32:59Z 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef SoundTouch_H +#define SoundTouch_H + +#include "FIFOSamplePipe.h" +#include "STTypes.h" + +namespace soundtouch +{ + +/// Soundtouch library version string +#define SOUNDTOUCH_VERSION "1.7.1" + +/// SoundTouch library version id +#define SOUNDTOUCH_VERSION_ID (10701) + +// +// Available setting IDs for the 'setSetting' & 'get_setting' functions: + +/// Enable/disable anti-alias filter in pitch transposer (0 = disable) +#define SETTING_USE_AA_FILTER 0 + +/// Pitch transposer anti-alias filter length (8 .. 128 taps, default = 32) +#define SETTING_AA_FILTER_LENGTH 1 + +/// Enable/disable quick seeking algorithm in tempo changer routine +/// (enabling quick seeking lowers CPU utilization but causes a minor sound +/// quality compromising) +#define SETTING_USE_QUICKSEEK 2 + +/// Time-stretch algorithm single processing sequence length in milliseconds. This determines +/// to how long sequences the original sound is chopped in the time-stretch algorithm. +/// See "STTypes.h" or README for more information. +#define SETTING_SEQUENCE_MS 3 + +/// Time-stretch algorithm seeking window length in milliseconds for algorithm that finds the +/// best possible overlapping location. This determines from how wide window the algorithm +/// may look for an optimal joining location when mixing the sound sequences back together. +/// See "STTypes.h" or README for more information. +#define SETTING_SEEKWINDOW_MS 4 + +/// Time-stretch algorithm overlap length in milliseconds. When the chopped sound sequences +/// are mixed back together, to form a continuous sound stream, this parameter defines over +/// how long period the two consecutive sequences are let to overlap each other. +/// See "STTypes.h" or README for more information. +#define SETTING_OVERLAP_MS 5 + + +/// Call "getSetting" with this ID to query nominal average processing sequence +/// size in samples. This value tells approcimate value how many input samples +/// SoundTouch needs to gather before it does DSP processing run for the sample batch. +/// +/// Notices: +/// - This is read-only parameter, i.e. setSetting ignores this parameter +/// - Returned value is approximate average value, exact processing batch +/// size may wary from time to time +/// - This parameter value is not constant but may change depending on +/// tempo/pitch/rate/samplerate settings. +#define SETTING_NOMINAL_INPUT_SEQUENCE 6 + + +/// Call "getSetting" with this ID to query nominal average processing output +/// size in samples. This value tells approcimate value how many output samples +/// SoundTouch outputs once it does DSP processing run for a batch of input samples. +/// +/// Notices: +/// - This is read-only parameter, i.e. setSetting ignores this parameter +/// - Returned value is approximate average value, exact processing batch +/// size may wary from time to time +/// - This parameter value is not constant but may change depending on +/// tempo/pitch/rate/samplerate settings. +#define SETTING_NOMINAL_OUTPUT_SEQUENCE 7 + +class SoundTouch : public FIFOProcessor +{ +private: + /// Rate transposer class instance + class RateTransposer *pRateTransposer; + + /// Time-stretch class instance + class TDStretch *pTDStretch; + + /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. + float virtualRate; + + /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. + float virtualTempo; + + /// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters. + float virtualPitch; + + /// Flag: Has sample rate been set? + BOOL bSrateSet; + + /// Calculates effective rate & tempo valuescfrom 'virtualRate', 'virtualTempo' and + /// 'virtualPitch' parameters. + void calcEffectiveRateAndTempo(); + +protected : + /// Number of channels + uint channels; + + /// Effective 'rate' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch' + float rate; + + /// Effective 'tempo' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch' + float tempo; + +public: + SoundTouch(); + virtual ~SoundTouch(); + + /// Get SoundTouch library version string + static const char *getVersionString(); + + /// Get SoundTouch library version Id + static uint getVersionId(); + + /// Sets new rate control value. Normal rate = 1.0, smaller values + /// represent slower rate, larger faster rates. + void setRate(float newRate); + + /// Sets new tempo control value. Normal tempo = 1.0, smaller values + /// represent slower tempo, larger faster tempo. + void setTempo(float newTempo); + + /// Sets new rate control value as a difference in percents compared + /// to the original rate (-50 .. +100 %) + void setRateChange(float newRate); + + /// Sets new tempo control value as a difference in percents compared + /// to the original tempo (-50 .. +100 %) + void setTempoChange(float newTempo); + + /// Sets new pitch control value. Original pitch = 1.0, smaller values + /// represent lower pitches, larger values higher pitch. + void setPitch(float newPitch); + + /// Sets pitch change in octaves compared to the original pitch + /// (-1.00 .. +1.00) + void setPitchOctaves(float newPitch); + + /// Sets pitch change in semi-tones compared to the original pitch + /// (-12 .. +12) + void setPitchSemiTones(int newPitch); + void setPitchSemiTones(float newPitch); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(uint numChannels); + + /// Sets sample rate. + void setSampleRate(uint srate); + + /// 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 flush(); + + /// Adds 'numSamples' pcs of samples from the 'samples' memory position into + /// the input of the object. Notice that sample rate _has_to_ be set before + /// calling this function, otherwise throws a runtime_error exception. + virtual void putSamples( + const SAMPLETYPE *samples, ///< Pointer to sample buffer. + uint numSamples ///< Number of samples in buffer. Notice + ///< that in case of stereo-sound a single sample + ///< contains data for both channels. + ); + + /// Clears all the samples in the object's output and internal processing + /// buffers. + virtual void clear(); + + /// Changes a setting controlling the processing system behaviour. See the + /// 'SETTING_...' defines for available setting ID's. + /// + /// \return 'TRUE' if the setting was succesfully changed + BOOL setSetting(int settingId, ///< Setting ID number. see SETTING_... defines. + int value ///< New setting value. + ); + + /// Reads a setting controlling the processing system behaviour. See the + /// 'SETTING_...' defines for available setting ID's. + /// + /// \return the setting value. + int getSetting(int settingId ///< Setting ID number, see SETTING_... defines. + ) const; + + /// Returns number of samples currently unprocessed. + virtual uint numUnprocessedSamples() const; + + + /// Other handy functions that are implemented in the ancestor classes (see + /// classes 'FIFOProcessor' and 'FIFOSamplePipe') + /// + /// - receiveSamples() : Use this function to receive 'ready' processed samples from SoundTouch. + /// - numSamples() : Get number of 'ready' samples that can be received with + /// function 'receiveSamples()' + /// - isEmpty() : Returns nonzero if there aren't any 'ready' samples. + /// - clear() : Clears all samples from ready/processing buffers. +}; + +} +#endif diff --git a/Externals/SoundTouch/SoundTouch.vcxproj b/Externals/SoundTouch/SoundTouch.vcxproj new file mode 100644 index 0000000000..8f5c475687 --- /dev/null +++ b/Externals/SoundTouch/SoundTouch.vcxproj @@ -0,0 +1,352 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {68A5DD20-7057-448B-8FE0-B6AC8D205509} + + + + + + StaticLibrary + false + MultiByte + + + StaticLibrary + false + MultiByte + + + StaticLibrary + false + MultiByte + + + StaticLibrary + false + MultiByte + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + Release\ + Release\ + Release\ + Release\ + Debug\ + Debug\ + Debug\ + Debug\ + AllRules.ruleset + AllRules.ruleset + + + + + AllRules.ruleset + AllRules.ruleset + + + + + + + + Full + AnySuitable + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreaded + true + + + Release/SoundTouch.pch + Release/ + Release/ + Release/ + Level3 + true + + + Default + + + Win32\SoundTouch.lib + true + + + + + + + NDEBUG;%(PreprocessorDefinitions) + 0x040b + + + + + Full + AnySuitable + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreaded + true + + + Release/SoundTouch.pch + Release/ + Release/ + Release/ + Level3 + true + + + Default + StreamingSIMDExtensions2 + + + Win64\SoundTouch.lib + true + + + + + + + NDEBUG;%(PreprocessorDefinitions) + 0x040b + + + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + + + Debug/SoundTouch.pch + Debug/ + Debug/ + Debug/ + true + Level3 + true + EditAndContinue + Default + + + Win32\SoundTouch.lib + true + + + + + + + _DEBUG;%(PreprocessorDefinitions) + 0x040b + + + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + + + Debug/SoundTouch.pch + Debug/ + Debug/ + Debug/ + true + Level3 + true + ProgramDatabase + Default + + + Win64\SoundTouchD.lib + true + + + + + + + _DEBUG;%(PreprocessorDefinitions) + 0x040b + + + + + Disabled + Disabled + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + EnableFastChecks + EnableFastChecks + true + true + MaxSpeed + MaxSpeed + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + Disabled + Disabled + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + EnableFastChecks + EnableFastChecks + true + true + MaxSpeed + MaxSpeed + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + Disabled + Disabled + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + EnableFastChecks + EnableFastChecks + true + true + MaxSpeed + MaxSpeed + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + Disabled + Disabled + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + EnableFastChecks + EnableFastChecks + true + true + MaxSpeed + MaxSpeed + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + Disabled + Disabled + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + EnableFastChecks + EnableFastChecks + true + true + MaxSpeed + MaxSpeed + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + Disabled + Disabled + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + EnableFastChecks + EnableFastChecks + true + true + MaxSpeed + MaxSpeed + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Externals/SoundTouch/SoundTouch.vcxproj.filters b/Externals/SoundTouch/SoundTouch.vcxproj.filters new file mode 100644 index 0000000000..1dee5b2a6e --- /dev/null +++ b/Externals/SoundTouch/SoundTouch.vcxproj.filters @@ -0,0 +1,60 @@ + + + + + {b7786182-6345-4203-8b48-39eec5ec85dc} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {75380bb9-1e58-4186-a9cd-ec7cd284e6a5} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\bpm + + + Source Files\bpm + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Externals/SoundTouch/TDStretch.cpp b/Externals/SoundTouch/TDStretch.cpp new file mode 100644 index 0000000000..779cf7aa65 --- /dev/null +++ b/Externals/SoundTouch/TDStretch.cpp @@ -0,0 +1,808 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo +/// while maintaining the original pitch by using a time domain WSOLA-like +/// method with several performance-increasing tweaks. +/// +/// Note : MMX optimized functions reside in a separate, platform-specific +/// file, e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-11-08 20:53:01 +0200 (Thu, 08 Nov 2012) $ +// File revision : $Revision: 1.12 $ +// +// $Id: TDStretch.cpp 160 2012-11-08 18:53:01Z 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 +#include +#include +#include +#include + +#include "STTypes.h" +#include "cpu_detect.h" +#include "TDStretch.h" + +#include + +using namespace soundtouch; + +#define max(x, y) (((x) > (y)) ? (x) : (y)) + + +/***************************************************************************** + * + * Constant definitions + * + *****************************************************************************/ + +// Table for the hierarchical mixing position seeking algorithm +static const short _scanOffsets[5][24]={ + { 124, 186, 248, 310, 372, 434, 496, 558, 620, 682, 744, 806, + 868, 930, 992, 1054, 1116, 1178, 1240, 1302, 1364, 1426, 1488, 0}, + {-100, -75, -50, -25, 25, 50, 75, 100, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { -20, -15, -10, -5, 5, 10, 15, 20, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { -4, -3, -2, -1, 1, 2, 3, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 121, 114, 97, 114, 98, 105, 108, 32, 104, 99, 117, 111, + 116, 100, 110, 117, 111, 115, 0, 0, 0, 0, 0, 0}}; + +/***************************************************************************** + * + * Implementation of the class 'TDStretch' + * + *****************************************************************************/ + + +TDStretch::TDStretch() : FIFOProcessor(&outputBuffer) +{ + bQuickSeek = FALSE; + channels = 2; + + pMidBuffer = NULL; + pMidBufferUnaligned = NULL; + overlapLength = 0; + + bAutoSeqSetting = TRUE; + bAutoSeekSetting = TRUE; + +// outDebt = 0; + skipFract = 0; + + tempo = 1.0f; + setParameters(44100, DEFAULT_SEQUENCE_MS, DEFAULT_SEEKWINDOW_MS, DEFAULT_OVERLAP_MS); + setTempo(1.0f); + + clear(); +} + + + +TDStretch::~TDStretch() +{ + delete[] pMidBufferUnaligned; +} + + + +// Sets routine control parameters. These control are certain time constants +// defining how the sound is stretched to the desired duration. +// +// 'sampleRate' = sample rate of the sound +// 'sequenceMS' = one processing sequence length in milliseconds (default = 82 ms) +// 'seekwindowMS' = seeking window length for scanning the best overlapping +// position (default = 28 ms) +// 'overlapMS' = overlapping length (default = 12 ms) + +void TDStretch::setParameters(int aSampleRate, int aSequenceMS, + int aSeekWindowMS, int aOverlapMS) +{ + // accept only positive parameter values - if zero or negative, use old values instead + if (aSampleRate > 0) this->sampleRate = aSampleRate; + if (aOverlapMS > 0) this->overlapMs = aOverlapMS; + + if (aSequenceMS > 0) + { + this->sequenceMs = aSequenceMS; + bAutoSeqSetting = FALSE; + } + else if (aSequenceMS == 0) + { + // if zero, use automatic setting + bAutoSeqSetting = TRUE; + } + + if (aSeekWindowMS > 0) + { + this->seekWindowMs = aSeekWindowMS; + bAutoSeekSetting = FALSE; + } + else if (aSeekWindowMS == 0) + { + // if zero, use automatic setting + bAutoSeekSetting = TRUE; + } + + calcSeqParameters(); + + calculateOverlapLength(overlapMs); + + // set tempo to recalculate 'sampleReq' + setTempo(tempo); + +} + + + +/// Get routine control parameters, see setParameters() function. +/// Any of the parameters to this function can be NULL, in such case corresponding parameter +/// value isn't returned. +void TDStretch::getParameters(int *pSampleRate, int *pSequenceMs, int *pSeekWindowMs, int *pOverlapMs) const +{ + if (pSampleRate) + { + *pSampleRate = sampleRate; + } + + if (pSequenceMs) + { + *pSequenceMs = (bAutoSeqSetting) ? (USE_AUTO_SEQUENCE_LEN) : sequenceMs; + } + + if (pSeekWindowMs) + { + *pSeekWindowMs = (bAutoSeekSetting) ? (USE_AUTO_SEEKWINDOW_LEN) : seekWindowMs; + } + + if (pOverlapMs) + { + *pOverlapMs = overlapMs; + } +} + + +// Overlaps samples in 'midBuffer' with the samples in 'pInput' +void TDStretch::overlapMono(SAMPLETYPE *pOutput, const SAMPLETYPE *pInput) const +{ + int i; + SAMPLETYPE m1, m2; + + m1 = (SAMPLETYPE)0; + m2 = (SAMPLETYPE)overlapLength; + + for (i = 0; i < overlapLength ; i ++) + { + pOutput[i] = (pInput[i] * m1 + pMidBuffer[i] * m2 ) / overlapLength; + m1 += 1; + m2 -= 1; + } +} + + + +void TDStretch::clearMidBuffer() +{ + memset(pMidBuffer, 0, 2 * sizeof(SAMPLETYPE) * overlapLength); +} + + +void TDStretch::clearInput() +{ + inputBuffer.clear(); + clearMidBuffer(); +} + + +// Clears the sample buffers +void TDStretch::clear() +{ + outputBuffer.clear(); + clearInput(); +} + + + +// Enables/disables the quick position seeking algorithm. Zero to disable, nonzero +// to enable +void TDStretch::enableQuickSeek(BOOL enable) +{ + bQuickSeek = enable; +} + + +// Returns nonzero if the quick seeking algorithm is enabled. +BOOL TDStretch::isQuickSeekEnabled() const +{ + return bQuickSeek; +} + + +// Seeks for the optimal overlap-mixing position. +int TDStretch::seekBestOverlapPosition(const SAMPLETYPE *refPos) +{ + if (bQuickSeek) + { + return seekBestOverlapPositionQuick(refPos); + } + else + { + return seekBestOverlapPositionFull(refPos); + } +} + + +// Overlaps samples in 'midBuffer' with the samples in 'pInputBuffer' at position +// of 'ovlPos'. +inline void TDStretch::overlap(SAMPLETYPE *pOutput, const SAMPLETYPE *pInput, uint ovlPos) const +{ + if (channels == 2) + { + // stereo sound + overlapStereo(pOutput, pInput + 2 * ovlPos); + } else { + // mono sound. + overlapMono(pOutput, pInput + ovlPos); + } +} + + + +// Seeks for the optimal overlap-mixing position. The 'stereo' version of the +// routine +// +// The best position is determined as the position where the two overlapped +// sample sequences are 'most alike', in terms of the highest cross-correlation +// value over the overlapping period +int TDStretch::seekBestOverlapPositionFull(const SAMPLETYPE *refPos) +{ + int bestOffs; + double bestCorr, corr; + int i; + + bestCorr = FLT_MIN; + bestOffs = 0; + + // Scans for the best correlation value by testing each possible position + // over the permitted range. + for (i = 0; i < seekLength; i ++) + { + // Calculates correlation value for the mixing position corresponding + // to 'i' + corr = calcCrossCorr(refPos + channels * i, pMidBuffer); + // heuristic rule to slightly favour values close to mid of the range + double tmp = (double)(2 * i - seekLength) / (double)seekLength; + corr = ((corr + 0.1) * (1.0 - 0.25 * tmp * tmp)); + + // Checks for the highest correlation value + if (corr > bestCorr) + { + bestCorr = corr; + bestOffs = i; + } + } + // clear cross correlation routine state if necessary (is so e.g. in MMX routines). + clearCrossCorrState(); + + return bestOffs; +} + + +// Seeks for the optimal overlap-mixing position. The 'stereo' version of the +// routine +// +// The best position is determined as the position where the two overlapped +// sample sequences are 'most alike', in terms of the highest cross-correlation +// value over the overlapping period +int TDStretch::seekBestOverlapPositionQuick(const SAMPLETYPE *refPos) +{ + int j; + int bestOffs; + double bestCorr, corr; + int scanCount, corrOffset, tempOffset; + + bestCorr = FLT_MIN; + bestOffs = _scanOffsets[0][0]; + corrOffset = 0; + tempOffset = 0; + + // Scans for the best correlation value using four-pass hierarchical search. + // + // The look-up table 'scans' has hierarchical position adjusting steps. + // In first pass the routine searhes for the highest correlation with + // relatively coarse steps, then rescans the neighbourhood of the highest + // correlation with better resolution and so on. + for (scanCount = 0;scanCount < 4; scanCount ++) + { + j = 0; + while (_scanOffsets[scanCount][j]) + { + tempOffset = corrOffset + _scanOffsets[scanCount][j]; + if (tempOffset >= seekLength) break; + + // Calculates correlation value for the mixing position corresponding + // to 'tempOffset' + corr = (double)calcCrossCorr(refPos + channels * tempOffset, pMidBuffer); + // heuristic rule to slightly favour values close to mid of the range + double tmp = (double)(2 * tempOffset - seekLength) / seekLength; + corr = ((corr + 0.1) * (1.0 - 0.25 * tmp * tmp)); + + // Checks for the highest correlation value + if (corr > bestCorr) + { + bestCorr = corr; + bestOffs = tempOffset; + } + j ++; + } + corrOffset = bestOffs; + } + // clear cross correlation routine state if necessary (is so e.g. in MMX routines). + clearCrossCorrState(); + + return bestOffs; +} + + + +/// clear cross correlation routine state if necessary +void TDStretch::clearCrossCorrState() +{ + // default implementation is empty. +} + + +/// Calculates processing sequence length according to tempo setting +void TDStretch::calcSeqParameters() +{ + // Adjust tempo param according to tempo, so that variating processing sequence length is used + // at varius tempo settings, between the given low...top limits + #define AUTOSEQ_TEMPO_LOW 0.5 // auto setting low tempo range (-50%) + #define AUTOSEQ_TEMPO_TOP 2.0 // auto setting top tempo range (+100%) + + // sequence-ms setting values at above low & top tempo + #define AUTOSEQ_AT_MIN 125.0 + #define AUTOSEQ_AT_MAX 50.0 + #define AUTOSEQ_K ((AUTOSEQ_AT_MAX - AUTOSEQ_AT_MIN) / (AUTOSEQ_TEMPO_TOP - AUTOSEQ_TEMPO_LOW)) + #define AUTOSEQ_C (AUTOSEQ_AT_MIN - (AUTOSEQ_K) * (AUTOSEQ_TEMPO_LOW)) + + // seek-window-ms setting values at above low & top tempo + #define AUTOSEEK_AT_MIN 25.0 + #define AUTOSEEK_AT_MAX 15.0 + #define AUTOSEEK_K ((AUTOSEEK_AT_MAX - AUTOSEEK_AT_MIN) / (AUTOSEQ_TEMPO_TOP - AUTOSEQ_TEMPO_LOW)) + #define AUTOSEEK_C (AUTOSEEK_AT_MIN - (AUTOSEEK_K) * (AUTOSEQ_TEMPO_LOW)) + + #define CHECK_LIMITS(x, mi, ma) (((x) < (mi)) ? (mi) : (((x) > (ma)) ? (ma) : (x))) + + double seq, seek; + + if (bAutoSeqSetting) + { + seq = AUTOSEQ_C + AUTOSEQ_K * tempo; + seq = CHECK_LIMITS(seq, AUTOSEQ_AT_MAX, AUTOSEQ_AT_MIN); + sequenceMs = (int)(seq + 0.5); + } + + if (bAutoSeekSetting) + { + seek = AUTOSEEK_C + AUTOSEEK_K * tempo; + seek = CHECK_LIMITS(seek, AUTOSEEK_AT_MAX, AUTOSEEK_AT_MIN); + seekWindowMs = (int)(seek + 0.5); + } + + // Update seek window lengths + seekWindowLength = (sampleRate * sequenceMs) / 1000; + if (seekWindowLength < 2 * overlapLength) + { + seekWindowLength = 2 * overlapLength; + } + seekLength = (sampleRate * seekWindowMs) / 1000; +} + + + +// Sets new target tempo. Normal tempo = 'SCALE', smaller values represent slower +// tempo, larger faster tempo. +void TDStretch::setTempo(float newTempo) +{ + int intskip; + + tempo = newTempo; + + // Calculate new sequence duration + calcSeqParameters(); + + // Calculate ideal skip length (according to tempo value) + nominalSkip = tempo * (seekWindowLength - overlapLength); + intskip = (int)(nominalSkip + 0.5f); + + // Calculate how many samples are needed in the 'inputBuffer' to + // process another batch of samples + //sampleReq = max(intskip + overlapLength, seekWindowLength) + seekLength / 2; + sampleReq = max(intskip + overlapLength, seekWindowLength) + seekLength; +} + + + +// Sets the number of channels, 1 = mono, 2 = stereo +void TDStretch::setChannels(int numChannels) +{ + assert(numChannels > 0); + if (channels == numChannels) return; + assert(numChannels == 1 || numChannels == 2); + + channels = numChannels; + inputBuffer.setChannels(channels); + outputBuffer.setChannels(channels); +} + + +// nominal tempo, no need for processing, just pass the samples through +// to outputBuffer +/* +void TDStretch::processNominalTempo() +{ + assert(tempo == 1.0f); + + if (bMidBufferDirty) + { + // If there are samples in pMidBuffer waiting for overlapping, + // do a single sliding overlapping with them in order to prevent a + // clicking distortion in the output sound + if (inputBuffer.numSamples() < overlapLength) + { + // wait until we've got overlapLength input samples + return; + } + // Mix the samples in the beginning of 'inputBuffer' with the + // samples in 'midBuffer' using sliding overlapping + overlap(outputBuffer.ptrEnd(overlapLength), inputBuffer.ptrBegin(), 0); + outputBuffer.putSamples(overlapLength); + inputBuffer.receiveSamples(overlapLength); + clearMidBuffer(); + // now we've caught the nominal sample flow and may switch to + // bypass mode + } + + // Simply bypass samples from input to output + outputBuffer.moveSamples(inputBuffer); +} +*/ + +#include + +// Processes as many processing frames of the samples 'inputBuffer', store +// the result into 'outputBuffer' +void TDStretch::processSamples() +{ + int ovlSkip, offset; + int temp; + + /* Removed this small optimization - can introduce a click to sound when tempo setting + crosses the nominal value + if (tempo == 1.0f) + { + // tempo not changed from the original, so bypass the processing + processNominalTempo(); + return; + } + */ + + // Process samples as long as there are enough samples in 'inputBuffer' + // to form a processing frame. + while ((int)inputBuffer.numSamples() >= sampleReq) + { + // If tempo differs from the normal ('SCALE'), scan for the best overlapping + // position + offset = seekBestOverlapPosition(inputBuffer.ptrBegin()); + + // Mix the samples in the 'inputBuffer' at position of 'offset' with the + // samples in 'midBuffer' using sliding overlapping + // ... first partially overlap with the end of the previous sequence + // (that's in 'midBuffer') + overlap(outputBuffer.ptrEnd((uint)overlapLength), inputBuffer.ptrBegin(), (uint)offset); + outputBuffer.putSamples((uint)overlapLength); + + // ... then copy sequence samples from 'inputBuffer' to output: + + // length of sequence + temp = (seekWindowLength - 2 * overlapLength); + + // crosscheck that we don't have buffer overflow... + if ((int)inputBuffer.numSamples() < (offset + temp + overlapLength * 2)) + { + continue; // just in case, shouldn't really happen + } + + outputBuffer.putSamples(inputBuffer.ptrBegin() + channels * (offset + overlapLength), (uint)temp); + + // Copies the end of the current sequence from 'inputBuffer' to + // 'midBuffer' for being mixed with the beginning of the next + // processing sequence and so on + assert((offset + temp + overlapLength * 2) <= (int)inputBuffer.numSamples()); + memcpy(pMidBuffer, inputBuffer.ptrBegin() + channels * (offset + temp + overlapLength), + channels * sizeof(SAMPLETYPE) * overlapLength); + + // Remove the processed samples from the input buffer. Update + // the difference between integer & nominal skip step to 'skipFract' + // in order to prevent the error from accumulating over time. + skipFract += nominalSkip; // real skip size + ovlSkip = (int)skipFract; // rounded to integer skip + skipFract -= ovlSkip; // maintain the fraction part, i.e. real vs. integer skip + inputBuffer.receiveSamples((uint)ovlSkip); + } +} + + +// Adds 'numsamples' pcs of samples from the 'samples' memory position into +// the input of the object. +void TDStretch::putSamples(const SAMPLETYPE *samples, uint nSamples) +{ + // Add the samples into the input buffer + inputBuffer.putSamples(samples, nSamples); + // Process the samples in input buffer + processSamples(); +} + + + +/// Set new overlap length parameter & reallocate RefMidBuffer if necessary. +void TDStretch::acceptNewOverlapLength(int newOverlapLength) +{ + int prevOvl; + + assert(newOverlapLength >= 0); + prevOvl = overlapLength; + overlapLength = newOverlapLength; + + if (overlapLength > prevOvl) + { + delete[] pMidBufferUnaligned; + + pMidBufferUnaligned = new SAMPLETYPE[overlapLength * 2 + 16 / sizeof(SAMPLETYPE)]; + // ensure that 'pMidBuffer' is aligned to 16 byte boundary for efficiency + pMidBuffer = (SAMPLETYPE *)SOUNDTOUCH_ALIGN_POINTER_16(pMidBufferUnaligned); + + clearMidBuffer(); + } +} + + +// Operator 'new' is overloaded so that it automatically creates a suitable instance +// depending on if we've a MMX/SSE/etc-capable CPU available or not. +void * TDStretch::operator new(size_t s) +{ + // Notice! don't use "new TDStretch" directly, use "newInstance" to create a new instance instead! + ST_THROW_RT_ERROR("Error in TDStretch::new: Don't use 'new TDStretch' directly, use 'newInstance' member instead!"); + return newInstance(); +} + + +TDStretch * TDStretch::newInstance() +{ + uint uExtensions; + + uExtensions = detectCPUextensions(); + + // Check if MMX/SSE instruction set extensions supported by CPU + +#ifdef SOUNDTOUCH_ALLOW_MMX + // MMX routines available only with integer sample types + if (uExtensions & SUPPORT_MMX) + { + return ::new TDStretchMMX; + } + else +#endif // SOUNDTOUCH_ALLOW_MMX + + +#ifdef SOUNDTOUCH_ALLOW_SSE + if (uExtensions & SUPPORT_SSE) + { + // SSE support + return ::new TDStretchSSE; + } + else +#endif // SOUNDTOUCH_ALLOW_SSE + + { + // ISA optimizations not supported, use plain C version + return ::new TDStretch; + } +} + + +////////////////////////////////////////////////////////////////////////////// +// +// Integer arithmetics specific algorithm implementations. +// +////////////////////////////////////////////////////////////////////////////// + +#ifdef SOUNDTOUCH_INTEGER_SAMPLES + +// Overlaps samples in 'midBuffer' with the samples in 'input'. The 'Stereo' +// version of the routine. +void TDStretch::overlapStereo(short *poutput, const short *input) const +{ + int i; + short temp; + int cnt2; + + for (i = 0; i < overlapLength ; i ++) + { + temp = (short)(overlapLength - i); + cnt2 = 2 * i; + poutput[cnt2] = (input[cnt2] * i + pMidBuffer[cnt2] * temp ) / overlapLength; + poutput[cnt2 + 1] = (input[cnt2 + 1] * i + pMidBuffer[cnt2 + 1] * temp ) / overlapLength; + } +} + +// Calculates the x having the closest 2^x value for the given value +static int _getClosest2Power(double value) +{ + return (int)(log(value) / log(2.0) + 0.5); +} + + +/// Calculates overlap period length in samples. +/// Integer version rounds overlap length to closest power of 2 +/// for a divide scaling operation. +void TDStretch::calculateOverlapLength(int aoverlapMs) +{ + int newOvl; + + assert(aoverlapMs >= 0); + + // calculate overlap length so that it's power of 2 - thus it's easy to do + // integer division by right-shifting. Term "-1" at end is to account for + // the extra most significatnt bit left unused in result by signed multiplication + overlapDividerBits = _getClosest2Power((sampleRate * aoverlapMs) / 1000.0) - 1; + if (overlapDividerBits > 9) overlapDividerBits = 9; + if (overlapDividerBits < 3) overlapDividerBits = 3; + newOvl = (int)pow(2.0, (int)overlapDividerBits + 1); // +1 => account for -1 above + + acceptNewOverlapLength(newOvl); + + // calculate sloping divider so that crosscorrelation operation won't + // overflow 32-bit register. Max. sum of the crosscorrelation sum without + // divider would be 2^30*(N^3-N)/3, where N = overlap length + slopingDivider = (newOvl * newOvl - 1) / 3; +} + + +double TDStretch::calcCrossCorr(const short *mixingPos, const short *compare) const +{ + long corr; + long norm; + int i; + + corr = norm = 0; + // Same routine for stereo and mono. For stereo, unroll loop for better + // efficiency and gives slightly better resolution against rounding. + // For mono it same routine, just unrolls loop by factor of 4 + for (i = 0; i < channels * overlapLength; i += 4) + { + corr += (mixingPos[i] * compare[i] + + mixingPos[i + 1] * compare[i + 1] + + mixingPos[i + 2] * compare[i + 2] + + mixingPos[i + 3] * compare[i + 3]) >> overlapDividerBits; + norm += (mixingPos[i] * mixingPos[i] + + mixingPos[i + 1] * mixingPos[i + 1] + + mixingPos[i + 2] * mixingPos[i + 2] + + mixingPos[i + 3] * mixingPos[i + 3]) >> overlapDividerBits; + } + + // Normalize result by dividing by sqrt(norm) - this step is easiest + // done using floating point operation + if (norm == 0) norm = 1; // to avoid div by zero + return (double)corr / sqrt((double)norm); +} + +#endif // SOUNDTOUCH_INTEGER_SAMPLES + +////////////////////////////////////////////////////////////////////////////// +// +// Floating point arithmetics specific algorithm implementations. +// + +#ifdef SOUNDTOUCH_FLOAT_SAMPLES + +// Overlaps samples in 'midBuffer' with the samples in 'pInput' +void TDStretch::overlapStereo(float *pOutput, const float *pInput) const +{ + int i; + float fScale; + float f1; + float f2; + + fScale = 1.0f / (float)overlapLength; + + f1 = 0; + f2 = 1.0f; + + for (i = 0; i < 2 * (int)overlapLength ; i += 2) + { + pOutput[i + 0] = pInput[i + 0] * f1 + pMidBuffer[i + 0] * f2; + pOutput[i + 1] = pInput[i + 1] * f1 + pMidBuffer[i + 1] * f2; + + f1 += fScale; + f2 -= fScale; + } +} + + +/// Calculates overlapInMsec period length in samples. +void TDStretch::calculateOverlapLength(int overlapInMsec) +{ + int newOvl; + + assert(overlapInMsec >= 0); + newOvl = (sampleRate * overlapInMsec) / 1000; + if (newOvl < 16) newOvl = 16; + + // must be divisible by 8 + newOvl -= newOvl % 8; + + acceptNewOverlapLength(newOvl); +} + + +double TDStretch::calcCrossCorr(const float *mixingPos, const float *compare) const +{ + double corr; + double norm; + int i; + + corr = norm = 0; + // Same routine for stereo and mono. For Stereo, unroll by factor of 2. + // For mono it's same routine yet unrollsd by factor of 4. + for (i = 0; i < channels * overlapLength; i += 4) + { + corr += mixingPos[i] * compare[i] + + mixingPos[i + 1] * compare[i + 1]; + + norm += mixingPos[i] * mixingPos[i] + + mixingPos[i + 1] * mixingPos[i + 1]; + + // unroll the loop for better CPU efficiency: + corr += mixingPos[i + 2] * compare[i + 2] + + mixingPos[i + 3] * compare[i + 3]; + + norm += mixingPos[i + 2] * mixingPos[i + 2] + + mixingPos[i + 3] * mixingPos[i + 3]; + } + + if (norm < 1e-9) norm = 1.0; // to avoid div by zero + return corr / sqrt(norm); +} + +#endif // SOUNDTOUCH_FLOAT_SAMPLES diff --git a/Externals/SoundTouch/TDStretch.h b/Externals/SoundTouch/TDStretch.h new file mode 100644 index 0000000000..6d6e7359ff --- /dev/null +++ b/Externals/SoundTouch/TDStretch.h @@ -0,0 +1,268 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo +/// while maintaining the original pitch by using a time domain WSOLA-like method +/// with several performance-increasing tweaks. +/// +/// Note : MMX/SSE optimized functions reside in separate, platform-specific files +/// 'mmx_optimized.cpp' and 'sse_optimized.cpp' +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-04-01 22:49:30 +0300 (Sun, 01 Apr 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: TDStretch.h 137 2012-04-01 19:49:30Z 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef TDStretch_H +#define TDStretch_H + +#include +#include "STTypes.h" +#include "RateTransposer.h" +#include "FIFOSamplePipe.h" + +namespace soundtouch +{ + +/// Default values for sound processing parameters: +/// Notice that the default parameters are tuned for contemporary popular music +/// processing. For speech processing applications these parameters suit better: +/// #define DEFAULT_SEQUENCE_MS 40 +/// #define DEFAULT_SEEKWINDOW_MS 15 +/// #define DEFAULT_OVERLAP_MS 8 +/// + +/// Default length of a single processing sequence, in milliseconds. This determines to how +/// long sequences the original sound is chopped in the time-stretch algorithm. +/// +/// The larger this value is, the lesser sequences are used in processing. In principle +/// a bigger value sounds better when slowing down tempo, but worse when increasing tempo +/// and vice versa. +/// +/// Increasing this value reduces computational burden & vice versa. +//#define DEFAULT_SEQUENCE_MS 40 +#define DEFAULT_SEQUENCE_MS USE_AUTO_SEQUENCE_LEN + +/// Giving this value for the sequence length sets automatic parameter value +/// according to tempo setting (recommended) +#define USE_AUTO_SEQUENCE_LEN 0 + +/// Seeking window default length in milliseconds for algorithm that finds the best possible +/// overlapping location. This determines from how wide window the algorithm may look for an +/// optimal joining location when mixing the sound sequences back together. +/// +/// The bigger this window setting is, the higher the possibility to find a better mixing +/// position will become, but at the same time large values may cause a "drifting" artifact +/// because consequent sequences will be taken at more uneven intervals. +/// +/// If there's a disturbing artifact that sounds as if a constant frequency was drifting +/// around, try reducing this setting. +/// +/// Increasing this value increases computational burden & vice versa. +//#define DEFAULT_SEEKWINDOW_MS 15 +#define DEFAULT_SEEKWINDOW_MS USE_AUTO_SEEKWINDOW_LEN + +/// Giving this value for the seek window length sets automatic parameter value +/// according to tempo setting (recommended) +#define USE_AUTO_SEEKWINDOW_LEN 0 + +/// Overlap length in milliseconds. When the chopped sound sequences are mixed back together, +/// to form a continuous sound stream, this parameter defines over how long period the two +/// consecutive sequences are let to overlap each other. +/// +/// This shouldn't be that critical parameter. If you reduce the DEFAULT_SEQUENCE_MS setting +/// by a large amount, you might wish to try a smaller value on this. +/// +/// Increasing this value increases computational burden & vice versa. +#define DEFAULT_OVERLAP_MS 8 + + +/// Class that does the time-stretch (tempo change) effect for the processed +/// sound. +class TDStretch : public FIFOProcessor +{ +protected: + int channels; + int sampleReq; + float tempo; + + SAMPLETYPE *pMidBuffer; + SAMPLETYPE *pMidBufferUnaligned; + int overlapLength; + int seekLength; + int seekWindowLength; + int overlapDividerBits; + int slopingDivider; + float nominalSkip; + float skipFract; + FIFOSampleBuffer outputBuffer; + FIFOSampleBuffer inputBuffer; + BOOL bQuickSeek; + + int sampleRate; + int sequenceMs; + int seekWindowMs; + int overlapMs; + BOOL bAutoSeqSetting; + BOOL bAutoSeekSetting; + + void acceptNewOverlapLength(int newOverlapLength); + + virtual void clearCrossCorrState(); + void calculateOverlapLength(int overlapMs); + + virtual double calcCrossCorr(const SAMPLETYPE *mixingPos, const SAMPLETYPE *compare) const; + + virtual int seekBestOverlapPositionFull(const SAMPLETYPE *refPos); + virtual int seekBestOverlapPositionQuick(const SAMPLETYPE *refPos); + int seekBestOverlapPosition(const SAMPLETYPE *refPos); + + virtual void overlapStereo(SAMPLETYPE *output, const SAMPLETYPE *input) const; + virtual void overlapMono(SAMPLETYPE *output, const SAMPLETYPE *input) const; + + void clearMidBuffer(); + void overlap(SAMPLETYPE *output, const SAMPLETYPE *input, uint ovlPos) const; + + void calcSeqParameters(); + + /// Changes the tempo of the given sound samples. + /// Returns amount of samples returned in the "output" buffer. + /// The maximum amount of samples that can be returned at a time is set by + /// the 'set_returnBuffer_size' function. + void processSamples(); + +public: + TDStretch(); + virtual ~TDStretch(); + + /// Operator 'new' is overloaded so that it automatically creates a suitable instance + /// depending on if we've a MMX/SSE/etc-capable CPU available or not. + static void *operator new(size_t s); + + /// Use this function instead of "new" operator to create a new instance of this class. + /// This function automatically chooses a correct feature set depending on if the CPU + /// supports MMX/SSE/etc extensions. + static TDStretch *newInstance(); + + /// Returns the output buffer object + FIFOSamplePipe *getOutput() { return &outputBuffer; }; + + /// Returns the input buffer object + FIFOSamplePipe *getInput() { return &inputBuffer; }; + + /// Sets new target tempo. Normal tempo = 'SCALE', smaller values represent slower + /// tempo, larger faster tempo. + void setTempo(float newTempo); + + /// Returns nonzero if there aren't any samples available for outputting. + virtual void clear(); + + /// Clears the input buffer + void clearInput(); + + /// Sets the number of channels, 1 = mono, 2 = stereo + void setChannels(int numChannels); + + /// Enables/disables the quick position seeking algorithm. Zero to disable, + /// nonzero to enable + void enableQuickSeek(BOOL enable); + + /// Returns nonzero if the quick seeking algorithm is enabled. + BOOL isQuickSeekEnabled() const; + + /// Sets routine control parameters. These control are certain time constants + /// defining how the sound is stretched to the desired duration. + // + /// 'sampleRate' = sample rate of the sound + /// 'sequenceMS' = one processing sequence length in milliseconds + /// 'seekwindowMS' = seeking window length for scanning the best overlapping + /// position + /// 'overlapMS' = overlapping length + void setParameters(int sampleRate, ///< Samplerate of sound being processed (Hz) + int sequenceMS = -1, ///< Single processing sequence length (ms) + int seekwindowMS = -1, ///< Offset seeking window length (ms) + int overlapMS = -1 ///< Sequence overlapping length (ms) + ); + + /// Get routine control parameters, see setParameters() function. + /// Any of the parameters to this function can be NULL, in such case corresponding parameter + /// value isn't returned. + void getParameters(int *pSampleRate, int *pSequenceMs, int *pSeekWindowMs, int *pOverlapMs) const; + + /// Adds 'numsamples' pcs of samples from the 'samples' memory position into + /// the input of the object. + virtual void putSamples( + const SAMPLETYPE *samples, ///< Input sample data + uint numSamples ///< Number of samples in 'samples' so that one sample + ///< contains both channels if stereo + ); + + /// return nominal input sample requirement for triggering a processing batch + int getInputSampleReq() const + { + return (int)(nominalSkip + 0.5); + } + + /// return nominal output sample amount when running a processing batch + int getOutputBatchSize() const + { + return seekWindowLength - overlapLength; + } +}; + + + +// Implementation-specific class declarations: + +#ifdef SOUNDTOUCH_ALLOW_MMX + /// Class that implements MMX optimized routines for 16bit integer samples type. + class TDStretchMMX : public TDStretch + { + protected: + double calcCrossCorr(const short *mixingPos, const short *compare) const; + virtual void overlapStereo(short *output, const short *input) const; + virtual void clearCrossCorrState(); + }; +#endif /// SOUNDTOUCH_ALLOW_MMX + + +#ifdef SOUNDTOUCH_ALLOW_SSE + /// Class that implements SSE optimized routines for floating point samples type. + class TDStretchSSE : public TDStretch + { + protected: + double calcCrossCorr(const float *mixingPos, const float *compare) const; + }; + +#endif /// SOUNDTOUCH_ALLOW_SSE + +} +#endif /// TDStretch_H diff --git a/Externals/SoundTouch/cpu_detect.h b/Externals/SoundTouch/cpu_detect.h new file mode 100644 index 0000000000..7859ffb55d --- /dev/null +++ b/Externals/SoundTouch/cpu_detect.h @@ -0,0 +1,62 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// A header file for detecting the Intel MMX instructions set extension. +/// +/// Please see 'mmx_win.cpp', 'mmx_cpp.cpp' and 'mmx_non_x86.cpp' for the +/// routine implementations for x86 Windows, x86 gnu version and non-x86 +/// platforms, respectively. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2008-02-10 18:26:55 +0200 (Sun, 10 Feb 2008) $ +// File revision : $Revision: 4 $ +// +// $Id: cpu_detect.h 11 2008-02-10 16:26:55Z 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 +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _CPU_DETECT_H_ +#define _CPU_DETECT_H_ + +#include "STTypes.h" + +#define SUPPORT_MMX 0x0001 +#define SUPPORT_3DNOW 0x0002 +#define SUPPORT_ALTIVEC 0x0004 +#define SUPPORT_SSE 0x0008 +#define SUPPORT_SSE2 0x0010 + +/// Checks which instruction set extensions are supported by the CPU. +/// +/// \return A bitmask of supported extensions, see SUPPORT_... defines. +uint detectCPUextensions(void); + +/// Disables given set of instruction extensions. See SUPPORT_... defines. +void disableExtensions(uint wDisableMask); + +#endif // _CPU_DETECT_H_ diff --git a/Externals/SoundTouch/cpu_detect_x86.cpp b/Externals/SoundTouch/cpu_detect_x86.cpp new file mode 100644 index 0000000000..fff3240d8a --- /dev/null +++ b/Externals/SoundTouch/cpu_detect_x86.cpp @@ -0,0 +1,137 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Generic version of the x86 CPU extension detection routine. +/// +/// This file is for GNU & other non-Windows compilers, see 'cpu_detect_x86_win.cpp' +/// for the Microsoft compiler version. +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-11-08 20:44:37 +0200 (Thu, 08 Nov 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: cpu_detect_x86.cpp 159 2012-11-08 18:44:37Z 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 "cpu_detect.h" +#include "STTypes.h" + +#if defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) + + #if defined(__GNUC__) && defined(__i386__) + // gcc + #include "cpuid.h" + #elif defined(_M_IX86) + // windows non-gcc + #include + #define bit_MMX (1 << 23) + #define bit_SSE (1 << 25) + #define bit_SSE2 (1 << 26) + #endif + +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// processor instructions extension detection routines +// +////////////////////////////////////////////////////////////////////////////// + +// Flag variable indicating whick ISA extensions are disabled (for debugging) +static uint _dwDisabledISA = 0x00; // 0xffffffff; //<- use this to disable all extensions + +// Disables given set of instruction extensions. See SUPPORT_... defines. +void disableExtensions(uint dwDisableMask) +{ + _dwDisabledISA = dwDisableMask; +} + + + +/// Checks which instruction set extensions are supported by the CPU. +uint detectCPUextensions(void) +{ +/// If building for a 64bit system (no Itanium) and the user wants optimizations. +/// Return the OR of SUPPORT_{MMX,SSE,SSE2}. 11001 or 0x19. +/// Keep the _dwDisabledISA test (2 more operations, could be eliminated). +#if ((defined(__GNUC__) && defined(__x86_64__)) \ + || defined(_M_X64)) \ + && defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) + return 0x19 & ~_dwDisabledISA; + +/// If building for a 32bit system and the user wants optimizations. +/// Keep the _dwDisabledISA test (2 more operations, could be eliminated). +#elif ((defined(__GNUC__) && defined(__i386__)) \ + || defined(_M_IX86)) \ + && defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) + + if (_dwDisabledISA == 0xffffffff) return 0; + + uint res = 0; + +#if defined(__GNUC__) + // GCC version of cpuid. Requires GCC 4.3.0 or later for __cpuid intrinsic support. + uint eax, ebx, ecx, edx; // unsigned int is the standard type. uint is defined by the compiler and not guaranteed to be portable. + + // Check if no cpuid support. + if (!__get_cpuid (1, &eax, &ebx, &ecx, &edx)) return 0; // always disable extensions. + + if (edx & bit_MMX) res = res | SUPPORT_MMX; + if (edx & bit_SSE) res = res | SUPPORT_SSE; + if (edx & bit_SSE2) res = res | SUPPORT_SSE2; + +#else + // Window / VS version of cpuid. Notice that Visual Studio 2005 or later required + // for __cpuid intrinsic support. + int reg[4] = {-1}; + + // Check if no cpuid support. + __cpuid(reg,0); + if ((unsigned int)reg[0] == 0) return 0; // always disable extensions. + + __cpuid(reg,1); + if ((unsigned int)reg[3] & bit_MMX) res = res | SUPPORT_MMX; + if ((unsigned int)reg[3] & bit_SSE) res = res | SUPPORT_SSE; + if ((unsigned int)reg[3] & bit_SSE2) res = res | SUPPORT_SSE2; + +#endif + + return res & ~_dwDisabledISA; + +#else + +/// One of these is true: +/// 1) We don't want optimizations. +/// 2) Using an unsupported compiler. +/// 3) Running on a non-x86 platform. + return 0; + +#endif +} diff --git a/Externals/SoundTouch/mmx_optimized.cpp b/Externals/SoundTouch/mmx_optimized.cpp new file mode 100644 index 0000000000..0952fd4f84 --- /dev/null +++ b/Externals/SoundTouch/mmx_optimized.cpp @@ -0,0 +1,317 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// MMX optimized routines. All MMX optimized functions have been gathered into +/// this single source code file, regardless to their class or original source +/// code file, in order to ease porting the library to other compiler and +/// processor platforms. +/// +/// The MMX-optimizations are programmed using MMX compiler intrinsics that +/// are supported both by Microsoft Visual C++ and GCC compilers, so this file +/// should compile with both toolsets. +/// +/// NOTICE: If using Visual Studio 6.0, you'll need to install the "Visual C++ +/// 6.0 processor pack" update to support compiler intrinsic syntax. The update +/// is available for download at Microsoft Developers Network, see here: +/// http://msdn.microsoft.com/en-us/vstudio/aa718349.aspx +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-11-08 20:53:01 +0200 (Thu, 08 Nov 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: mmx_optimized.cpp 160 2012-11-08 18:53:01Z 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 "STTypes.h" + +#ifdef SOUNDTOUCH_ALLOW_MMX +// MMX routines available only with integer sample type + +using namespace soundtouch; + +////////////////////////////////////////////////////////////////////////////// +// +// implementation of MMX optimized functions of class 'TDStretchMMX' +// +////////////////////////////////////////////////////////////////////////////// + +#include "TDStretch.h" +#include +#include +#include + + +// Calculates cross correlation of two buffers +double TDStretchMMX::calcCrossCorr(const short *pV1, const short *pV2) const +{ + const __m64 *pVec1, *pVec2; + __m64 shifter; + __m64 accu, normaccu; + long corr, norm; + int i; + + pVec1 = (__m64*)pV1; + pVec2 = (__m64*)pV2; + + shifter = _m_from_int(overlapDividerBits); + normaccu = accu = _mm_setzero_si64(); + + // Process 4 parallel sets of 2 * stereo samples or 4 * mono samples + // during each round for improved CPU-level parallellization. + for (i = 0; i < channels * overlapLength / 16; i ++) + { + __m64 temp, temp2; + + // dictionary of instructions: + // _m_pmaddwd : 4*16bit multiply-add, resulting two 32bits = [a0*b0+a1*b1 ; a2*b2+a3*b3] + // _mm_add_pi32 : 2*32bit add + // _m_psrad : 32bit right-shift + + temp = _mm_add_pi32(_mm_madd_pi16(pVec1[0], pVec2[0]), + _mm_madd_pi16(pVec1[1], pVec2[1])); + temp2 = _mm_add_pi32(_mm_madd_pi16(pVec1[0], pVec1[0]), + _mm_madd_pi16(pVec1[1], pVec1[1])); + accu = _mm_add_pi32(accu, _mm_sra_pi32(temp, shifter)); + normaccu = _mm_add_pi32(normaccu, _mm_sra_pi32(temp2, shifter)); + + temp = _mm_add_pi32(_mm_madd_pi16(pVec1[2], pVec2[2]), + _mm_madd_pi16(pVec1[3], pVec2[3])); + temp2 = _mm_add_pi32(_mm_madd_pi16(pVec1[2], pVec1[2]), + _mm_madd_pi16(pVec1[3], pVec1[3])); + accu = _mm_add_pi32(accu, _mm_sra_pi32(temp, shifter)); + normaccu = _mm_add_pi32(normaccu, _mm_sra_pi32(temp2, shifter)); + + pVec1 += 4; + pVec2 += 4; + } + + // copy hi-dword of mm0 to lo-dword of mm1, then sum mmo+mm1 + // and finally store the result into the variable "corr" + + accu = _mm_add_pi32(accu, _mm_srli_si64(accu, 32)); + corr = _m_to_int(accu); + + normaccu = _mm_add_pi32(normaccu, _mm_srli_si64(normaccu, 32)); + norm = _m_to_int(normaccu); + + // Clear MMS state + _m_empty(); + + // Normalize result by dividing by sqrt(norm) - this step is easiest + // done using floating point operation + if (norm == 0) norm = 1; // to avoid div by zero + + return (double)corr / sqrt((double)norm); + // Note: Warning about the missing EMMS instruction is harmless + // as it'll be called elsewhere. +} + + + +void TDStretchMMX::clearCrossCorrState() +{ + // Clear MMS state + _m_empty(); + //_asm EMMS; +} + + + +// MMX-optimized version of the function overlapStereo +void TDStretchMMX::overlapStereo(short *output, const short *input) const +{ + const __m64 *pVinput, *pVMidBuf; + __m64 *pVdest; + __m64 mix1, mix2, adder, shifter; + int i; + + pVinput = (const __m64*)input; + pVMidBuf = (const __m64*)pMidBuffer; + pVdest = (__m64*)output; + + // mix1 = mixer values for 1st stereo sample + // mix1 = mixer values for 2nd stereo sample + // adder = adder for updating mixer values after each round + + mix1 = _mm_set_pi16(0, overlapLength, 0, overlapLength); + adder = _mm_set_pi16(1, -1, 1, -1); + mix2 = _mm_add_pi16(mix1, adder); + adder = _mm_add_pi16(adder, adder); + + // Overlaplength-division by shifter. "+1" is to account for "-1" deduced in + // overlapDividerBits calculation earlier. + shifter = _m_from_int(overlapDividerBits + 1); + + for (i = 0; i < overlapLength / 4; i ++) + { + __m64 temp1, temp2; + + // load & shuffle data so that input & mixbuffer data samples are paired + temp1 = _mm_unpacklo_pi16(pVMidBuf[0], pVinput[0]); // = i0l m0l i0r m0r + temp2 = _mm_unpackhi_pi16(pVMidBuf[0], pVinput[0]); // = i1l m1l i1r m1r + + // temp = (temp .* mix) >> shifter + temp1 = _mm_sra_pi32(_mm_madd_pi16(temp1, mix1), shifter); + temp2 = _mm_sra_pi32(_mm_madd_pi16(temp2, mix2), shifter); + pVdest[0] = _mm_packs_pi32(temp1, temp2); // pack 2*2*32bit => 4*16bit + + // update mix += adder + mix1 = _mm_add_pi16(mix1, adder); + mix2 = _mm_add_pi16(mix2, adder); + + // --- second round begins here --- + + // load & shuffle data so that input & mixbuffer data samples are paired + temp1 = _mm_unpacklo_pi16(pVMidBuf[1], pVinput[1]); // = i2l m2l i2r m2r + temp2 = _mm_unpackhi_pi16(pVMidBuf[1], pVinput[1]); // = i3l m3l i3r m3r + + // temp = (temp .* mix) >> shifter + temp1 = _mm_sra_pi32(_mm_madd_pi16(temp1, mix1), shifter); + temp2 = _mm_sra_pi32(_mm_madd_pi16(temp2, mix2), shifter); + pVdest[1] = _mm_packs_pi32(temp1, temp2); // pack 2*2*32bit => 4*16bit + + // update mix += adder + mix1 = _mm_add_pi16(mix1, adder); + mix2 = _mm_add_pi16(mix2, adder); + + pVinput += 2; + pVMidBuf += 2; + pVdest += 2; + } + + _m_empty(); // clear MMS state +} + + +////////////////////////////////////////////////////////////////////////////// +// +// implementation of MMX optimized functions of class 'FIRFilter' +// +////////////////////////////////////////////////////////////////////////////// + +#include "FIRFilter.h" + + +FIRFilterMMX::FIRFilterMMX() : FIRFilter() +{ + filterCoeffsUnalign = NULL; +} + + +FIRFilterMMX::~FIRFilterMMX() +{ + delete[] filterCoeffsUnalign; +} + + +// (overloaded) Calculates filter coefficients for MMX routine +void FIRFilterMMX::setCoefficients(const short *coeffs, uint newLength, uint uResultDivFactor) +{ + uint i; + FIRFilter::setCoefficients(coeffs, newLength, uResultDivFactor); + + // Ensure that filter coeffs array is aligned to 16-byte boundary + delete[] filterCoeffsUnalign; + filterCoeffsUnalign = new short[2 * newLength + 8]; + filterCoeffsAlign = (short *)SOUNDTOUCH_ALIGN_POINTER_16(filterCoeffsUnalign); + + // rearrange the filter coefficients for mmx routines + for (i = 0;i < length; i += 4) + { + filterCoeffsAlign[2 * i + 0] = coeffs[i + 0]; + filterCoeffsAlign[2 * i + 1] = coeffs[i + 2]; + filterCoeffsAlign[2 * i + 2] = coeffs[i + 0]; + filterCoeffsAlign[2 * i + 3] = coeffs[i + 2]; + + filterCoeffsAlign[2 * i + 4] = coeffs[i + 1]; + filterCoeffsAlign[2 * i + 5] = coeffs[i + 3]; + filterCoeffsAlign[2 * i + 6] = coeffs[i + 1]; + filterCoeffsAlign[2 * i + 7] = coeffs[i + 3]; + } +} + + + +// mmx-optimized version of the filter routine for stereo sound +uint FIRFilterMMX::evaluateFilterStereo(short *dest, const short *src, uint numSamples) const +{ + // Create stack copies of the needed member variables for asm routines : + uint i, j; + __m64 *pVdest = (__m64*)dest; + + if (length < 2) return 0; + + for (i = 0; i < (numSamples - length) / 2; i ++) + { + __m64 accu1; + __m64 accu2; + const __m64 *pVsrc = (const __m64*)src; + const __m64 *pVfilter = (const __m64*)filterCoeffsAlign; + + accu1 = accu2 = _mm_setzero_si64(); + for (j = 0; j < lengthDiv8 * 2; j ++) + { + __m64 temp1, temp2; + + temp1 = _mm_unpacklo_pi16(pVsrc[0], pVsrc[1]); // = l2 l0 r2 r0 + temp2 = _mm_unpackhi_pi16(pVsrc[0], pVsrc[1]); // = l3 l1 r3 r1 + + accu1 = _mm_add_pi32(accu1, _mm_madd_pi16(temp1, pVfilter[0])); // += l2*f2+l0*f0 r2*f2+r0*f0 + accu1 = _mm_add_pi32(accu1, _mm_madd_pi16(temp2, pVfilter[1])); // += l3*f3+l1*f1 r3*f3+r1*f1 + + temp1 = _mm_unpacklo_pi16(pVsrc[1], pVsrc[2]); // = l4 l2 r4 r2 + + accu2 = _mm_add_pi32(accu2, _mm_madd_pi16(temp2, pVfilter[0])); // += l3*f2+l1*f0 r3*f2+r1*f0 + accu2 = _mm_add_pi32(accu2, _mm_madd_pi16(temp1, pVfilter[1])); // += l4*f3+l2*f1 r4*f3+r2*f1 + + // accu1 += l2*f2+l0*f0 r2*f2+r0*f0 + // += l3*f3+l1*f1 r3*f3+r1*f1 + + // accu2 += l3*f2+l1*f0 r3*f2+r1*f0 + // l4*f3+l2*f1 r4*f3+r2*f1 + + pVfilter += 2; + pVsrc += 2; + } + // accu >>= resultDivFactor + accu1 = _mm_srai_pi32(accu1, resultDivFactor); + accu2 = _mm_srai_pi32(accu2, resultDivFactor); + + // pack 2*2*32bits => 4*16 bits + pVdest[0] = _mm_packs_pi32(accu1, accu2); + src += 4; + pVdest ++; + } + + _m_empty(); // clear emms state + + return (numSamples & 0xfffffffe) - length; +} + +#endif // SOUNDTOUCH_ALLOW_MMX diff --git a/Externals/SoundTouch/sse_optimized.cpp b/Externals/SoundTouch/sse_optimized.cpp new file mode 100644 index 0000000000..ffb6706288 --- /dev/null +++ b/Externals/SoundTouch/sse_optimized.cpp @@ -0,0 +1,361 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// SSE optimized routines for Pentium-III, Athlon-XP and later CPUs. All SSE +/// optimized functions have been gathered into this single source +/// code file, regardless to their class or original source code file, in order +/// to ease porting the library to other compiler and processor platforms. +/// +/// The SSE-optimizations are programmed using SSE compiler intrinsics that +/// are supported both by Microsoft Visual C++ and GCC compilers, so this file +/// should compile with both toolsets. +/// +/// NOTICE: If using Visual Studio 6.0, you'll need to install the "Visual C++ +/// 6.0 processor pack" update to support SSE instruction set. The update is +/// available for download at Microsoft Developers Network, see here: +/// http://msdn.microsoft.com/en-us/vstudio/aa718349.aspx +/// +/// If the above URL is expired or removed, go to "http://msdn.microsoft.com" and +/// perform a search with keywords "processor pack". +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date: 2012-11-08 20:53:01 +0200 (Thu, 08 Nov 2012) $ +// File revision : $Revision: 4 $ +// +// $Id: sse_optimized.cpp 160 2012-11-08 18:53:01Z 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 "cpu_detect.h" +#include "STTypes.h" + +using namespace soundtouch; + +#ifdef SOUNDTOUCH_ALLOW_SSE + +// SSE routines available only with float sample type + +////////////////////////////////////////////////////////////////////////////// +// +// implementation of SSE optimized functions of class 'TDStretchSSE' +// +////////////////////////////////////////////////////////////////////////////// + +#include "TDStretch.h" +#include +#include + +// Calculates cross correlation of two buffers +double TDStretchSSE::calcCrossCorr(const float *pV1, const float *pV2) const +{ + int i; + const float *pVec1; + const __m128 *pVec2; + __m128 vSum, vNorm; + + // Note. It means a major slow-down if the routine needs to tolerate + // unaligned __m128 memory accesses. It's way faster if we can skip + // unaligned slots and use _mm_load_ps instruction instead of _mm_loadu_ps. + // This can mean up to ~ 10-fold difference (incl. part of which is + // due to skipping every second round for stereo sound though). + // + // Compile-time define SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION is provided + // for choosing if this little cheating is allowed. + +#ifdef SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION + // Little cheating allowed, return valid correlation only for + // aligned locations, meaning every second round for stereo sound. + + #define _MM_LOAD _mm_load_ps + + if (((ulongptr)pV1) & 15) return -1e50; // skip unaligned locations + +#else + // No cheating allowed, use unaligned load & take the resulting + // performance hit. + #define _MM_LOAD _mm_loadu_ps +#endif + + // ensure overlapLength is divisible by 8 + assert((overlapLength % 8) == 0); + + // Calculates the cross-correlation value between 'pV1' and 'pV2' vectors + // Note: pV2 _must_ be aligned to 16-bit boundary, pV1 need not. + pVec1 = (const float*)pV1; + pVec2 = (const __m128*)pV2; + vSum = vNorm = _mm_setzero_ps(); + + // Unroll the loop by factor of 4 * 4 operations. Use same routine for + // stereo & mono, for mono it just means twice the amount of unrolling. + for (i = 0; i < channels * overlapLength / 16; i ++) + { + __m128 vTemp; + // vSum += pV1[0..3] * pV2[0..3] + vTemp = _MM_LOAD(pVec1); + vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp ,pVec2[0])); + vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); + + // vSum += pV1[4..7] * pV2[4..7] + vTemp = _MM_LOAD(pVec1 + 4); + vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[1])); + vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); + + // vSum += pV1[8..11] * pV2[8..11] + vTemp = _MM_LOAD(pVec1 + 8); + vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[2])); + vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); + + // vSum += pV1[12..15] * pV2[12..15] + vTemp = _MM_LOAD(pVec1 + 12); + vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[3])); + vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); + + pVec1 += 16; + pVec2 += 4; + } + + // return value = vSum[0] + vSum[1] + vSum[2] + vSum[3] + float *pvNorm = (float*)&vNorm; + double norm = sqrt(pvNorm[0] + pvNorm[1] + pvNorm[2] + pvNorm[3]); + if (norm < 1e-9) norm = 1.0; // to avoid div by zero + + float *pvSum = (float*)&vSum; + return (double)(pvSum[0] + pvSum[1] + pvSum[2] + pvSum[3]) / norm; + + /* This is approximately corresponding routine in C-language yet without normalization: + double corr, norm; + uint i; + + // Calculates the cross-correlation value between 'pV1' and 'pV2' vectors + corr = norm = 0.0; + for (i = 0; i < channels * overlapLength / 16; i ++) + { + corr += pV1[0] * pV2[0] + + pV1[1] * pV2[1] + + pV1[2] * pV2[2] + + pV1[3] * pV2[3] + + pV1[4] * pV2[4] + + pV1[5] * pV2[5] + + pV1[6] * pV2[6] + + pV1[7] * pV2[7] + + pV1[8] * pV2[8] + + pV1[9] * pV2[9] + + pV1[10] * pV2[10] + + pV1[11] * pV2[11] + + pV1[12] * pV2[12] + + pV1[13] * pV2[13] + + pV1[14] * pV2[14] + + pV1[15] * pV2[15]; + + for (j = 0; j < 15; j ++) norm += pV1[j] * pV1[j]; + + pV1 += 16; + pV2 += 16; + } + return corr / sqrt(norm); + */ +} + + +////////////////////////////////////////////////////////////////////////////// +// +// implementation of SSE optimized functions of class 'FIRFilter' +// +////////////////////////////////////////////////////////////////////////////// + +#include "FIRFilter.h" + +FIRFilterSSE::FIRFilterSSE() : FIRFilter() +{ + filterCoeffsAlign = NULL; + filterCoeffsUnalign = NULL; +} + + +FIRFilterSSE::~FIRFilterSSE() +{ + delete[] filterCoeffsUnalign; + filterCoeffsAlign = NULL; + filterCoeffsUnalign = NULL; +} + + +// (overloaded) Calculates filter coefficients for SSE routine +void FIRFilterSSE::setCoefficients(const float *coeffs, uint newLength, uint uResultDivFactor) +{ + uint i; + float fDivider; + + FIRFilter::setCoefficients(coeffs, newLength, uResultDivFactor); + + // Scale the filter coefficients so that it won't be necessary to scale the filtering result + // also rearrange coefficients suitably for SSE + // Ensure that filter coeffs array is aligned to 16-byte boundary + delete[] filterCoeffsUnalign; + filterCoeffsUnalign = new float[2 * newLength + 4]; + filterCoeffsAlign = (float *)SOUNDTOUCH_ALIGN_POINTER_16(filterCoeffsUnalign); + + fDivider = (float)resultDivider; + + // rearrange the filter coefficients for mmx routines + for (i = 0; i < newLength; i ++) + { + filterCoeffsAlign[2 * i + 0] = + filterCoeffsAlign[2 * i + 1] = coeffs[i + 0] / fDivider; + } +} + + + +// SSE-optimized version of the filter routine for stereo sound +uint FIRFilterSSE::evaluateFilterStereo(float *dest, const float *source, uint numSamples) const +{ + int count = (int)((numSamples - length) & (uint)-2); + int j; + + assert(count % 2 == 0); + + if (count < 2) return 0; + + assert(source != NULL); + assert(dest != NULL); + assert((length % 8) == 0); + assert(filterCoeffsAlign != NULL); + assert(((ulongptr)filterCoeffsAlign) % 16 == 0); + + // filter is evaluated for two stereo samples with each iteration, thus use of 'j += 2' + for (j = 0; j < count; j += 2) + { + const float *pSrc; + const __m128 *pFil; + __m128 sum1, sum2; + uint i; + + pSrc = (const float*)source; // source audio data + pFil = (const __m128*)filterCoeffsAlign; // filter coefficients. NOTE: Assumes coefficients + // are aligned to 16-byte boundary + sum1 = sum2 = _mm_setzero_ps(); + + for (i = 0; i < length / 8; i ++) + { + // Unroll loop for efficiency & calculate filter for 2*2 stereo samples + // at each pass + + // sum1 is accu for 2*2 filtered stereo sound data at the primary sound data offset + // sum2 is accu for 2*2 filtered stereo sound data for the next sound sample offset. + + sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc) , pFil[0])); + sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 2), pFil[0])); + + sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 4), pFil[1])); + sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 6), pFil[1])); + + sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 8) , pFil[2])); + sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 10), pFil[2])); + + sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 12), pFil[3])); + sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 14), pFil[3])); + + pSrc += 16; + pFil += 4; + } + + // Now sum1 and sum2 both have a filtered 2-channel sample each, but we still need + // to sum the two hi- and lo-floats of these registers together. + + // post-shuffle & add the filtered values and store to dest. + _mm_storeu_ps(dest, _mm_add_ps( + _mm_shuffle_ps(sum1, sum2, _MM_SHUFFLE(1,0,3,2)), // s2_1 s2_0 s1_3 s1_2 + _mm_shuffle_ps(sum1, sum2, _MM_SHUFFLE(3,2,1,0)) // s2_3 s2_2 s1_1 s1_0 + )); + source += 4; + dest += 4; + } + + // Ideas for further improvement: + // 1. If it could be guaranteed that 'source' were always aligned to 16-byte + // boundary, a faster aligned '_mm_load_ps' instruction could be used. + // 2. If it could be guaranteed that 'dest' were always aligned to 16-byte + // boundary, a faster '_mm_store_ps' instruction could be used. + + return (uint)count; + + /* original routine in C-language. please notice the C-version has differently + organized coefficients though. + double suml1, suml2; + double sumr1, sumr2; + uint i, j; + + for (j = 0; j < count; j += 2) + { + const float *ptr; + const float *pFil; + + suml1 = sumr1 = 0.0; + suml2 = sumr2 = 0.0; + ptr = src; + pFil = filterCoeffs; + for (i = 0; i < lengthLocal; i ++) + { + // unroll loop for efficiency. + + suml1 += ptr[0] * pFil[0] + + ptr[2] * pFil[2] + + ptr[4] * pFil[4] + + ptr[6] * pFil[6]; + + sumr1 += ptr[1] * pFil[1] + + ptr[3] * pFil[3] + + ptr[5] * pFil[5] + + ptr[7] * pFil[7]; + + suml2 += ptr[8] * pFil[0] + + ptr[10] * pFil[2] + + ptr[12] * pFil[4] + + ptr[14] * pFil[6]; + + sumr2 += ptr[9] * pFil[1] + + ptr[11] * pFil[3] + + ptr[13] * pFil[5] + + ptr[15] * pFil[7]; + + ptr += 16; + pFil += 8; + } + dest[0] = (float)suml1; + dest[1] = (float)sumr1; + dest[2] = (float)suml2; + dest[3] = (float)sumr2; + + src += 4; + dest += 4; + } + */ +} + +#endif // SOUNDTOUCH_ALLOW_SSE diff --git a/Source/Core/AudioCommon/AudioCommon.vcxproj b/Source/Core/AudioCommon/AudioCommon.vcxproj index 4bcee7559b..85608330dd 100644 --- a/Source/Core/AudioCommon/AudioCommon.vcxproj +++ b/Source/Core/AudioCommon/AudioCommon.vcxproj @@ -115,8 +115,8 @@ true - OpenAL32.lib;dsound.lib;dxerr.lib - ..\..\..\Externals\OpenAL\Win32;%(AdditionalLibraryDirectories) + SoundTouchD.lib;OpenAL32.lib;dsound.lib;dxerr.lib + ..\..\..\Externals\OpenAL\Win32;..\..\..\Externals\SoundTouch\Win32;%(AdditionalLibraryDirectories) @@ -127,8 +127,8 @@ true - OpenAL32.lib;dsound.lib;dxerr.lib - ..\..\..\Externals\OpenAL\Win64;%(AdditionalLibraryDirectories) + SoundTouchD.lib;OpenAL32.lib;dsound.lib;dxerr.lib + ..\..\..\Externals\OpenAL\Win64;..\..\..\Externals\SoundTouch\Win64;%(AdditionalLibraryDirectories) @@ -141,8 +141,8 @@ true - OpenAL32.lib;dsound.lib;dxerr.lib - ..\..\..\Externals\OpenAL\Win32;%(AdditionalLibraryDirectories) + SoundTouch.lib;OpenAL32.lib;dsound.lib;dxerr.lib + ..\..\..\Externals\OpenAL\Win32;..\..\..\Externals\SoundTouch\Win32;%(AdditionalLibraryDirectories) @@ -155,8 +155,8 @@ true - OpenAL32.lib;dsound.lib;dxerr.lib - ..\..\..\Externals\OpenAL\Win32;%(AdditionalLibraryDirectories) + SoundTouch.lib;OpenAL32.lib;dsound.lib;dxerr.lib + ..\..\..\Externals\OpenAL\Win32;..\..\..\Externals\SoundTouch\Win32;%(AdditionalLibraryDirectories) @@ -169,8 +169,8 @@ true - OpenAL32.lib;dsound.lib;dxerr.lib - ..\..\..\Externals\OpenAL\Win64;%(AdditionalLibraryDirectories) + SoundTouch.lib;OpenAL32.lib;dsound.lib;dxerr.lib + ..\..\..\Externals\OpenAL\Win64;..\..\..\Externals\SoundTouch\Win64;%(AdditionalLibraryDirectories) @@ -183,8 +183,8 @@ true - OpenAL32.lib;dsound.lib;dxerr.lib - ..\..\..\Externals\OpenAL\Win64;%(AdditionalLibraryDirectories) + SoundTouch.lib;OpenAL32.lib;dsound.lib;dxerr.lib + ..\..\..\Externals\OpenAL\Win64;..\..\..\Externals\SoundTouch\Win64;%(AdditionalLibraryDirectories) diff --git a/Source/Core/AudioCommon/Src/Mixer.h b/Source/Core/AudioCommon/Src/Mixer.h index da456d5bf7..c1a387cf49 100644 --- a/Source/Core/AudioCommon/Src/Mixer.h +++ b/Source/Core/AudioCommon/Src/Mixer.h @@ -92,6 +92,9 @@ public: std::mutex& MixerCritical() { return m_csMixing; } + volatile float GetCurrentSpeed() const { return m_speed; } + void UpdateSpeed(volatile float val) { m_speed = val; } + protected: unsigned int m_sampleRate; unsigned int m_aiSampleRate; @@ -113,6 +116,8 @@ protected: bool m_AIplaying; std::mutex m_csMixing; + + volatile float m_speed; // Current rate of the emulation (1.0 = 100% speed) private: }; diff --git a/Source/Core/AudioCommon/Src/OpenALStream.cpp b/Source/Core/AudioCommon/Src/OpenALStream.cpp index 4cc64a17de..6f429db495 100644 --- a/Source/Core/AudioCommon/Src/OpenALStream.cpp +++ b/Source/Core/AudioCommon/Src/OpenALStream.cpp @@ -19,11 +19,12 @@ #include "aldlist.h" #include "OpenALStream.h" -#include "../../Core/Src/HW/SystemTimers.h" -#include "../../Core/Src/HW/AudioInterface.h" #if defined HAVE_OPENAL && HAVE_OPENAL +using namespace soundtouch; +SoundTouch soundTouch; + // // AyuanX: Spec says OpenAL1.1 is thread safe already // @@ -67,6 +68,7 @@ bool OpenALStream::Start() PanicAlertT("OpenAL: can't find sound devices"); } + soundTouch.clear(); return bReturn; } @@ -76,6 +78,8 @@ void OpenALStream::Stop() // kick the thread if it's waiting soundSyncEvent.Set(); + soundTouch.clear(); + thread.join(); alSourceStop(uiSource); @@ -105,6 +109,7 @@ void OpenALStream::SetVolume(int volume) void OpenALStream::Update() { soundSyncEvent.Set(); + mainSyncEvent.Wait(); } void OpenALStream::Clear(bool mute) @@ -113,6 +118,7 @@ void OpenALStream::Clear(bool mute) if(m_muted) { + soundTouch.clear(); alSourceStop(uiSource); } else @@ -136,9 +142,10 @@ void OpenALStream::SoundLoop() alGenSources(1, &uiSource); // Short Silence + memset(sampleBuffer, 0, OAL_MAX_SAMPLES * 4 * OAL_NUM_BUFFERS); memset(realtimeBuffer, 0, OAL_MAX_SAMPLES * 4); for (int i = 0; i < OAL_NUM_BUFFERS; i++) - alBufferData(uiBuffers[i], AL_FORMAT_STEREO16, realtimeBuffer, OAL_MAX_SAMPLES, ulFrequency); + alBufferData(uiBuffers[i], AL_FORMAT_STEREO16, realtimeBuffer, OAL_MAX_SAMPLES * 4, ulFrequency); alSourceQueueBuffers(uiSource, OAL_NUM_BUFFERS, uiBuffers); alSourcePlay(uiSource); @@ -152,42 +159,68 @@ void OpenALStream::SoundLoop() ALint iBuffersProcessed = 0; ALuint uiBufferTemp[OAL_NUM_BUFFERS] = {0}; + soundTouch.setChannels(2); + soundTouch.setSampleRate(ulFrequency); + soundTouch.setSetting(SETTING_USE_QUICKSEEK, 0); + soundTouch.setSetting(SETTING_USE_AA_FILTER, 0); + soundTouch.setSetting(SETTING_SEQUENCE_MS, 1); + soundTouch.setSetting(SETTING_SEEKWINDOW_MS, 28); + soundTouch.setSetting(SETTING_OVERLAP_MS, 12); + while (!threadData) { + // num_samples_to_render in this update - depends on SystemTimers::AUDIO_DMA_PERIOD. + const u32 stereo_16_bit_size = 4; + const u32 dma_length = 32; + const u64 ais_samples_per_second = 48000 * stereo_16_bit_size; + u64 audio_dma_period = SystemTimers::GetTicksPerSecond() / (AudioInterface::GetAIDSampleRate() * stereo_16_bit_size / dma_length); + u64 num_samples_to_render = (audio_dma_period * ais_samples_per_second) / SystemTimers::GetTicksPerSecond(); + + unsigned int numSamples = (unsigned int)num_samples_to_render; + + numSamples = (numSamples > OAL_MAX_SAMPLES) ? OAL_MAX_SAMPLES : numSamples; + numSamples = m_mixer->Mix(realtimeBuffer, numSamples); + soundTouch.putSamples(realtimeBuffer, numSamples); + if (iBuffersProcessed == iBuffersFilled) { alGetSourcei(uiSource, AL_BUFFERS_PROCESSED, &iBuffersProcessed); iBuffersFilled = 0; } - // num_samples_to_render in this update - depends on SystemTimers::AUDIO_DMA_PERIOD. - const u32 stereo_16_bit_size = 4; - const u32 dma_length = 32; - const u64 audio_dma_period = SystemTimers::GetTicksPerSecond() / (AudioInterface::GetAIDSampleRate() * stereo_16_bit_size / dma_length); - const u64 ais_samples_per_second = 48000 * stereo_16_bit_size; - const u64 num_samples_to_render = (audio_dma_period * ais_samples_per_second) / SystemTimers::GetTicksPerSecond(); - - unsigned int numSamples = (unsigned int)num_samples_to_render; - if (iBuffersProcessed) { - numSamples = (numSamples > OAL_MAX_SAMPLES) ? OAL_MAX_SAMPLES : numSamples; - // Remove the Buffer from the Queue. (uiBuffer contains the Buffer ID for the unqueued Buffer) - if (iBuffersFilled == 0) - alSourceUnqueueBuffers(uiSource, iBuffersProcessed, uiBufferTemp); + float rate = m_mixer->GetCurrentSpeed(); + if (rate <= 0) + { + Core::RequestRefreshInfo(); + rate = m_mixer->GetCurrentSpeed(); + } + if (rate > 0) + { + // Adjust SETTING_SEQUENCE_MS to balance between lag vs hollow audio + soundTouch.setSetting(SETTING_SEQUENCE_MS, (int)pow(1 / rate, 2)); + soundTouch.setTempo(rate); + } + unsigned int nSamples = soundTouch.receiveSamples(sampleBuffer, OAL_MAX_SAMPLES * 2 * OAL_NUM_BUFFERS); + if (nSamples > 0) + { + // Remove the Buffer from the Queue. (uiBuffer contains the Buffer ID for the unqueued Buffer) + if (iBuffersFilled == 0) + alSourceUnqueueBuffers(uiSource, iBuffersProcessed, uiBufferTemp); + alBufferData(uiBufferTemp[iBuffersFilled], AL_FORMAT_STEREO16, sampleBuffer, nSamples * 4, ulFrequency); + alSourceQueueBuffers(uiSource, 1, &uiBufferTemp[iBuffersFilled]); + iBuffersFilled++; - m_mixer->Mix(realtimeBuffer, numSamples); - alBufferData(uiBufferTemp[iBuffersFilled], AL_FORMAT_STEREO16, realtimeBuffer, numSamples * 4, ulFrequency); - alSourceQueueBuffers(uiSource, 1, &uiBufferTemp[iBuffersFilled]); - iBuffersFilled++; - - if (iBuffersFilled == OAL_NUM_BUFFERS) - alSourcePlay(uiSource); + if (iBuffersFilled == OAL_NUM_BUFFERS) + alSourcePlay(uiSource); + } } else { soundSyncEvent.Wait(); } + mainSyncEvent.Set(); } } diff --git a/Source/Core/AudioCommon/Src/OpenALStream.h b/Source/Core/AudioCommon/Src/OpenALStream.h index a9c1f51399..c6dfe519ae 100644 --- a/Source/Core/AudioCommon/Src/OpenALStream.h +++ b/Source/Core/AudioCommon/Src/OpenALStream.h @@ -34,10 +34,16 @@ #include #endif +#include "../../Core/Src/Core.h" +#include "../../Core/Src/HW/SystemTimers.h" +#include "../../Core/Src/HW/AudioInterface.h" +#include "../../../../Externals/SoundTouch/STTypes.h" +#include "../../../../Externals/SoundTouch/SoundTouch.h" + // 16 bit Stereo #define SFX_MAX_SOURCE 1 #define OAL_NUM_BUFFERS 16 -#define OAL_MAX_SAMPLES 512 // AyuanX: Don't make it too large, as larger buffer means longer delay +#define OAL_MAX_SAMPLES 512 #endif class OpenALStream: public SoundStream @@ -63,8 +69,10 @@ public: private: std::thread thread; Common::Event soundSyncEvent; - + Common::Event mainSyncEvent; + short realtimeBuffer[OAL_MAX_SAMPLES * 2]; + soundtouch::SAMPLETYPE sampleBuffer[OAL_MAX_SAMPLES * 2 * OAL_NUM_BUFFERS]; ALuint uiBuffers[OAL_NUM_BUFFERS]; ALuint uiSource; ALfloat fVolume; diff --git a/Source/Core/Core/Src/Core.cpp b/Source/Core/Core/Src/Core.cpp index e92b9088bc..c06755a90b 100644 --- a/Source/Core/Core/Src/Core.cpp +++ b/Source/Core/Core/Src/Core.cpp @@ -683,7 +683,14 @@ void VideoThrottle() SMessage; // Show message - g_video_backend->UpdateFPSDisplay(SMessage.c_str()); + g_video_backend->UpdateFPSDisplay(SMessage.c_str()); + + // Update the audio timestretcher with the current speed + if (soundStream) + { + CMixer* pMixer = soundStream->GetMixer(); + pMixer->UpdateSpeed((float)Speed / 100); + } if (_CoreParameter.bRenderToMain && SConfig::GetInstance().m_InterfaceStatusbar) { diff --git a/Source/Dolphin_2010.sln b/Source/Dolphin_2010.sln index 041f6c95bb..a65ba2e894 100644 --- a/Source/Dolphin_2010.sln +++ b/Source/Dolphin_2010.sln @@ -108,6 +108,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "png", "..\Externals\libpng\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SCMRevGen", "Core\Common\SVNRevGen.vcxproj", "{69F00340-5C3D-449F-9A80-958435C6CF06}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SoundTouch", "..\Externals\SoundTouch\SoundTouch.vcxproj", "{68A5DD20-7057-448B-8FE0-B6AC8D205509}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -382,6 +384,18 @@ Global {69F00340-5C3D-449F-9A80-958435C6CF06}.Release|Win32.Build.0 = Release|x64 {69F00340-5C3D-449F-9A80-958435C6CF06}.Release|x64.ActiveCfg = Release|x64 {69F00340-5C3D-449F-9A80-958435C6CF06}.Release|x64.Build.0 = Release|x64 + {68A5DD20-7057-448B-8FE0-B6AC8D205509}.Debug|Win32.ActiveCfg = Debug|Win32 + {68A5DD20-7057-448B-8FE0-B6AC8D205509}.Debug|Win32.Build.0 = Debug|Win32 + {68A5DD20-7057-448B-8FE0-B6AC8D205509}.Debug|x64.ActiveCfg = Debug|x64 + {68A5DD20-7057-448B-8FE0-B6AC8D205509}.Debug|x64.Build.0 = Debug|x64 + {68A5DD20-7057-448B-8FE0-B6AC8D205509}.DebugFast|Win32.ActiveCfg = Debug|Win32 + {68A5DD20-7057-448B-8FE0-B6AC8D205509}.DebugFast|Win32.Build.0 = Debug|Win32 + {68A5DD20-7057-448B-8FE0-B6AC8D205509}.DebugFast|x64.ActiveCfg = Release|x64 + {68A5DD20-7057-448B-8FE0-B6AC8D205509}.DebugFast|x64.Build.0 = Release|x64 + {68A5DD20-7057-448B-8FE0-B6AC8D205509}.Release|Win32.ActiveCfg = Release|Win32 + {68A5DD20-7057-448B-8FE0-B6AC8D205509}.Release|Win32.Build.0 = Release|Win32 + {68A5DD20-7057-448B-8FE0-B6AC8D205509}.Release|x64.ActiveCfg = Release|x64 + {68A5DD20-7057-448B-8FE0-B6AC8D205509}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE