diff --git a/benchmarks/BM_pan_arm.cpp b/benchmarks/BM_pan_arm.cpp new file mode 100644 index 00000000..9ae03b2f --- /dev/null +++ b/benchmarks/BM_pan_arm.cpp @@ -0,0 +1,134 @@ +// 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 "Panning.h" +#include "simd/Common.h" +#include +#include +#include +#include "absl/types/span.h" +#include + +#include +template +using aligned_vector = std::vector>; + +// Number of elements in the table, odd for equal volume at center +constexpr int panSize = 4095; + +// Table of pan values for the left channel, extra element for safety +static const auto panData = []() +{ + std::array pan; + int i = 0; + + for (; i < panSize; ++i) + pan[i] = std::cos(i * (piTwo() / (panSize - 1))); + + for (; i < static_cast(pan.size()); ++i) + pan[i] = pan[panSize - 1]; + + return pan; +}(); + +float _panLookup(float pan) +{ + // reduce range, round to nearest + int index = lroundPositive(pan * (panSize - 1)); + return panData[index]; +} + +void panScalar(const float* panEnvelope, float* leftBuffer, float* rightBuffer, unsigned size) noexcept +{ + const auto sentinel = panEnvelope + size; + while (panEnvelope < sentinel) { + auto p =(*panEnvelope + 1.0f) * 0.5f; + p = clamp(p, 0.0f, 1.0f); + *leftBuffer *= _panLookup(p); + *rightBuffer *= _panLookup(1 - p); + incrementAll(panEnvelope, leftBuffer, rightBuffer); + } +} + +void panSIMD(const float* panEnvelope, float* leftBuffer, float* rightBuffer, unsigned size) noexcept +{ + const auto sentinel = panEnvelope + size; + int32_t indices[4]; + while (panEnvelope < sentinel) { + float32x4_t mmPan = vld1q_f32(panEnvelope); + mmPan = vaddq_f32(mmPan, vdupq_n_f32(1.0f)); + mmPan = vmulq_n_f32(mmPan, 0.5f * panSize); + mmPan = vaddq_f32(mmPan, vdupq_n_f32(0.5f)); + mmPan = vminq_f32(mmPan, vdupq_n_f32(panSize)); + mmPan = vmaxq_f32(mmPan, vdupq_n_f32(0.0f)); + int32x4_t mmIdx = vcvtq_s32_f32(mmPan); + vst1q_s32(indices, mmIdx); + + leftBuffer[0] *= panData[indices[0]]; + rightBuffer[0] *= panData[panSize - indices[0] - 1]; + leftBuffer[1] *= panData[indices[1]]; + rightBuffer[1] *= panData[panSize - indices[1]- 1]; + leftBuffer[2] *= panData[indices[2]]; + rightBuffer[2] *= panData[panSize - indices[2]- 1]; + leftBuffer[3] *= panData[indices[3]]; + rightBuffer[3] *= panData[panSize - indices[3]- 1]; + + incrementAll<4>(panEnvelope, leftBuffer, rightBuffer); + } +} + +class PanFixture : 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 }; + pan.resize(state.range(0)); + right.resize(state.range(0)); + left.resize(state.range(0)); + + if (!willAlign<16>(pan.data(), left.data(), right.data())) + std::cout << "Will not align!" << '\n'; + absl::c_generate(pan, [&]() { return dist(gen); }); + absl::c_generate(left, [&]() { return dist(gen); }); + absl::c_generate(right, [&]() { return dist(gen); }); + } + + void TearDown(const ::benchmark::State& /* state */) { + + } + + aligned_vector pan; + aligned_vector right; + aligned_vector left; +}; + +BENCHMARK_DEFINE_F(PanFixture, PanScalar)(benchmark::State& state) { + for (auto _ : state) + { + panScalar(pan.data(), left.data(), right.data(), state.range(0)); + } +} + +BENCHMARK_DEFINE_F(PanFixture, PanSIMD)(benchmark::State& state) { + for (auto _ : state) + { + panSIMD(pan.data(), left.data(), right.data(), state.range(0)); + } +} + +BENCHMARK_DEFINE_F(PanFixture, PanSfizz)(benchmark::State& state) { + for (auto _ : state) + { + sfz::pan(pan.data(), left.data(), right.data(), state.range(0)); + } +} + +// Register the function as a benchmark +BENCHMARK_REGISTER_F(PanFixture, PanScalar)->RangeMultiplier(4)->Range((1 << 4), (1 << 12)); +BENCHMARK_REGISTER_F(PanFixture, PanSIMD)->RangeMultiplier(4)->Range((1 << 4), (1 << 12)); +BENCHMARK_REGISTER_F(PanFixture, PanSfizz)->RangeMultiplier(4)->Range((1 << 4), (1 << 12)); +BENCHMARK_MAIN(); diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 0ecd4494..d53ff15f 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -147,6 +147,12 @@ if (TARGET bm_resample) add_dependencies(sfizz_benchmarks bm_resample) endif() +if (SFIZZ_SYSTEM_PROCESSOR MATCHES "armv7l") + sfizz_add_benchmark(bm_pan_arm BM_pan_arm.cpp ../src/sfizz/Panning.cpp) + target_link_libraries(bm_pan_arm PRIVATE sfizz-jsl) + add_dependencies(sfizz_benchmarks bm_pan_arm) +endif() + configure_file("sample.wav" "${CMAKE_BINARY_DIR}/benchmarks/sample1.wav" COPYONLY) configure_file("sample.wav" "${CMAKE_BINARY_DIR}/benchmarks/sample2.wav" COPYONLY) configure_file("sample.wav" "${CMAKE_BINARY_DIR}/benchmarks/sample3.wav" COPYONLY) diff --git a/cmake/SfizzConfig.cmake b/cmake/SfizzConfig.cmake index 15376c7d..95b0adaa 100644 --- a/cmake/SfizzConfig.cmake +++ b/cmake/SfizzConfig.cmake @@ -60,9 +60,9 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") add_compile_options(-Werror=return-type) if (SFIZZ_SYSTEM_PROCESSOR MATCHES "^(i.86|x86_64)$") add_compile_options(-msse2) - elseif (SFIZZ_SYSTEM_PROCESSOR MATCHES "^(armv.*)$") - add_compile_options(-mfloat-abi=hard) + elseif(SFIZZ_SYSTEM_PROCESSOR MATCHES "^(arm.*)$") add_compile_options(-mfpu=neon) + add_compile_options(-mfloat-abi=hard) endif() elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") set(CMAKE_CXX_STANDARD 17) diff --git a/cmake/SfizzSIMDSourceFiles.cmake b/cmake/SfizzSIMDSourceFiles.cmake index 3cc9afdd..83957c5d 100644 --- a/cmake/SfizzSIMDSourceFiles.cmake +++ b/cmake/SfizzSIMDSourceFiles.cmake @@ -3,6 +3,7 @@ macro(sfizz_add_simd_sources SOURCES_VAR PREFIX) list (APPEND ${SOURCES_VAR} ${PREFIX}/sfizz/SIMDHelpers.cpp + ${PREFIX}/sfizz/simd/HelpersNEON.cpp ${PREFIX}/sfizz/simd/HelpersSSE.cpp ${PREFIX}/sfizz/simd/HelpersAVX.cpp) diff --git a/src/sfizz/Panning.cpp b/src/sfizz/Panning.cpp index 299f2955..f7b96bd9 100644 --- a/src/sfizz/Panning.cpp +++ b/src/sfizz/Panning.cpp @@ -1,11 +1,21 @@ #include "Panning.h" +#include "MathHelpers.h" #include #include +#if SFIZZ_HAVE_NEON +#include +#include "simd/Common.h" +using Type = float; +constexpr unsigned TypeAlignment = 4; +constexpr unsigned ByteAlignment = TypeAlignment * sizeof(Type); +#endif + + namespace sfz { -// Number of elements in the table, odd for equal volume at center -constexpr int panSize = 4095; + +constexpr int panSize { 4095 }; // Table of pan values for the left channel, extra element for safety static const auto panData = []() @@ -25,35 +35,134 @@ static const auto panData = []() float panLookup(float pan) { // reduce range, round to nearest - int index = lroundPositive(pan * (panSize - 1)); + const int index = lroundPositive(pan * (panSize - 1)); return panData[index]; } +inline void tickPan(const float* pan, float* leftBuffer, float* rightBuffer) +{ + auto p = (*pan + 1.0f) * 0.5f; + p = clamp(p, 0.0f, 1.0f); + *leftBuffer *= panLookup(p); + *rightBuffer *= panLookup(1 - p); +} + void pan(const float* panEnvelope, float* leftBuffer, float* rightBuffer, unsigned size) noexcept { const auto sentinel = panEnvelope + size; + +#if SFIZZ_HAVE_NEON + const auto firstAligned = prevAligned(panEnvelope + TypeAlignment - 1); + + if (willAlign(panEnvelope, leftBuffer, rightBuffer) && (firstAligned < sentinel)) { + while (panEnvelope < firstAligned) { + tickPan(panEnvelope, leftBuffer, rightBuffer); + incrementAll(panEnvelope, leftBuffer, rightBuffer); + } + + uint32_t indices[TypeAlignment]; + float leftPan[TypeAlignment]; + float rightPan[TypeAlignment]; + const auto lastAligned = prevAligned(sentinel); + while (panEnvelope < lastAligned) { + float32x4_t mmPan = vld1q_f32(panEnvelope); + mmPan = vaddq_f32(mmPan, vdupq_n_f32(1.0f)); + mmPan = vmulq_n_f32(mmPan, 0.5f * panSize); + mmPan = vaddq_f32(mmPan, vdupq_n_f32(0.5f)); + uint32x4_t mmIdx = vcvtq_u32_f32(mmPan); + mmIdx = vminq_u32(mmIdx, vdupq_n_u32(panSize - 1)); + mmIdx = vmaxq_u32(mmIdx, vdupq_n_u32(0)); + vst1q_u32(indices, mmIdx); + + leftPan[0] = panData[indices[0]]; + rightPan[0] = panData[panSize - indices[0] - 1]; + leftPan[1] = panData[indices[1]]; + rightPan[1] = panData[panSize - indices[1] - 1]; + leftPan[2] = panData[indices[2]]; + rightPan[2] = panData[panSize - indices[2] - 1]; + leftPan[3] = panData[indices[3]]; + rightPan[3] = panData[panSize - indices[3] - 1]; + + vst1q_f32(leftBuffer, vmulq_f32(vld1q_f32(leftBuffer), vld1q_f32(leftPan))); + vst1q_f32(rightBuffer, vmulq_f32(vld1q_f32(rightBuffer), vld1q_f32(rightPan))); + + incrementAll(panEnvelope, leftBuffer, rightBuffer); + } + } +#endif + while (panEnvelope < sentinel) { - auto p =(*panEnvelope + 1.0f) * 0.5f; - p = clamp(p, 0.0f, 1.0f); - *leftBuffer *= panLookup(p); - *rightBuffer *= panLookup(1 - p); + tickPan(panEnvelope, leftBuffer, rightBuffer); incrementAll(panEnvelope, leftBuffer, rightBuffer); } + +} + +inline void tickWidth(const float* width, float* leftBuffer, float* rightBuffer) +{ + float w = (*width + 1.0f) * 0.5f; + w = clamp(w, 0.0f, 1.0f); + const auto coeff1 = panLookup(w); + const auto coeff2 = panLookup(1 - w); + const auto l = *leftBuffer; + const auto r = *rightBuffer; + *leftBuffer = l * coeff2 + r * coeff1; + *rightBuffer = l * coeff1 + r * coeff2; } void width(const float* widthEnvelope, float* leftBuffer, float* rightBuffer, unsigned size) noexcept { const auto sentinel = widthEnvelope + size; + +#if SFIZZ_HAVE_NEON + const auto firstAligned = prevAligned(widthEnvelope + TypeAlignment - 1); + + if (willAlign(widthEnvelope, leftBuffer, rightBuffer) && firstAligned < sentinel) { + while (widthEnvelope < firstAligned) { + tickWidth(widthEnvelope, leftBuffer, rightBuffer); + incrementAll(widthEnvelope, leftBuffer, rightBuffer); + } + + uint32_t indices[TypeAlignment]; + float coeff1[TypeAlignment]; + float coeff2[TypeAlignment]; + const auto lastAligned = prevAligned(sentinel); + while (widthEnvelope < lastAligned) { + float32x4_t mmWidth = vld1q_f32(widthEnvelope); + mmWidth = vaddq_f32(mmWidth, vdupq_n_f32(1.0f)); + mmWidth = vmulq_n_f32(mmWidth, 0.5f * panSize); + mmWidth = vaddq_f32(mmWidth, vdupq_n_f32(0.5f)); + uint32x4_t mmIdx = vcvtq_u32_f32(mmWidth); + mmIdx = vminq_u32(mmIdx, vdupq_n_u32(panSize - 1)); + mmIdx = vmaxq_u32(mmIdx, vdupq_n_u32(0)); + vst1q_u32(indices, mmIdx); + + coeff1[0] = panData[indices[0]]; + coeff2[0] = panData[panSize - indices[0] - 1]; + coeff1[1] = panData[indices[1]]; + coeff2[1] = panData[panSize - indices[1] - 1]; + coeff1[2] = panData[indices[2]]; + coeff2[2] = panData[panSize - indices[2] - 1]; + coeff1[3] = panData[indices[3]]; + coeff2[3] = panData[panSize - indices[3] - 1]; + + float32x4_t mmCoeff1 = vld1q_f32(coeff1); + float32x4_t mmCoeff2 = vld1q_f32(coeff2); + float32x4_t mmLeft = vld1q_f32(leftBuffer); + float32x4_t mmRight = vld1q_f32(rightBuffer); + + vst1q_f32(leftBuffer, vaddq_f32(vmulq_f32(mmCoeff2, mmLeft), vmulq_f32(mmCoeff1, mmRight))); + vst1q_f32(rightBuffer, vaddq_f32(vmulq_f32(mmCoeff1, mmLeft), vmulq_f32(mmCoeff2, mmRight))); + + incrementAll(widthEnvelope, leftBuffer, rightBuffer); + } + } +#endif // SFIZZ_HAVE_NEON + while (widthEnvelope < sentinel) { - float w = (*widthEnvelope + 1.0f) * 0.5f; - w = clamp(w, 0.0f, 1.0f); - const auto coeff1 = panLookup(w); - const auto coeff2 = panLookup(1 - w); - const auto l = *leftBuffer; - const auto r = *rightBuffer; - *leftBuffer = l * coeff2 + r * coeff1; - *rightBuffer = l * coeff1 + r * coeff2; + tickWidth(widthEnvelope, leftBuffer, rightBuffer); incrementAll(widthEnvelope, leftBuffer, rightBuffer); } } + } diff --git a/src/sfizz/Panning.h b/src/sfizz/Panning.h index 75d31ea4..eddb4d48 100644 --- a/src/sfizz/Panning.h +++ b/src/sfizz/Panning.h @@ -6,13 +6,16 @@ namespace sfz { /** - * @brief Lookup a value from the pan table - * - * @param pan - * @return float - */ +* @brief Lookup a value from the pan table +* No check is done on the range, needs to be capped +* between 0 and panSize. +* +* @param pan +* @return float +*/ float panLookup(float pan); + /** * @brief Pans a mono signal left or right * diff --git a/src/sfizz/effects/Width.cpp b/src/sfizz/effects/Width.cpp index 52d2876b..a9c3c202 100644 --- a/src/sfizz/effects/Width.cpp +++ b/src/sfizz/effects/Width.cpp @@ -15,8 +15,8 @@ */ #include "Width.h" -#include "Opcode.h" #include "Panning.h" +#include "Opcode.h" #include "absl/memory/memory.h" namespace sfz { diff --git a/src/sfizz/simd/Common.h b/src/sfizz/simd/Common.h index 6fdddeab..493f46f8 100644 --- a/src/sfizz/simd/Common.h +++ b/src/sfizz/simd/Common.h @@ -24,7 +24,7 @@ T* prevAligned(const T* ptr) template bool unaligned(const T* ptr) { - return (reinterpret_cast(ptr) & ByteAlignmentMask(N) )!= 0; + return (reinterpret_cast(ptr) & ByteAlignmentMask(N) ) != 0; } template @@ -32,3 +32,20 @@ bool unaligned(const T* ptr1, Args... rest) { return unaligned(ptr1) || unaligned(rest...); } + +template +bool willAlign(const T* ptr1, const T* ptr2) +{ + const auto p1 = reinterpret_cast(ptr1); + const auto p2 = reinterpret_cast(ptr2); + return ( + (p1 & ByteAlignmentMask(N)) == (p2 & ByteAlignmentMask(N)) + && ((p1 & ByteAlignmentMask(sizeof(T))) == 0) + ); +} + +template +bool willAlign(const T* ptr1, const T* ptr2, Args... rest) +{ + return willAlign(ptr1, ptr2) && willAlign(ptr2, rest...); +} diff --git a/src/sfizz/simd/HelpersNEON.cpp b/src/sfizz/simd/HelpersNEON.cpp new file mode 100644 index 00000000..746deb5e --- /dev/null +++ b/src/sfizz/simd/HelpersNEON.cpp @@ -0,0 +1,16 @@ +// 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 "HelpersNEON.h" +#include "Common.h" + +#if SFIZZ_HAVE_NEON +#include +#endif + +using Type = float; +constexpr unsigned TypeAlignment = 4; +constexpr unsigned ByteAlignment = TypeAlignment * sizeof(Type); diff --git a/src/sfizz/simd/HelpersNEON.h b/src/sfizz/simd/HelpersNEON.h new file mode 100644 index 00000000..2746084a --- /dev/null +++ b/src/sfizz/simd/HelpersNEON.h @@ -0,0 +1,7 @@ +// 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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0090620c..eba64aea 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -46,7 +46,7 @@ set(SFIZZ_TEST_SOURCES ) add_executable(sfizz_tests ${SFIZZ_TEST_SOURCES}) -target_link_libraries(sfizz_tests PRIVATE sfizz::sfizz) +target_link_libraries(sfizz_tests PRIVATE sfizz::sfizz sfizz-jsl) sfizz_enable_lto_if_needed(sfizz_tests) sfizz_enable_fast_math(sfizz_tests) # target_link_libraries(sfizz_tests PRIVATE absl::strings absl::str_format absl::flat_hash_map cnpy absl::span absl::algorithm) diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 27502881..bbf9b70b 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.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 "sfizz/simd/Common.h" #include "sfizz/SIMDHelpers.h" #include "sfizz/Panning.h" #include "catch2/catch.hpp" @@ -12,8 +13,12 @@ #include #include #include +#include using namespace Catch::literals; +template +using aligned_vector = std::vector>; + constexpr int smallBufferSize { 3 }; constexpr int bigBufferSize { 4095 }; constexpr int medBufferSize { 127 }; @@ -49,6 +54,40 @@ inline bool approxEqual(absl::Span lhs, absl::Span rhs, return true; } +TEST_CASE("[Helpers] willAlign, prevAligned and unaligned tests") +{ + aligned_vector array(16); + REQUIRE( !unaligned<16>(&array[0]) ); + REQUIRE( !unaligned<16>(&array[4]) ); + REQUIRE( !unaligned<32>(&array[8]) ); + REQUIRE( unaligned<32>(&array[7]) ); + REQUIRE( unaligned<32>(&array[4]) ); + REQUIRE( unaligned<16>(&array[3]) ); + REQUIRE( !unaligned<16>(&array[0], &array[4]) ); + REQUIRE( !unaligned<16>(&array[0], &array[4], &array[8]) ); + REQUIRE( unaligned<16>(&array[0], &array[3], &array[8]) ); + + REQUIRE( prevAligned<16>(&array[0]) == &array[0] ); + REQUIRE( prevAligned<16>(&array[1]) == &array[0] ); + REQUIRE( prevAligned<16>(&array[2]) == &array[0] ); + REQUIRE( prevAligned<16>(&array[3]) == &array[0] ); + REQUIRE( prevAligned<16>(&array[4]) == &array[4] ); + REQUIRE( prevAligned<16>(&array[5]) == &array[4] ); + REQUIRE( prevAligned<32>(&array[7]) == &array[0] ); + REQUIRE( prevAligned<32>(&array[8]) == &array[8] ); + REQUIRE( prevAligned<32>(&array[9]) == &array[8] ); + + REQUIRE( willAlign<16>(&array[0], &array[4]) ); + REQUIRE( willAlign<16>(&array[5], &array[1]) ); + REQUIRE( !willAlign<16>(&array[2], &array[1]) ); + REQUIRE( willAlign<32>(&array[9], &array[1]) ); + REQUIRE( willAlign<32>(&array[8], &array[0]) ); + + float* meanPointer = (float*)((uint8_t*)&array[1] + 1); + REQUIRE( !willAlign<16>(&array[0], meanPointer) ); + REQUIRE( !willAlign<16>(&array[4], &array[0], meanPointer) ); +} + TEST_CASE("[Helpers] Interleaved read") { std::array input { 0.0f, 10.0f, 1.0f, 11.0f, 2.0f, 12.0f, 3.0f, 13.0f, 4.0f, 14.0f, 5.0f, 15.0f, 6.0f, 16.0f, 7.0f, 17.0f }; @@ -834,62 +873,71 @@ TEST_CASE("[Helpers] Diff (SIMD vs Scalar)") REQUIRE(approxEqual(outputScalar, outputSIMD)); } -TEST_CASE("[Helpers] Pan Scalar") +template +void panTest(float leftValue, float rightValue, float panValue, float expectedLeft, float expectedRight) { - std::array leftValue { 1.0f }; - std::array rightValue { 1.0f }; - auto left = absl::MakeSpan(leftValue); - auto right = absl::MakeSpan(rightValue); - SECTION("Pan = 0") - { - std::array pan { 0.0f }; - sfz::pan(pan, left, right); - REQUIRE(left[0] == Approx(0.70711f).margin(0.001f)); - REQUIRE(right[0] == Approx(0.70711f).margin(0.001f)); - } - SECTION("Pan = 1") - { - std::array pan { 1.0f }; - sfz::pan(pan, left, right); - REQUIRE(left[0] == Approx(0.0f).margin(0.001f)); - REQUIRE(right[0] == Approx(1.0f).margin(0.001f)); - } - SECTION("Pan = -1") - { - std::array pan { -1.0f }; - sfz::pan(pan, left, right); - REQUIRE(left[0] == Approx(1.0f).margin(0.001f)); - REQUIRE(right[0] == Approx(0.0f).margin(0.001f)); - } + std::vector leftChannel(N); + std::vector rightChannel(N); + std::vector pan(N); + std::vector expectedLeftChannel(N); + std::vector expectedRightChannel(N); + std::fill(leftChannel.begin(), leftChannel.end(), leftValue); + std::fill(expectedLeftChannel.begin(), expectedLeftChannel.end(), expectedLeft); + std::fill(rightChannel.begin(), rightChannel.end(), rightValue); + std::fill(expectedRightChannel.begin(), expectedRightChannel.end(), expectedRight); + std::fill(pan.begin(), pan.end(), panValue); + auto left = absl::MakeSpan(leftChannel); + auto right = absl::MakeSpan(rightChannel); + sfz::pan(pan, left, right); + REQUIRE_THAT( leftChannel, Catch::Approx(expectedLeftChannel).margin(0.001) ); + REQUIRE_THAT( rightChannel, Catch::Approx(expectedRightChannel).margin(0.001) ); } -TEST_CASE("[Helpers] Width Scalar") +template +void widthTest(float leftValue, float rightValue, float widthValue, float expectedLeft, float expectedRight) { - std::array leftValue { 1.0f }; - std::array rightValue { 1.0f }; - auto left = absl::MakeSpan(leftValue); - auto right = absl::MakeSpan(rightValue); - SECTION("width = 1") - { - std::array width { 1.0f }; - sfz::width(width, left, right); - REQUIRE(left[0] == Approx(1.0f).margin(0.001f)); - REQUIRE(right[0] == Approx(1.0f).margin(0.001f)); - } - SECTION("width = 0") - { - std::array width { 0.0f }; - sfz::width(width, left, right); - REQUIRE(left[0] == Approx(1.414f).margin(0.001f)); - REQUIRE(right[0] == Approx(1.414f).margin(0.001f)); - } - SECTION("width = -1") - { - std::array width { -1.0f }; - sfz::width(width, left, right); - REQUIRE(left[0] == Approx(1.0f).margin(0.001f)); - REQUIRE(right[0] == Approx(1.0f).margin(0.001f)); - } + std::vector leftChannel(N); + std::vector rightChannel(N); + std::vector width(N); + std::vector expectedLeftChannel(N); + std::vector expectedRightChannel(N); + std::fill(leftChannel.begin(), leftChannel.end(), leftValue); + std::fill(expectedLeftChannel.begin(), expectedLeftChannel.end(), expectedLeft); + std::fill(rightChannel.begin(), rightChannel.end(), rightValue); + std::fill(expectedRightChannel.begin(), expectedRightChannel.end(), expectedRight); + std::fill(width.begin(), width.end(), widthValue); + auto left = absl::MakeSpan(leftChannel); + auto right = absl::MakeSpan(rightChannel); + sfz::width(width, left, right); + REQUIRE_THAT( leftChannel, Catch::Approx(expectedLeftChannel).margin(0.001) ); + REQUIRE_THAT( rightChannel, Catch::Approx(expectedRightChannel).margin(0.001) ); +} + +TEST_CASE("[Helpers] Pan tests") +{ + // Testing different sizes to check that SIMD and unrolling works as expected + panTest<1>(1.0f, 1.0f, 0.0f, 0.70711f, 0.70711f); + panTest<1>(1.0f, 1.0f, 1.0f, 0.0f, 1.0f); + panTest<1>(1.0f, 1.0f, -1.0f, 1.0f, 0.0f); + panTest<3>(1.0f, 1.0f, 0.0f, 0.70711f, 0.70711f); + panTest<3>(1.0f, 1.0f, 1.0f, 0.0f, 1.0f); + panTest<3>(1.0f, 1.0f, -1.0f, 1.0f, 0.0f); + panTest<10>(1.0f, 1.0f, 0.0f, 0.70711f, 0.70711f); + panTest<10>(1.0f, 1.0f, 1.0f, 0.0f, 1.0f); + panTest<10>(1.0f, 1.0f, -1.0f, 1.0f, 0.0f); +} + +TEST_CASE("[Helpers] Width tests") +{ + widthTest<1>(1.0f, 1.0f, 0.0f, 1.414f, 1.414f); + widthTest<1>(1.0f, 1.0f, 1.0f, 1.0f, 1.0f); + widthTest<1>(1.0f, 1.0f, -1.0f, 1.0f, 1.0f); + widthTest<3>(1.0f, 1.0f, 0.0f, 1.414f, 1.414f); + widthTest<3>(1.0f, 1.0f, 1.0f, 1.0f, 1.0f); + widthTest<3>(1.0f, 1.0f, -1.0f, 1.0f, 1.0f); + widthTest<10>(1.0f, 1.0f, 0.0f, 1.414f, 1.414f); + widthTest<10>(1.0f, 1.0f, 1.0f, 1.0f, 1.0f); + widthTest<10>(1.0f, 1.0f, -1.0f, 1.0f, 1.0f); } TEST_CASE("[Helpers] clampAll")