From ff1cbb6c4949ec9074c56513fc10ce7f9fd9dfd1 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 15 May 2020 17:58:28 +0200 Subject: [PATCH 01/19] Resample one-coeff only --- benchmarks/BM_interpolationCast.cpp | 14 ++++++-------- src/sfizz/MathHelpers.h | 4 ++-- src/sfizz/SIMDDummy.cpp | 4 ++-- src/sfizz/SIMDHelpers.h | 21 +++++++++------------ src/sfizz/SIMDNEON.cpp | 4 ++-- src/sfizz/SIMDSSE.cpp | 26 +++++++++++--------------- src/sfizz/Voice.cpp | 25 +++++++++++-------------- 7 files changed, 43 insertions(+), 55 deletions(-) diff --git a/benchmarks/BM_interpolationCast.cpp b/benchmarks/BM_interpolationCast.cpp index ea12845a..b3743530 100644 --- a/benchmarks/BM_interpolationCast.cpp +++ b/benchmarks/BM_interpolationCast.cpp @@ -22,8 +22,7 @@ public: std::mt19937 gen { rd() }; std::uniform_real_distribution dist { 0, maxJump }; jumps = std::vector(state.range(0)); - leftCoeffs = std::vector(state.range(0)); - rightCoeffs = std::vector(state.range(0)); + coeffs = std::vector(state.range(0)); floatJumps = std::vector(state.range(0)); absl::c_generate(floatJumps, [&]() { return dist(gen); }); } @@ -33,8 +32,7 @@ public: } std::vector jumps; - std::vector leftCoeffs; - std::vector rightCoeffs; + std::vector coeffs; std::vector floatJumps; }; @@ -42,28 +40,28 @@ public: BENCHMARK_DEFINE_F(InterpolationCast, Scalar)(benchmark::State& state) { for (auto _ : state) { - sfz::sfzInterpolationCast(floatJumps, absl::MakeSpan(jumps), absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs)); + sfz::sfzInterpolationCast(floatJumps, absl::MakeSpan(jumps), absl::MakeSpan(coeffs)); } } BENCHMARK_DEFINE_F(InterpolationCast, SIMD)(benchmark::State& state) { for (auto _ : state) { - sfz::sfzInterpolationCast(floatJumps, absl::MakeSpan(jumps), absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs)); + sfz::sfzInterpolationCast(floatJumps, absl::MakeSpan(jumps), absl::MakeSpan(coeffs)); } } BENCHMARK_DEFINE_F(InterpolationCast, Scalar_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::sfzInterpolationCast(absl::MakeSpan(floatJumps).subspan(1), absl::MakeSpan(jumps).subspan(3), absl::MakeSpan(leftCoeffs).subspan(2), absl::MakeSpan(rightCoeffs).subspan(1)); + sfz::sfzInterpolationCast(absl::MakeSpan(floatJumps).subspan(1), absl::MakeSpan(jumps).subspan(3), absl::MakeSpan(coeffs).subspan(1)); } } BENCHMARK_DEFINE_F(InterpolationCast, SIMD_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::sfzInterpolationCast(absl::MakeSpan(floatJumps).subspan(1), absl::MakeSpan(jumps).subspan(3), absl::MakeSpan(leftCoeffs).subspan(2), absl::MakeSpan(rightCoeffs).subspan(1)); + sfz::sfzInterpolationCast(absl::MakeSpan(floatJumps).subspan(1), absl::MakeSpan(jumps).subspan(3), absl::MakeSpan(coeffs).subspan(1)); } } diff --git a/src/sfizz/MathHelpers.h b/src/sfizz/MathHelpers.h index d8976c88..284d3580 100644 --- a/src/sfizz/MathHelpers.h +++ b/src/sfizz/MathHelpers.h @@ -156,9 +156,9 @@ inline CXX14_CONSTEXPR void incrementAll(T& first, Args&... rest) } template -constexpr ValueType linearInterpolation(ValueType left, ValueType right, ValueType leftCoeff, ValueType rightCoeff) +constexpr ValueType linearInterpolation(ValueType left, ValueType right, ValueType coeff) { - return left * leftCoeff + right * rightCoeff; + return left * (static_cast(1.0) - coeff) + right * coeff; } template diff --git a/src/sfizz/SIMDDummy.cpp b/src/sfizz/SIMDDummy.cpp index 2a0ca34d..718d8a1c 100644 --- a/src/sfizz/SIMDDummy.cpp +++ b/src/sfizz/SIMDDummy.cpp @@ -163,9 +163,9 @@ void sfz::cumsum(absl::Span input, absl::Span o } template<> -void sfz::sfzInterpolationCast(absl::Span floatJumps, absl::Span jumps, absl::Span leftCoeffs, absl::Span rightCoeffs) noexcept +void sfz::sfzInterpolationCast(absl::Span floatJumps, absl::Span jumps, absl::Span coeffs) noexcept { - sfzInterpolationCast(floatJumps, jumps, leftCoeffs, rightCoeffs); + sfzInterpolationCast(floatJumps, jumps, coeffs); } template <> diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index 7773b794..0600ddd6 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -939,12 +939,11 @@ void cumsum(absl::Span input, absl::Span output namespace _internals { template - void snippetSFZInterpolationCast(const T*& floatJump, int*& jump, T*& leftCoeff, T*& rightCoeff) + void snippetSFZInterpolationCast(const T*& floatJump, int*& jump, T*& coeff) { *jump = static_cast(*floatJump); - *rightCoeff = *floatJump - static_cast(*jump); - *leftCoeff = static_cast(1.0) - *rightCoeff; - incrementAll(floatJump, leftCoeff, rightCoeff, jump); + *coeff = *floatJump - static_cast(*jump); + incrementAll(floatJump, coeff, jump); } } @@ -960,24 +959,22 @@ namespace _internals { * @param rightCoeffs the right interpolation coefficients */ template -void sfzInterpolationCast(absl::Span floatJumps, absl::Span jumps, absl::Span leftCoeffs, absl::Span rightCoeffs) noexcept +void sfzInterpolationCast(absl::Span floatJumps, absl::Span jumps, absl::Span coeffs) noexcept { CHECK(jumps.size() >= floatJumps.size()); - CHECK(jumps.size() == leftCoeffs.size()); - CHECK(jumps.size() == rightCoeffs.size()); + CHECK(jumps.size() == coeffs.size()); auto floatJump = floatJumps.data(); auto jump = jumps.data(); - auto leftCoeff = leftCoeffs.data(); - auto rightCoeff = rightCoeffs.data(); - const auto sentinel = floatJump + min(floatJumps.size(), jumps.size(), leftCoeffs.size(), rightCoeffs.size()); + auto coeff = coeffs.data(); + const auto sentinel = floatJump + min(floatJumps.size(), jumps.size(), coeffs.size()); while (floatJump < sentinel) - _internals::snippetSFZInterpolationCast(floatJump, jump, leftCoeff, rightCoeff); + _internals::snippetSFZInterpolationCast(floatJump, jump, coeff); } template <> -void sfzInterpolationCast(absl::Span floatJumps, absl::Span jumps, absl::Span leftCoeffs, absl::Span rightCoeffs) noexcept; +void sfzInterpolationCast(absl::Span floatJumps, absl::Span jumps, absl::Span coeffs) noexcept; namespace _internals { template diff --git a/src/sfizz/SIMDNEON.cpp b/src/sfizz/SIMDNEON.cpp index 6a6dc8d1..e229d954 100644 --- a/src/sfizz/SIMDNEON.cpp +++ b/src/sfizz/SIMDNEON.cpp @@ -227,9 +227,9 @@ void sfz::cumsum(absl::Span input, absl::Span o } template<> -void sfz::sfzInterpolationCast(absl::Span floatJumps, absl::Span jumps, absl::Span leftCoeffs, absl::Span rightCoeffs) noexcept +void sfz::sfzInterpolationCast(absl::Span floatJumps, absl::Span jumps, absl::Span coeffs) noexcept { - sfzInterpolationCast(floatJumps, jumps, leftCoeffs, rightCoeffs); + sfzInterpolationCast(floatJumps, jumps, coeffs); } template <> diff --git a/src/sfizz/SIMDSSE.cpp b/src/sfizz/SIMDSSE.cpp index 062d5476..ae0cd07e 100644 --- a/src/sfizz/SIMDSSE.cpp +++ b/src/sfizz/SIMDSSE.cpp @@ -786,37 +786,33 @@ void sfz::cumsum(absl::Span input, absl::Span o } template <> -void sfz::sfzInterpolationCast(absl::Span floatJumps, absl::Span jumps, absl::Span leftCoeffs, absl::Span rightCoeffs) noexcept +void sfz::sfzInterpolationCast(absl::Span floatJumps, absl::Span jumps, absl::Span coeffs) noexcept { - sfz::sfzInterpolationCast(floatJumps, jumps, leftCoeffs, rightCoeffs); + sfz::sfzInterpolationCast(floatJumps, jumps, coeffs); // CHECK(jumps.size() >= floatJumps.size()); - // CHECK(jumps.size() == leftCoeffs.size()); - // CHECK(jumps.size() == rightCoeffs.size()); + // CHECK(jumps.size() == coeffs.size()); // auto floatJump = floatJumps.data(); // auto jump = jumps.data(); - // auto leftCoeff = leftCoeffs.data(); - // auto rightCoeff = rightCoeffs.data(); - // const auto sentinel = floatJump + min(floatJumps.size(), jumps.size(), leftCoeffs.size(), rightCoeffs.size()); + // auto coeff = coeffs.data(); + // const auto sentinel = floatJump + min(floatJumps.size(), jumps.size(), coeffs.size()); // const auto lastAligned = prevAligned(sentinel); - // while (unaligned(floatJump, reinterpret_cast(jump), leftCoeff, rightCoeff) && floatJump < lastAligned) - // _internals::snippetSFZInterpolationCast(floatJump, jump, leftCoeff, rightCoeff); + // while (unaligned(floatJump, reinterpret_cast(jump), coeff) && floatJump < lastAligned) + // _internals::snippetSFZInterpolationCast(floatJump, jump, coeff); // while (floatJump < lastAligned) { // auto mmFloatJumps = _mm_load_ps(floatJump); // auto mmIndices = _mm_cvtps_epi32(_mm_sub_ps(mmFloatJumps, _mm_set_ps1(0.4999999552965164184570312f))); // _mm_store_si128(reinterpret_cast<__m128i*>(jump), mmIndices); - // auto mmRight = _mm_sub_ps(mmFloatJumps, _mm_cvtepi32_ps(mmIndices)); - // auto mmLeft = _mm_sub_ps(_mm_set_ps1(1.0f), mmRight); - // _mm_store_ps(leftCoeff, mmLeft); - // _mm_store_ps(rightCoeff, mmRight); - // incrementAll(floatJump, jump, leftCoeff, rightCoeff); + // auto mmCoeff = _mm_sub_ps(mmFloatJumps, _mm_cvtepi32_ps(mmIndices)); + // _mm_store_ps(coeff, mmCoeff); + // incrementAll(floatJump, jump, coeff); // } // while(floatJump < sentinel) - // _internals::snippetSFZInterpolationCast(floatJump, jump, leftCoeff, rightCoeff); + // _internals::snippetSFZInterpolationCast(floatJump, jump, coeff); } template <> diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 3c4bc442..b35f3e43 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -443,10 +443,9 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept auto jumps = resources.bufferPool.getBuffer(numSamples); auto bends = resources.bufferPool.getBuffer(numSamples); - auto leftCoeffs = resources.bufferPool.getBuffer(numSamples); - auto rightCoeffs = resources.bufferPool.getBuffer(numSamples); + auto coeffs = resources.bufferPool.getBuffer(numSamples); auto indices = resources.bufferPool.getIndexBuffer(numSamples); - if (!jumps || !bends || !indices || !rightCoeffs || !leftCoeffs) + if (!jumps || !bends || !indices || !coeffs) return; fill(*jumps, pitchRatio * speedRatio); @@ -470,7 +469,7 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept jumps->front() += floatPositionOffset; cumsum(*jumps, *jumps); - sfzInterpolationCast(*jumps, *indices, *leftCoeffs, *rightCoeffs); + sfzInterpolationCast(*jumps, *indices, *coeffs); add(sourcePosition, *indices); if (region->shouldLoop() && region->loopEnd(currentPromise->oversamplingFactor) <= source.getNumFrames()) { @@ -500,35 +499,33 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept #endif egEnvelope.startRelease(i, true); fill(indices->subspan(i), sampleEnd); - fill(leftCoeffs->subspan(i), 0.0f); - fill(rightCoeffs->subspan(i), 1.0f); + fill(coeffs->subspan(i), 1.0f); break; } } } auto ind = indices->data(); - auto leftCoeff = leftCoeffs->data(); - auto rightCoeff = rightCoeffs->data(); + auto coeff = coeffs->data(); auto leftSource = source.getConstSpan(0); auto left = buffer.getChannel(0); if (source.getNumChannels() == 1) { while (ind < indices->end()) { - *left = linearInterpolation(leftSource[*ind], leftSource[*ind + 1], *leftCoeff, *rightCoeff); - incrementAll(ind, left, leftCoeff, rightCoeff); + *left = linearInterpolation(leftSource[*ind], leftSource[*ind + 1], *coeff); + incrementAll(ind, left, coeff); } } else { auto right = buffer.getChannel(1); auto rightSource = source.getConstSpan(1); while (ind < indices->end()) { - *left = linearInterpolation(leftSource[*ind], leftSource[*ind + 1], *leftCoeff, *rightCoeff); - *right = linearInterpolation(rightSource[*ind], rightSource[*ind + 1], *leftCoeff, *rightCoeff); - incrementAll(ind, left, right, leftCoeff, rightCoeff); + *left = linearInterpolation(leftSource[*ind], leftSource[*ind + 1], *coeff); + *right = linearInterpolation(rightSource[*ind], rightSource[*ind + 1], *coeff); + incrementAll(ind, left, right, coeff); } } sourcePosition = indices->back(); - floatPositionOffset = rightCoeffs->back(); + floatPositionOffset = coeffs->back(); #if 0 ASSERT(!hasNanInf(buffer.getConstSpan(0))); From 626e92f1a33f953143295924be73803ca9bf336c Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 16 May 2020 00:07:27 +0200 Subject: [PATCH 02/19] Add polynomial interpolation --- src/sfizz/Config.h | 1 + src/sfizz/FilePool.cpp | 10 ++- src/sfizz/FilePool.h | 3 +- src/sfizz/MathHelpers.h | 164 +++++++++++++++++++++++++++++++++++++- src/sfizz/Oversampler.cpp | 2 +- src/sfizz/Voice.cpp | 8 +- 6 files changed, 176 insertions(+), 12 deletions(-) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index b8f9d4ff..1c135740 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -59,6 +59,7 @@ namespace config { constexpr int maxCurves { 256 }; constexpr int chunkSize { 1024 }; constexpr int filtersInPool { maxVoices * 2 }; + constexpr int excessFileFrames { 8 }; /** * @brief The threshold for age stealing. * In percentage of the voice's max age. diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 5e61b2b7..ad987477 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -42,7 +42,8 @@ template void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer& output, uint32_t numFrames, bool reverse) { output.reset(); - output.resize(numFrames); + output.resize(numFrames + sfz::config::excessFileFrames); + output.clear(); if (reverse) sndFile.seek(-static_cast(numFrames), SEEK_END); @@ -57,13 +58,13 @@ void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer& output, uint32_t output.addChannel(); sfz::Buffer tempReadBuffer { 2 * numFrames }; sndFile.readf(tempReadBuffer.data(), numFrames); - sfz::readInterleaved(tempReadBuffer, output.getSpan(0), output.getSpan(1)); + sfz::readInterleaved(tempReadBuffer, output.getSpan(0).first(numFrames), output.getSpan(1).first(numFrames)); } if (reverse) { for (unsigned c = 0; c < channels; ++c) { // TODO: consider optimizing with SIMD - absl::Span channel = output.getSpan(c); + absl::Span channel = output.getSpan(c).first(numFrames); std::reverse(channel.begin(), channel.end()); } } @@ -97,7 +98,8 @@ void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversamplin auto baseBuffer = readFromFile(sndFile, numFrames, sfz::Oversampling::x1, reverse); output.reset(); output.addChannels(baseBuffer->getNumChannels()); - output.resize(numFrames * static_cast(factor)); + output.resize(numFrames * static_cast(factor) + sfz::config::excessFileFrames); + output.clear(); sfz::Oversampler oversampler { factor }; oversampler.stream(*baseBuffer, output, filledFrames); } diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index 7f5e15d9..b60e5cc2 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -65,7 +65,8 @@ struct FilePromise AudioSpan getData() { if (dataStatus == DataStatus::Ready) - return AudioSpan(fileData); + return AudioSpan(fileData) + .first(fileData.getNumFrames() - sfz::config::excessFileFrames); else if (availableFrames > preloadedData->getNumFrames()) return AudioSpan(fileData).first(availableFrames); else diff --git a/src/sfizz/MathHelpers.h b/src/sfizz/MathHelpers.h index 284d3580..ed7679cb 100644 --- a/src/sfizz/MathHelpers.h +++ b/src/sfizz/MathHelpers.h @@ -11,11 +11,15 @@ #pragma once #include "Config.h" #include "Macros.h" +#include "SIMDConfig.h" #include "absl/types/span.h" #include #include #include #include +#if SFIZZ_HAVE_SSE +#include +#endif template constexpr T max(T op1, T op2) @@ -156,9 +160,165 @@ inline CXX14_CONSTEXPR void incrementAll(T& first, Args&... rest) } template -constexpr ValueType linearInterpolation(ValueType left, ValueType right, ValueType coeff) +constexpr ValueType linearInterpolation(const ValueType values[2], ValueType coeff) { - return left * (static_cast(1.0) - coeff) + right * coeff; + return values[0] * (static_cast(1.0) - coeff) + values[1] * coeff; +} + +/** + * @brief Compute the 3rd-order Hermite interpolation polynomial. + * + * @tparam R + * @param x + * @return R + */ +template +R hermite3(R x) +{ + x = std::abs(x); + R x2 = x * x; + R x3 = x2 * x; + R y = 0; + R q = R(5./2.) * x2; // a reoccurring term + R p1 = R(1) - q + R(3./2.) * x3; + R p2 = R(2) - R(4) * x + q - R(1./2.) * x3; + y = (x < R(2)) ? p2 : y; + y = (x < R(1)) ? p1 : y; + return y; +} + +#if SFIZZ_HAVE_SSE +/** + * @brief Compute 4 parallel elements of the 3rd-order Hermite interpolation polynomial. + * + * @param x + * @return __m128 + */ +inline __m128 hermite3x4(__m128 x) +{ + x = _mm_andnot_ps(_mm_set1_ps(-0.0f), x); + __m128 x2 = _mm_mul_ps(x, x); + __m128 x3 = _mm_mul_ps(x2, x); + __m128 y = _mm_set1_ps(0.0f); + __m128 q = _mm_mul_ps(_mm_set1_ps(5./2.), x2); + __m128 p1 = _mm_add_ps(_mm_sub_ps(_mm_set1_ps(1), q), _mm_mul_ps(_mm_set1_ps(3./2.), x3)); + __m128 p2 = _mm_sub_ps(_mm_add_ps(_mm_sub_ps(_mm_set1_ps(2), _mm_mul_ps(_mm_set1_ps(4), x)), q), _mm_mul_ps(_mm_set1_ps(1./2.), x3)); + __m128 m2 = _mm_cmple_ps(x, _mm_set1_ps(2)); + y = _mm_or_ps(_mm_and_ps(m2, p2), _mm_andnot_ps(m2, y)); + __m128 m1 = _mm_cmple_ps(x, _mm_set1_ps(1)); + y = _mm_or_ps(_mm_and_ps(m1, p1), _mm_andnot_ps(m1, y)); + return y; +} +#endif + +template +ValueType hermite3Interpolation(const ValueType values[4], ValueType coeff); + +#if SFIZZ_HAVE_SSE +template <> +inline float hermite3Interpolation(const float values[4], float coeff) +{ + __m128 x = _mm_sub_ps(_mm_setr_ps(-1, 0, 1, 2), _mm_set1_ps(coeff)); + __m128 h = hermite3x4(x); + __m128 y = _mm_mul_ps(h, _mm_loadu_ps(values)); + // sum 4 to 1 + __m128 xmm0 = y; + __m128 xmm1 = _mm_shuffle_ps(xmm0, xmm0, 0xe5); + __m128 xmm2 = _mm_movehl_ps(xmm0, xmm0); + xmm1 = _mm_add_ss(xmm1, xmm0); + xmm0 = _mm_shuffle_ps(xmm0, xmm0, 0xe7); + xmm2 = _mm_add_ss(xmm2, xmm1); + xmm0 = _mm_add_ss(xmm0, xmm2); + return _mm_cvtss_f32(xmm0); +} +#endif + +template +ValueType hermite3Interpolation(const ValueType values[4], ValueType coeff) +{ + ValueType y = 0; + for (int i = 0; i < 4; ++i) { + ValueType h = hermite3(i - 1 - coeff); + y += h * values[i]; + } + return y; +} + +/** + * @brief Compute the 3rd-order B-spline interpolation polynomial. + * + * @tparam R + * @param x + * @return R + */ +template +R bspline3(R x) +{ + x = std::abs(x); + R x2 = x * x; + R x3 = x2 * x; + R y = 0; + R p1 = R(2./3.) - x2 + R(1./2.) * x3; + R p2 = R(4./3.) - R(2) * x + x2 - R(1./6.) * x3; + y = (x < R(2)) ? p2 : y; + y = (x < R(1)) ? p1 : y; + return y; +} + +#if SFIZZ_HAVE_SSE +/** + * @brief Compute 4 parallel elements of the 3rd-order B-spline interpolation polynomial. + * + * @param x + * @return __m128 + */ +inline __m128 bspline3x4(__m128 x) +{ + x = _mm_andnot_ps(_mm_set1_ps(-0.0f), x); + __m128 x2 = _mm_mul_ps(x, x); + __m128 x3 = _mm_mul_ps(x2, x); + __m128 y = _mm_set1_ps(0.0f); + __m128 p1 = _mm_set1_ps(2./3.) - x2 + _mm_mul_ps(_mm_set1_ps(1./2.), x3); + __m128 p2 = _mm_sub_ps(_mm_add_ps(_mm_sub_ps(_mm_set1_ps(4./3.), _mm_mul_ps(_mm_set1_ps(2), x)), x2), _mm_mul_ps(_mm_set1_ps(1./6.), x3)); + __m128 m2 = _mm_cmple_ps(x, _mm_set1_ps(2)); + y = _mm_or_ps(_mm_and_ps(m2, p2), _mm_andnot_ps(m2, y)); + __m128 m1 = _mm_cmple_ps(x, _mm_set1_ps(1)); + y = _mm_or_ps(_mm_and_ps(m1, p1), _mm_andnot_ps(m1, y)); + return y; +} +#endif + +template +ValueType bspline3Interpolation(const ValueType values[4], ValueType coeff); + +#if SFIZZ_HAVE_SSE +template <> +inline float bspline3Interpolation(const float values[4], float coeff) +{ + __m128 x = _mm_sub_ps(_mm_setr_ps(-1, 0, 1, 2), _mm_set1_ps(coeff)); + __m128 h = bspline3x4(x); + __m128 y = _mm_mul_ps(h, _mm_loadu_ps(values)); + // sum 4 to 1 + __m128 xmm0 = y; + __m128 xmm1 = _mm_shuffle_ps(xmm0, xmm0, 0xe5); + __m128 xmm2 = _mm_movehl_ps(xmm0, xmm0); + xmm1 = _mm_add_ss(xmm1, xmm0); + xmm0 = _mm_shuffle_ps(xmm0, xmm0, 0xe7); + xmm2 = _mm_add_ss(xmm2, xmm1); + xmm0 = _mm_add_ss(xmm0, xmm2); + return _mm_cvtss_f32(xmm0); +} +#endif + +template +ValueType bspline3Interpolation(const ValueType values[4], ValueType coeff) +{ + ValueType y = 0; + for (int i = 0; i < 4; ++i) { + ValueType h = bspline3(i - 1 - coeff); + y += h * values[i]; + } + return y; } template diff --git a/src/sfizz/Oversampler.cpp b/src/sfizz/Oversampler.cpp index 422e5250..f3313b88 100644 --- a/src/sfizz/Oversampler.cpp +++ b/src/sfizz/Oversampler.cpp @@ -89,7 +89,7 @@ void sfz::Oversampler::stream(const sfz::AudioBuffer& input, sfz::AudioBu break; case Oversampling::x1: for (size_t i = 0; i < numChannels; ++i) - copy(input.getConstSpan(i), output.getSpan(i)); + copy(input.getConstSpan(i), output.getSpan(i).first(numFrames)); return; } diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index b35f3e43..4e034a36 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -485,7 +485,7 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept const auto sampleEnd = min( static_cast(region->trueSampleEnd(currentPromise->oversamplingFactor)), static_cast(source.getNumFrames()) - ) - 2; + ) - 1; for (unsigned i = 0; i < indices->size(); ++i) { if ((*indices)[i] >= sampleEnd) { #ifndef NDEBUG @@ -511,15 +511,15 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept auto left = buffer.getChannel(0); if (source.getNumChannels() == 1) { while (ind < indices->end()) { - *left = linearInterpolation(leftSource[*ind], leftSource[*ind + 1], *coeff); + *left = bspline3Interpolation(&leftSource[*ind], *coeff); incrementAll(ind, left, coeff); } } else { auto right = buffer.getChannel(1); auto rightSource = source.getConstSpan(1); while (ind < indices->end()) { - *left = linearInterpolation(leftSource[*ind], leftSource[*ind + 1], *coeff); - *right = linearInterpolation(rightSource[*ind], rightSource[*ind + 1], *coeff); + *left = bspline3Interpolation(&leftSource[*ind], *coeff); + *right = bspline3Interpolation(&rightSource[*ind], *coeff); incrementAll(ind, left, right, coeff); } } From 51502eaed3a76a4765f94eadf4850b1e173befe0 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 16 May 2020 15:51:11 +0200 Subject: [PATCH 03/19] Moved interpolators to their own file --- src/sfizz/Interpolators.h | 25 ++++++++ src/sfizz/Interpolators.hpp | 123 ++++++++++++++++++++++++++++++++++++ src/sfizz/MathHelpers.h | 72 --------------------- src/sfizz/Voice.cpp | 7 +- 4 files changed, 152 insertions(+), 75 deletions(-) create mode 100644 src/sfizz/Interpolators.h create mode 100644 src/sfizz/Interpolators.hpp diff --git a/src/sfizz/Interpolators.h b/src/sfizz/Interpolators.h new file mode 100644 index 00000000..834113bf --- /dev/null +++ b/src/sfizz/Interpolators.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once + +namespace sfz { + +enum InterpolatorModel : int { + // a linear interpolator + kInterpolatorLinear, + // a Hermite 3rd order interpolator + kInterpolatorHermite3, + // a B-spline 3rd order interpolator + kInterpolatorBspline3, +}; + +template +R interpolate(const R* values, R coeff); + +} // namespace sfz + +#include "Interpolators.hpp" diff --git a/src/sfizz/Interpolators.hpp b/src/sfizz/Interpolators.hpp new file mode 100644 index 00000000..ef7e0324 --- /dev/null +++ b/src/sfizz/Interpolators.hpp @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "Interpolators.h" +#include "MathHelpers.h" +#include "SIMDConfig.h" + +namespace sfz { + +template +class Interpolator; + +template +inline R interpolate(const R* values, R coeff) +{ + return Interpolator::process(values, coeff); +} + +//------------------------------------------------------------------------------ +// Linear + +template +class Interpolator +{ +public: + static inline R process(const R* values, R coeff) + { + return values[0] * (static_cast(1.0) - coeff) + values[1] * coeff; + } +}; + +//------------------------------------------------------------------------------ +// Hermite 3rd order, SSE specialization + +#if SFIZZ_HAVE_SSE +template <> +class Interpolator +{ +public: + static inline float process(const float* values, float coeff) + { + __m128 x = _mm_sub_ps(_mm_setr_ps(-1, 0, 1, 2), _mm_set1_ps(coeff)); + __m128 h = hermite3x4(x); + __m128 y = _mm_mul_ps(h, _mm_loadu_ps(values)); + // sum 4 to 1 + __m128 xmm0 = y; + __m128 xmm1 = _mm_shuffle_ps(xmm0, xmm0, 0xe5); + __m128 xmm2 = _mm_movehl_ps(xmm0, xmm0); + xmm1 = _mm_add_ss(xmm1, xmm0); + xmm0 = _mm_shuffle_ps(xmm0, xmm0, 0xe7); + xmm2 = _mm_add_ss(xmm2, xmm1); + xmm0 = _mm_add_ss(xmm0, xmm2); + return _mm_cvtss_f32(xmm0); + } +}; +#endif + +//------------------------------------------------------------------------------ +// Hermite 3rd order, generic + +template +class Interpolator +{ +public: + static inline R process(const R* values, R coeff) + { + R y = 0; + for (int i = 0; i < 4; ++i) { + R h = hermite3(i - 1 - coeff); + y += h * values[i]; + } + return y; + } +}; + +//------------------------------------------------------------------------------ +// B-spline 3rd order, SSE specialization + +#if SFIZZ_HAVE_SSE +template <> +class Interpolator +{ +public: + static inline float process(const float* values, float coeff) + { + __m128 x = _mm_sub_ps(_mm_setr_ps(-1, 0, 1, 2), _mm_set1_ps(coeff)); + __m128 h = bspline3x4(x); + __m128 y = _mm_mul_ps(h, _mm_loadu_ps(values)); + // sum 4 to 1 + __m128 xmm0 = y; + __m128 xmm1 = _mm_shuffle_ps(xmm0, xmm0, 0xe5); + __m128 xmm2 = _mm_movehl_ps(xmm0, xmm0); + xmm1 = _mm_add_ss(xmm1, xmm0); + xmm0 = _mm_shuffle_ps(xmm0, xmm0, 0xe7); + xmm2 = _mm_add_ss(xmm2, xmm1); + xmm0 = _mm_add_ss(xmm0, xmm2); + return _mm_cvtss_f32(xmm0); + } +}; +#endif + +//------------------------------------------------------------------------------ +// B-spline 3rd order, generic + +template +class Interpolator +{ +public: + static inline R process(const R* values, R coeff) + { + R y = 0; + for (int i = 0; i < 4; ++i) { + R h = bspline3(i - 1 - coeff); + y += h * values[i]; + } + return y; + } +}; + +} // namespace sfz diff --git a/src/sfizz/MathHelpers.h b/src/sfizz/MathHelpers.h index ed7679cb..f20962d7 100644 --- a/src/sfizz/MathHelpers.h +++ b/src/sfizz/MathHelpers.h @@ -159,12 +159,6 @@ inline CXX14_CONSTEXPR void incrementAll(T& first, Args&... rest) incrementAll(rest...); } -template -constexpr ValueType linearInterpolation(const ValueType values[2], ValueType coeff) -{ - return values[0] * (static_cast(1.0) - coeff) + values[1] * coeff; -} - /** * @brief Compute the 3rd-order Hermite interpolation polynomial. * @@ -211,39 +205,6 @@ inline __m128 hermite3x4(__m128 x) } #endif -template -ValueType hermite3Interpolation(const ValueType values[4], ValueType coeff); - -#if SFIZZ_HAVE_SSE -template <> -inline float hermite3Interpolation(const float values[4], float coeff) -{ - __m128 x = _mm_sub_ps(_mm_setr_ps(-1, 0, 1, 2), _mm_set1_ps(coeff)); - __m128 h = hermite3x4(x); - __m128 y = _mm_mul_ps(h, _mm_loadu_ps(values)); - // sum 4 to 1 - __m128 xmm0 = y; - __m128 xmm1 = _mm_shuffle_ps(xmm0, xmm0, 0xe5); - __m128 xmm2 = _mm_movehl_ps(xmm0, xmm0); - xmm1 = _mm_add_ss(xmm1, xmm0); - xmm0 = _mm_shuffle_ps(xmm0, xmm0, 0xe7); - xmm2 = _mm_add_ss(xmm2, xmm1); - xmm0 = _mm_add_ss(xmm0, xmm2); - return _mm_cvtss_f32(xmm0); -} -#endif - -template -ValueType hermite3Interpolation(const ValueType values[4], ValueType coeff) -{ - ValueType y = 0; - for (int i = 0; i < 4; ++i) { - ValueType h = hermite3(i - 1 - coeff); - y += h * values[i]; - } - return y; -} - /** * @brief Compute the 3rd-order B-spline interpolation polynomial. * @@ -288,39 +249,6 @@ inline __m128 bspline3x4(__m128 x) } #endif -template -ValueType bspline3Interpolation(const ValueType values[4], ValueType coeff); - -#if SFIZZ_HAVE_SSE -template <> -inline float bspline3Interpolation(const float values[4], float coeff) -{ - __m128 x = _mm_sub_ps(_mm_setr_ps(-1, 0, 1, 2), _mm_set1_ps(coeff)); - __m128 h = bspline3x4(x); - __m128 y = _mm_mul_ps(h, _mm_loadu_ps(values)); - // sum 4 to 1 - __m128 xmm0 = y; - __m128 xmm1 = _mm_shuffle_ps(xmm0, xmm0, 0xe5); - __m128 xmm2 = _mm_movehl_ps(xmm0, xmm0); - xmm1 = _mm_add_ss(xmm1, xmm0); - xmm0 = _mm_shuffle_ps(xmm0, xmm0, 0xe7); - xmm2 = _mm_add_ss(xmm2, xmm1); - xmm0 = _mm_add_ss(xmm0, xmm2); - return _mm_cvtss_f32(xmm0); -} -#endif - -template -ValueType bspline3Interpolation(const ValueType values[4], ValueType coeff) -{ - ValueType y = 0; - for (int i = 0; i < 4; ++i) { - ValueType h = bspline3(i - 1 - coeff); - y += h * values[i]; - } - return y; -} - template constexpr Type pi() { return static_cast(3.141592653589793238462643383279502884); }; template diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 4e034a36..d198b22f 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -11,6 +11,7 @@ #include "MathHelpers.h" #include "SIMDHelpers.h" #include "SfzHelpers.h" +#include "Interpolators.h" #include "absl/algorithm/container.h" sfz::Voice::Voice(sfz::Resources& resources) @@ -511,15 +512,15 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept auto left = buffer.getChannel(0); if (source.getNumChannels() == 1) { while (ind < indices->end()) { - *left = bspline3Interpolation(&leftSource[*ind], *coeff); + *left = interpolate(&leftSource[*ind], *coeff); incrementAll(ind, left, coeff); } } else { auto right = buffer.getChannel(1); auto rightSource = source.getConstSpan(1); while (ind < indices->end()) { - *left = bspline3Interpolation(&leftSource[*ind], *coeff); - *right = bspline3Interpolation(&rightSource[*ind], *coeff); + *left = interpolate(&leftSource[*ind], *coeff); + *right = interpolate(&rightSource[*ind], *coeff); incrementAll(ind, left, right, coeff); } } From 4ff54b315833e2bdd94f6c1a984fbf9fd326f1a7 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 16 May 2020 16:16:52 +0200 Subject: [PATCH 04/19] Add the opcode `sample_quality` and default value to 2 --- src/sfizz/AudioSpan.h | 6 ++--- src/sfizz/Defaults.h | 3 +++ src/sfizz/Region.cpp | 6 +++++ src/sfizz/Region.h | 1 + src/sfizz/Voice.cpp | 61 +++++++++++++++++++++++++++++++------------ src/sfizz/Voice.h | 15 +++++++++++ 6 files changed, 72 insertions(+), 20 deletions(-) diff --git a/src/sfizz/AudioSpan.h b/src/sfizz/AudioSpan.h index e11e2ebb..8f9f0228 100644 --- a/src/sfizz/AudioSpan.h +++ b/src/sfizz/AudioSpan.h @@ -246,7 +246,7 @@ public: * @param channelIndex the channel * @return absl::Span */ - absl::Span getConstSpan(size_t channelIndex) + absl::Span getConstSpan(size_t channelIndex) const { ASSERT(channelIndex < numChannels); if (channelIndex < numChannels) @@ -382,7 +382,7 @@ public: * * @returns size_type the number of frames in the AudioSpan */ - size_type getNumFrames() + size_type getNumFrames() const { return numFrames; } @@ -392,7 +392,7 @@ public: * * @returns size_type the number of channels in the AudioSpan */ - size_t getNumChannels() + size_t getNumChannels() const { return numChannels; } diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 6796380f..4ebe7577 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -221,6 +221,9 @@ namespace Default constexpr Range egOnCCPercentRange { -100.0, 100.0 }; // ***** SFZ v2 ******** + constexpr int sampleQuality { 2 }; + constexpr Range sampleQualityRange { 1, 10 }; // sample_quality + constexpr bool checkSustain { true }; // sustain_sw constexpr bool checkSostenuto { true }; // sostenuto_sw constexpr Range octaveOffsetRange { -10, 10 }; // octave_offset diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 20d44048..35e6b0fc 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -60,6 +60,12 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) sampleId = FileId(std::move(filename), sampleId.isReverse()); } break; + case hash("sample_quality"): + { + setValueFromOpcode(opcode, sampleQuality, Default::sampleQualityRange); + break; + } + break; case hash("direction"): sampleId = sampleId.reversed(opcode.value == "reverse"); break; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 1332d1ec..5ce49ff8 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -240,6 +240,7 @@ struct Region { // Sound source: sample playback FileId sampleId {}; // Sample + int sampleQuality { Default::sampleQuality }; float delay { Default::delay }; // delay float delayRandom { Default::delayRandom }; // delay_random int64_t offset { Default::offset }; // offset diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index d198b22f..6ae90db2 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -506,23 +506,25 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept } } - auto ind = indices->data(); - auto coeff = coeffs->data(); - auto leftSource = source.getConstSpan(0); - auto left = buffer.getChannel(0); - if (source.getNumChannels() == 1) { - while (ind < indices->end()) { - *left = interpolate(&leftSource[*ind], *coeff); - incrementAll(ind, left, coeff); - } - } else { - auto right = buffer.getChannel(1); - auto rightSource = source.getConstSpan(1); - while (ind < indices->end()) { - *left = interpolate(&leftSource[*ind], *coeff); - *right = interpolate(&rightSource[*ind], *coeff); - incrementAll(ind, left, right, coeff); - } + const int quality = region->sampleQuality; + + switch (quality) { + default: + if (quality > 2) + goto high; // TODO sinc, not implemented + // fall through + case 1: + fillInterpolated(source, buffer, *indices, *coeffs); + break; + case 2: high: +#if 1 + // B-spline response has faster decay of aliasing, but not zero-crossings at integer positions + fillInterpolated(source, buffer, *indices, *coeffs); +#else + // Hermite polynomial + fillInterpolated(source, buffer, *indices, *coeffs); +#endif + break; } sourcePosition = indices->back(); @@ -536,6 +538,31 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept #endif } +template +void sfz::Voice::fillInterpolated( + const sfz::AudioSpan& source, sfz::AudioSpan& dest, + absl::Span indices, absl::Span coeffs) +{ + auto ind = indices.data(); + auto coeff = coeffs.data(); + auto leftSource = source.getConstSpan(0); + auto left = dest.getChannel(0); + if (source.getNumChannels() == 1) { + while (ind < indices.end()) { + *left = sfz::interpolate(&leftSource[*ind], *coeff); + incrementAll(ind, left, coeff); + } + } else { + auto right = dest.getChannel(1); + auto rightSource = source.getConstSpan(1); + while (ind < indices.end()) { + *left = sfz::interpolate(&leftSource[*ind], *coeff); + *right = sfz::interpolate(&rightSource[*ind], *coeff); + incrementAll(ind, left, right, coeff); + } + } +} + void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept { const auto leftSpan = buffer.getSpan(0); diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 46b3a3f0..b1ff292a 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -19,6 +19,7 @@ #include namespace sfz { +enum InterpolatorModel : int; /** * @brief The SFZ voice are the polyphony holders. They get activated by the synth * and tasked to play a given region until the end, stopping on note-offs, off-groups @@ -242,6 +243,20 @@ private: * @param buffer */ void fillWithGenerator(AudioSpan buffer) noexcept; + + /** + * @brief Fill a destination with an interpolated source. + * + * @param source the source sample + * @param dest the destination buffer + * @param indices the integral parts of the source positions + * @param coeffs the fractional parts of the source positions + */ + template + static void fillInterpolated( + const sfz::AudioSpan& source, sfz::AudioSpan& dest, + absl::Span indices, absl::Span coeffs); + void amplitudeEnvelope(absl::Span modulationSpan) noexcept; void ampStageMono(AudioSpan buffer) noexcept; void ampStageStereo(AudioSpan buffer) noexcept; From 99fa28782f406675ea9746497695b39b50eaff80 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 16 May 2020 16:37:28 +0200 Subject: [PATCH 05/19] Add interpolators benchmark --- benchmarks/BM_interpolators.cpp | 86 +++++++++++++++++++++++++++++++++ benchmarks/CMakeLists.txt | 2 + 2 files changed, 88 insertions(+) create mode 100644 benchmarks/BM_interpolators.cpp diff --git a/benchmarks/BM_interpolators.cpp b/benchmarks/BM_interpolators.cpp new file mode 100644 index 00000000..5935a6de --- /dev/null +++ b/benchmarks/BM_interpolators.cpp @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "Interpolators.h" +#include "ScopedFTZ.h" +#include "absl/types/span.h" +#include +#include + +class Interpolators : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) + { + std::random_device rd { }; + std::mt19937 gen { rd() }; + std::uniform_real_distribution dist { -1.0f, 1.0f }; + + const size_t numFramesIn = state.range(0); + inputBuffer = std::vector(numFramesIn + excessFrames); + + // any ratio will do, compute time will be proportional + static constexpr float ratio = 1.234; + + const size_t numFramesOut = static_cast(std::ceil(numFramesIn * ratio)); + input = absl::MakeSpan(inputBuffer.data(), numFramesIn); + output = std::vector(numFramesOut); + std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); + } + + void TearDown(const ::benchmark::State& /* state */) + { + } + + std::vector inputBuffer; + absl::Span input; + std::vector output; + + enum { excessFrames = 8 }; +}; + +template +static void doInterpolation(absl::Span input, absl::Span output) +{ + const float kOutToIn = static_cast(input.size()) / output.size(); + + for (size_t iOut = 0; iOut < output.size(); ++iOut) { + float posIn = iOut * kOutToIn; + unsigned dec = static_cast(posIn); + float frac = posIn - dec; + output[iOut] = sfz::interpolate(&input[dec], frac); + } +} + +BENCHMARK_DEFINE_F(Interpolators, Linear)(benchmark::State& state) +{ + ScopedFTZ ftz; + + for (auto _ : state) { + doInterpolation(input, absl::MakeSpan(output)); + } +} + +BENCHMARK_DEFINE_F(Interpolators, Hermite3)(benchmark::State& state) +{ + ScopedFTZ ftz; + + for (auto _ : state) { + doInterpolation(input, absl::MakeSpan(output)); + } +} + +BENCHMARK_DEFINE_F(Interpolators, Bspline3)(benchmark::State& state) +{ + ScopedFTZ ftz; + + for (auto _ : state) { + doInterpolation(input, absl::MakeSpan(output)); + } +} + +BENCHMARK_REGISTER_F(Interpolators, Linear)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(Interpolators, Hermite3)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(Interpolators, Bspline3)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 96595e47..1b2fcdbe 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -89,6 +89,8 @@ target_link_libraries(bm_readChunkFlac PRIVATE sfizz-sndfile) sfizz_add_benchmark(bm_resampleChunk BM_resampleChunk.cpp) target_link_libraries(bm_resampleChunk PRIVATE sfizz-sndfile) +sfizz_add_benchmark(bm_interpolators BM_interpolators.cpp) + sfizz_add_benchmark(bm_filterModulation BM_filterModulation.cpp ../src/sfizz/SfzFilter.cpp) target_link_libraries(bm_filterModulation PRIVATE sfizz-sndfile) From e195cdd2189e8b5e045f5008acda2ee93a049d59 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 16 May 2020 17:07:32 +0200 Subject: [PATCH 06/19] For some SSE intrinsics expressions, for msvc compat --- src/sfizz/MathHelpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/MathHelpers.h b/src/sfizz/MathHelpers.h index f20962d7..88116dab 100644 --- a/src/sfizz/MathHelpers.h +++ b/src/sfizz/MathHelpers.h @@ -239,7 +239,7 @@ inline __m128 bspline3x4(__m128 x) __m128 x2 = _mm_mul_ps(x, x); __m128 x3 = _mm_mul_ps(x2, x); __m128 y = _mm_set1_ps(0.0f); - __m128 p1 = _mm_set1_ps(2./3.) - x2 + _mm_mul_ps(_mm_set1_ps(1./2.), x3); + __m128 p1 = _mm_add_ps(_mm_sub_ps(_mm_set1_ps(2./3.), x2), _mm_mul_ps(_mm_set1_ps(1./2.), x3)); __m128 p2 = _mm_sub_ps(_mm_add_ps(_mm_sub_ps(_mm_set1_ps(4./3.), _mm_mul_ps(_mm_set1_ps(2), x)), x2), _mm_mul_ps(_mm_set1_ps(1./6.), x3)); __m128 m2 = _mm_cmple_ps(x, _mm_set1_ps(2)); y = _mm_or_ps(_mm_and_ps(m2, p2), _mm_andnot_ps(m2, y)); From 5abfa5ef188125006b74e032fe5794ca2a5badb3 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 16 May 2020 17:25:51 +0200 Subject: [PATCH 07/19] Forgot to pad and clear the buffer in one case --- src/sfizz/FilePool.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index ad987477..fb362170 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -79,7 +79,8 @@ std::unique_ptr> readFromFile(SndfileHandle& sndFile, uint32 if (factor == sfz::Oversampling::x1) return baseBuffer; - auto outputBuffer = absl::make_unique>(sndFile.channels(), numFrames * static_cast(factor)); + auto outputBuffer = absl::make_unique>(sndFile.channels(), numFrames * static_cast(factor) + sfz::config::excessFileFrames); + outputBuffer->clear(); sfz::Oversampler oversampler { factor }; oversampler.stream(*baseBuffer, *outputBuffer); return outputBuffer; From 4f0414cb4ab5be17245b408f2b0662bec683bfb9 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 16 May 2020 17:27:39 +0200 Subject: [PATCH 08/19] Clear buffer after adding channels, not before --- src/sfizz/FilePool.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index fb362170..e0028e36 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -43,7 +43,6 @@ void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer& output, uint32_t { output.reset(); output.resize(numFrames + sfz::config::excessFileFrames); - output.clear(); if (reverse) sndFile.seek(-static_cast(numFrames), SEEK_END); @@ -52,10 +51,12 @@ void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer& output, uint32_t if (channels == 1) { output.addChannel(); + output.clear(); sndFile.readf(output.channelWriter(0), numFrames); } else if (channels == 2) { output.addChannel(); output.addChannel(); + output.clear(); sfz::Buffer tempReadBuffer { 2 * numFrames }; sndFile.readf(tempReadBuffer.data(), numFrames); sfz::readInterleaved(tempReadBuffer, output.getSpan(0).first(numFrames), output.getSpan(1).first(numFrames)); From c48e14ee2b1c06b2a9d64b0eb788cd84a2c1ff57 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Thu, 28 May 2020 23:33:48 +0200 Subject: [PATCH 09/19] Add interpolator tests --- tests/CMakeLists.txt | 1 + tests/InterpolatorsT.cpp | 71 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 tests/InterpolatorsT.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c871114c..af4981dc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,6 +17,7 @@ set(SFIZZ_TEST_SOURCES FilesT.cpp MidiStateT.cpp OnePoleFilterT.cpp + InterpolatorsT.cpp RegionActivationT.cpp RegionValueComputationsT.cpp # If we're tweaking the curves this kind of tests does not make sense diff --git a/tests/InterpolatorsT.cpp b/tests/InterpolatorsT.cpp new file mode 100644 index 00000000..04742547 --- /dev/null +++ b/tests/InterpolatorsT.cpp @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "sfizz/Interpolators.h" +#include "catch2/catch.hpp" +#include +using namespace Catch::literals; + +TEST_CASE("[Interpolators] Sample at points") +{ + std::array values; + std::iota(values.begin(), values.end(), 0.0f); + for (unsigned i = 2; i < values.size() - 2; ++i) { + REQUIRE(sfz::interpolate(&values[i], 0.0f) + == Approx(values[i]).margin(1e-2)); + REQUIRE(sfz::interpolate(&values[i], 0.0f) + == Approx(values[i]).margin(1e-2)); + REQUIRE(sfz::interpolate(&values[i], 0.0f) + == Approx(values[i]).margin(1e-2)); + } +} + +TEST_CASE("[Interpolators] Sample next") +{ + std::array values; + std::iota(values.begin(), values.end(), 0.0f); + for (unsigned i = 2; i < values.size() - 2; ++i) { + REQUIRE(sfz::interpolate(&values[i], 1.0f) + == Approx(values[i + 1]).margin(1e-2)); + REQUIRE(sfz::interpolate(&values[i], 1.0f) + == Approx(values[i + 1]).margin(1e-2)); + REQUIRE(sfz::interpolate(&values[i], 1.0f) + == Approx(values[i + 1]).margin(1e-2)); + } +} + +TEST_CASE("[Interpolators] Straight line") +{ + std::array values; + std::iota(values.begin(), values.end(), 0.0f); + for (unsigned i = 2; i < values.size() - 2; ++i) { + REQUIRE(sfz::interpolate(&values[i], 0.5f) + == Approx(values[i] + 0.5f).margin(1e-2)); + REQUIRE(sfz::interpolate(&values[i], 0.5f) + == Approx(values[i] + 0.5f).margin(1e-2)); + REQUIRE(sfz::interpolate(&values[i], 0.5f) + == Approx(values[i] + 0.5f).margin(1e-2)); + } +} + +TEST_CASE("[Interpolators] Squares") +{ + std::array x; + std::array y; + for (unsigned i = 0; i < x.size(); ++i) { + x[i] = static_cast(i) / static_cast(x.size()); + y[i] = x[i] * x[i]; + } + + for (unsigned i = 2; i < x.size() - 2; ++i) { + const auto half_x = x[i] + 0.5f / static_cast(x.size()); + const auto expected = (half_x) * (half_x); + REQUIRE(sfz::interpolate(&y[i], 0.5f) + == Approx(expected).margin(1e-2)); + REQUIRE(sfz::interpolate(&y[i], 0.5f) + == Approx(expected).margin(1e-2)); + } +} From 590af858237268bb1a82203bf4cd6b76a5daf336 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Thu, 28 May 2020 23:34:48 +0200 Subject: [PATCH 10/19] Add excessFileFrames at the beginning of the file dataRecenter the 4-sample splines from -1 to 2 (relative to the interpolated sample) rather than 0 to 3 --- src/sfizz/FilePool.cpp | 24 ++++++++++++++++-------- src/sfizz/FilePool.h | 9 ++++++--- src/sfizz/Interpolators.h | 13 +++++++++++++ src/sfizz/Interpolators.hpp | 12 ++++++------ src/sfizz/Oversampler.cpp | 2 +- src/sfizz/Oversampler.h | 3 ++- 6 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index e0028e36..6143ebef 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -27,6 +27,7 @@ #include "FileInstrument.h" #include "Buffer.h" #include "AudioBuffer.h" +#include "AudioSpan.h" #include "Config.h" #include "Debug.h" #include "Oversampler.h" @@ -42,7 +43,7 @@ template void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer& output, uint32_t numFrames, bool reverse) { output.reset(); - output.resize(numFrames + sfz::config::excessFileFrames); + output.resize(numFrames + 2 * sfz::config::excessFileFrames); if (reverse) sndFile.seek(-static_cast(numFrames), SEEK_END); @@ -52,20 +53,23 @@ void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer& output, uint32_t if (channels == 1) { output.addChannel(); output.clear(); - sndFile.readf(output.channelWriter(0), numFrames); + sndFile.readf(output.channelWriter(0) + sfz::config::excessFileFrames, numFrames); } else if (channels == 2) { output.addChannel(); output.addChannel(); output.clear(); sfz::Buffer tempReadBuffer { 2 * numFrames }; sndFile.readf(tempReadBuffer.data(), numFrames); - sfz::readInterleaved(tempReadBuffer, output.getSpan(0).first(numFrames), output.getSpan(1).first(numFrames)); + sfz::readInterleaved(tempReadBuffer, + output.getSpan(0).subspan(sfz::config::excessFileFrames, numFrames), + output.getSpan(1).subspan(sfz::config::excessFileFrames, numFrames) + ); } if (reverse) { for (unsigned c = 0; c < channels; ++c) { // TODO: consider optimizing with SIMD - absl::Span channel = output.getSpan(c).first(numFrames); + absl::Span channel = output.getSpan(c).subspan(sfz::config::excessFileFrames, numFrames); std::reverse(channel.begin(), channel.end()); } } @@ -80,10 +84,12 @@ std::unique_ptr> readFromFile(SndfileHandle& sndFile, uint32 if (factor == sfz::Oversampling::x1) return baseBuffer; - auto outputBuffer = absl::make_unique>(sndFile.channels(), numFrames * static_cast(factor) + sfz::config::excessFileFrames); + auto outputBuffer = absl::make_unique>(sndFile.channels(), numFrames * static_cast(factor) + 2 * sfz::config::excessFileFrames); outputBuffer->clear(); + auto baseSpan = sfz::AudioSpan(*baseBuffer).subspan(sfz::config::excessFileFrames, numFrames); + auto outputSpan = sfz::AudioSpan(*outputBuffer).subspan(sfz::config::excessFileFrames, numFrames * static_cast(factor)); sfz::Oversampler oversampler { factor }; - oversampler.stream(*baseBuffer, *outputBuffer); + oversampler.stream(baseSpan, outputSpan); return outputBuffer; } @@ -100,10 +106,12 @@ void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversamplin auto baseBuffer = readFromFile(sndFile, numFrames, sfz::Oversampling::x1, reverse); output.reset(); output.addChannels(baseBuffer->getNumChannels()); - output.resize(numFrames * static_cast(factor) + sfz::config::excessFileFrames); + output.resize(numFrames * static_cast(factor) + 2 * sfz::config::excessFileFrames); output.clear(); sfz::Oversampler oversampler { factor }; - oversampler.stream(*baseBuffer, output, filledFrames); + auto baseSpan = sfz::AudioSpan(*baseBuffer).subspan(sfz::config::excessFileFrames, numFrames); + auto outputSpan = sfz::AudioSpan(output).subspan(sfz::config::excessFileFrames, numFrames * static_cast(factor)); + oversampler.stream(baseSpan, outputSpan, filledFrames); } sfz::FilePool::FilePool(sfz::Logger& logger) diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index b60e5cc2..94e9fa1d 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -66,11 +66,14 @@ struct FilePromise { if (dataStatus == DataStatus::Ready) return AudioSpan(fileData) - .first(fileData.getNumFrames() - sfz::config::excessFileFrames); + .subspan(sfz::config::excessFileFrames, + fileData.getNumFrames() - 2 * sfz::config::excessFileFrames); else if (availableFrames > preloadedData->getNumFrames()) - return AudioSpan(fileData).first(availableFrames); + return AudioSpan(fileData).subspan(sfz::config::excessFileFrames, availableFrames); else - return AudioSpan(*preloadedData); + return AudioSpan(*preloadedData) + .subspan(sfz::config::excessFileFrames, + preloadedData->getNumFrames() - sfz::config::excessFileFrames); } void reset() diff --git a/src/sfizz/Interpolators.h b/src/sfizz/Interpolators.h index 834113bf..f784eefd 100644 --- a/src/sfizz/Interpolators.h +++ b/src/sfizz/Interpolators.h @@ -17,6 +17,19 @@ enum InterpolatorModel : int { kInterpolatorBspline3, }; +/** + * @brief Interpolate from a vector of values + * + * @tparam M the interpolator model + * @tparam R the sample type + * @param values Pointer to a value in a larger vector of values. + * Depending on the interpolator the algorithm may + * read samples before and after. Usually you need + * to ensure that you have order - 1 samples available + * before and after the pointer, padding if necessary. + * @param coeff the interpolation coefficient + * @return R + */ template R interpolate(const R* values, R coeff); diff --git a/src/sfizz/Interpolators.hpp b/src/sfizz/Interpolators.hpp index ef7e0324..1f462578 100644 --- a/src/sfizz/Interpolators.hpp +++ b/src/sfizz/Interpolators.hpp @@ -44,7 +44,7 @@ public: { __m128 x = _mm_sub_ps(_mm_setr_ps(-1, 0, 1, 2), _mm_set1_ps(coeff)); __m128 h = hermite3x4(x); - __m128 y = _mm_mul_ps(h, _mm_loadu_ps(values)); + __m128 y = _mm_mul_ps(h, _mm_loadu_ps(values - 1)); // sum 4 to 1 __m128 xmm0 = y; __m128 xmm1 = _mm_shuffle_ps(xmm0, xmm0, 0xe5); @@ -68,8 +68,8 @@ public: static inline R process(const R* values, R coeff) { R y = 0; - for (int i = 0; i < 4; ++i) { - R h = hermite3(i - 1 - coeff); + for (int i = -1; i < 3; ++i) { + R h = hermite3(i - coeff); y += h * values[i]; } return y; @@ -88,7 +88,7 @@ public: { __m128 x = _mm_sub_ps(_mm_setr_ps(-1, 0, 1, 2), _mm_set1_ps(coeff)); __m128 h = bspline3x4(x); - __m128 y = _mm_mul_ps(h, _mm_loadu_ps(values)); + __m128 y = _mm_mul_ps(h, _mm_loadu_ps(values - 1)); // sum 4 to 1 __m128 xmm0 = y; __m128 xmm1 = _mm_shuffle_ps(xmm0, xmm0, 0xe5); @@ -112,8 +112,8 @@ public: static inline R process(const R* values, R coeff) { R y = 0; - for (int i = 0; i < 4; ++i) { - R h = bspline3(i - 1 - coeff); + for (int i = -1; i < 3; ++i) { + R h = bspline3(i - coeff); y += h * values[i]; } return y; diff --git a/src/sfizz/Oversampler.cpp b/src/sfizz/Oversampler.cpp index f3313b88..1027b8ba 100644 --- a/src/sfizz/Oversampler.cpp +++ b/src/sfizz/Oversampler.cpp @@ -61,7 +61,7 @@ sfz::Oversampler::Oversampler(sfz::Oversampling factor, size_t chunkSize) } -void sfz::Oversampler::stream(const sfz::AudioBuffer& input, sfz::AudioBuffer& output, std::atomic* framesReady) +void sfz::Oversampler::stream(AudioSpan input, AudioSpan output, std::atomic* framesReady) { ASSERT(output.getNumFrames() >= input.getNumFrames() * static_cast(factor)); ASSERT(output.getNumChannels() == input.getNumChannels()); diff --git a/src/sfizz/Oversampler.h b/src/sfizz/Oversampler.h index d6daa4a9..25e74eaf 100644 --- a/src/sfizz/Oversampler.h +++ b/src/sfizz/Oversampler.h @@ -11,6 +11,7 @@ #include "Debug.h" #include "Buffer.h" #include "AudioBuffer.h" +#include "AudioSpan.h" #include "Config.h" namespace sfz { @@ -39,7 +40,7 @@ public: * @param output * @param framesReady an atomic counter for the ready frames. If null no signaling is done. */ - void stream(const AudioBuffer& input, AudioBuffer& output, std::atomic* framesReady = nullptr); + void stream(AudioSpan input, AudioSpan output, std::atomic* framesReady = nullptr); Oversampler() = delete; Oversampler(const Oversampler&) = delete; From 0b4c3fcae0748edb7ccb167237a7af165b966059 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 29 May 2020 10:05:27 +0200 Subject: [PATCH 11/19] Add padding to the benchmarks --- benchmarks/BM_interpolators.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/benchmarks/BM_interpolators.cpp b/benchmarks/BM_interpolators.cpp index 5935a6de..6fab4df9 100644 --- a/benchmarks/BM_interpolators.cpp +++ b/benchmarks/BM_interpolators.cpp @@ -4,6 +4,7 @@ // license. You should have receive a LICENSE.md file along with the code. // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz +#include "Config.h" #include "Interpolators.h" #include "ScopedFTZ.h" #include "absl/types/span.h" @@ -46,7 +47,8 @@ static void doInterpolation(absl::Span input, absl::Span out { const float kOutToIn = static_cast(input.size()) / output.size(); - for (size_t iOut = 0; iOut < output.size(); ++iOut) { + for (size_t iOut = sfz::config::excessFileFrames; + iOut < output.size() - sfz::config::excessFileFrames; ++iOut) { float posIn = iOut * kOutToIn; unsigned dec = static_cast(posIn); float frac = posIn - dec; @@ -81,6 +83,6 @@ BENCHMARK_DEFINE_F(Interpolators, Bspline3)(benchmark::State& state) } } -BENCHMARK_REGISTER_F(Interpolators, Linear)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); -BENCHMARK_REGISTER_F(Interpolators, Hermite3)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); -BENCHMARK_REGISTER_F(Interpolators, Bspline3)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(Interpolators, Linear)->RangeMultiplier(4)->Range(1 << 4, 1 << 12); +BENCHMARK_REGISTER_F(Interpolators, Hermite3)->RangeMultiplier(4)->Range(1 << 4, 1 << 12); +BENCHMARK_REGISTER_F(Interpolators, Bspline3)->RangeMultiplier(4)->Range(1 << 4, 1 << 12); From acf4f00b9d13618ca42c8ea7beb4e1e93825cbd3 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 29 May 2020 10:11:26 +0200 Subject: [PATCH 12/19] Remove redundant namespaces --- src/sfizz/Voice.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index b1ff292a..a709eb58 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -252,9 +252,9 @@ private: * @param indices the integral parts of the source positions * @param coeffs the fractional parts of the source positions */ - template + template static void fillInterpolated( - const sfz::AudioSpan& source, sfz::AudioSpan& dest, + const AudioSpan& source, AudioSpan& dest, absl::Span indices, absl::Span coeffs); void amplitudeEnvelope(absl::Span modulationSpan) noexcept; From fe730333c65555de8a92cff4b274b2e39f999d2d Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 29 May 2020 11:29:30 +0200 Subject: [PATCH 13/19] For streaming data, pass the available frame - the padding size Otherwise in some cases you might have interpolators reading uninitialized/zero data if they're close to the streaming bound --- src/sfizz/FilePool.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index 94e9fa1d..1e996756 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -68,12 +68,13 @@ struct FilePromise return AudioSpan(fileData) .subspan(sfz::config::excessFileFrames, fileData.getNumFrames() - 2 * sfz::config::excessFileFrames); - else if (availableFrames > preloadedData->getNumFrames()) - return AudioSpan(fileData).subspan(sfz::config::excessFileFrames, availableFrames); + else if (availableFrames > preloadedData->getNumFrames() + sfz::config::excessFileFrames) + return AudioSpan(fileData) + .subspan(sfz::config::excessFileFrames, availableFrames - sfz::config::excessFileFrames); else return AudioSpan(*preloadedData) .subspan(sfz::config::excessFileFrames, - preloadedData->getNumFrames() - sfz::config::excessFileFrames); + preloadedData->getNumFrames() - 2 * sfz::config::excessFileFrames); } void reset() From abd951a92bc2f150a281f07d5ddc44fd45cebaa4 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 29 May 2020 13:39:05 +0200 Subject: [PATCH 14/19] Revert and amend the benchmark correction --- benchmarks/BM_interpolators.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/benchmarks/BM_interpolators.cpp b/benchmarks/BM_interpolators.cpp index 6fab4df9..77337576 100644 --- a/benchmarks/BM_interpolators.cpp +++ b/benchmarks/BM_interpolators.cpp @@ -20,13 +20,13 @@ public: std::uniform_real_distribution dist { -1.0f, 1.0f }; const size_t numFramesIn = state.range(0); - inputBuffer = std::vector(numFramesIn + excessFrames); + inputBuffer = std::vector(numFramesIn + 2 * sfz::config::excessFileFrames); // any ratio will do, compute time will be proportional static constexpr float ratio = 1.234; const size_t numFramesOut = static_cast(std::ceil(numFramesIn * ratio)); - input = absl::MakeSpan(inputBuffer.data(), numFramesIn); + input = absl::MakeSpan(inputBuffer).subspan(sfz::config::excessFileFrames, numFramesIn); output = std::vector(numFramesOut); std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); } @@ -47,8 +47,7 @@ static void doInterpolation(absl::Span input, absl::Span out { const float kOutToIn = static_cast(input.size()) / output.size(); - for (size_t iOut = sfz::config::excessFileFrames; - iOut < output.size() - sfz::config::excessFileFrames; ++iOut) { + for (size_t iOut = 0; iOut < output.size(); ++iOut) { float posIn = iOut * kOutToIn; unsigned dec = static_cast(posIn); float frac = posIn - dec; From 50efcfc95afbb7d3d81026797c667751b243e42a Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Jun 2020 02:54:10 +0200 Subject: [PATCH 15/19] Support fixed padding for AudioBuffer --- src/sfizz/AudioBuffer.h | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/sfizz/AudioBuffer.h b/src/sfizz/AudioBuffer.h index 51500d0a..011e5a70 100644 --- a/src/sfizz/AudioBuffer.h +++ b/src/sfizz/AudioBuffer.h @@ -26,7 +26,9 @@ namespace sfz * @tparam MaxChannels the maximum number of channels in the buffer * @tparam Alignment the alignment for the buffers */ -template +template class AudioBuffer { public: using value_type = typename std::remove_cv::type; @@ -35,6 +37,13 @@ public: using iterator = pointer; using const_iterator = const_pointer; + enum { + //! Increased left padding to preserve required alignment + PaddingLeft = PaddingLeft_ + (Alignment - (PaddingLeft_ % Alignment)) % Alignment, + //! Total padding left and right + PaddingTotal = PaddingLeft + PaddingRight, + }; + /** * @brief Construct a new Audio Buffer object * @@ -55,7 +64,7 @@ public: , numFrames(numFrames) { for (size_t i = 0; i < numChannels; ++i) - buffers[i] = absl::make_unique(numFrames); + buffers[i] = absl::make_unique(numFrames + PaddingTotal); } /** @@ -70,7 +79,7 @@ public: bool returnedOK = true; for (size_t i = 0; i < numChannels; ++i) - returnedOK &= buffers[i]->resize(newSize, std::nothrow); + returnedOK &= buffers[i]->resize(newSize + PaddingTotal, std::nothrow); if (returnedOK) numFrames = newSize; @@ -86,7 +95,7 @@ public: void resize(size_t newSize) { for (size_t i = 0; i < numChannels; ++i) - buffers[i]->resize(newSize); + buffers[i]->resize(newSize + PaddingTotal); numFrames = newSize; } @@ -101,7 +110,7 @@ public: { ASSERT(channelIndex < numChannels); if (channelIndex < numChannels) - return buffers[channelIndex]->data(); + return buffers[channelIndex]->data() + PaddingLeft; return {}; } @@ -116,7 +125,7 @@ public: { ASSERT(channelIndex < numChannels); if (channelIndex < numChannels) - return buffers[channelIndex]->end(); + return buffers[channelIndex]->end() - PaddingRight; return {}; } @@ -131,7 +140,7 @@ public: { ASSERT(channelIndex < numChannels); if (channelIndex < numChannels) - return buffers[channelIndex]->data(); + return buffers[channelIndex]->data() + PaddingLeft; return {}; } @@ -146,7 +155,7 @@ public: { ASSERT(channelIndex < numChannels); if (channelIndex < numChannels) - return buffers[channelIndex]->end(); + return buffers[channelIndex]->end() - PaddingRight; return {}; } @@ -161,7 +170,7 @@ public: { ASSERT(channelIndex < numChannels); if (channelIndex < numChannels) - return { buffers[channelIndex]->data(), buffers[channelIndex]->size() }; + return { buffers[channelIndex]->data() + PaddingLeft, numFrames }; return {}; } @@ -184,7 +193,7 @@ public: void addChannel() { if (numChannels < MaxChannels) - buffers[numChannels++] = absl::make_unique(numFrames); + buffers[numChannels++] = absl::make_unique(numFrames + PaddingTotal); } /** @@ -233,7 +242,7 @@ public: ASSERT(buffers[channelIndex] != nullptr); ASSERT(frameIndex < numFrames); - return *(buffers[channelIndex]->data() + frameIndex); + return *(buffers[channelIndex]->data() + PaddingLeft + frameIndex); } /** @@ -265,8 +274,10 @@ public: */ void clear() { - for (size_t i = 0; i < numChannels; ++i) - fill(getSpan(i), Type{ 0.0 }); + for (size_t i = 0; i < numChannels; ++i) { + absl::Span paddedSpan { buffers[i]->data(), numFrames + PaddingTotal }; + fill(paddedSpan, Type{ 0.0 }); + } } /** @@ -284,7 +295,7 @@ public: /** * @brief Convert implicitly to a pointer of channels */ - operator const float* const*() const noexcept + operator const Type* const*() const noexcept { return buffers.data(); } @@ -292,7 +303,7 @@ public: /** * @brief Convert implicitly to a pointer of channels */ - operator float* const*() noexcept + operator Type* const*() noexcept { return buffers.data(); } From dd5d11f4a397768e45e0315e03f1209f98568e2f Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Jun 2020 03:05:43 +0200 Subject: [PATCH 16/19] Allow AudioSpan to accept padded AudioBuffer --- src/sfizz/AudioSpan.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sfizz/AudioSpan.h b/src/sfizz/AudioSpan.h index 8f9f0228..23c9d340 100644 --- a/src/sfizz/AudioSpan.h +++ b/src/sfizz/AudioSpan.h @@ -148,8 +148,8 @@ public: * @tparam Alignment the alignment block size for the platform * @param audioBuffer the source AudioBuffer. */ - template ::type, typename = typename std::enable_if::value, int>::type> - AudioSpan(AudioBuffer& audioBuffer) + template ::type, typename = typename std::enable_if::value, int>::type> + AudioSpan(AudioBuffer& audioBuffer) : numFrames(audioBuffer.getNumFrames()) , numChannels(audioBuffer.getNumChannels()) { @@ -169,8 +169,8 @@ public: * @tparam Alignment the alignment block size for the platform * @param audioBuffer the source AudioBuffer. */ - template > - AudioSpan(AudioBuffer& audioBuffer) + template > + AudioSpan(AudioBuffer& audioBuffer) : numFrames(audioBuffer.getNumFrames()) , numChannels(audioBuffer.getNumChannels()) { From c94eea2a3f939a5e0209ca4c89cb909f69a8df80 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Jun 2020 03:12:31 +0200 Subject: [PATCH 17/19] Eliminate excessFileFrames from FilePool, use fixed padding buffer --- src/sfizz/FilePool.cpp | 47 +++++++++++++++++------------------------- src/sfizz/FilePool.h | 23 +++++++++------------ 2 files changed, 29 insertions(+), 41 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 6143ebef..d0ac449e 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -39,11 +39,10 @@ #include #include -template -void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer& output, uint32_t numFrames, bool reverse) +void readBaseFile(SndfileHandle& sndFile, sfz::FileAudioBuffer& output, uint32_t numFrames, bool reverse) { output.reset(); - output.resize(numFrames + 2 * sfz::config::excessFileFrames); + output.resize(numFrames); if (reverse) sndFile.seek(-static_cast(numFrames), SEEK_END); @@ -53,48 +52,42 @@ void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer& output, uint32_t if (channels == 1) { output.addChannel(); output.clear(); - sndFile.readf(output.channelWriter(0) + sfz::config::excessFileFrames, numFrames); + sndFile.readf(output.channelWriter(0), numFrames); } else if (channels == 2) { output.addChannel(); output.addChannel(); output.clear(); - sfz::Buffer tempReadBuffer { 2 * numFrames }; + sfz::Buffer tempReadBuffer { 2 * numFrames }; sndFile.readf(tempReadBuffer.data(), numFrames); - sfz::readInterleaved(tempReadBuffer, - output.getSpan(0).subspan(sfz::config::excessFileFrames, numFrames), - output.getSpan(1).subspan(sfz::config::excessFileFrames, numFrames) - ); + sfz::readInterleaved(tempReadBuffer, output.getSpan(0), output.getSpan(1)); } if (reverse) { for (unsigned c = 0; c < channels; ++c) { // TODO: consider optimizing with SIMD - absl::Span channel = output.getSpan(c).subspan(sfz::config::excessFileFrames, numFrames); + absl::Span channel = output.getSpan(c); std::reverse(channel.begin(), channel.end()); } } } -template -std::unique_ptr> readFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, bool reverse) +std::unique_ptr readFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, bool reverse) { - auto baseBuffer = absl::make_unique>(); + auto baseBuffer = absl::make_unique(); readBaseFile(sndFile, *baseBuffer, numFrames, reverse); if (factor == sfz::Oversampling::x1) return baseBuffer; - auto outputBuffer = absl::make_unique>(sndFile.channels(), numFrames * static_cast(factor) + 2 * sfz::config::excessFileFrames); + auto outputBuffer = absl::make_unique(sndFile.channels(), numFrames * static_cast(factor)); outputBuffer->clear(); - auto baseSpan = sfz::AudioSpan(*baseBuffer).subspan(sfz::config::excessFileFrames, numFrames); - auto outputSpan = sfz::AudioSpan(*outputBuffer).subspan(sfz::config::excessFileFrames, numFrames * static_cast(factor)); sfz::Oversampler oversampler { factor }; - oversampler.stream(baseSpan, outputSpan); + oversampler.stream(*baseBuffer, *outputBuffer); return outputBuffer; } template -void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, bool reverse, sfz::AudioBuffer& output, std::atomic* filledFrames = nullptr) +void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, bool reverse, sfz::FileAudioBuffer& output, std::atomic* filledFrames = nullptr) { if (factor == sfz::Oversampling::x1) { readBaseFile(sndFile, output, numFrames, reverse); @@ -103,15 +96,13 @@ void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversamplin return; } - auto baseBuffer = readFromFile(sndFile, numFrames, sfz::Oversampling::x1, reverse); + auto baseBuffer = readFromFile(sndFile, numFrames, sfz::Oversampling::x1, reverse); output.reset(); output.addChannels(baseBuffer->getNumChannels()); - output.resize(numFrames * static_cast(factor) + 2 * sfz::config::excessFileFrames); + output.resize(numFrames * static_cast(factor)); output.clear(); sfz::Oversampler oversampler { factor }; - auto baseSpan = sfz::AudioSpan(*baseBuffer).subspan(sfz::config::excessFileFrames, numFrames); - auto outputSpan = sfz::AudioSpan(output).subspan(sfz::config::excessFileFrames, numFrames * static_cast(factor)); - oversampler.stream(baseSpan, outputSpan, filledFrames); + oversampler.stream(*baseBuffer, output, filledFrames); } sfz::FilePool::FilePool(sfz::Logger& logger) @@ -272,12 +263,12 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce const auto existingFile = preloadedFiles.find(fileId); if (existingFile != preloadedFiles.end()) { if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) { - preloadedFiles[fileId].preloadedData = readFromFile(sndFile, framesToLoad, oversamplingFactor, fileId.isReverse()); + preloadedFiles[fileId].preloadedData = readFromFile(sndFile, framesToLoad, oversamplingFactor, fileId.isReverse()); } } else { fileInformation->sampleRate = static_cast(oversamplingFactor) * static_cast(sndFile.samplerate()); FileDataHandle handle { - readFromFile(sndFile, framesToLoad, oversamplingFactor, fileId.isReverse()), + readFromFile(sndFile, framesToLoad, oversamplingFactor, fileId.isReverse()), *fileInformation }; preloadedFiles.insert_or_assign(fileId, handle); @@ -302,7 +293,7 @@ absl::optional sfz::FilePool::loadFile(const FileId& fileId } else { fileInformation->sampleRate = static_cast(oversamplingFactor) * static_cast(sndFile.samplerate()); FileDataHandle handle { - readFromFile(sndFile, frames, oversamplingFactor, fileId.isReverse()), + readFromFile(sndFile, frames, oversamplingFactor, fileId.isReverse()), *fileInformation }; loadedFiles.insert_or_assign(fileId, handle); @@ -352,7 +343,7 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept const auto maxOffset = numFrames > this->preloadSize ? static_cast(numFrames) - this->preloadSize : 0; fs::path file { rootDirectory / preloadedFile.first.filename() }; SndfileHandle sndFile(file.string().c_str()); - preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, oversamplingFactor, preloadedFile.first.isReverse()); + preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, oversamplingFactor, preloadedFile.first.isReverse()); } this->preloadSize = preloadSize; } @@ -481,7 +472,7 @@ void sfz::FilePool::setOversamplingFactor(sfz::Oversampling factor) noexcept const uint32_t maxOffset = numFrames > this->preloadSize ? static_cast(numFrames) - this->preloadSize : 0; fs::path file { rootDirectory / preloadedFile.first.filename() }; SndfileHandle sndFile(file.string().c_str()); - preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, factor, preloadedFile.first.isReverse()); + preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, factor, preloadedFile.first.isReverse()); preloadedFile.second.information.sampleRate *= samplerateChange; } diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index 1e996756..70973a87 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -43,7 +43,9 @@ #include namespace sfz { -using AudioBufferPtr = std::shared_ptr>; +using FileAudioBuffer = AudioBuffer; +using FileAudioBufferPtr = std::shared_ptr; struct FileInformation { uint32_t end { Default::sampleEndRange.getEnd() }; @@ -56,7 +58,7 @@ struct FileInformation { // Strict C++11 disallows member initialization if aggregate initialization is to be used... struct FileDataHandle { - std::shared_ptr> preloadedData; + FileAudioBufferPtr preloadedData; FileInformation information; }; @@ -65,16 +67,11 @@ struct FilePromise AudioSpan getData() { if (dataStatus == DataStatus::Ready) - return AudioSpan(fileData) - .subspan(sfz::config::excessFileFrames, - fileData.getNumFrames() - 2 * sfz::config::excessFileFrames); - else if (availableFrames > preloadedData->getNumFrames() + sfz::config::excessFileFrames) - return AudioSpan(fileData) - .subspan(sfz::config::excessFileFrames, availableFrames - sfz::config::excessFileFrames); + return AudioSpan(fileData); + else if (availableFrames > preloadedData->getNumFrames()) + return AudioSpan(fileData).first(availableFrames); else - return AudioSpan(*preloadedData) - .subspan(sfz::config::excessFileFrames, - preloadedData->getNumFrames() - 2 * sfz::config::excessFileFrames); + return AudioSpan(*preloadedData); } void reset() @@ -101,8 +98,8 @@ struct FilePromise }; FileId fileId {}; - AudioBufferPtr preloadedData {}; - AudioBuffer fileData {}; + FileAudioBufferPtr preloadedData {}; + FileAudioBuffer fileData {}; float sampleRate { config::defaultSampleRate }; Oversampling oversamplingFactor { config::defaultOversamplingFactor }; std::atomic availableFrames { 0 }; From b0e7c9dba3888458714e54fffab39371ca1d523e Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Jun 2020 03:36:49 +0200 Subject: [PATCH 18/19] Add test for AudioBuffer padding --- tests/AudioBufferT.cpp | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/AudioBufferT.cpp b/tests/AudioBufferT.cpp index ff483b13..f1665a89 100644 --- a/tests/AudioBufferT.cpp +++ b/tests/AudioBufferT.cpp @@ -83,3 +83,38 @@ TEST_CASE("[AudioSpan] Constructions") sfz::AudioSpan manualSpan2 { {buffer.getSpan(0), buffer.getSpan(1) } }; sfz::AudioSpan manualConstSpan2 { { buffer.getConstSpan(0), buffer.getConstSpan(1) } }; } + +TEST_CASE("[AudioBuffer] Padding") +{ + constexpr size_t channels = 2; + constexpr size_t numFrames = 7777; + constexpr unsigned alignment = 32; + constexpr size_t padLeft = 13; + constexpr size_t padRight = 51; + + sfz::AudioBuffer padded { channels, numFrames }; + + // ensure padding to be extended to respect alignment + constexpr size_t extendedPadLeft = padded.PaddingLeft; + REQUIRE(extendedPadLeft == 32); + + // ensure access functions to return consistent addresses with offset + for (size_t c = 0; c < channels; ++c) { + REQUIRE(padded.getSpan(c).data() == &padded.getSample(c, 0)); + REQUIRE(padded.getConstSpan(c).data() == &padded.getSample(c, 0)); + REQUIRE(padded.getSpan(c).data() == padded.channelReader(c)); + REQUIRE(padded.getSpan(c).data() == padded.channelWriter(c)); + REQUIRE(padded.getSpan(c).end() == padded.channelReaderEnd(c)); + REQUIRE(padded.getSpan(c).end() == padded.channelWriterEnd(c)); + } + + padded.clear(); + + // ensure all padding areas to be accessible and zero after clearing + for (size_t c = 0; c < channels; ++c) { + absl::Span leftSpan { padded.getSpan(0).data() - extendedPadLeft, extendedPadLeft }; + absl::Span rightSpan { padded.getSpan(0).end(), padRight }; + REQUIRE(std::all_of(leftSpan.begin(), leftSpan.end(), [](float value) { return value == 0.0f; })); + REQUIRE(std::all_of(rightSpan.begin(), rightSpan.end(), [](float value) { return value == 0.0f; })); + } +} From d564a289c949330078ec4e23c2d01b1e850b959a Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Jun 2020 03:49:49 +0200 Subject: [PATCH 19/19] Remove unused and broken AudioBuffer implicit conversions --- src/sfizz/AudioBuffer.h | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/sfizz/AudioBuffer.h b/src/sfizz/AudioBuffer.h index 011e5a70..0c47ba0a 100644 --- a/src/sfizz/AudioBuffer.h +++ b/src/sfizz/AudioBuffer.h @@ -292,22 +292,6 @@ public: addChannel(); } - /** - * @brief Convert implicitly to a pointer of channels - */ - operator const Type* const*() const noexcept - { - return buffers.data(); - } - - /** - * @brief Convert implicitly to a pointer of channels - */ - operator Type* const*() noexcept - { - return buffers.data(); - } - private: using buffer_type = Buffer; using buffer_ptr = std::unique_ptr;