Cleanups and added writeInterleaved

This commit is contained in:
paul 2019-08-07 23:39:35 +02:00
parent d0264f3416
commit 4f998cad8f
30 changed files with 241 additions and 22 deletions

View file

@ -134,7 +134,7 @@ add_executable(sfizz_tests ${TEST_SOURCES} ${COMMON_SOURCES})
if(UNIX)
target_link_libraries(sfizz_tests stdc++fs)
endif(UNIX)
target_link_libraries(sfizz_tests Catch2::Catch2 absl::strings)
target_link_libraries(sfizz_tests Catch2::Catch2 absl::strings absl::flat_hash_map sndfile readerwriterqueue)
target_include_directories(sfizz_tests SYSTEM PRIVATE sources)
file(COPY "tests" DESTINATION ${CMAKE_BINARY_DIR})
@ -147,9 +147,17 @@ endif(UNIX)
target_link_libraries(bench_fill absl::strings benchmark)
###############################
add_executable(bench_read benchmarks/Stereo_Read_Interleaved.cpp ${COMMON_SOURCES})
add_executable(bench_read benchmarks/Stereo_Read_Interleaved.cpp sources/StereoBufferSSE.cpp)
# Per OS properties
if(UNIX)
target_link_libraries(bench_read stdc++fs)
endif(UNIX)
target_link_libraries(bench_read absl::strings benchmark)
###############################
add_executable(bench_write benchmarks/Stereo_Write_Interleaved.cpp sources/StereoBufferSSE.cpp)
# Per OS properties
if(UNIX)
target_link_libraries(bench_write stdc++fs)
endif(UNIX)
target_link_libraries(bench_write absl::strings benchmark)

View file

@ -0,0 +1,53 @@
#include <benchmark/benchmark.h>
#include "../sources/StereoBuffer.h"
#include <algorithm>
#include <numeric>
static void Interleaved_Write(benchmark::State& state) {
StereoBuffer<float> buffer (state.range(0));
std::vector<float> input (state.range(0) * 2);
buffer.readInterleaved(input.data(), state.range(0));
std::vector<float> output (state.range(0) * 2);
std::iota(input.begin(), input.end(), 1.0f);
for (auto _ : state) {
buffer.writeInterleaved(output.data(), state.range(0));
}
}
BENCHMARK(Interleaved_Write)->Range((8<<10) + 3, (8<<20) + 3);
static void Interleaved_Write_SSE(benchmark::State& state) {
StereoBuffer<float> buffer (state.range(0));
std::vector<float> input (state.range(0) * 2);
buffer.readInterleaved<true>(input.data(), state.range(0));
std::vector<float> output (state.range(0) * 2);
std::iota(input.begin(), input.end(), 1.0f);
for (auto _ : state) {
buffer.writeInterleaved<true>(output.data(), state.range(0));
}
}
BENCHMARK(Interleaved_Write_SSE)->Range((8<<10) + 3, (8<<20) + 3);
static void Unaligned_Interleaved_Write(benchmark::State& state) {
StereoBuffer<float> buffer (state.range(0));
std::vector<float> input (state.range(0) * 2);
buffer.readInterleaved(input.data(), state.range(0));
std::vector<float> output (state.range(0) * 2 + 1);
std::iota(input.begin(), input.end(), 1.0f);
for (auto _ : state) {
buffer.writeInterleaved(output.data() + 1, state.range(0));
}
}
BENCHMARK(Unaligned_Interleaved_Write)->Range((8<<10) + 3, (8<<20) + 3);
static void Unaligned_Interleaved_Write_SSE(benchmark::State& state) {
StereoBuffer<float> buffer (state.range(0));
std::vector<float> input (state.range(0) * 2);
buffer.readInterleaved<true>(input.data(), state.range(0));
std::vector<float> output (state.range(0) * 2 + 1);
std::iota(input.begin(), input.end(), 1.0f);
for (auto _ : state) {
buffer.writeInterleaved<true>(output.data() + 1, state.range(0));
}
}
BENCHMARK(Unaligned_Interleaved_Write_SSE)->Range((8<<10) + 3, (8<<20) + 3);
BENCHMARK_MAIN();

View file

@ -33,6 +33,8 @@ public:
normalEnd = normalData + alignedSize;
if (auto endMisalignment = (alignedSize & TypeAlignmentMask); endMisalignment != 0)
_alignedEnd = normalEnd + Alignment - endMisalignment;
else
_alignedEnd = normalEnd;
return true;
}

View file

@ -1,4 +1,6 @@
#include "FilePool.h"
#include <chrono>
using namespace std::chrono_literals;
std::optional<sfz::FilePool::FileInformation> sfz::FilePool::getFileInformation(std::string_view filename)
{
@ -38,9 +40,11 @@ void sfz::FilePool::enqueueLoading(Voice* voice, std::string_view sample, int nu
void sfz::FilePool::loadingThread()
{
FileLoadingInformation fileToLoad;
while (true)
while (!quitThread)
{
loadingQueue.wait_dequeue(fileToLoad);
if (!loadingQueue.wait_dequeue_timed(fileToLoad, 1ms))
continue;
if (fileToLoad.voice == nullptr)
{
DBG("Background thread error: voice is null.");

View file

@ -19,7 +19,13 @@ public:
FilePool()
: fileLoadingThread(std::thread(&FilePool::loadingThread, this))
{
}
~FilePool()
{
quitThread = true;
fileLoadingThread.join();
}
void setRootDirectory(const std::filesystem::path& directory) { rootDirectory = directory; }
size_t getNumPreloadedSamples() { return preloadedData.size(); }
@ -45,6 +51,7 @@ private:
moodycamel::BlockingReaderWriterQueue<FileLoadingInformation> loadingQueue;
void loadingThread();
std::thread fileLoadingThread;
bool quitThread { false };
Buffer<float> tempReadBuffer { config::preloadSize * 2 };
// std::map<std::string_view, std::shared_ptr<StereoBuffer<float>>> preloadedData;
absl::flat_hash_map<std::string_view, std::shared_ptr<StereoBuffer<float>>> preloadedData;

View file

@ -0,0 +1,48 @@
#include "Globals.h"
template<class Type, unsigned int Alignment = SIMDConfig::defaultAlignment>
class InterleavedStereoBuffer
{
public:
static constexpr int numChannels { 2 };
InterleavedStereoBuffer() = default;
InterleavedStereoBuffer(int numFrames)
{
resize(numFrames);
}
bool resize(int numFrames)
{
// should have a positive number of frames...
ASSERT(numFrames >= 0);
if (buffer.resize(static_cast<size_t>(2*numFrames)))
{
this->numFrames = numFrames;
return true;
}
return false;
}
struct Frame
{
Type left;
Type right;
};
Frame& getFrame(int frameIndex)
{
return *static_cast<Frame*>(buffer.data() + 2 * frameIndex);
}
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> buffer {};
Type trash { 0 };
};

View file

@ -67,13 +67,13 @@ private:
template<class Type>
bool operator==(const Range<Type>& lhs, const Range<Type>& rhs)
{
return (lhs.start() == rhs.start()) && (lhs.end() == rhs.end());
return (lhs.getStart() == rhs.getStart()) && (lhs.getEnd() == rhs.getEnd());
}
template<class Type>
bool operator==(const Range<Type>& lhs, const std::pair<Type, Type>& rhs)
{
return (lhs.start() == rhs.first) && (lhs.end() == rhs.second);
return (lhs.getStart() == rhs.first) && (lhs.getEnd() == rhs.second);
}
template<class Type>

View file

@ -70,6 +70,20 @@ public:
}
}
template<bool useSIMD=false>
void writeInterleaved(Type* output, int numFrames) noexcept
{
ASSERT(numFrames <= this->numFrames);
auto [lIn, rIn] = getChannels();
auto* out = output;
auto* end = output + numChannels * numFrames;
while (out < end)
{
*out++ = *lIn++;
*out++ = *rIn++;
}
}
Type* getChannel(Channel channel) noexcept
{
switch(channel)

View file

@ -5,6 +5,7 @@ template <>
template <>
void StereoBuffer<float, 16>::readInterleaved<true>(float *input, int numFrames) noexcept
{
// The number of frames to read has to fit!
ASSERT(this->numFrames >= numFrames);
const int residualFrames = numFrames & (2 * TypeAlignment - 1);
const int lastAligned = numFrames - residualFrames;
@ -47,6 +48,7 @@ void StereoBuffer<float, 16>::fill<true>(float value) noexcept
const __m128 mmValue = _mm_set_ps1(value);
auto [lBegin, rBegin] = getChannels();
auto [lEnd, rEnd] = alignedEnds();
// Cast to m128 so that pointer arithmetics make sense
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
@ -56,4 +58,38 @@ void StereoBuffer<float, 16>::fill<true>(float value) noexcept
mmLeft++;
mmRight++;
}
}
template<>
template<>
void StereoBuffer<float, 16>::writeInterleaved<true>(float* output, int numFrames) noexcept
{
ASSERT(numFrames <= this->numFrames);
const int residualFrames = numFrames & (TypeAlignment - 1);
const int lastAligned = numFrames - residualFrames;
auto [lIn, rIn] = getChannels();
auto* out = output;
const auto* sseEnd = lIn + lastAligned;
while (lIn < sseEnd)
{
const auto lInRegister = _mm_load_ps(lIn);
const auto rInRegister = _mm_load_ps(rIn);
const auto outRegister1 = _mm_unpacklo_ps(lInRegister, rInRegister);
_mm_storeu_ps(out, outRegister1);
out += TypeAlignment;
const auto outRegister2 = _mm_unpackhi_ps(lInRegister, rInRegister);
_mm_storeu_ps(out, outRegister2);
out += TypeAlignment;
lIn += TypeAlignment;
rIn += TypeAlignment;
}
const auto* end = output + numChannels * numFrames;
while (out < end)
{
*out++ = *lIn++;
*out++ = *rIn++;
}
}

View file

@ -32,11 +32,11 @@ TEST_CASE("[Buffer] Empty (uint8_t)")
}
template<class Type>
void checkBoundaries(Buffer<Type>& buffer, size_t expectedSize)
void checkBoundaries(Buffer<Type>& buffer, int expectedSize)
{
REQUIRE(buffer.size() == expectedSize);
REQUIRE(((size_t)buffer.data() & (config::defaultAlignment - 1)) == 0);
REQUIRE(((size_t)buffer.alignedEnd() & (config::defaultAlignment - 1)) == 0);
REQUIRE((int)buffer.size() == expectedSize);
REQUIRE(((size_t)buffer.data() & (SIMDConfig::defaultAlignment - 1)) == 0);
REQUIRE(((size_t)buffer.alignedEnd() & (SIMDConfig::defaultAlignment - 1)) == 0);
REQUIRE(std::distance(buffer.begin(), buffer.end()) == expectedSize);
REQUIRE(std::distance(buffer.begin(), buffer.alignedEnd()) >= expectedSize);
}

View file

@ -79,18 +79,20 @@ TEST_CASE("[Files] Subdir include Win")
REQUIRE( synth.getRegionView(0)->sample == "dummy_subdir.wav" );
}
TEST_CASE("[Files] Recursive include")
TEST_CASE("[Files] Recursive include (with include guard)")
{
sfz::Synth synth;
synth.enableRecursiveIncludeGuard();
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Includes/root_recursive.sfz");
REQUIRE( synth.getNumRegions() == 2 );
REQUIRE( synth.getRegionView(0)->sample == "dummy_recursive2.wav" );
REQUIRE( synth.getRegionView(1)->sample == "dummy_recursive1.wav" );
}
TEST_CASE("[Files] Include loops")
TEST_CASE("[Files] Include loops (with include guard)")
{
sfz::Synth synth;
synth.enableRecursiveIncludeGuard();
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Includes/root_loop.sfz");
REQUIRE( synth.getNumRegions() == 2 );
REQUIRE( synth.getRegionView(0)->sample == "dummy_loop2.wav" );

View file

@ -200,10 +200,10 @@ TEST_CASE("[AudioBuffer] fills")
REQUIRE( real == expected );
}
SECTION("Floats - 0.0 - SSE")
SECTION("Floats - 0.0 - SIMD")
{
StereoBuffer<float> buffer(10);
buffer.fill<SIMD::sse>(0.0f);
buffer.fill<true>(0.0f);
std::array<float, 10> expected;
std::fill(expected.begin(), expected.end(), 0.0f);
std::array<float, 10> real { 2.0f };
@ -217,10 +217,10 @@ TEST_CASE("[AudioBuffer] fills")
REQUIRE( real == expected );
}
SECTION("Floats - 1.0 - SSE")
SECTION("Floats - 1.0 - SIMD")
{
StereoBuffer<float> buffer(10);
buffer.fill<SIMD::sse>(1.0f);
buffer.fill<true>(1.0f);
std::array<float, 10> expected { 1.0f };
std::fill(expected.begin(), expected.end(), 1.0f);
std::array<float, 10> real { 2.0f };
@ -250,12 +250,12 @@ TEST_CASE("[StereoBuffer] Interleave read")
REQUIRE( real == expected );
}
TEST_CASE("[StereoBuffer] Interleave read -- SSE")
TEST_CASE("[StereoBuffer] Interleave read -- SIMD")
{
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);
buffer.readInterleaved<true>(input.data(), 8);
std::array<float, 16> real { 0.0f };
auto realIdx = 0;
for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx)
@ -265,12 +265,12 @@ TEST_CASE("[StereoBuffer] Interleave read -- SSE")
REQUIRE( real == expected );
}
TEST_CASE("[StereoBuffer] Interleave read unaligned end -- SSE")
TEST_CASE("[StereoBuffer] Interleave read unaligned end -- SIMD")
{
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);
buffer.readInterleaved<true>(input.data(), 10);
std::array<float, 20> real { 0.0f };
auto realIdx = 0;
for (auto frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx)
@ -280,7 +280,22 @@ TEST_CASE("[StereoBuffer] Interleave read unaligned end -- SSE")
REQUIRE( real == expected );
}
TEST_CASE("[StereoBuffer] SSE vs scalar")
TEST_CASE("[StereoBuffer] small interleaved read unaligned end -- SIMD")
{
StereoBuffer<float> buffer(3);
std::array<float, 20> input = { 0.0f, 10.0f, 1.0f, 11.0f, 2.0f, 12.0f};
std::array<float, 20> expected = { 0.0f, 1.0f, 2.0f, 10.0f, 11.0f, 12.0f};
buffer.readInterleaved<true>(input.data(), 3);
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] SIMD vs scalar")
{
constexpr int numFrames { 91 };
StereoBuffer<float> buffer(numFrames);
@ -288,7 +303,7 @@ TEST_CASE("[StereoBuffer] SSE vs scalar")
std::iota(input.begin(), input.end(), 0.0f);
StereoBuffer<float> expectedBuffer (numFrames);
expectedBuffer.readInterleaved(input.data(), 10);
buffer.readInterleaved<SIMD::sse>(input.data(), 10);
buffer.readInterleaved<true>(input.data(), 10);
std::array<float, numFrames * 2> expectedArray { 0.0f };
std::array<float, numFrames * 2> realArray { 0.0f };
auto sampleIdx = 0;
@ -312,4 +327,34 @@ TEST_CASE("[AudioBuffer] Fill a big Audiobuffer")
std::vector<float> input (2*size);
std::iota(input.begin(), input.end(), 1.0f);
buffer.readInterleaved(input.data(), size);
}
TEST_CASE("[StereoBuffer] Interleaved write -- Scalar")
{
StereoBuffer<float> buffer(10);
std::array<float, 20> input = { 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};
std::array<float, 20> output { 0.0f };
buffer.readInterleaved<false>(input.data(), 10);
buffer.writeInterleaved<false>(output.data(), 10);
REQUIRE( output == input );
}
TEST_CASE("[StereoBuffer] Interleaved write -- SIMD")
{
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> output { 0.0f };
buffer.readInterleaved<false>(input.data(), 10);
buffer.writeInterleaved<true>(output.data(), 10);
REQUIRE( output == input );
}
TEST_CASE("[StereoBuffer] Small interleaved write -- SIMD")
{
StereoBuffer<float> buffer(3);
std::array<float, 20> input = { 0.0f, 10.0f, 1.0f, 11.0f, 2.0f, 12.0f};
std::array<float, 20> output { 0.0f };
buffer.readInterleaved<false>(input.data(), 3);
buffer.writeInterleaved<true>(output.data(), 3);
REQUIRE( output == input );
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tests/TestFiles/Regions/dummy.1.wav Normal file → Executable file

Binary file not shown.

BIN
tests/TestFiles/Regions/dummy.2.wav Normal file → Executable file

Binary file not shown.

BIN
tests/TestFiles/Regions/dummy.wav Normal file → Executable file

Binary file not shown.

BIN
tests/TestFiles/closedhat.wav Executable file

Binary file not shown.

BIN
tests/TestFiles/kick.wav Executable file

Binary file not shown.

BIN
tests/TestFiles/snare.wav Executable file

Binary file not shown.