/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { #if JUCE_UNIT_TESTS class InterpolatorTests : public UnitTest { public: InterpolatorTests() : UnitTest ("InterpolatorTests", UnitTestCategories::audio) { } private: template void runInterplatorTests (const String& interpolatorName) { auto createGaussian = [] (std::vector& destination, float scale, float centreInSamples, float width) { for (size_t i = 0; i < destination.size(); ++i) { auto x = (((float) i) - centreInSamples) * width; destination[i] = std::exp (-(x * x)); } FloatVectorOperations::multiply (destination.data(), scale, (int) destination.size()); }; auto findGaussianPeak = [] (const std::vector& input) -> float { auto max = std::max_element (std::begin (input), std::end (input)); auto maxPrev = max - 1; jassert (maxPrev >= std::begin (input)); auto maxNext = max + 1; jassert (maxNext < std::end (input)); auto quadraticMaxLoc = (*maxPrev - *maxNext) / (2.0f * ((*maxNext + *maxPrev) - (2.0f * *max))); return quadraticMaxLoc + (float) std::distance (std::begin (input), max); }; auto expectAllElementsWithin = [this] (const std::vector& v1, const std::vector& v2, float tolerance) { expectEquals ((int) v1.size(), (int) v2.size()); for (size_t i = 0; i < v1.size(); ++i) expectWithinAbsoluteError (v1[i], v2[i], tolerance); }; InterpolatorType interpolator; constexpr size_t inputSize = 1001; static_assert (inputSize > 800 + InterpolatorType::getBaseLatency(), "The test InterpolatorTests input buffer is too small"); std::vector input (inputSize); constexpr auto inputGaussianMidpoint = (float) (inputSize - 1) / 2.0f; constexpr auto inputGaussianValueAtEnds = 0.000001f; const auto inputGaussianWidth = std::sqrt (-std::log (inputGaussianValueAtEnds)) / inputGaussianMidpoint; createGaussian (input, 1.0f, inputGaussianMidpoint, inputGaussianWidth); for (auto speedRatio : { 0.4, 0.8263, 1.0, 1.05, 1.2384, 1.6 }) { const auto expectedGaussianMidpoint = (inputGaussianMidpoint + InterpolatorType::getBaseLatency()) / (float) speedRatio; const auto expectedGaussianWidth = inputGaussianWidth * (float) speedRatio; const auto outputBufferSize = (size_t) std::floor ((float) input.size() / speedRatio); for (int numBlocks : { 1, 5 }) { const auto inputBlockSize = (float) input.size() / (float) numBlocks; const auto outputBlockSize = (int) std::floor (inputBlockSize / speedRatio); std::vector output (outputBufferSize, std::numeric_limits::min()); beginTest (interpolatorName + " process " + String (numBlocks) + " blocks ratio " + String (speedRatio)); interpolator.reset(); { auto* inputPtr = input.data(); auto* outputPtr = output.data(); for (int i = 0; i < numBlocks; ++i) { auto numInputSamplesRead = interpolator.process (speedRatio, inputPtr, outputPtr, outputBlockSize); inputPtr += numInputSamplesRead; outputPtr += outputBlockSize; } } expectWithinAbsoluteError (findGaussianPeak (output), expectedGaussianMidpoint, 0.1f); std::vector expectedOutput (output.size()); createGaussian (expectedOutput, 1.0f, expectedGaussianMidpoint, expectedGaussianWidth); expectAllElementsWithin (output, expectedOutput, 0.02f); beginTest (interpolatorName + " process adding " + String (numBlocks) + " blocks ratio " + String (speedRatio)); interpolator.reset(); constexpr float addingGain = 0.7384f; { auto* inputPtr = input.data(); auto* outputPtr = output.data(); for (int i = 0; i < numBlocks; ++i) { auto numInputSamplesRead = interpolator.processAdding (speedRatio, inputPtr, outputPtr, outputBlockSize, addingGain); inputPtr += numInputSamplesRead; outputPtr += outputBlockSize; } } expectWithinAbsoluteError (findGaussianPeak (output), expectedGaussianMidpoint, 0.1f); std::vector additionalOutput (output.size()); createGaussian (additionalOutput, addingGain, expectedGaussianMidpoint, expectedGaussianWidth); FloatVectorOperations::add (expectedOutput.data(), additionalOutput.data(), (int) additionalOutput.size()); expectAllElementsWithin (output, expectedOutput, 0.02f); } beginTest (interpolatorName + " process wrap 0 ratio " + String (speedRatio)); std::vector doubleLengthOutput (2 * outputBufferSize, std::numeric_limits::min()); interpolator.reset(); interpolator.process (speedRatio, input.data(), doubleLengthOutput.data(), (int) doubleLengthOutput.size(), (int) input.size(), 0); std::vector expectedDoubleLengthOutput (doubleLengthOutput.size()); createGaussian (expectedDoubleLengthOutput, 1.0f, expectedGaussianMidpoint, expectedGaussianWidth); expectAllElementsWithin (doubleLengthOutput, expectedDoubleLengthOutput, 0.02f); beginTest (interpolatorName + " process wrap double ratio " + String (speedRatio)); interpolator.reset(); interpolator.process (speedRatio, input.data(), doubleLengthOutput.data(), (int) doubleLengthOutput.size(), (int) input.size(), (int) input.size()); std::vector secondGaussian (doubleLengthOutput.size()); createGaussian (secondGaussian, 1.0f, expectedGaussianMidpoint + (float) outputBufferSize, expectedGaussianWidth); FloatVectorOperations::add (expectedDoubleLengthOutput.data(), secondGaussian.data(), (int) expectedDoubleLengthOutput.size()); expectAllElementsWithin (doubleLengthOutput, expectedDoubleLengthOutput, 0.02f); } } public: void runTest() override { runInterplatorTests ("WindowedSincInterpolator"); runInterplatorTests ("LagrangeInterpolator"); runInterplatorTests ("CatmullRomInterpolator"); runInterplatorTests ("LinearInterpolator"); } }; static InterpolatorTests interpolatorTests; #endif } // namespace juce