From 78156231159cafc15f21cb243277bd4812d01c0f Mon Sep 17 00:00:00 2001 From: paul Date: Fri, 23 Aug 2019 01:27:39 +0200 Subject: [PATCH] Started implementing voices --- CMakeLists.txt | 5 +++- benchmarks/BM_add.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++ sources/FilePool.cpp | 2 +- sources/Globals.h | 1 + sources/Region.h | 2 +- sources/SIMDDummy.cpp | 6 ++++ sources/SIMDHelpers.h | 23 ++++++++++++++-- sources/SIMDSSE.cpp | 23 ++++++++++++++++ sources/StereoBuffer.h | 19 +++++++++++++ sources/Synth.cpp | 10 +++---- sources/Synth.h | 40 ++++++++++++++++++++++++--- sources/Voice.h | 42 ++++++++++++++++++++++++++++ tests/SIMDHelpersT.cpp | 34 ++++++++++++++++++++++- 13 files changed, 254 insertions(+), 15 deletions(-) create mode 100644 benchmarks/BM_add.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1efc8675..65a78406 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,4 +173,7 @@ add_executable(bm_ramp benchmarks/BM_ramp.cpp sources/SIMDSSE.cpp) target_link_libraries(bm_ramp benchmark absl::span absl::algorithm) add_executable(bm_ADSR benchmarks/BM_ADSR.cpp sources/SIMDSSE.cpp) -target_link_libraries(bm_ADSR benchmark absl::span absl::algorithm) \ No newline at end of file +target_link_libraries(bm_ADSR benchmark absl::span absl::algorithm) + +add_executable(bm_add benchmarks/BM_add.cpp sources/SIMDSSE.cpp) +target_link_libraries(bm_add benchmark absl::span absl::algorithm) \ No newline at end of file diff --git a/benchmarks/BM_add.cpp b/benchmarks/BM_add.cpp new file mode 100644 index 00000000..186ca4e8 --- /dev/null +++ b/benchmarks/BM_add.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include "../sources/SIMDHelpers.h" + +class AddArray : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) { + std::random_device rd { }; + std::mt19937 gen { rd() }; + std::uniform_real_distribution dist { 0, 1 }; + input = std::vector(state.range(0)); + output = std::vector(state.range(0)); + std::generate(output.begin(), output.end(), [&]() { return dist(gen); }); + std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); + } + + void TearDown(const ::benchmark::State& state [[maybe_unused]]) { + + } + + std::vector input; + std::vector output; +}; + + +BENCHMARK_DEFINE_F(AddArray, Scalar)(benchmark::State& state) { + for (auto _ : state) + { + add(input, absl::MakeSpan(output)); + } +} + +BENCHMARK_DEFINE_F(AddArray, SIMD)(benchmark::State& state) { + for (auto _ : state) + { + add(input, absl::MakeSpan(output)); + } +} + +BENCHMARK_DEFINE_F(AddArray, Scalar_Unaligned)(benchmark::State& state) { + for (auto _ : state) + { + add(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + } +} + +BENCHMARK_DEFINE_F(AddArray, SIMD_Unaligned)(benchmark::State& state) { + for (auto _ : state) + { + add(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + } +} + +BENCHMARK_REGISTER_F(AddArray, Scalar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(AddArray, SIMD)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(AddArray, Scalar_Unaligned)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(AddArray, SIMD_Unaligned)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/sources/FilePool.cpp b/sources/FilePool.cpp index d11320a1..716c239a 100644 --- a/sources/FilePool.cpp +++ b/sources/FilePool.cpp @@ -45,7 +45,7 @@ void sfz::FilePool::loadingThread() FileLoadingInformation fileToLoad {}; while (!quitThread) { - if (!loadingQueue.wait_dequeue_timed(fileToLoad, 1s)) + if (!loadingQueue.wait_dequeue_timed(fileToLoad, 1ms)) continue; if (fileToLoad.voice == nullptr) diff --git a/sources/Globals.h b/sources/Globals.h index 701c1463..4bd0768c 100644 --- a/sources/Globals.h +++ b/sources/Globals.h @@ -31,6 +31,7 @@ namespace SIMDConfig constexpr bool loopingSFZIndex { true }; constexpr bool linearRamp { false }; constexpr bool multiplicativeRamp { true }; + constexpr bool add { false }; #if USE_SIMD constexpr bool useSIMD { true }; #else diff --git a/sources/Region.h b/sources/Region.h index f6e612c4..879504b5 100644 --- a/sources/Region.h +++ b/sources/Region.h @@ -3,8 +3,8 @@ #include #include #include "Opcode.h" -#include "FilePool.h" #include "EGDescription.h" +#include "StereoBuffer.h" #include "Defaults.h" #include "CCMap.h" #include diff --git a/sources/SIMDDummy.cpp b/sources/SIMDDummy.cpp index e9b4654d..85b10314 100644 --- a/sources/SIMDDummy.cpp +++ b/sources/SIMDDummy.cpp @@ -77,4 +77,10 @@ template<> float multiplicativeRamp(absl::Span output, float start, float step) noexcept { return multiplicativeRamp(output, start, step); +} + +template<> +void add(absl::Span input, absl::Span output) noexcept +{ + add(input, output); } \ No newline at end of file diff --git a/sources/SIMDHelpers.h b/sources/SIMDHelpers.h index b6f1c37c..cecd5d13 100644 --- a/sources/SIMDHelpers.h +++ b/sources/SIMDHelpers.h @@ -231,9 +231,28 @@ T multiplicativeRamp(absl::Span output, T start, T step) noexcept return start; } - template<> float linearRamp(absl::Span output, float start, float step) noexcept; template<> -float multiplicativeRamp(absl::Span output, float start, float step) noexcept; \ No newline at end of file +float multiplicativeRamp(absl::Span output, float start, float step) noexcept; + +template +inline void snippetAdd(const T*& input, T*& output) +{ + *output++ += *input++; +} + +template +void add(absl::Span input, absl::Span output) noexcept +{ + ASSERT(output.size() >= input.size()); + auto* in = input.begin(); + auto* out = output.begin(); + auto* sentinel = out + min(input.size(), output.size()); + while (out < sentinel) + snippetAdd(in, out); +} + +template<> +void add(absl::Span input, absl::Span output) noexcept; \ No newline at end of file diff --git a/sources/SIMDSSE.cpp b/sources/SIMDSSE.cpp index 5864883b..134ba28f 100644 --- a/sources/SIMDSSE.cpp +++ b/sources/SIMDSSE.cpp @@ -363,4 +363,27 @@ float multiplicativeRamp(absl::Span output, float value, flo while(out < output.end()) snippetRampMultiplicative(out, value, step); return value; +} + +template<> +void add(absl::Span input, absl::Span output) noexcept +{ + ASSERT(output.size() >= input.size()); + auto* in = input.begin(); + auto* out = output.begin(); + auto* sentinel = out + min(input.size(), output.size()); + const auto* lastAligned = prevAligned(sentinel); + + while(unaligned(in, out) && out < lastAligned) + snippetAdd(in, out); + + while(out < lastAligned) + { + _mm_store_ps(out, _mm_add_ps(_mm_load_ps(in), _mm_load_ps(out))); + out += TypeAlignment; + in += TypeAlignment; + } + + while(out < sentinel) + snippetAdd(in, out); } \ No newline at end of file diff --git a/sources/StereoBuffer.h b/sources/StereoBuffer.h index 792ceab9..1d6c51dc 100644 --- a/sources/StereoBuffer.h +++ b/sources/StereoBuffer.h @@ -34,6 +34,19 @@ public: return false; } + absl::Span getSpan(Channel channel) const + { + switch(channel) + { + case Channel::left: return leftBuffer; + case Channel::right: return rightBuffer; + // Should not be here by construction... + default: + ASSERTFALSE; + return {}; + } + } + Type& getSample(Channel channel, int sampleIndex) noexcept { ASSERT(sampleIndex >= 0); @@ -66,6 +79,12 @@ public: ::writeInterleaved(leftBuffer, rightBuffer, output); } + void add(const StereoBuffer& buffer) + { + ::add(buffer.getSpan(Channel::left), absl::MakeSpan(leftBuffer)); + ::add(buffer.getSpan(Channel::right), absl::MakeSpan(rightBuffer)); + } + Type* getChannel(Channel channel) noexcept { switch(channel) diff --git a/sources/Synth.cpp b/sources/Synth.cpp index 851d3440..fc68b096 100644 --- a/sources/Synth.cpp +++ b/sources/Synth.cpp @@ -44,7 +44,7 @@ void sfz::Synth::callback(std::string_view header, std::vector members) void sfz::Synth::buildRegion(const std::vector& regionOpcodes) { - auto lastRegion = std::make_shared(); + auto lastRegion = std::make_unique(); auto parseOpcodes = [&](const auto& opcodes) { for (auto& opcode: opcodes) @@ -57,7 +57,7 @@ void sfz::Synth::buildRegion(const std::vector& regionOpcodes) parseOpcodes(groupOpcodes); parseOpcodes(regionOpcodes); - regions.push_back(lastRegion); + regions.push_back(std::move(lastRegion)); } void sfz::Synth::clear() @@ -127,7 +127,7 @@ bool sfz::Synth::loadSfzFile(const std::filesystem::path& filename) auto currentRegion = regions.begin(); while (currentRegion <= lastRegion) { - auto region = *currentRegion; + auto region = &**currentRegion; if (region->isGenerator()) { @@ -150,10 +150,10 @@ bool sfz::Synth::loadSfzFile(const std::filesystem::path& filename) region->sampleRate = fileInformation->sampleRate; for (auto note = region->keyRange.getStart(); note <= region->keyRange.getEnd(); note++) - noteActivationLists[note].push_back(*currentRegion); + noteActivationLists[note].push_back(region); for (auto cc = region->keyRange.getStart(); cc <= region->keyRange.getEnd(); cc++) - ccActivationLists[cc].push_back(*currentRegion); + ccActivationLists[cc].push_back(region); // Defaults for (int ccIndex = 1; ccIndex < 128; ccIndex++) diff --git a/sources/Synth.h b/sources/Synth.h index 7188ebb0..97f4bde1 100644 --- a/sources/Synth.h +++ b/sources/Synth.h @@ -14,6 +14,11 @@ namespace sfz class Synth: public Parser { public: + Synth() + { + for (int i = 0; i < config::numVoices; ++i) + voices.push_back(std::make_unique()); + } bool loadSfzFile(const std::filesystem::path& file) final; int getNumRegions() const noexcept { return static_cast(regions.size()); } int getNumGroups() const noexcept { return numGroups; } @@ -22,8 +27,30 @@ public: const Region* getRegionView(int idx) const noexcept { return (size_t)idx < regions.size() ? regions[idx].get() : nullptr; } auto getUnknownOpcodes() { return unknownOpcodes; } size_t getNumPreloadedSamples() { return filePool.getNumPreloadedSamples(); } - void prepareToPlay(int samplesPerBlock, double sampleRate); - void renderBlock(StereoBuffer& buffer); + + void prepareToPlay(int samplesPerBlock, double sampleRate) + { + this->samplesPerBlock = samplesPerBlock; + this->sampleRate = sampleRate; + this->tempBuffer = StereoBuffer(samplesPerBlock); + } + + void renderBlock(StereoBuffer& buffer) + { + for (auto& voice: voices) + { + voice->renderBlock(tempBuffer); + buffer.add(tempBuffer); + } + } + + void noteOn(int delay, int channel, int noteNumber, uint8_t velocity); + void noteOff(int delay, int channel, int noteNumber, uint8_t velocity); + void cc(int delay, int channel, int ccNumber, uint8_t ccValue); + void pitchWheel(int delay, int channel, int pitch); + void aftertouch(int delay, int channel, uint8_t aftertouch); + void tempo(int delay, float secondsPerQuarter); + protected: void callback(std::string_view header, std::vector members) final; private: @@ -44,11 +71,16 @@ private: std::vector ccNames; std::optional defaultSwitch; std::set unknownOpcodes; - using RegionPtrVector = std::vector>; - std::vector> regions; + using RegionPtrVector = std::vector; + std::vector> regions; + std::vector> voices; std::array noteActivationLists; std::array ccActivationLists; void buildRegion(const std::vector& regionOpcodes); + + StereoBuffer tempBuffer { config::defaultSamplesPerBlock }; + int samplesPerBlock { config::defaultSamplesPerBlock }; + float sampleRate { config::defaultSampleRate }; }; } \ No newline at end of file diff --git a/sources/Voice.h b/sources/Voice.h index 0f7207a1..56f83c07 100644 --- a/sources/Voice.h +++ b/sources/Voice.h @@ -1,5 +1,7 @@ #pragma once #include "StereoBuffer.h" +#include "Globals.h" +#include "Region.h" #include #include @@ -8,13 +10,53 @@ namespace sfz class Voice { public: + enum class TriggerType { NoteOn, NoteOff, CC }; + void startVoice(Region* region, int channel, int number, uint8_t value, TriggerType triggerType) + { + this->triggerType = triggerType; + triggerNumber = number; + triggerChannel = channel; + triggerValue = value; + + this->region = region; + } + void setFileData(std::unique_ptr> file) { fileData = std::move(file); dataReady.store(true); } + + bool registerNoteOn(int delay, int channel, int noteNumber, uint8_t velocity); + void registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity); + bool registerCC(int delay, int channel, int ccNumber, uint8_t ccValue); + void registerPitchWheel(int delay, int channel, int pitch); + void registerAftertouch(int delay, int channel, uint8_t aftertouch); + void registerTempo(int delay, float secondsPerQuarter); + + void prepareToPlay(int samplesPerBlock, double sampleRate); + void renderBlock(StereoBuffer& buffer) + { + buffer.fill(0.0f); + } + + void reset() + { + dataReady.store(false); + } private: + Region* region; + + TriggerType triggerType; + int triggerNumber; + int triggerChannel; + uint8_t triggerValue; + std::atomic dataReady; std::unique_ptr> fileData; + + int samplesPerBlock { config::defaultSamplesPerBlock }; + double sampleRate { config::defaultSampleRate }; }; + } \ No newline at end of file diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index f2801736..3a77d144 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -486,4 +486,36 @@ TEST_CASE("[Helpers] Multiplicative Ramp unaligned (SIMD vs scalar)") multiplicativeRamp(absl::MakeSpan(outputScalar).subspan(1), start, fillValue); multiplicativeRamp(absl::MakeSpan(outputSIMD).subspan(1), start, fillValue); REQUIRE( approxEqual(outputScalar, outputSIMD) ); -} \ No newline at end of file +} + +TEST_CASE("[Helpers] Add") +{ + std::array input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + std::array output { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; + std::array expected { 2.0, 3.0, 4.0, 5.0, 6.0 }; + add(input, absl::MakeSpan(output)); + REQUIRE( output == expected ); +} + +TEST_CASE("[Helpers] Add (SIMD)") +{ + std::array input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + std::array output { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; + std::array expected { 2.0, 3.0, 4.0, 5.0, 6.0 }; + add(input, absl::MakeSpan(output)); + REQUIRE( output == expected ); +} + +TEST_CASE("[Helpers] Add (SIMD vs scalar)") +{ + std::vector input(bigBufferSize); + std::vector outputScalar(bigBufferSize); + std::vector outputSIMD(bigBufferSize); + absl::c_iota(input, 0.0); + absl::c_fill(outputScalar, 0.0); + absl::c_fill(outputSIMD, 0.0); + + add(input, absl::MakeSpan(outputScalar)); + add(input, absl::MakeSpan(outputSIMD)); + REQUIRE( approxEqual(outputScalar, outputSIMD) ); +}