This commit is contained in:
paul 2019-08-03 18:14:58 +02:00
parent 88a5f0d0d7
commit 834a907c2f
20 changed files with 669 additions and 931 deletions

View file

@ -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)
target_link_libraries(bench_read absl::strings benchmark)

View file

@ -1,71 +0,0 @@
#include <benchmark/benchmark.h>
#include "../sources/AudioBuffer.h"
#include <algorithm>
#include <numeric>
static void Joint_Fill_float(benchmark::State& state) {
AudioBuffer<float> 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<float> 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<float> buffer (100001);
float fillValue = 0.0f;
for (auto _ : state) {
buffer.fill<VectorOperations::sse>(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<float> buffer (100001);
float fillValue = 0.0f;
for (auto _ : state) {
buffer.fill<VectorOperations::sse>(fillValue);
fillValue += 1.0f;
}
}
BENCHMARK(Split_Fill_float_SSE);
static void Joint_Fill_double(benchmark::State& state) {
AudioBuffer<double> 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<double> buffer (100001);
double fillValue = 0.0;
for (auto _ : state) {
buffer.fill(fillValue);
fillValue += 1.0;
}
}
BENCHMARK(Split_Fill_double);
BENCHMARK_MAIN();

View file

@ -0,0 +1,39 @@
#include <benchmark/benchmark.h>
#include "../sources/StereoBuffer.h"
#include <algorithm>
#include <numeric>
static void Fill_float(benchmark::State& state) {
StereoBuffer<float> 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<float> buffer (100001);
float fillValue = 0.0f;
for (auto _ : state) {
buffer.fill<SIMD::sse>(fillValue);
fillValue += 1.0f;
}
}
// Register the function as a benchmark
BENCHMARK(Fill_float_SSE);
static void Fill_double(benchmark::State& state) {
StereoBuffer<double> 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();

View file

@ -1,10 +1,10 @@
#include <benchmark/benchmark.h>
#include "../sources/AudioBuffer.h"
#include "../sources/StereoBuffer.h"
#include <algorithm>
#include <numeric>
static void Interleaved_Read(benchmark::State& state) {
AudioBuffer<float> buffer (state.range(0));
StereoBuffer<float> buffer (state.range(0));
std::vector<float> 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<float> buffer (state.range(0));
StereoBuffer<float> buffer (state.range(0));
std::vector<float> input (state.range(0) * 2);
std::iota(input.begin(), input.end(), 1.0f);
for (auto _ : state) {
buffer.readInterleaved<VectorOperations::sse>(input.data(), state.range(0));
buffer.readInterleaved<SIMD::sse>(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<float> buffer (state.range(0));
StereoBuffer<float> buffer (state.range(0));
std::vector<float> input (state.range(0) * 2);
std::iota(input.begin(), input.end(), 1.0f);
for (auto _ : state) {
buffer.readInterleaved<VectorOperations::sse>(input.data() + 1, state.range(0) - 1);
buffer.readInterleaved<SIMD::sse>(input.data() + 1, state.range(0) - 1);
}
}
BENCHMARK(Unaligned_Interleaved_Read_SSE)->Range((8<<10) + 3, (8<<20) + 3);

View file

@ -1,255 +0,0 @@
#pragma once
#include "Buffer.h"
#include "Helpers.h"
#include "Globals.h"
#include <array>
#include <iostream>
#include <type_traits>
template<class Type, unsigned int NumChannels = sfz::config::numChannels, unsigned int Alignment = config::defaultAlignment>
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<size_t>(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<VectorOperations op = VectorOperations::standard>
void fill(Type value) noexcept
{
if constexpr (op == VectorOperations::sse && std::is_same<Type, float>::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<VectorOperations op = VectorOperations::standard>
void readInterleaved(float* input, int numFrames) noexcept
{
ASSERT(this->numFrames >= numFrames);
if constexpr (op == VectorOperations::sse && std::is_same<Type, float>::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<Type*, NumChannels> channels;
Buffer<Type, Alignment> buffer {};
Buffer<Type, Alignment> tempBuffer {2 * Alignment};
};
template<class Type, unsigned int NumChannels = sfz::config::numChannels, unsigned int Alignment = config::defaultAlignment>
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<size_t>(numFrames));
if (resizedOK)
this->numFrames = numFrames;
else
this->numFrames = std::min(numFrames, this->numFrames);
return resizedOK;
}
template<VectorOperations op = VectorOperations::standard>
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<Buffer<Type, Alignment>, NumChannels> buffers;
};

View file

@ -7,9 +7,6 @@
template<class Type, unsigned int Alignment = config::defaultAlignment>
class Buffer
{
static_assert(std::is_arithmetic<Type>::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<Type*>(newData);
alignedData = static_cast<Type*>(std::align(Alignment, alignedSize, newData, tempSize));
paddedData = static_cast<Type*>(newData);
normalData = static_cast<Type*>(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<Type>::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 };
};

View file

@ -1,5 +1,5 @@
#pragma once
#include "AudioBuffer.h"
#include "StereoBuffer.h"
#include <filesystem>
#include <map>
#include <string_view>
@ -12,6 +12,6 @@ public:
FilePool() = default;
private:
std::filesystem::path rootDirectory;
std::map<std::string_view, std::shared_ptr<AudioBuffer<float>>> preloadedData;
std::map<std::string_view, std::shared_ptr<StereoBuffer<float>>> preloadedData;
};
}

View file

@ -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 <x86intrin.h>
#if HAVE_X86INTRIN_H
#include <x86intrin.h>
#endif

58
sources/StereoBuffer.cpp Normal file
View file

@ -0,0 +1,58 @@
#include "StereoBuffer.h"
template<>
template<>
void StereoBuffer<float>::readInterleaved<SIMD::sse>(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<float, 16>::fill<SIMD::sse>(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<float*>(mmLeft), mmValue);
_mm_store_ps(reinterpret_cast<float*>(mmRight), mmValue);
mmLeft++;
mmRight++;
}
}

136
sources/StereoBuffer.h Normal file
View file

@ -0,0 +1,136 @@
#pragma once
#include "Buffer.h"
#include "Helpers.h"
#include "Globals.h"
#include <array>
#include <iostream>
#include <type_traits>
enum class Channel {left, right};
template<class Type, unsigned int Alignment = config::defaultAlignment>
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<size_t>(numFrames)) && rightBuffer.resize(static_cast<size_t>(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<SIMD op = SIMD::scalar>
void fill(Type value) noexcept
{
std::fill(begin(Channel::left), end(Channel::left), value);
std::fill(begin(Channel::right), end(Channel::right), value);
}
template<SIMD op = SIMD::scalar>
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<Type*, Type*> getChannels() noexcept { return { leftBuffer.data(), rightBuffer.data() }; }
std::pair<Type*, Type*> 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<Type*, Type*> 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<Type*, Type*> 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<Type, Alignment> leftBuffer {};
Buffer<Type, Alignment> rightBuffer {};
Type trash { 0 };
};

View file

@ -1,524 +0,0 @@
#include "catch2/catch.hpp"
#include "../sources/AudioBuffer.h"
#include <algorithm>
using namespace Catch::literals;
TEST_CASE("[AudioBuffer/SplitBuffer] Empty buffers")
{
AudioBuffer<float> floatBuffer;
REQUIRE(floatBuffer.empty());
REQUIRE(floatBuffer.getNumFrames() == 0);
AudioBuffer<double> doubleBuffer;
REQUIRE(doubleBuffer.empty());
REQUIRE(doubleBuffer.getNumFrames() == 0);
AudioBuffer<int> intBuffer;
REQUIRE(intBuffer.empty());
REQUIRE(intBuffer.getNumFrames() == 0);
SplitAudioBuffer<float> floatSplitBuffer;
REQUIRE(floatSplitBuffer.empty());
REQUIRE(floatSplitBuffer.getNumFrames() == 0);
SplitAudioBuffer<double> doubleSplitBuffer;
REQUIRE(doubleSplitBuffer.empty());
REQUIRE(doubleSplitBuffer.getNumFrames() == 0);
SplitAudioBuffer<int> intSplitBuffer;
REQUIRE(intSplitBuffer.empty());
REQUIRE(intSplitBuffer.getNumFrames() == 0);
}
TEST_CASE("[AudioBuffer/SplitBuffer] Non-empty")
{
AudioBuffer<float> floatBuffer(10);
REQUIRE(!floatBuffer.empty());
REQUIRE(floatBuffer.getNumFrames() == 10);
AudioBuffer<double> doubleBuffer(10);
REQUIRE(!doubleBuffer.empty());
REQUIRE(doubleBuffer.getNumFrames() == 10);
AudioBuffer<int> intBuffer(10);
REQUIRE(!intBuffer.empty());
REQUIRE(intBuffer.getNumFrames() == 10);
SplitAudioBuffer<float> floatSplitBuffer(10);
REQUIRE(!floatSplitBuffer.empty());
REQUIRE(floatSplitBuffer.getNumFrames() == 10);
SplitAudioBuffer<double> doubleSplitBuffer(10);
REQUIRE(!doubleSplitBuffer.empty());
REQUIRE(doubleSplitBuffer.getNumFrames() == 10);
SplitAudioBuffer<int> intSplitBuffer(10);
REQUIRE(!intSplitBuffer.empty());
REQUIRE(intSplitBuffer.getNumFrames() == 10);
}
TEST_CASE("[AudioBuffer/SplitBuffer] Access")
{
const int size { 5 };
AudioBuffer<double> doubleBuffer(size);
for (auto chanIdx = 0; chanIdx < doubleBuffer.getNumChannels(); ++chanIdx)
for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx)
doubleBuffer.getSample(chanIdx, frameIdx) = static_cast<double>(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<double>(doubleBuffer.getNumFrames()) * chanIdx + frameIdx);
SplitAudioBuffer<double> splitDoubleBuffer(size);
for (auto chanIdx = 0; chanIdx < splitDoubleBuffer.getNumChannels(); ++chanIdx)
for (auto frameIdx = 0; frameIdx < splitDoubleBuffer.getNumFrames(); ++frameIdx)
splitDoubleBuffer.getSample(chanIdx, frameIdx) = static_cast<double>(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<double>(splitDoubleBuffer.getNumFrames()) * chanIdx + frameIdx);
}
TEST_CASE("[AudioBuffer/SplitBuffer] Access 2")
{
const int size { 5 };
AudioBuffer<int> doubleBuffer(size);
for (auto chanIdx = 0; chanIdx < doubleBuffer.getNumChannels(); ++chanIdx)
for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx)
doubleBuffer(chanIdx, frameIdx) = static_cast<int>(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<int>(doubleBuffer.getNumFrames()) * chanIdx + frameIdx);
SplitAudioBuffer<int> splitDoubleBuffer(size);
for (auto chanIdx = 0; chanIdx < splitDoubleBuffer.getNumChannels(); ++chanIdx)
for (auto frameIdx = 0; frameIdx < splitDoubleBuffer.getNumFrames(); ++frameIdx)
splitDoubleBuffer(chanIdx, frameIdx) = static_cast<int>(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<int>(splitDoubleBuffer.getNumFrames()) * chanIdx + frameIdx);
}
TEST_CASE("[AudioBuffer/SplitBuffer] Iterators")
{
const int size { 256 };
const float fillValue { 2.0f };
AudioBuffer<float> 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<float> 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<class Type, unsigned int NumChannels, unsigned int Alignment = 16>
void channelAlignmentTest(int size)
{
static constexpr auto AlignmentMask { Alignment - 1 };
const Type fillValue { 2 };
AudioBuffer<Type, NumChannels, Alignment> buffer(size);
for (auto chanIdx = 0; chanIdx < NumChannels; ++chanIdx)
REQUIRE( ((size_t)buffer.getChannel(chanIdx) & AlignmentMask) == 0 );
SplitAudioBuffer<Type, NumChannels, Alignment> 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<float, 1>(4);
channelAlignmentTest<float, 1>(5);
channelAlignmentTest<float, 1>(8);
channelAlignmentTest<float, 1>(256);
channelAlignmentTest<float, 1>(257);
channelAlignmentTest<float, 1>(1023);
channelAlignmentTest<float, 1>(1024);
channelAlignmentTest<float, 1>(65537);
channelAlignmentTest<float, 1>(65536);
channelAlignmentTest<float, 1>(65535);
channelAlignmentTest<float, 2>(4);
channelAlignmentTest<float, 2>(5);
channelAlignmentTest<float, 2>(8);
channelAlignmentTest<float, 2>(256);
channelAlignmentTest<float, 2>(257);
channelAlignmentTest<float, 2>(1023);
channelAlignmentTest<float, 2>(1024);
channelAlignmentTest<float, 2>(65537);
channelAlignmentTest<float, 2>(65536);
channelAlignmentTest<float, 2>(65535);
channelAlignmentTest<float, 3>(4);
channelAlignmentTest<float, 3>(5);
channelAlignmentTest<float, 3>(8);
channelAlignmentTest<float, 3>(256);
channelAlignmentTest<float, 3>(257);
channelAlignmentTest<float, 3>(1023);
channelAlignmentTest<float, 3>(1024);
channelAlignmentTest<float, 3>(65537);
channelAlignmentTest<float, 3>(65536);
channelAlignmentTest<float, 3>(65535);
channelAlignmentTest<float, 4>(4);
channelAlignmentTest<float, 4>(5);
channelAlignmentTest<float, 4>(8);
channelAlignmentTest<float, 4>(256);
channelAlignmentTest<float, 4>(257);
channelAlignmentTest<float, 4>(1023);
channelAlignmentTest<float, 4>(1024);
channelAlignmentTest<float, 4>(65537);
channelAlignmentTest<float, 4>(65536);
channelAlignmentTest<float, 4>(65535);
channelAlignmentTest<float, 1, 4>(4);
channelAlignmentTest<float, 1, 4>(5);
channelAlignmentTest<float, 1, 4>(8);
channelAlignmentTest<float, 1, 4>(256);
channelAlignmentTest<float, 1, 4>(257);
channelAlignmentTest<float, 1, 4>(1023);
channelAlignmentTest<float, 1, 4>(1024);
channelAlignmentTest<float, 1, 4>(65537);
channelAlignmentTest<float, 1, 4>(65536);
channelAlignmentTest<float, 1, 4>(65535);
channelAlignmentTest<float, 2, 4>(4);
channelAlignmentTest<float, 2, 4>(5);
channelAlignmentTest<float, 2, 4>(8);
channelAlignmentTest<float, 2, 4>(256);
channelAlignmentTest<float, 2, 4>(257);
channelAlignmentTest<float, 2, 4>(1023);
channelAlignmentTest<float, 2, 4>(1024);
channelAlignmentTest<float, 2, 4>(65537);
channelAlignmentTest<float, 2, 4>(65536);
channelAlignmentTest<float, 2, 4>(65535);
channelAlignmentTest<float, 1, 8>(4);
channelAlignmentTest<float, 1, 8>(5);
channelAlignmentTest<float, 1, 8>(8);
channelAlignmentTest<float, 1, 8>(256);
channelAlignmentTest<float, 1, 8>(257);
channelAlignmentTest<float, 1, 8>(1023);
channelAlignmentTest<float, 1, 8>(1024);
channelAlignmentTest<float, 1, 8>(65537);
channelAlignmentTest<float, 1, 8>(65536);
channelAlignmentTest<float, 1, 8>(65535);
channelAlignmentTest<float, 2, 8>(4);
channelAlignmentTest<float, 2, 8>(5);
channelAlignmentTest<float, 2, 8>(8);
channelAlignmentTest<float, 2, 8>(256);
channelAlignmentTest<float, 2, 8>(257);
channelAlignmentTest<float, 2, 8>(1023);
channelAlignmentTest<float, 2, 8>(1024);
channelAlignmentTest<float, 2, 8>(65537);
channelAlignmentTest<float, 2, 8>(65536);
channelAlignmentTest<float, 2, 8>(65535);
}
TEST_CASE("[AudioBuffer/SplitBuffer] Channel alignments (doubles)")
{
channelAlignmentTest<double, 1>(4);
channelAlignmentTest<double, 1>(5);
channelAlignmentTest<double, 1>(8);
channelAlignmentTest<double, 1>(256);
channelAlignmentTest<double, 1>(257);
channelAlignmentTest<double, 1>(1023);
channelAlignmentTest<double, 1>(1024);
channelAlignmentTest<double, 1>(65537);
channelAlignmentTest<double, 1>(65536);
channelAlignmentTest<double, 1>(65535);
channelAlignmentTest<double, 2>(4);
channelAlignmentTest<double, 2>(5);
channelAlignmentTest<double, 2>(8);
channelAlignmentTest<double, 2>(256);
channelAlignmentTest<double, 2>(257);
channelAlignmentTest<double, 2>(1023);
channelAlignmentTest<double, 2>(1024);
channelAlignmentTest<double, 2>(65537);
channelAlignmentTest<double, 2>(65536);
channelAlignmentTest<double, 2>(65535);
channelAlignmentTest<double, 3>(4);
channelAlignmentTest<double, 3>(5);
channelAlignmentTest<double, 3>(8);
channelAlignmentTest<double, 3>(256);
channelAlignmentTest<double, 3>(257);
channelAlignmentTest<double, 3>(1023);
channelAlignmentTest<double, 3>(1024);
channelAlignmentTest<double, 3>(65537);
channelAlignmentTest<double, 3>(65536);
channelAlignmentTest<double, 3>(65535);
channelAlignmentTest<double, 4>(4);
channelAlignmentTest<double, 4>(5);
channelAlignmentTest<double, 4>(8);
channelAlignmentTest<double, 4>(256);
channelAlignmentTest<double, 4>(257);
channelAlignmentTest<double, 4>(1023);
channelAlignmentTest<double, 4>(1024);
channelAlignmentTest<double, 4>(65537);
channelAlignmentTest<double, 4>(65536);
channelAlignmentTest<double, 4>(65535);
channelAlignmentTest<double, 1, 8>(4);
channelAlignmentTest<double, 1, 8>(5);
channelAlignmentTest<double, 1, 8>(8);
channelAlignmentTest<double, 1, 8>(256);
channelAlignmentTest<double, 1, 8>(257);
channelAlignmentTest<double, 1, 8>(1023);
channelAlignmentTest<double, 1, 8>(1024);
channelAlignmentTest<double, 1, 8>(65537);
channelAlignmentTest<double, 1, 8>(65536);
channelAlignmentTest<double, 1, 8>(65535);
channelAlignmentTest<double, 2, 8>(4);
channelAlignmentTest<double, 2, 8>(5);
channelAlignmentTest<double, 2, 8>(8);
channelAlignmentTest<double, 2, 8>(256);
channelAlignmentTest<double, 2, 8>(257);
channelAlignmentTest<double, 2, 8>(1023);
channelAlignmentTest<double, 2, 8>(1024);
channelAlignmentTest<double, 2, 8>(65537);
channelAlignmentTest<double, 2, 8>(65536);
channelAlignmentTest<double, 2, 8>(65535);
}
TEST_CASE("[AudioBuffer] fills")
{
SECTION("Floats - 0.0")
{
AudioBuffer<float> buffer(10);
buffer.fill(0.0f);
std::array<float, 10> expected;
std::fill(expected.begin(), expected.end(), 0.0f);
std::array<float, 10> 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<float> buffer(10);
buffer.fill(1.0f);
std::array<float, 10> expected;
std::fill(expected.begin(), expected.end(), 1.0f);
std::array<float, 10> 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<double> buffer(10);
buffer.fill(0.0);
std::array<double, 10> expected;
std::fill(expected.begin(), expected.end(), 0.0);
std::array<double, 10> 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<double> buffer(10);
buffer.fill(1.0);
std::array<double, 10> expected;
std::fill(expected.begin(), expected.end(), 1.0);
std::array<double, 10> 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<float> buffer(10);
buffer.fill<VectorOperations::sse>(0.0f);
std::array<float, 10> expected;
std::fill(expected.begin(), expected.end(), 0.0f);
std::array<float, 10> 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<float> buffer(10);
buffer.fill<VectorOperations::sse>(1.0f);
std::array<float, 10> expected { 1.0f };
std::fill(expected.begin(), expected.end(), 1.0f);
std::array<float, 10> 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<float> buffer(10);
buffer.fill(0.0f);
std::array<float, 10> expected;
std::fill(expected.begin(), expected.end(), 0.0f);
std::array<float, 10> 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<float> buffer(10);
buffer.fill(1.0f);
std::array<float, 10> expected;
std::fill(expected.begin(), expected.end(), 1.0f);
std::array<float, 10> 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<double> buffer(10);
buffer.fill(0.0);
std::array<double, 10> expected;
std::fill(expected.begin(), expected.end(), 0.0);
std::array<double, 10> 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<double> buffer(10);
buffer.fill(1.0);
std::array<double, 10> expected;
std::fill(expected.begin(), expected.end(), 1.0);
std::array<double, 10> 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<float> buffer(10);
buffer.fill<VectorOperations::sse>(0.0f);
std::array<float, 10> expected;
std::fill(expected.begin(), expected.end(), 0.0f);
std::array<float, 10> 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<float> buffer(10);
buffer.fill<VectorOperations::sse>(1.0f);
std::array<float, 10> expected { 1.0f };
std::fill(expected.begin(), expected.end(), 1.0f);
std::array<float, 10> 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<float> buffer(8);
std::array<float, 16> 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<float, 16> 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<float, 16> 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<float> buffer(8);
std::array<float, 16> 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<float, 16> 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<VectorOperations::sse>(input.data(), 8);
std::array<float, 16> 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<float> buffer(10);
std::array<float, 20> 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<float, 20> 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<VectorOperations::sse>(input.data(), 10);
std::array<float, 20> 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<float> buffer (size);
std::vector<float> input (2*size);
std::iota(input.begin(), input.end(), 1.0f);
buffer.readInterleaved(input.data(), size);
}

View file

@ -31,18 +31,29 @@ TEST_CASE("[Buffer] Empty (uint8_t)")
REQUIRE(emptyBuffer.size() == 0);
}
template<class Type>
void checkBoundaries(Buffer<Type>& 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<float> buffer(10);
REQUIRE(!buffer.empty());
REQUIRE(buffer.size() == 10);
REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0);
const int baseSize { 10 };
Buffer<float> 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<float> 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<float> 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<float> buffer(baseSize);
Buffer<float> 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);
}

315
tests/StereoBufferT.cpp Normal file
View file

@ -0,0 +1,315 @@
#include "catch2/catch.hpp"
#include "../sources/StereoBuffer.h"
#include <algorithm>
using namespace Catch::literals;
TEST_CASE("[StereoBuffer] Empty buffers")
{
StereoBuffer<float> floatBuffer;
REQUIRE(floatBuffer.empty());
REQUIRE(floatBuffer.getNumFrames() == 0);
StereoBuffer<double> doubleBuffer;
REQUIRE(doubleBuffer.empty());
REQUIRE(doubleBuffer.getNumFrames() == 0);
StereoBuffer<int> intBuffer;
REQUIRE(intBuffer.empty());
REQUIRE(intBuffer.getNumFrames() == 0);
}
TEST_CASE("[StereoBuffer] Non-empty")
{
StereoBuffer<float> floatBuffer(10);
REQUIRE(!floatBuffer.empty());
REQUIRE(floatBuffer.getNumFrames() == 10);
StereoBuffer<double> doubleBuffer(10);
REQUIRE(!doubleBuffer.empty());
REQUIRE(doubleBuffer.getNumFrames() == 10);
StereoBuffer<int> intBuffer(10);
REQUIRE(!intBuffer.empty());
REQUIRE(intBuffer.getNumFrames() == 10);
}
TEST_CASE("[StereoBuffer] Access")
{
const int size { 5 };
StereoBuffer<double> doubleBuffer(size);
for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx)
{
doubleBuffer.getSample(Channel::left, frameIdx) = static_cast<double>(doubleBuffer.getNumFrames()) + frameIdx;
doubleBuffer.getSample(Channel::right, frameIdx) = static_cast<double>(doubleBuffer.getNumFrames()) - frameIdx;
}
for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx)
{
REQUIRE(doubleBuffer.getSample(Channel::left, frameIdx) == static_cast<double>(doubleBuffer.getNumFrames()) + frameIdx);
REQUIRE(doubleBuffer(Channel::left, frameIdx) == static_cast<double>(doubleBuffer.getNumFrames()) + frameIdx);
REQUIRE(doubleBuffer.getSample(Channel::right, frameIdx) == static_cast<double>(doubleBuffer.getNumFrames()) - frameIdx);
REQUIRE(doubleBuffer(Channel::right, frameIdx) == static_cast<double>(doubleBuffer.getNumFrames()) - frameIdx);
}
}
TEST_CASE("[StereoBuffer] Iterators")
{
const int size { 256 };
const float fillValue { 2.0f };
StereoBuffer<float> 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<class Type, unsigned int Alignment = 16>
void channelAlignmentTest(int size)
{
static constexpr auto AlignmentMask { Alignment - 1 };
StereoBuffer<Type, Alignment> 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<float>(4);
channelAlignmentTest<float>(5);
channelAlignmentTest<float>(8);
channelAlignmentTest<float>(256);
channelAlignmentTest<float>(257);
channelAlignmentTest<float>(1023);
channelAlignmentTest<float>(1024);
channelAlignmentTest<float>(65537);
channelAlignmentTest<float>(65536);
channelAlignmentTest<float>(65535);
channelAlignmentTest<float, 4>(4);
channelAlignmentTest<float, 4>(5);
channelAlignmentTest<float, 4>(8);
channelAlignmentTest<float, 4>(256);
channelAlignmentTest<float, 4>(257);
channelAlignmentTest<float, 4>(1023);
channelAlignmentTest<float, 4>(1024);
channelAlignmentTest<float, 4>(65537);
channelAlignmentTest<float, 4>(65536);
channelAlignmentTest<float, 4>(65535);
channelAlignmentTest<float, 8>(4);
channelAlignmentTest<float, 8>(5);
channelAlignmentTest<float, 8>(8);
channelAlignmentTest<float, 8>(256);
channelAlignmentTest<float, 8>(257);
channelAlignmentTest<float, 8>(1023);
channelAlignmentTest<float, 8>(1024);
channelAlignmentTest<float, 8>(65537);
channelAlignmentTest<float, 8>(65536);
channelAlignmentTest<float, 8>(65535);
}
TEST_CASE("[StereoBuffer] Channel alignments (doubles)")
{
channelAlignmentTest<double>(4);
channelAlignmentTest<double>(5);
channelAlignmentTest<double>(8);
channelAlignmentTest<double>(256);
channelAlignmentTest<double>(257);
channelAlignmentTest<double>(1023);
channelAlignmentTest<double>(1024);
channelAlignmentTest<double>(65537);
channelAlignmentTest<double>(65536);
channelAlignmentTest<double>(65535);
channelAlignmentTest<double, 8>(4);
channelAlignmentTest<double, 8>(5);
channelAlignmentTest<double, 8>(8);
channelAlignmentTest<double, 8>(256);
channelAlignmentTest<double, 8>(257);
channelAlignmentTest<double, 8>(1023);
channelAlignmentTest<double, 8>(1024);
channelAlignmentTest<double, 8>(65537);
channelAlignmentTest<double, 8>(65536);
channelAlignmentTest<double, 8>(65535);
}
TEST_CASE("[AudioBuffer] fills")
{
SECTION("Floats - 0.0")
{
StereoBuffer<float> buffer(10);
buffer.fill(0.0f);
std::array<float, 10> expected;
std::fill(expected.begin(), expected.end(), 0.0f);
std::array<float, 10> 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<float> buffer(10);
buffer.fill(1.0f);
std::array<float, 10> expected;
std::fill(expected.begin(), expected.end(), 1.0f);
std::array<float, 10> 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<double> buffer(10);
buffer.fill(0.0);
std::array<double, 10> expected;
std::fill(expected.begin(), expected.end(), 0.0);
std::array<double, 10> 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<double> buffer(10);
buffer.fill(1.0);
std::array<double, 10> expected;
std::fill(expected.begin(), expected.end(), 1.0);
std::array<double, 10> 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<float> buffer(10);
buffer.fill<SIMD::sse>(0.0f);
std::array<float, 10> expected;
std::fill(expected.begin(), expected.end(), 0.0f);
std::array<float, 10> 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<float> buffer(10);
buffer.fill<SIMD::sse>(1.0f);
std::array<float, 10> expected { 1.0f };
std::fill(expected.begin(), expected.end(), 1.0f);
std::array<float, 10> 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<float> buffer(8);
std::array<float, 16> 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<float, 16> 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<float, 16> 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<float> buffer(8);
std::array<float, 16> 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<float, 16> 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<SIMD::sse>(input.data(), 8);
std::array<float, 16> 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<float> buffer(10);
std::array<float, 20> 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<float, 20> 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<SIMD::sse>(input.data(), 10);
std::array<float, 20> 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<float> buffer(numFrames);
std::array<float, numFrames * 2> input;
std::iota(input.begin(), input.end(), 0.0f);
StereoBuffer<float> expectedBuffer (numFrames);
expectedBuffer.readInterleaved(input.data(), 10);
buffer.readInterleaved<SIMD::sse>(input.data(), 10);
std::array<float, numFrames * 2> expectedArray { 0.0f };
std::array<float, numFrames * 2> 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<float> buffer (size);
std::vector<float> input (2*size);
std::iota(input.begin(), input.end(), 1.0f);
buffer.readInterleaved(input.data(), size);
}