diff --git a/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp b/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp new file mode 100644 index 0000000000..ec91ac15fd --- /dev/null +++ b/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp @@ -0,0 +1,63 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE 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 General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + + +struct CatmullRomAlgorithm +{ + static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept + { + const float y0 = inputs[3]; + const float y1 = inputs[2]; + const float y2 = inputs[1]; + const float y3 = inputs[0]; + + const float halfY0 = 0.5f * y0; + const float halfY3 = 0.5f * y3; + + return y1 + offset * ((0.5f * y2 - halfY0) + + (offset * (((y0 + 2.0f * y2) - (halfY3 + 2.5f * y1)) + + (offset * ((halfY3 + 1.5f * y1) - (halfY0 + 1.5f * y2)))))); + } +}; + +CatmullRomInterpolator::CatmullRomInterpolator() noexcept { reset(); } +CatmullRomInterpolator::~CatmullRomInterpolator() noexcept {} + +void CatmullRomInterpolator::reset() noexcept +{ + subSamplePos = 1.0; + + for (int i = 0; i < numElementsInArray (lastInputSamples); ++i) + lastInputSamples[i] = 0; +} + +int CatmullRomInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept +{ + return interpolate (lastInputSamples, subSamplePos, actualRatio, in, out, numOut); +} + +int CatmullRomInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept +{ + return interpolateAdding (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain); +} diff --git a/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h b/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h new file mode 100644 index 0000000000..466c2278d0 --- /dev/null +++ b/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h @@ -0,0 +1,89 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE 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 General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + + +/** + Interpolator for resampling a stream of floats using Catmull-Rom interpolation. + + Note that the resampler is stateful, so when there's a break in the continuity + of the input stream you're feeding it, you should call reset() before feeding + it any new data. And like with any other stateful filter, if you're resampling + multiple channels, make sure each one uses its own CatmullRomInterpolator + object. + + @see LagrangeInterpolator +*/ +class JUCE_API CatmullRomInterpolator +{ +public: + CatmullRomInterpolator() noexcept; + ~CatmullRomInterpolator() noexcept; + + /** Resets the state of the interpolator. + Call this when there's a break in the continuity of the input data stream. + */ + void reset() noexcept; + + /** Resamples a stream of samples. + + @param speedRatio the number of input samples to use for each output sample + @param inputSamples the source data to read from. This must contain at + least (speedRatio * numOutputSamplesToProduce) samples. + @param outputSamples the buffer to write the results into + @param numOutputSamplesToProduce the number of output samples that should be created + + @returns the actual number of input samples that were used + */ + int process (double speedRatio, + const float* inputSamples, + float* outputSamples, + int numOutputSamplesToProduce) noexcept; + + /** Resamples a stream of samples, adding the results to the output data + with a gain. + + @param speedRatio the number of input samples to use for each output sample + @param inputSamples the source data to read from. This must contain at + least (speedRatio * numOutputSamplesToProduce) samples. + @param outputSamples the buffer to write the results to - the result values will be added + to any pre-existing data in this buffer after being multiplied by + the gain factor + @param numOutputSamplesToProduce the number of output samples that should be created + @param gain a gain factor to multiply the resulting samples by before + adding them to the destination buffer + + @returns the actual number of input samples that were used + */ + int processAdding (double speedRatio, + const float* inputSamples, + float* outputSamples, + int numOutputSamplesToProduce, + float gain) noexcept; + +private: + float lastInputSamples[5]; + double subSamplePos; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CatmullRomInterpolator) +}; diff --git a/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp b/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp index 0c9abd9a85..03e5680254 100644 --- a/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp +++ b/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp @@ -22,185 +22,179 @@ ============================================================================== */ -namespace LagrangeHelpers +namespace { - template - struct ResampleHelper - { - static forcedinline void calc (float& a, float b) { a *= b * (1.0f / k); } - }; - - template<> - struct ResampleHelper <0> - { - static forcedinline void calc (float&, float) {} - }; - - template - static forcedinline float calcCoefficient (float input, const float offset) noexcept + static forcedinline void pushInterpolationSample (float* lastInputSamples, const float newValue) noexcept { - ResampleHelper <0 - k>::calc (input, -2.0f - offset); - ResampleHelper <1 - k>::calc (input, -1.0f - offset); - ResampleHelper <2 - k>::calc (input, 0.0f - offset); - ResampleHelper <3 - k>::calc (input, 1.0f - offset); - ResampleHelper <4 - k>::calc (input, 2.0f - offset); - return input; + lastInputSamples[4] = lastInputSamples[3]; + lastInputSamples[3] = lastInputSamples[2]; + lastInputSamples[2] = lastInputSamples[1]; + lastInputSamples[1] = lastInputSamples[0]; + lastInputSamples[0] = newValue; } - static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept - { - return calcCoefficient<0> (inputs[4], offset) - + calcCoefficient<1> (inputs[3], offset) - + calcCoefficient<2> (inputs[2], offset) - + calcCoefficient<3> (inputs[1], offset) - + calcCoefficient<4> (inputs[0], offset); - } - - static forcedinline void push (float* inputs, const float newValue) noexcept - { - inputs[4] = inputs[3]; - inputs[3] = inputs[2]; - inputs[2] = inputs[1]; - inputs[1] = inputs[0]; - inputs[0] = newValue; - } -} - -//============================================================================== -LagrangeInterpolator::LagrangeInterpolator() { reset(); } -LagrangeInterpolator::~LagrangeInterpolator() {} - -void LagrangeInterpolator::reset() noexcept -{ - subSamplePos = 1.0; - - for (int i = 0; i < numElementsInArray (lastInputSamples); ++i) - lastInputSamples[i] = 0; -} - -int LagrangeInterpolator::process (const double actualRatio, const float* in, - float* out, const int numOut) noexcept -{ - if (actualRatio == 1.0) + static forcedinline void pushInterpolationSamples (float* lastInputSamples, const float* input, int numOut) noexcept { - memcpy (out, in, (size_t) numOut * sizeof (float)); - - if (numOut >= 4) + if (numOut >= 5) { - const float* end = in + numOut; - - for (int i = 0; i < 4; ++i) - lastInputSamples[i] = *--end; + for (int i = 0; i < 5; ++i) + lastInputSamples[i] = input[--numOut]; } else { for (int i = 0; i < numOut; ++i) - LagrangeHelpers::push (lastInputSamples, in[i]); + pushInterpolationSample (lastInputSamples, input[i]); } - - return numOut; } - const float* const originalIn = in; - double pos = subSamplePos; - - if (actualRatio < 1.0) + template + static int interpolate (float* lastInputSamples, double& subSamplePos, const double actualRatio, + const float* in, float* out, const int numOut) noexcept { - for (int i = numOut; --i >= 0;) + if (actualRatio == 1.0) { - if (pos >= 1.0) + memcpy (out, in, (size_t) numOut * sizeof (float)); + pushInterpolationSamples (lastInputSamples, in, numOut); + return numOut; + } + + const float* const originalIn = in; + double pos = subSamplePos; + + if (actualRatio < 1.0) + { + for (int i = numOut; --i >= 0;) { - LagrangeHelpers::push (lastInputSamples, *in++); - pos -= 1.0; + if (pos >= 1.0) + { + pushInterpolationSample (lastInputSamples, *in++); + pos -= 1.0; + } + + *out++ = InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); + pos += actualRatio; } - - *out++ = LagrangeHelpers::valueAtOffset (lastInputSamples, (float) pos); - pos += actualRatio; } - } - else - { - for (int i = numOut; --i >= 0;) + else { - while (pos < actualRatio) + for (int i = numOut; --i >= 0;) { - LagrangeHelpers::push (lastInputSamples, *in++); - pos += 1.0; + while (pos < actualRatio) + { + pushInterpolationSample (lastInputSamples, *in++); + pos += 1.0; + } + + pos -= actualRatio; + *out++ = InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); } - - pos -= actualRatio; - *out++ = LagrangeHelpers::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); } - } - subSamplePos = pos; - return (int) (in - originalIn); -} + subSamplePos = pos; + return (int) (in - originalIn); + } -int LagrangeInterpolator::processAdding (const double actualRatio, const float* in, - float* out, const int numOut, const float gain) noexcept -{ - if (actualRatio == 1.0) + template + static int interpolateAdding (float* lastInputSamples, double& subSamplePos, const double actualRatio, + const float* in, float* out, const int numOut, const float gain) noexcept { - if (gain != 1.0f) - { - for (int i = 0; i < numOut; ++i) - out[i] += in[i] * gain; - } - else + if (actualRatio == 1.0) { - for (int i = 0; i < numOut; ++i) - out[i] += in[i]; + FloatVectorOperations::addWithMultiply (out, in, gain, numOut); + pushInterpolationSamples (lastInputSamples, in, numOut); + return numOut; } - if (numOut >= 4) - { - const float* end = in + numOut; + const float* const originalIn = in; + double pos = subSamplePos; - for (int i = 0; i < 4; ++i) - lastInputSamples[i] = *--end; + if (actualRatio < 1.0) + { + for (int i = numOut; --i >= 0;) + { + if (pos >= 1.0) + { + pushInterpolationSample (lastInputSamples, *in++); + pos -= 1.0; + } + + *out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); + pos += actualRatio; + } } else { - for (int i = 0; i < numOut; ++i) - LagrangeHelpers::push (lastInputSamples, in[i]); + for (int i = numOut; --i >= 0;) + { + while (pos < actualRatio) + { + pushInterpolationSample (lastInputSamples, *in++); + pos += 1.0; + } + + pos -= actualRatio; + *out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); + } } - return numOut; + subSamplePos = pos; + return (int) (in - originalIn); } +} - const float* const originalIn = in; - double pos = subSamplePos; +//============================================================================== +template +struct LagrangeResampleHelper +{ + static forcedinline void calc (float& a, float b) noexcept { a *= b * (1.0f / k); } +}; - if (actualRatio < 1.0) - { - for (int i = numOut; --i >= 0;) - { - if (pos >= 1.0) - { - LagrangeHelpers::push (lastInputSamples, *in++); - pos -= 1.0; - } +template<> +struct LagrangeResampleHelper<0> +{ + static forcedinline void calc (float&, float) noexcept {} +}; - *out++ += gain * LagrangeHelpers::valueAtOffset (lastInputSamples, (float) pos); - pos += actualRatio; - } - } - else +struct LagrangeAlgorithm +{ + static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept { - for (int i = numOut; --i >= 0;) - { - while (pos < actualRatio) - { - LagrangeHelpers::push (lastInputSamples, *in++); - pos += 1.0; - } + return calcCoefficient<0> (inputs[4], offset) + + calcCoefficient<1> (inputs[3], offset) + + calcCoefficient<2> (inputs[2], offset) + + calcCoefficient<3> (inputs[1], offset) + + calcCoefficient<4> (inputs[0], offset); + } - pos -= actualRatio; - *out++ += gain * LagrangeHelpers::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); - } + template + static forcedinline float calcCoefficient (float input, const float offset) noexcept + { + LagrangeResampleHelper<0 - k>::calc (input, -2.0f - offset); + LagrangeResampleHelper<1 - k>::calc (input, -1.0f - offset); + LagrangeResampleHelper<2 - k>::calc (input, 0.0f - offset); + LagrangeResampleHelper<3 - k>::calc (input, 1.0f - offset); + LagrangeResampleHelper<4 - k>::calc (input, 2.0f - offset); + return input; } +}; + +LagrangeInterpolator::LagrangeInterpolator() noexcept { reset(); } +LagrangeInterpolator::~LagrangeInterpolator() noexcept {} - subSamplePos = pos; - return (int) (in - originalIn); +void LagrangeInterpolator::reset() noexcept +{ + subSamplePos = 1.0; + + for (int i = 0; i < numElementsInArray (lastInputSamples); ++i) + lastInputSamples[i] = 0; +} + +int LagrangeInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept +{ + return interpolate (lastInputSamples, subSamplePos, actualRatio, in, out, numOut); +} + +int LagrangeInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept +{ + return interpolateAdding (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain); } diff --git a/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h b/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h index c3f7fd2985..d3231556e5 100644 --- a/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h +++ b/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h @@ -22,11 +22,7 @@ ============================================================================== */ -#ifndef JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED -#define JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED - -//============================================================================== /** Interpolator for resampling a stream of floats using 4-point lagrange interpolation. @@ -35,12 +31,14 @@ it any new data. And like with any other stateful filter, if you're resampling multiple channels, make sure each one uses its own LagrangeInterpolator object. + + @see CatmullRomInterpolator */ class JUCE_API LagrangeInterpolator { public: - LagrangeInterpolator(); - ~LagrangeInterpolator(); + LagrangeInterpolator() noexcept; + ~LagrangeInterpolator() noexcept; /** Resets the state of the interpolator. Call this when there's a break in the continuity of the input data stream. @@ -89,6 +87,3 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LagrangeInterpolator) }; - - -#endif // JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED diff --git a/modules/juce_audio_basics/juce_audio_basics.cpp b/modules/juce_audio_basics/juce_audio_basics.cpp index e0064b998b..bfef0bd022 100644 --- a/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/modules/juce_audio_basics/juce_audio_basics.cpp @@ -78,6 +78,7 @@ namespace juce #include "buffers/juce_FloatVectorOperations.cpp" #include "effects/juce_IIRFilter.cpp" #include "effects/juce_LagrangeInterpolator.cpp" +#include "effects/juce_CatmullRomInterpolator.cpp" #include "effects/juce_FFT.cpp" #include "midi/juce_MidiBuffer.cpp" #include "midi/juce_MidiFile.cpp" diff --git a/modules/juce_audio_basics/juce_audio_basics.h b/modules/juce_audio_basics/juce_audio_basics.h index de85e113b5..1d797ed64b 100644 --- a/modules/juce_audio_basics/juce_audio_basics.h +++ b/modules/juce_audio_basics/juce_audio_basics.h @@ -40,6 +40,7 @@ namespace juce #include "effects/juce_Decibels.h" #include "effects/juce_IIRFilter.h" #include "effects/juce_LagrangeInterpolator.h" +#include "effects/juce_CatmullRomInterpolator.h" #include "effects/juce_FFT.h" #include "effects/juce_LinearSmoothedValue.h" #include "effects/juce_Reverb.h"