From 834a907c2fae0aed17479145fb3cce793735b28c Mon Sep 17 00:00:00 2001 From: paul Date: Sat, 3 Aug 2019 18:14:58 +0200 Subject: [PATCH] SSE work --- CMakeLists.txt | 72 ++- benchmarks/AudioBuffer_Fill.cpp | 71 --- benchmarks/Stereo_Fill.cpp | 39 ++ ...d_Read.cpp => Stereo_Read_Interleaved.cpp} | 12 +- sources/AudioBuffer.h | 255 --------- sources/Buffer.h | 43 +- sources/FilePool.h | 4 +- sources/Globals.h | 9 +- sources/StereoBuffer.cpp | 58 ++ sources/StereoBuffer.h | 136 +++++ tests/AudioBuffer.cpp | 524 ------------------ tests/{Buffer.cpp => BufferT.cpp} | 62 ++- tests/{Files.cpp => FilesT.cpp} | 0 tests/{Helpers.cpp => HelpersT.cpp} | 0 tests/{Main.cpp => MainT.cpp} | 0 tests/{Opcode.cpp => OpcodeT.cpp} | 0 tests/{Range.cpp => RangeT.cpp} | 0 tests/{Regex.cpp => RegexT.cpp} | 0 tests/{Region.cpp => RegionT.cpp} | 0 tests/StereoBufferT.cpp | 315 +++++++++++ 20 files changed, 669 insertions(+), 931 deletions(-) delete mode 100644 benchmarks/AudioBuffer_Fill.cpp create mode 100644 benchmarks/Stereo_Fill.cpp rename benchmarks/{AudioBuffer_Interleaved_Read.cpp => Stereo_Read_Interleaved.cpp} (71%) delete mode 100644 sources/AudioBuffer.h create mode 100644 sources/StereoBuffer.cpp create mode 100644 sources/StereoBuffer.h delete mode 100644 tests/AudioBuffer.cpp rename tests/{Buffer.cpp => BufferT.cpp} (68%) rename tests/{Files.cpp => FilesT.cpp} (100%) rename tests/{Helpers.cpp => HelpersT.cpp} (100%) rename tests/{Main.cpp => MainT.cpp} (100%) rename tests/{Opcode.cpp => OpcodeT.cpp} (100%) rename tests/{Range.cpp => RangeT.cpp} (100%) rename tests/{Regex.cpp => RegexT.cpp} (100%) rename tests/{Region.cpp => RegionT.cpp} (100%) create mode 100644 tests/StereoBufferT.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b53ab80..0e9d667f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,47 +13,65 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT ANDROID) add_link_options(-stdlib=libc++) # New command on CMake master, not in 3.12 release endif() -include(CheckCXXCompilerFlag) -# CHECK_CXX_COMPILER_FLAG("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE) -# if(COMPILER_SUPPORTS_MARCH_NATIVE) -# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") -# endif() +if (UNIX) + add_compile_options(-Wall) + add_compile_options(-Wextra) + # add_compile_options(-fno-exceptions) + add_compile_options(-ffast-math) + add_compile_options(-fno-rtti) + add_compile_options(-fno-omit-frame-pointer) +endif() -# CHECK_CXX_COMPILER_FLAG("-ffast-math" COMPILER_SUPPORTS_FASTMATH) -# if(COMPILER_SUPPORTS_FASTMATH) -# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffast-math") -# endif() +message("Cmake flags: ${CMAKE_CXX_FLAGS}") +# Check SIMD files +include(CheckIncludeFiles) +CHECK_INCLUDE_FILES(x86intrin.h HAVE_X86INTRIN_H) +CHECK_INCLUDE_FILES(arm_neon.h HAVE_ARM_NEON_H) +if (HAVE_X86INTRIN_H) + if(UNIX) + add_compile_options(-DHAVE_X86INTRIN_H) + endif() +endif() +if (HAVE_ARM_NEON_H) + if(UNIX) + add_compile_options(-DHAVE_ARM_NEON_H) + endif() +endif() + +# Build options and includes set(BUILD_TESTING OFF CACHE BOOL "Disable Abseil's tests") +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "Disable Google Benchmark tests") +set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "Disable Google Benchmark install") add_subdirectory(external/abseil-cpp) add_subdirectory(external/spdlog) add_subdirectory(external/Catch2) add_subdirectory(external/cxxopts) - -set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "Disable Google Benchmark tests") -set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "Disable Google Benchmark install") add_subdirectory(external/benchmark) +# Export the compile_commands.json file set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + set(COMMON_SOURCES sources/Opcode.cpp sources/Synth.cpp sources/FilePool.cpp sources/Region.cpp + sources/StereoBuffer.cpp sources/Parser.cpp ) set(TEST_SOURCES - tests/Regex.cpp - tests/Helpers.cpp - tests/Region.cpp - tests/Range.cpp - tests/Opcode.cpp - tests/Buffer.cpp - tests/AudioBuffer.cpp - tests/Files.cpp - tests/Main.cpp + tests/RegexT.cpp + tests/HelpersT.cpp + tests/RegionT.cpp + tests/RangeT.cpp + tests/OpcodeT.cpp + tests/BufferT.cpp + tests/StereoBufferT.cpp + tests/FilesT.cpp + tests/MainT.cpp ) @@ -87,17 +105,17 @@ target_include_directories(sfizz_tests SYSTEM PRIVATE sources) file(COPY "tests" DESTINATION ${CMAKE_BINARY_DIR}) ############################### -add_executable(bench_ab_fill benchmarks/AudioBuffer_Fill.cpp ${COMMON_SOURCES}) +add_executable(bench_fill benchmarks/Stereo_Fill.cpp ${COMMON_SOURCES}) # Per OS properties if(UNIX) - target_link_libraries(bench_ab_fill stdc++fs) + target_link_libraries(bench_fill stdc++fs) endif(UNIX) -target_link_libraries(bench_ab_fill absl::strings benchmark) +target_link_libraries(bench_fill absl::strings benchmark) ############################### -add_executable(bench_ab_read benchmarks/AudioBuffer_Interleaved_Read.cpp ${COMMON_SOURCES}) +add_executable(bench_read benchmarks/Stereo_Read_Interleaved.cpp ${COMMON_SOURCES}) # Per OS properties if(UNIX) - target_link_libraries(bench_ab_read stdc++fs) + target_link_libraries(bench_read stdc++fs) endif(UNIX) -target_link_libraries(bench_ab_read absl::strings benchmark) \ No newline at end of file +target_link_libraries(bench_read absl::strings benchmark) \ No newline at end of file diff --git a/benchmarks/AudioBuffer_Fill.cpp b/benchmarks/AudioBuffer_Fill.cpp deleted file mode 100644 index 752a47f2..00000000 --- a/benchmarks/AudioBuffer_Fill.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include -#include "../sources/AudioBuffer.h" -#include -#include - -static void Joint_Fill_float(benchmark::State& state) { - AudioBuffer buffer (100001); - float fillValue = 0.0f; - for (auto _ : state) { - buffer.fill(fillValue); - fillValue += 1.0f; - } -} -// Register the function as a benchmark -BENCHMARK(Joint_Fill_float); - -static void Split_Fill_float(benchmark::State& state) { - SplitAudioBuffer buffer (100001); - float fillValue = 0.0f; - for (auto _ : state) { - buffer.fill(fillValue); - fillValue += 1.0f; - } -} -BENCHMARK(Split_Fill_float); - -static void Joint_Fill_float_SSE(benchmark::State& state) { - AudioBuffer buffer (100001); - float fillValue = 0.0f; - for (auto _ : state) { - buffer.fill(fillValue); - fillValue += 1.0f; - } -} -// Register the function as a benchmark -BENCHMARK(Joint_Fill_float_SSE); - - -static void Split_Fill_float_SSE(benchmark::State& state) { - SplitAudioBuffer buffer (100001); - float fillValue = 0.0f; - for (auto _ : state) { - buffer.fill(fillValue); - fillValue += 1.0f; - } -} - -BENCHMARK(Split_Fill_float_SSE); - -static void Joint_Fill_double(benchmark::State& state) { - AudioBuffer buffer (100001); - double fillValue = 0.0; - for (auto _ : state) { - buffer.fill(fillValue); - fillValue += 1.0; - } -} -// Register the function as a benchmark -BENCHMARK(Joint_Fill_double); - -static void Split_Fill_double(benchmark::State& state) { - SplitAudioBuffer buffer (100001); - double fillValue = 0.0; - for (auto _ : state) { - buffer.fill(fillValue); - fillValue += 1.0; - } -} - -BENCHMARK(Split_Fill_double); -BENCHMARK_MAIN(); \ No newline at end of file diff --git a/benchmarks/Stereo_Fill.cpp b/benchmarks/Stereo_Fill.cpp new file mode 100644 index 00000000..7f55cd0d --- /dev/null +++ b/benchmarks/Stereo_Fill.cpp @@ -0,0 +1,39 @@ +#include +#include "../sources/StereoBuffer.h" +#include +#include + +static void Fill_float(benchmark::State& state) { + StereoBuffer buffer (100001); + float fillValue = 0.0f; + for (auto _ : state) { + buffer.fill(fillValue); + fillValue += 1.0f; + } +} +// Register the function as a benchmark +BENCHMARK(Fill_float); + +static void Fill_float_SSE(benchmark::State& state) { + StereoBuffer buffer (100001); + float fillValue = 0.0f; + for (auto _ : state) { + buffer.fill(fillValue); + fillValue += 1.0f; + } +} +// Register the function as a benchmark +BENCHMARK(Fill_float_SSE); + +static void Fill_double(benchmark::State& state) { + StereoBuffer buffer (100001); + double fillValue = 0.0; + for (auto _ : state) { + buffer.fill(fillValue); + fillValue += 1.0; + } +} +// Register the function as a benchmark +BENCHMARK(Fill_double); + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/benchmarks/AudioBuffer_Interleaved_Read.cpp b/benchmarks/Stereo_Read_Interleaved.cpp similarity index 71% rename from benchmarks/AudioBuffer_Interleaved_Read.cpp rename to benchmarks/Stereo_Read_Interleaved.cpp index ee7cdafc..945393fd 100644 --- a/benchmarks/AudioBuffer_Interleaved_Read.cpp +++ b/benchmarks/Stereo_Read_Interleaved.cpp @@ -1,10 +1,10 @@ #include -#include "../sources/AudioBuffer.h" +#include "../sources/StereoBuffer.h" #include #include static void Interleaved_Read(benchmark::State& state) { - AudioBuffer buffer (state.range(0)); + StereoBuffer buffer (state.range(0)); std::vector input (state.range(0) * 2); std::iota(input.begin(), input.end(), 1.0f); for (auto _ : state) { @@ -14,21 +14,21 @@ static void Interleaved_Read(benchmark::State& state) { BENCHMARK(Interleaved_Read)->Range((8<<10) + 3, (8<<20) + 3); static void Interleaved_Read_SSE(benchmark::State& state) { - AudioBuffer buffer (state.range(0)); + StereoBuffer buffer (state.range(0)); std::vector input (state.range(0) * 2); std::iota(input.begin(), input.end(), 1.0f); for (auto _ : state) { - buffer.readInterleaved(input.data(), state.range(0)); + buffer.readInterleaved(input.data(), state.range(0)); } } BENCHMARK(Interleaved_Read_SSE)->Range((8<<10) + 3, (8<<20) + 3); static void Unaligned_Interleaved_Read_SSE(benchmark::State& state) { - AudioBuffer buffer (state.range(0)); + StereoBuffer buffer (state.range(0)); std::vector input (state.range(0) * 2); std::iota(input.begin(), input.end(), 1.0f); for (auto _ : state) { - buffer.readInterleaved(input.data() + 1, state.range(0) - 1); + buffer.readInterleaved(input.data() + 1, state.range(0) - 1); } } BENCHMARK(Unaligned_Interleaved_Read_SSE)->Range((8<<10) + 3, (8<<20) + 3); diff --git a/sources/AudioBuffer.h b/sources/AudioBuffer.h deleted file mode 100644 index fbd2197f..00000000 --- a/sources/AudioBuffer.h +++ /dev/null @@ -1,255 +0,0 @@ -#pragma once -#include "Buffer.h" -#include "Helpers.h" -#include "Globals.h" -#include -#include -#include - -template - -class AudioBuffer -{ -public: - AudioBuffer() = default; - AudioBuffer(int numFrames) - { - DBG("Building an audiobuffer of size " << numFrames); - resize(numFrames); - } - - bool resize(int numFrames) - { - // should have a positive number of frames... - ASSERT(numFrames >= 0); - padding = TypeAlignment - (numFrames & TypeAlignmentMask); - totalSize = NumChannels * (numFrames + padding); - if (buffer.resize(static_cast(totalSize))) - { - this->numFrames = numFrames; - for (auto channelIndex = 0; channelIndex < NumChannels; ++channelIndex) - channels[channelIndex] = buffer.data() + channelIndex * (numFrames + padding); - return true; - } - - return false; - } - - Type& getSample(int channelIndex, int sampleIndex) noexcept - { - ASSERT(channelIndex >= 0); - ASSERT(sampleIndex >= 0); - return *(channels[channelIndex] + sampleIndex); - } - - template - void fill(Type value) noexcept - { - if constexpr (op == VectorOperations::sse && std::is_same::value) - { - static_assert(Alignment == 16, "Wrong alignment"); - const __m128 mmValue = _mm_set_ps1(value); - for(auto i = 0; i < NumChannels; ++i) - for (auto mm = (__m128*)alignedBegin(i); mm < (__m128*)alignedEnd(i); mm++) - _mm_store_ps((float*)mm, mmValue); - } - else - { - for(auto i = 0; i < NumChannels; ++i) - std::fill(begin(i), end(i), value); - } - } - - template - void readInterleaved(float* input, int numFrames) noexcept - { - ASSERT(this->numFrames >= numFrames); - if constexpr (op == VectorOperations::sse && std::is_same::value && NumChannels == 2) - { - static_assert(Alignment == 16, "Wrong alignment"); - const int residualFrames = numFrames & (2 * TypeAlignment - 1); - const int lastAligned = numFrames - residualFrames; - float* in = input; - float* out0 = getChannel(0); - float* out1 = getChannel(1); - const float* end = input + 2 * lastAligned; - while (in < end) - { - const auto input0 = _mm_loadu_ps(in); - in += 4; - const auto input1 = _mm_loadu_ps(in); - in += 4; - const auto intermediate0 = _mm_unpacklo_ps(input0, input1); - const auto intermediate1 = _mm_unpackhi_ps(input0, input1); - const auto output0 = _mm_unpacklo_ps(intermediate0, intermediate1); - const auto output1 = _mm_unpackhi_ps(intermediate0, intermediate1); - _mm_store_ps(out0, output0); - _mm_store_ps(out1, output1); - out0 += 4; - out1 += 4; - } - for (auto chanIdx = 0; chanIdx < NumChannels; chanIdx++) - { - auto* _in = input + 2 * lastAligned + chanIdx; - auto* _end = input + numFrames * NumChannels; - auto* _out = getChannel(chanIdx) + lastAligned; - while (_in < _end) - { - *_out = *_in; - _in += 2; - _out += 1; - } - } - } - else - { - for (auto chanIdx = 0; chanIdx < NumChannels; chanIdx++) - { - auto* _in = input + chanIdx; - auto* _end = input + numFrames * NumChannels; - auto* _out = getChannel(chanIdx); - while (_in < _end) - { - *_out = *_in; - _in += NumChannels; - _out += 1; - } - } - } - } - - Type* getChannel(int channelIndex) noexcept - { - return channels[channelIndex]; - } - - Type* begin(int channelIndex) noexcept - { - return channels[channelIndex]; - } - - Type* end(int channelIndex) noexcept - { - return buffer.data() + numFrames * (channelIndex + 1) + padding * channelIndex; - } - - Type* alignedBegin(int channelIndex) noexcept - { - return begin(channelIndex); - } - - Type* alignedEnd(int channelIndex) noexcept - { - return buffer.data() + (channelIndex + 1) * (numFrames + padding); - } - - Type& operator()(int channelIndex, int sampleIndex) noexcept - { - return getSample(channelIndex, sampleIndex); - } - - int getNumFrames() const noexcept { return numFrames; } - int getNumChannels() const noexcept { return NumChannels; } - bool empty() const noexcept { return numFrames == 0; } - -private: - static constexpr auto TypeAlignment { Alignment / sizeof(Type) }; - static constexpr auto TypeAlignmentMask { TypeAlignment - 1 }; - static_assert(TypeAlignment * sizeof(Type) == Alignment, "The alignment does not appear to be divided by the size of the Type"); - int numFrames { 0 }; - int totalSize { 0 }; - int padding { 0 }; - std::array channels; - Buffer buffer {}; - Buffer tempBuffer {2 * Alignment}; -}; - -template -class SplitAudioBuffer -{ -public: - SplitAudioBuffer() = default; - SplitAudioBuffer(int numFrames) - { - resize(numFrames); - } - - bool resize(int numFrames) - { - // should have a positive number of frames... - ASSERT(numFrames >= 0); - bool resizedOK = true; - - for (auto& buffer: buffers) - resizedOK &= buffer.resize(static_cast(numFrames)); - - if (resizedOK) - this->numFrames = numFrames; - else - this->numFrames = std::min(numFrames, this->numFrames); - - return resizedOK; - } - - template - void fill(Type value) noexcept - { - if constexpr (op == VectorOperations::sse) - { - static_assert(Alignment == 16, "Wrong alignment"); - const __m128 mmValue = _mm_set_ps1(value); - for(auto i = 0; i < NumChannels; ++i) - for (auto mm = (__m128*)alignedBegin(i); mm < (__m128*)alignedEnd(i); mm++) - _mm_store_ps((float*)mm, mmValue); - } - else - { - for(auto i = 0; i < NumChannels; ++i) - std::fill(begin(i), end(i), value); - } - } - - Type& getSample(int channelIndex, int sampleIndex) noexcept - { - ASSERT(channelIndex >= 0); - ASSERT(sampleIndex >= 0); - return *(buffers[channelIndex].data() + sampleIndex); - } - - Type& operator()(int channelIndex, int sampleIndex) noexcept - { - return getSample(channelIndex, sampleIndex); - } - - Type* getChannel(int channelIndex) noexcept - { - return buffers[channelIndex].data(); - } - - Type* begin(int channelIndex) noexcept - { - return buffers[channelIndex].begin(); - } - - Type* end(int channelIndex) noexcept - { - return buffers[channelIndex].end(); - } - - Type* alignedBegin(int channelIndex) noexcept - { - return begin(channelIndex); - } - - Type* alignedEnd(int channelIndex) noexcept - { - return buffers[channelIndex].alignedEnd(); - } - - int getNumFrames() const noexcept { return numFrames; } - int getNumChannels() const noexcept { return NumChannels; } - bool empty() const noexcept { return numFrames == 0; } -private: - int numFrames { 0 }; - std::array, NumChannels> buffers; -}; \ No newline at end of file diff --git a/sources/Buffer.h b/sources/Buffer.h index 7cd0a812..b5f38956 100644 --- a/sources/Buffer.h +++ b/sources/Buffer.h @@ -7,9 +7,6 @@ template class Buffer { -static_assert(std::is_arithmetic::value, "Type should be arithmetic"); -static_assert(Alignment == 0 || Alignment == 4 || Alignment == 8 || Alignment == 16, "Bad alignment value"); -static constexpr auto AlignmentMask { Alignment - 1 }; public: Buffer() { } Buffer(size_t size) @@ -25,42 +22,52 @@ public: } auto tempSize = newSize + 2 * AlignmentMask; // To ensure that we have leeway at the beginning and at the end - auto* newData = largerData != nullptr ? std::realloc(largerData, tempSize * sizeof(Type)) : std::malloc(tempSize * sizeof(Type)); + auto* newData = paddedData != nullptr ? std::realloc(paddedData, tempSize * sizeof(Type)) : std::malloc(tempSize * sizeof(Type)); if (newData == nullptr) return false; largerSize = tempSize; alignedSize = newSize; - largerData = static_cast(newData); - alignedData = static_cast(std::align(Alignment, alignedSize, newData, tempSize)); + paddedData = static_cast(newData); + normalData = static_cast(std::align(Alignment, alignedSize, newData, tempSize)); + normalEnd = normalData + alignedSize; + if (auto endMisalignment = (alignedSize & TypeAlignmentMask); endMisalignment != 0) + _alignedEnd = normalEnd + Alignment - endMisalignment; return true; } - Type* data() { return alignedData; } + Type* data() { return normalData; } void clear() { - std::free(largerData); largerSize = 0; alignedSize = 0; - alignedData = nullptr; + std::free(paddedData); + normalData = nullptr; + normalEnd = nullptr; + _alignedEnd = nullptr; } ~Buffer() { - std::free(largerData); + std::free(paddedData); } - Type& operator[](int idx) { return *(alignedData + idx); } + Type& operator[](int idx) { return *(normalData + idx); } size_t size() const noexcept { return alignedSize; } bool empty() const noexcept { return alignedSize == 0; } - Type* begin() noexcept { return data(); } - Type* end() noexcept { return data() + alignedSize; } - Type* alignedEnd() noexcept { return data() + alignedSize; } - // const Type* cbegin() const noexcept { return data(); } - // const Type* cend() const noexcept { return data() + alignedSize; } + Type* end() noexcept { return normalEnd; } + Type* alignedEnd() noexcept { return _alignedEnd; } private: + static constexpr auto AlignmentMask { Alignment - 1 }; + static constexpr auto TypeAlignment { Alignment / sizeof(Type) }; + static constexpr auto TypeAlignmentMask { TypeAlignment - 1 }; + static_assert(std::is_arithmetic::value, "Type should be arithmetic"); + static_assert(Alignment == 0 || Alignment == 4 || Alignment == 8 || Alignment == 16, "Bad alignment value"); + static_assert(TypeAlignment * sizeof(Type) == Alignment, "The alignment does not appear to be divided by the size of the Type"); size_t largerSize { 0 }; size_t alignedSize { 0 }; - Type* largerData { nullptr }; - Type* alignedData { nullptr }; + Type* normalData { nullptr }; + Type* paddedData { nullptr }; + Type* normalEnd { nullptr }; + Type* _alignedEnd { nullptr }; }; \ No newline at end of file diff --git a/sources/FilePool.h b/sources/FilePool.h index f2fe7a35..643ffe50 100644 --- a/sources/FilePool.h +++ b/sources/FilePool.h @@ -1,5 +1,5 @@ #pragma once -#include "AudioBuffer.h" +#include "StereoBuffer.h" #include #include #include @@ -12,6 +12,6 @@ public: FilePool() = default; private: std::filesystem::path rootDirectory; - std::map>> preloadedData; + std::map>> preloadedData; }; } \ No newline at end of file diff --git a/sources/Globals.h b/sources/Globals.h index ff8e188e..2eed9933 100644 --- a/sources/Globals.h +++ b/sources/Globals.h @@ -20,12 +20,13 @@ namespace config } // namespace sfz -enum class VectorOperations { standard, sse, neon }; +enum class SIMD { scalar, sse, neon }; namespace config { inline constexpr unsigned int defaultAlignment { 16 }; - - inline constexpr VectorOperations vectorOperation { VectorOperations::standard }; + inline constexpr SIMD vectorOperation { SIMD::scalar }; } // namespace config -#include \ No newline at end of file +#if HAVE_X86INTRIN_H +#include +#endif \ No newline at end of file diff --git a/sources/StereoBuffer.cpp b/sources/StereoBuffer.cpp new file mode 100644 index 00000000..0627a96e --- /dev/null +++ b/sources/StereoBuffer.cpp @@ -0,0 +1,58 @@ +#include "StereoBuffer.h" + +template<> +template<> +void StereoBuffer::readInterleaved(float* input, int numFrames) noexcept +{ + ASSERT(this->numFrames >= numFrames); + const int residualFrames = numFrames & (2 * TypeAlignment - 1); + const int lastAligned = numFrames - residualFrames; + float* in = input; + auto [lOut, rOut] = getChannels(); + const float* end = input + 2 * lastAligned; + while (in < end) + { + auto register0 = _mm_loadu_ps(in); + in += 4; + auto register1 = _mm_loadu_ps(in); + in += 4; + auto register2 = register0; + // register 2 holds the copy of register 0 that is going to get erased by the first operation + // Remember that the bit mask reads from the end; 10 00 10 00 means + // "take 0 from a, take 2 from a, take 0 from b, take 2 from b" + register0 = _mm_shuffle_ps(register0, register1, 0b10001000); + register1 = _mm_shuffle_ps(register2, register1, 0b11011101); + _mm_store_ps(lOut, register0); + _mm_store_ps(rOut, register1); + lOut += 4; + rOut += 4; + } + end = input + numChannels * numFrames; + while (in < end) + { + *lOut = *in; + in++; + *rOut = *in; + in++; + lOut += 1; + rOut += 1; + } +} + +template<> +template<> +void StereoBuffer::fill(float value) noexcept +{ + const __m128 mmValue = _mm_set_ps1(value); + auto [lBegin, rBegin] = getChannels(); + auto [lEnd, rEnd] = alignedEnds(); + auto mmLeft = reinterpret_cast<__m128*>(lBegin); + auto mmRight = reinterpret_cast<__m128*>(rBegin); + while(mmLeft < reinterpret_cast<__m128*>(lEnd)) // we should only need to test a single channel + { + _mm_store_ps(reinterpret_cast(mmLeft), mmValue); + _mm_store_ps(reinterpret_cast(mmRight), mmValue); + mmLeft++; + mmRight++; + } +} \ No newline at end of file diff --git a/sources/StereoBuffer.h b/sources/StereoBuffer.h new file mode 100644 index 00000000..962763a6 --- /dev/null +++ b/sources/StereoBuffer.h @@ -0,0 +1,136 @@ +#pragma once +#include "Buffer.h" +#include "Helpers.h" +#include "Globals.h" +#include +#include +#include + +enum class Channel {left, right}; + +template +class StereoBuffer +{ +public: + static constexpr int numChannels { 2 }; + StereoBuffer() = default; + StereoBuffer(int numFrames) + { + resize(numFrames); + } + + bool resize(int numFrames) + { + // should have a positive number of frames... + ASSERT(numFrames >= 0); + if (leftBuffer.resize(static_cast(numFrames)) && rightBuffer.resize(static_cast(numFrames))) + { + this->numFrames = numFrames; + return true; + } + + return false; + } + + Type& getSample(Channel channel, int sampleIndex) noexcept + { + ASSERT(sampleIndex >= 0); + switch(channel) + { + case Channel::left: return leftBuffer[sampleIndex]; + case Channel::right: return rightBuffer[sampleIndex]; + // Should not be here by construction... + default: + ASSERTFALSE; + return trash; + } + } + + template + void fill(Type value) noexcept + { + std::fill(begin(Channel::left), end(Channel::left), value); + std::fill(begin(Channel::right), end(Channel::right), value); + } + + template + void readInterleaved(Type* input, int numFrames) noexcept + { + auto* in = input; + auto* end = input + numChannels * numFrames; + auto [lOut, rOut] = getChannels(); + while (in < end) + { + *lOut = *in; + in++; + *rOut = *in; + in++; + lOut += 1; + rOut += 1; + } + } + + Type* getChannel(Channel channel) noexcept + { + switch(channel) + { + case Channel::left: return leftBuffer.data(); + case Channel::right: return rightBuffer.data(); + default: return {}; + } + } + + Type* begin(Channel channel) noexcept + { + switch(channel) + { + case Channel::left: return leftBuffer.data(); + case Channel::right: return rightBuffer.data(); + default: return {}; + } + } + std::pair getChannels() noexcept { return { leftBuffer.data(), rightBuffer.data() }; } + std::pair begins() noexcept { return { leftBuffer.data(), rightBuffer.data() }; } + + Type* end(Channel channel) noexcept + { + switch(channel) + { + case Channel::left: return leftBuffer.end(); + case Channel::right: return rightBuffer.end(); + default: return {}; + } + } + std::pair ends() { return { leftBuffer.end(), rightBuffer.end() }; } + + Type* alignedEnd(Channel channel) noexcept + { + switch(channel) + { + case Channel::left: return leftBuffer.alignedEnd(); + case Channel::right: return rightBuffer.alignedEnd(); + default: return {}; + } + } + std::pair alignedEnds() { return { leftBuffer.alignedEnd(), rightBuffer.alignedEnd() }; } + + Type& operator()(Channel channel, int sampleIndex) noexcept + { + return getSample(channel, sampleIndex); + } + + int getNumFrames() const noexcept { return numFrames; } + int getNumChannels() const noexcept { return numChannels; } + bool empty() const noexcept { return numFrames == 0; } + +private: + static constexpr auto TypeAlignment { Alignment / sizeof(Type) }; + static constexpr auto TypeAlignmentMask { TypeAlignment - 1 }; + static_assert(TypeAlignment * sizeof(Type) == Alignment, "The alignment does not appear to be divided by the size of the Type"); + int numFrames { 0 }; + int totalSize { 0 }; + int padding { 0 }; + Buffer leftBuffer {}; + Buffer rightBuffer {}; + Type trash { 0 }; +}; diff --git a/tests/AudioBuffer.cpp b/tests/AudioBuffer.cpp deleted file mode 100644 index 34b310da..00000000 --- a/tests/AudioBuffer.cpp +++ /dev/null @@ -1,524 +0,0 @@ -#include "catch2/catch.hpp" -#include "../sources/AudioBuffer.h" -#include -using namespace Catch::literals; - -TEST_CASE("[AudioBuffer/SplitBuffer] Empty buffers") -{ - AudioBuffer floatBuffer; - REQUIRE(floatBuffer.empty()); - REQUIRE(floatBuffer.getNumFrames() == 0); - AudioBuffer doubleBuffer; - REQUIRE(doubleBuffer.empty()); - REQUIRE(doubleBuffer.getNumFrames() == 0); - AudioBuffer intBuffer; - REQUIRE(intBuffer.empty()); - REQUIRE(intBuffer.getNumFrames() == 0); - - SplitAudioBuffer floatSplitBuffer; - REQUIRE(floatSplitBuffer.empty()); - REQUIRE(floatSplitBuffer.getNumFrames() == 0); - SplitAudioBuffer doubleSplitBuffer; - REQUIRE(doubleSplitBuffer.empty()); - REQUIRE(doubleSplitBuffer.getNumFrames() == 0); - SplitAudioBuffer intSplitBuffer; - REQUIRE(intSplitBuffer.empty()); - REQUIRE(intSplitBuffer.getNumFrames() == 0); -} - -TEST_CASE("[AudioBuffer/SplitBuffer] Non-empty") -{ - AudioBuffer floatBuffer(10); - REQUIRE(!floatBuffer.empty()); - REQUIRE(floatBuffer.getNumFrames() == 10); - AudioBuffer doubleBuffer(10); - REQUIRE(!doubleBuffer.empty()); - REQUIRE(doubleBuffer.getNumFrames() == 10); - AudioBuffer intBuffer(10); - REQUIRE(!intBuffer.empty()); - REQUIRE(intBuffer.getNumFrames() == 10); - - SplitAudioBuffer floatSplitBuffer(10); - REQUIRE(!floatSplitBuffer.empty()); - REQUIRE(floatSplitBuffer.getNumFrames() == 10); - SplitAudioBuffer doubleSplitBuffer(10); - REQUIRE(!doubleSplitBuffer.empty()); - REQUIRE(doubleSplitBuffer.getNumFrames() == 10); - SplitAudioBuffer intSplitBuffer(10); - REQUIRE(!intSplitBuffer.empty()); - REQUIRE(intSplitBuffer.getNumFrames() == 10); -} - -TEST_CASE("[AudioBuffer/SplitBuffer] Access") -{ - const int size { 5 }; - AudioBuffer doubleBuffer(size); - for (auto chanIdx = 0; chanIdx < doubleBuffer.getNumChannels(); ++chanIdx) - for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx) - doubleBuffer.getSample(chanIdx, frameIdx) = static_cast(doubleBuffer.getNumFrames()) * chanIdx + frameIdx; - - for (auto chanIdx = 0; chanIdx < doubleBuffer.getNumChannels(); ++chanIdx) - for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx) - REQUIRE(doubleBuffer.getSample(chanIdx, frameIdx) == static_cast(doubleBuffer.getNumFrames()) * chanIdx + frameIdx); - - SplitAudioBuffer splitDoubleBuffer(size); - for (auto chanIdx = 0; chanIdx < splitDoubleBuffer.getNumChannels(); ++chanIdx) - for (auto frameIdx = 0; frameIdx < splitDoubleBuffer.getNumFrames(); ++frameIdx) - splitDoubleBuffer.getSample(chanIdx, frameIdx) = static_cast(splitDoubleBuffer.getNumFrames()) * chanIdx + frameIdx; - - for (auto chanIdx = 0; chanIdx < splitDoubleBuffer.getNumChannels(); ++chanIdx) - for (auto frameIdx = 0; frameIdx < splitDoubleBuffer.getNumFrames(); ++frameIdx) - REQUIRE(splitDoubleBuffer.getSample(chanIdx, frameIdx) == static_cast(splitDoubleBuffer.getNumFrames()) * chanIdx + frameIdx); -} - -TEST_CASE("[AudioBuffer/SplitBuffer] Access 2") -{ - const int size { 5 }; - AudioBuffer doubleBuffer(size); - for (auto chanIdx = 0; chanIdx < doubleBuffer.getNumChannels(); ++chanIdx) - for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx) - doubleBuffer(chanIdx, frameIdx) = static_cast(doubleBuffer.getNumFrames()) * chanIdx + frameIdx; - - for (auto chanIdx = 0; chanIdx < doubleBuffer.getNumChannels(); ++chanIdx) - for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx) - REQUIRE(doubleBuffer(chanIdx, frameIdx) == static_cast(doubleBuffer.getNumFrames()) * chanIdx + frameIdx); - - SplitAudioBuffer splitDoubleBuffer(size); - for (auto chanIdx = 0; chanIdx < splitDoubleBuffer.getNumChannels(); ++chanIdx) - for (auto frameIdx = 0; frameIdx < splitDoubleBuffer.getNumFrames(); ++frameIdx) - splitDoubleBuffer(chanIdx, frameIdx) = static_cast(splitDoubleBuffer.getNumFrames()) * chanIdx + frameIdx; - - for (auto chanIdx = 0; chanIdx < splitDoubleBuffer.getNumChannels(); ++chanIdx) - for (auto frameIdx = 0; frameIdx < splitDoubleBuffer.getNumFrames(); ++frameIdx) - REQUIRE(splitDoubleBuffer(chanIdx, frameIdx) == static_cast(splitDoubleBuffer.getNumFrames()) * chanIdx + frameIdx); -} - -TEST_CASE("[AudioBuffer/SplitBuffer] Iterators") -{ - const int size { 256 }; - const float fillValue { 2.0f }; - AudioBuffer buffer(size); - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - std::fill(buffer.begin(chanIdx), buffer.end(chanIdx), fillValue); - - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - REQUIRE( std::all_of(buffer.begin(chanIdx), buffer.end(chanIdx), [fillValue](auto value) { return value == fillValue; }) ); - - SplitAudioBuffer splitBuffer(size); - for (auto chanIdx = 0; chanIdx < splitBuffer.getNumChannels(); ++chanIdx) - std::fill(splitBuffer.begin(chanIdx), splitBuffer.end(chanIdx), fillValue); - - for (auto chanIdx = 0; chanIdx < splitBuffer.getNumChannels(); ++chanIdx) - REQUIRE( std::all_of(splitBuffer.begin(chanIdx), splitBuffer.end(chanIdx), [fillValue](auto value) { return value == fillValue; }) ); -} - -template -void channelAlignmentTest(int size) -{ - static constexpr auto AlignmentMask { Alignment - 1 }; - const Type fillValue { 2 }; - AudioBuffer buffer(size); - for (auto chanIdx = 0; chanIdx < NumChannels; ++chanIdx) - REQUIRE( ((size_t)buffer.getChannel(chanIdx) & AlignmentMask) == 0 ); - - SplitAudioBuffer splitBuffer(size); - for (auto chanIdx = 0; chanIdx < NumChannels; ++chanIdx) - REQUIRE( ((size_t)splitBuffer.getChannel(chanIdx) & AlignmentMask) == 0 ); -} - -TEST_CASE("[AudioBuffer/SplitBuffer] Channel alignments (floats)") -{ - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); - - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); - - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); - - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); - - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); - - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); - - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); - - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); -} - -TEST_CASE("[AudioBuffer/SplitBuffer] Channel alignments (doubles)") -{ - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); - - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); - - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); - - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); - - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); - - channelAlignmentTest(4); - channelAlignmentTest(5); - channelAlignmentTest(8); - channelAlignmentTest(256); - channelAlignmentTest(257); - channelAlignmentTest(1023); - channelAlignmentTest(1024); - channelAlignmentTest(65537); - channelAlignmentTest(65536); - channelAlignmentTest(65535); -} - -TEST_CASE("[AudioBuffer] fills") -{ - SECTION("Floats - 0.0") - { - AudioBuffer buffer(10); - buffer.fill(0.0f); - std::array expected; - std::fill(expected.begin(), expected.end(), 0.0f); - std::array real { 2.0f }; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - { - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[frameIdx] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); - } - } - - SECTION("Floats - 1.0") - { - AudioBuffer buffer(10); - buffer.fill(1.0f); - std::array expected; - std::fill(expected.begin(), expected.end(), 1.0f); - std::array real { 2.0f }; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - { - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[frameIdx] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); - } - } - - SECTION("Doubles - 0.0") - { - AudioBuffer buffer(10); - buffer.fill(0.0); - std::array expected; - std::fill(expected.begin(), expected.end(), 0.0); - std::array real { 2.0 }; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - { - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[frameIdx] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); - } - } - - SECTION("Doubles - 1.0") - { - AudioBuffer buffer(10); - buffer.fill(1.0); - std::array expected; - std::fill(expected.begin(), expected.end(), 1.0); - std::array real { 2.0 }; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - { - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[frameIdx] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); - } - } - - SECTION("Floats - 0.0 - SSE") - { - AudioBuffer buffer(10); - buffer.fill(0.0f); - std::array expected; - std::fill(expected.begin(), expected.end(), 0.0f); - std::array real { 2.0f }; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - { - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[frameIdx] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); - } - } - - SECTION("Floats - 1.0 - SSE") - { - AudioBuffer buffer(10); - buffer.fill(1.0f); - std::array expected { 1.0f }; - std::fill(expected.begin(), expected.end(), 1.0f); - std::array real { 2.0f }; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - { - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[frameIdx] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); - } - } -} - -TEST_CASE("[SplitAudioBuffer] fills") -{ - SECTION("Floats - 0.0") - { - SplitAudioBuffer buffer(10); - buffer.fill(0.0f); - std::array expected; - std::fill(expected.begin(), expected.end(), 0.0f); - std::array real { 2.0f }; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - { - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[frameIdx] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); - } - } - - SECTION("Floats - 1.0") - { - SplitAudioBuffer buffer(10); - buffer.fill(1.0f); - std::array expected; - std::fill(expected.begin(), expected.end(), 1.0f); - std::array real { 2.0f }; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - { - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[frameIdx] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); - } - } - - SECTION("Doubles - 0.0") - { - SplitAudioBuffer buffer(10); - buffer.fill(0.0); - std::array expected; - std::fill(expected.begin(), expected.end(), 0.0); - std::array real { 2.0 }; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - { - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[frameIdx] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); - } - } - - SECTION("Doubles - 1.0") - { - SplitAudioBuffer buffer(10); - buffer.fill(1.0); - std::array expected; - std::fill(expected.begin(), expected.end(), 1.0); - std::array real { 2.0 }; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - { - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[frameIdx] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); - } - } - - SECTION("Floats - 0.0 - SSE") - { - SplitAudioBuffer buffer(10); - buffer.fill(0.0f); - std::array expected; - std::fill(expected.begin(), expected.end(), 0.0f); - std::array real { 2.0f }; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - { - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[frameIdx] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); - } - } - - SECTION("Floats - 1.0 - SSE") - { - SplitAudioBuffer buffer(10); - buffer.fill(1.0f); - std::array expected { 1.0f }; - std::fill(expected.begin(), expected.end(), 1.0f); - std::array real { 2.0f }; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - { - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[frameIdx] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); - } - } -} - -TEST_CASE("[AudioBuffer] Interleave read") -{ - AudioBuffer buffer(8); - 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}; - std::array expected = { 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f }; - buffer.readInterleaved(input.data(), 8); - std::array real { 0.0f }; - auto realIdx = 0; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[realIdx++] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); -} - -TEST_CASE("[AudioBuffer] Interleave read -- SSE") -{ - AudioBuffer buffer(8); - 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}; - std::array expected = { 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f }; - buffer.readInterleaved(input.data(), 8); - std::array real { 0.0f }; - auto realIdx = 0; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[realIdx++] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); -} - -TEST_CASE("[AudioBuffer] Interleave read unaligned end -- SSE") -{ - AudioBuffer buffer(10); - 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, 8.0f, 18.0f, 9.0f, 19.0f}; - std::array expected = { 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f, 18.0f, 19.0f}; - buffer.readInterleaved(input.data(), 10); - std::array real { 0.0f }; - auto realIdx = 0; - for (auto chanIdx = 0; chanIdx < buffer.getNumChannels(); ++chanIdx) - for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) - real[realIdx++] = buffer(chanIdx, frameIdx); - REQUIRE( real == expected ); -} - -TEST_CASE("[AudioBuffer] Fill a big Audiobuffer") -{ - constexpr int size { 2039247 }; - AudioBuffer buffer (size); - std::vector input (2*size); - std::iota(input.begin(), input.end(), 1.0f); - buffer.readInterleaved(input.data(), size); -} \ No newline at end of file diff --git a/tests/Buffer.cpp b/tests/BufferT.cpp similarity index 68% rename from tests/Buffer.cpp rename to tests/BufferT.cpp index 80467c1f..09df03b2 100644 --- a/tests/Buffer.cpp +++ b/tests/BufferT.cpp @@ -31,18 +31,29 @@ TEST_CASE("[Buffer] Empty (uint8_t)") REQUIRE(emptyBuffer.size() == 0); } +template +void checkBoundaries(Buffer& buffer, size_t expectedSize) +{ + REQUIRE(buffer.size() == expectedSize); + REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0); + REQUIRE(((size_t)buffer.alignedEnd() & (config::defaultAlignment - 1)) == 0); + REQUIRE(std::distance(buffer.begin(), buffer.end()) == expectedSize); + REQUIRE(std::distance(buffer.begin(), buffer.alignedEnd()) >= expectedSize); +} + TEST_CASE("[Buffer] 10 floats ") { - Buffer buffer(10); - REQUIRE(!buffer.empty()); - REQUIRE(buffer.size() == 10); - REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0); + const int baseSize { 10 }; + Buffer buffer(baseSize); + checkBoundaries(buffer, baseSize); + for (auto& element: buffer) element = 0.0f; for (auto& element: buffer) REQUIRE(element == 0.0f); } + TEST_CASE("[Buffer] Resize 10 floats ") { const int baseSize { 10 }; @@ -50,37 +61,39 @@ TEST_CASE("[Buffer] Resize 10 floats ") const int bigSize { baseSize * 2 }; Buffer buffer(baseSize); REQUIRE(!buffer.empty()); - REQUIRE(buffer.size() == baseSize); - REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0); + checkBoundaries(buffer, baseSize); + std::fill(buffer.begin(), buffer.end(), 1.0f); + REQUIRE( buffer.resize(smallSize) ); - REQUIRE( buffer.size() == smallSize ); - REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0); + checkBoundaries(buffer, smallSize); + REQUIRE( std::all_of(buffer.begin(), buffer.end(), [](auto value) { return value == 1.0f; }) ); + REQUIRE( buffer.resize(bigSize) ); - REQUIRE( buffer.size() == bigSize ); - REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0); + checkBoundaries(buffer, bigSize); for (auto i = 0; i < smallSize; ++i) REQUIRE(buffer[i] == 1.0f); } TEST_CASE("[Buffer] Resize 4096 floats ") { - const int baseSize { 10 }; + const int baseSize { 4096 }; const int smallSize { baseSize / 2 }; const int bigSize { baseSize * 2 }; Buffer buffer(baseSize); REQUIRE(!buffer.empty()); - REQUIRE(buffer.size() == baseSize); - REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0); + checkBoundaries(buffer, baseSize); + std::fill(buffer.begin(), buffer.end(), 1.0f); + REQUIRE( buffer.resize(smallSize) ); - REQUIRE( buffer.size() == smallSize ); - REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0); + checkBoundaries(buffer, smallSize); + REQUIRE( std::all_of(buffer.begin(), buffer.end(), [](auto value) { return value == 1.0f; }) ); + REQUIRE( buffer.resize(bigSize) ); - REQUIRE( buffer.size() == bigSize ); - REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0); + checkBoundaries(buffer, bigSize); for (auto i = 0; i < smallSize; ++i) REQUIRE(buffer[i] == 1.0f); } @@ -90,18 +103,19 @@ TEST_CASE("[Buffer] Resize 65536 floats ") const int baseSize { 10 }; const int smallSize { baseSize / 2 }; const int bigSize { baseSize * 2 }; - Buffer buffer(baseSize); + Buffer buffer(baseSize); REQUIRE(!buffer.empty()); - REQUIRE(buffer.size() == baseSize); - REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0); + checkBoundaries(buffer, baseSize); + std::fill(buffer.begin(), buffer.end(), 1.0f); + REQUIRE( buffer.resize(smallSize) ); - REQUIRE( buffer.size() == smallSize ); - REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0); + checkBoundaries(buffer, smallSize); + REQUIRE( std::all_of(buffer.begin(), buffer.end(), [](auto value) { return value == 1.0f; }) ); + REQUIRE( buffer.resize(bigSize) ); - REQUIRE( buffer.size() == bigSize ); - REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0); + checkBoundaries(buffer, bigSize); for (auto i = 0; i < smallSize; ++i) REQUIRE(buffer[i] == 1.0f); } \ No newline at end of file diff --git a/tests/Files.cpp b/tests/FilesT.cpp similarity index 100% rename from tests/Files.cpp rename to tests/FilesT.cpp diff --git a/tests/Helpers.cpp b/tests/HelpersT.cpp similarity index 100% rename from tests/Helpers.cpp rename to tests/HelpersT.cpp diff --git a/tests/Main.cpp b/tests/MainT.cpp similarity index 100% rename from tests/Main.cpp rename to tests/MainT.cpp diff --git a/tests/Opcode.cpp b/tests/OpcodeT.cpp similarity index 100% rename from tests/Opcode.cpp rename to tests/OpcodeT.cpp diff --git a/tests/Range.cpp b/tests/RangeT.cpp similarity index 100% rename from tests/Range.cpp rename to tests/RangeT.cpp diff --git a/tests/Regex.cpp b/tests/RegexT.cpp similarity index 100% rename from tests/Regex.cpp rename to tests/RegexT.cpp diff --git a/tests/Region.cpp b/tests/RegionT.cpp similarity index 100% rename from tests/Region.cpp rename to tests/RegionT.cpp diff --git a/tests/StereoBufferT.cpp b/tests/StereoBufferT.cpp new file mode 100644 index 00000000..2f803cec --- /dev/null +++ b/tests/StereoBufferT.cpp @@ -0,0 +1,315 @@ +#include "catch2/catch.hpp" +#include "../sources/StereoBuffer.h" +#include +using namespace Catch::literals; + +TEST_CASE("[StereoBuffer] Empty buffers") +{ + StereoBuffer floatBuffer; + REQUIRE(floatBuffer.empty()); + REQUIRE(floatBuffer.getNumFrames() == 0); + StereoBuffer doubleBuffer; + REQUIRE(doubleBuffer.empty()); + REQUIRE(doubleBuffer.getNumFrames() == 0); + StereoBuffer intBuffer; + REQUIRE(intBuffer.empty()); + REQUIRE(intBuffer.getNumFrames() == 0); +} + +TEST_CASE("[StereoBuffer] Non-empty") +{ + StereoBuffer floatBuffer(10); + REQUIRE(!floatBuffer.empty()); + REQUIRE(floatBuffer.getNumFrames() == 10); + StereoBuffer doubleBuffer(10); + REQUIRE(!doubleBuffer.empty()); + REQUIRE(doubleBuffer.getNumFrames() == 10); + StereoBuffer intBuffer(10); + REQUIRE(!intBuffer.empty()); + REQUIRE(intBuffer.getNumFrames() == 10); +} + +TEST_CASE("[StereoBuffer] Access") +{ + const int size { 5 }; + StereoBuffer doubleBuffer(size); + for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx) + { + doubleBuffer.getSample(Channel::left, frameIdx) = static_cast(doubleBuffer.getNumFrames()) + frameIdx; + doubleBuffer.getSample(Channel::right, frameIdx) = static_cast(doubleBuffer.getNumFrames()) - frameIdx; + } + + for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx) + { + REQUIRE(doubleBuffer.getSample(Channel::left, frameIdx) == static_cast(doubleBuffer.getNumFrames()) + frameIdx); + REQUIRE(doubleBuffer(Channel::left, frameIdx) == static_cast(doubleBuffer.getNumFrames()) + frameIdx); + REQUIRE(doubleBuffer.getSample(Channel::right, frameIdx) == static_cast(doubleBuffer.getNumFrames()) - frameIdx); + REQUIRE(doubleBuffer(Channel::right, frameIdx) == static_cast(doubleBuffer.getNumFrames()) - frameIdx); + } +} + +TEST_CASE("[StereoBuffer] Iterators") +{ + const int size { 256 }; + const float fillValue { 2.0f }; + StereoBuffer buffer(size); + std::fill(buffer.begin(Channel::left), buffer.end(Channel::left), fillValue); + std::fill(buffer.begin(Channel::right), buffer.end(Channel::right), fillValue); + + REQUIRE( std::all_of(buffer.begin(Channel::left), buffer.end(Channel::left), [fillValue](auto value) { return value == fillValue; }) ); + REQUIRE( std::all_of(buffer.begin(Channel::right), buffer.end(Channel::right), [fillValue](auto value) { return value == fillValue; }) ); +} + +template +void channelAlignmentTest(int size) +{ + static constexpr auto AlignmentMask { Alignment - 1 }; + StereoBuffer buffer(size); + REQUIRE( ((size_t)buffer.getChannel(Channel::left) & AlignmentMask) == 0 ); + REQUIRE( ((size_t)buffer.getChannel(Channel::right) & AlignmentMask) == 0 ); +} + +TEST_CASE("[StereoBuffer] Channel alignments (floats)") +{ + channelAlignmentTest(4); + channelAlignmentTest(5); + channelAlignmentTest(8); + channelAlignmentTest(256); + channelAlignmentTest(257); + channelAlignmentTest(1023); + channelAlignmentTest(1024); + channelAlignmentTest(65537); + channelAlignmentTest(65536); + channelAlignmentTest(65535); + + channelAlignmentTest(4); + channelAlignmentTest(5); + channelAlignmentTest(8); + channelAlignmentTest(256); + channelAlignmentTest(257); + channelAlignmentTest(1023); + channelAlignmentTest(1024); + channelAlignmentTest(65537); + channelAlignmentTest(65536); + channelAlignmentTest(65535); + + channelAlignmentTest(4); + channelAlignmentTest(5); + channelAlignmentTest(8); + channelAlignmentTest(256); + channelAlignmentTest(257); + channelAlignmentTest(1023); + channelAlignmentTest(1024); + channelAlignmentTest(65537); + channelAlignmentTest(65536); + channelAlignmentTest(65535); +} + +TEST_CASE("[StereoBuffer] Channel alignments (doubles)") +{ + channelAlignmentTest(4); + channelAlignmentTest(5); + channelAlignmentTest(8); + channelAlignmentTest(256); + channelAlignmentTest(257); + channelAlignmentTest(1023); + channelAlignmentTest(1024); + channelAlignmentTest(65537); + channelAlignmentTest(65536); + channelAlignmentTest(65535); + + channelAlignmentTest(4); + channelAlignmentTest(5); + channelAlignmentTest(8); + channelAlignmentTest(256); + channelAlignmentTest(257); + channelAlignmentTest(1023); + channelAlignmentTest(1024); + channelAlignmentTest(65537); + channelAlignmentTest(65536); + channelAlignmentTest(65535); +} + +TEST_CASE("[AudioBuffer] fills") +{ + SECTION("Floats - 0.0") + { + StereoBuffer buffer(10); + buffer.fill(0.0f); + std::array expected; + std::fill(expected.begin(), expected.end(), 0.0f); + std::array real { 2.0f }; + + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[frameIdx] = buffer(Channel::left, frameIdx); + REQUIRE( real == expected ); + + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[frameIdx] = buffer(Channel::right, frameIdx); + REQUIRE( real == expected ); + } + + SECTION("Floats - 1.0") + { + StereoBuffer buffer(10); + buffer.fill(1.0f); + std::array expected; + std::fill(expected.begin(), expected.end(), 1.0f); + std::array real { 2.0f }; + + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[frameIdx] = buffer(Channel::left, frameIdx); + REQUIRE( real == expected ); + + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[frameIdx] = buffer(Channel::right, frameIdx); + REQUIRE( real == expected ); + } + + SECTION("Doubles - 0.0") + { + StereoBuffer buffer(10); + buffer.fill(0.0); + std::array expected; + std::fill(expected.begin(), expected.end(), 0.0); + std::array real { 2.0 }; + + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[frameIdx] = buffer(Channel::left, frameIdx); + REQUIRE( real == expected ); + + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[frameIdx] = buffer(Channel::right, frameIdx); + REQUIRE( real == expected ); + } + + SECTION("Doubles - 1.0") + { + StereoBuffer buffer(10); + buffer.fill(1.0); + std::array expected; + std::fill(expected.begin(), expected.end(), 1.0); + std::array real { 2.0 }; + + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[frameIdx] = buffer(Channel::left, frameIdx); + REQUIRE( real == expected ); + + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[frameIdx] = buffer(Channel::right, frameIdx); + REQUIRE( real == expected ); + } + + SECTION("Floats - 0.0 - SSE") + { + StereoBuffer buffer(10); + buffer.fill(0.0f); + std::array expected; + std::fill(expected.begin(), expected.end(), 0.0f); + std::array real { 2.0f }; + + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[frameIdx] = buffer(Channel::left, frameIdx); + REQUIRE( real == expected ); + + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[frameIdx] = buffer(Channel::right, frameIdx); + REQUIRE( real == expected ); + } + + SECTION("Floats - 1.0 - SSE") + { + StereoBuffer buffer(10); + buffer.fill(1.0f); + std::array expected { 1.0f }; + std::fill(expected.begin(), expected.end(), 1.0f); + std::array real { 2.0f }; + + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[frameIdx] = buffer(Channel::left, frameIdx); + REQUIRE( real == expected ); + + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[frameIdx] = buffer(Channel::right, frameIdx); + REQUIRE( real == expected ); + } +} + +TEST_CASE("[StereoBuffer] Interleave read") +{ + StereoBuffer buffer(8); + 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}; + std::array expected = { 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f }; + buffer.readInterleaved(input.data(), 8); + std::array real { 0.0f }; + auto realIdx = 0; + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[realIdx++] = buffer(Channel::left, frameIdx); + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[realIdx++] = buffer(Channel::right, frameIdx); + REQUIRE( real == expected ); +} + +TEST_CASE("[StereoBuffer] Interleave read -- SSE") +{ + StereoBuffer buffer(8); + 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}; + std::array expected = { 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f }; + buffer.readInterleaved(input.data(), 8); + std::array real { 0.0f }; + auto realIdx = 0; + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[realIdx++] = buffer(Channel::left, frameIdx); + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[realIdx++] = buffer(Channel::right, frameIdx); + REQUIRE( real == expected ); +} + +TEST_CASE("[StereoBuffer] Interleave read unaligned end -- SSE") +{ + StereoBuffer buffer(10); + 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, 8.0f, 18.0f, 9.0f, 19.0f}; + std::array expected = { 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f, 18.0f, 19.0f}; + buffer.readInterleaved(input.data(), 10); + std::array real { 0.0f }; + auto realIdx = 0; + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[realIdx++] = buffer(Channel::left, frameIdx); + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + real[realIdx++] = buffer(Channel::right, frameIdx); + REQUIRE( real == expected ); +} + +TEST_CASE("[StereoBuffer] SSE vs scalar") +{ + constexpr int numFrames { 91 }; + StereoBuffer buffer(numFrames); + std::array input; + std::iota(input.begin(), input.end(), 0.0f); + StereoBuffer expectedBuffer (numFrames); + expectedBuffer.readInterleaved(input.data(), 10); + buffer.readInterleaved(input.data(), 10); + std::array expectedArray { 0.0f }; + std::array realArray { 0.0f }; + auto sampleIdx = 0; + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + { + expectedArray[sampleIdx] = buffer(Channel::left, frameIdx); + realArray[sampleIdx++] = buffer(Channel::left, frameIdx); + } + for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) + { + expectedArray[sampleIdx] = buffer(Channel::right, frameIdx); + realArray[sampleIdx++] = buffer(Channel::right, frameIdx); + } + REQUIRE( realArray == expectedArray ); +} + +TEST_CASE("[AudioBuffer] Fill a big Audiobuffer") +{ + constexpr int size { 2039247 }; + StereoBuffer buffer (size); + std::vector input (2*size); + std::iota(input.begin(), input.end(), 1.0f); + buffer.readInterleaved(input.data(), size); +} \ No newline at end of file