From a0433a08169fe03dee0e0ebb804d07a6ac30464c Mon Sep 17 00:00:00 2001 From: paulfd Date: Fri, 13 Sep 2019 23:20:57 +0200 Subject: [PATCH] Added an historical tracking of the mean squared voice power --- benchmarks/BM_meanSquared.cpp | 90 +++++++++++++++++++++++++++++++++++ benchmarks/CMakeLists.txt | 4 ++ sfizz/AudioSpan.h | 10 ++++ sfizz/Config.h | 5 +- sfizz/HistoricalBuffer.h | 39 +++++++++++++++ sfizz/SIMDDummy.cpp | 6 +++ sfizz/SIMDHelpers.h | 21 +++++++- sfizz/SIMDSSE.cpp | 43 +++++++++++++++-- sfizz/Synth.cpp | 27 +++++++++-- sfizz/Synth.h | 2 + sfizz/Voice.cpp | 37 ++++++++++---- sfizz/Voice.h | 6 +++ tests/SIMDHelpersT.cpp | 16 ++++++- 13 files changed, 287 insertions(+), 19 deletions(-) create mode 100644 benchmarks/BM_meanSquared.cpp create mode 100644 sfizz/HistoricalBuffer.h diff --git a/benchmarks/BM_meanSquared.cpp b/benchmarks/BM_meanSquared.cpp new file mode 100644 index 00000000..0e5999cb --- /dev/null +++ b/benchmarks/BM_meanSquared.cpp @@ -0,0 +1,90 @@ +// Copyright (c) 2019, Paul Ferrand +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "../sfizz/SIMDHelpers.h" +#include +#include +#include +#include +#include +#include + +class MeanSquaredArray : 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)); + std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); + } + + void TearDown(const ::benchmark::State& state [[maybe_unused]]) + { + } + + std::vector input; +}; + +BENCHMARK_DEFINE_F(MeanSquaredArray, Scalar) +(benchmark::State& state) +{ + for (auto _ : state) { + auto result = meanSquared(input); + benchmark::DoNotOptimize(result); + } +} + +BENCHMARK_DEFINE_F(MeanSquaredArray, SIMD) +(benchmark::State& state) +{ + for (auto _ : state) { + auto result = meanSquared(input); + benchmark::DoNotOptimize(result); + } +} + +BENCHMARK_DEFINE_F(MeanSquaredArray, Scalar_Unaligned) +(benchmark::State& state) +{ + for (auto _ : state) { + auto result = meanSquared(absl::MakeSpan(input).subspan(1)); + benchmark::DoNotOptimize(result); + } +} + +BENCHMARK_DEFINE_F(MeanSquaredArray, SIMD_Unaligned) +(benchmark::State& state) +{ + for (auto _ : state) { + auto result = meanSquared(absl::MakeSpan(input).subspan(1)); + benchmark::DoNotOptimize(result); + } +} + +BENCHMARK_REGISTER_F(MeanSquaredArray, Scalar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(MeanSquaredArray, SIMD)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(MeanSquaredArray, Scalar_Unaligned)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(MeanSquaredArray, SIMD_Unaligned)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index c0df472f..618b942b 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -72,12 +72,16 @@ target_link_libraries(bm_pan benchmark absl::span absl::algorithm) add_executable(bm_mean BM_mean.cpp ${SFIZZ_SIMD_SOURCES}) target_link_libraries(bm_mean benchmark absl::span absl::algorithm) +add_executable(bm_meanSquared BM_meanSquared.cpp ${SFIZZ_SIMD_SOURCES}) +target_link_libraries(bm_meanSquared benchmark absl::span absl::algorithm) + add_custom_target(sfizz_benchmarks) add_dependencies(sfizz_benchmarks bm_opf_high_vs_low bm_write bm_read bm_mean + bm_meanSquared bm_fill bm_mathfuns bm_gain diff --git a/sfizz/AudioSpan.h b/sfizz/AudioSpan.h index d796bf46..dc26aa73 100644 --- a/sfizz/AudioSpan.h +++ b/sfizz/AudioSpan.h @@ -125,6 +125,16 @@ public: return {}; } + Type meanSquared() noexcept + { + if (numChannels == 0) + return 0.0; + Type result = 0.0; + for (int i = 0; i < numChannels; ++i) + result += ::meanSquared(getConstSpan(i)); + return result / numChannels; + } + void fill(Type value) noexcept { for (int i = 0; i < numChannels; ++i) diff --git a/sfizz/Config.h b/sfizz/Config.h index 5b471b70..7ac7bb06 100644 --- a/sfizz/Config.h +++ b/sfizz/Config.h @@ -39,6 +39,8 @@ namespace config { constexpr char defineCharacter { '$' }; constexpr int oversamplingFactor { 2 }; constexpr float A440 { 440.0 }; + constexpr unsigned powerHistoryLength { 16 }; + constexpr float voiceStealingThreshold { 0.00001 }; } // namespace config } // namespace sfz @@ -59,5 +61,6 @@ namespace SIMDConfig { constexpr bool multiplyAdd { false }; constexpr bool copy { false }; constexpr bool pan { true }; - constexpr bool mean { true }; + constexpr bool mean { false }; + constexpr bool meanSquared { false }; } \ No newline at end of file diff --git a/sfizz/HistoricalBuffer.h b/sfizz/HistoricalBuffer.h new file mode 100644 index 00000000..26725519 --- /dev/null +++ b/sfizz/HistoricalBuffer.h @@ -0,0 +1,39 @@ +#include +#include "SIMDHelpers.h" +#include "absl/types/span.h" + +template +class HistoricalBuffer { +public: + HistoricalBuffer() = delete; + HistoricalBuffer(size_t size) + : size(size) + { + resize(size); + } + + void resize(int size) + { + buffer.resize(size); + ::fill(absl::MakeSpan(buffer), 0.0); + index = 0; + } + + void push(ValueType value) + { + if (size > 0) { + buffer[index] = value; + if (++index == size) + index = 0; + } + } + + ValueType getAverage() const + { + return ::mean(buffer); + } +private: + std::vector buffer; + size_t size { 0 }; + size_t index { 0 }; +}; diff --git a/sfizz/SIMDDummy.cpp b/sfizz/SIMDDummy.cpp index 76fdee1c..f00e587c 100644 --- a/sfizz/SIMDDummy.cpp +++ b/sfizz/SIMDDummy.cpp @@ -136,4 +136,10 @@ template <> float mean(absl::Span vector) noexcept { return mean(vector); +} + +template <> +float meanSquared(absl::Span vector) noexcept +{ + return meanSquared(vector); } \ No newline at end of file diff --git a/sfizz/SIMDHelpers.h b/sfizz/SIMDHelpers.h index bec081b7..0196922c 100644 --- a/sfizz/SIMDHelpers.h +++ b/sfizz/SIMDHelpers.h @@ -429,4 +429,23 @@ T mean(absl::Span vector) noexcept } template <> -float mean(absl::Span vector) noexcept; \ No newline at end of file +float mean(absl::Span vector) noexcept; + +template +T meanSquared(absl::Span vector) noexcept +{ + T result { 0.0 }; + if (vector.size() == 0) + return result; + + auto* value = vector.begin(); + while (value < vector.end()) { + result += (*value) * (*value); + value++; + } + + return result / static_cast(vector.size()); +} + +template <> +float meanSquared(absl::Span vector) noexcept; \ No newline at end of file diff --git a/sfizz/SIMDSSE.cpp b/sfizz/SIMDSSE.cpp index a65b604f..46733213 100644 --- a/sfizz/SIMDSSE.cpp +++ b/sfizz/SIMDSSE.cpp @@ -616,14 +616,14 @@ float mean(absl::Span vector) noexcept while (unaligned(value)) result += *value++; - auto mmValues = _mm_setzero_ps(); + auto mmSums = _mm_setzero_ps(); while(value < lastAligned) { - mmValues = _mm_add_ps(mmValues, _mm_load_ps(value)); + mmSums = _mm_add_ps(mmSums, _mm_load_ps(value)); value += TypeAlignment; } std::array sseResult; - _mm_store_ps(sseResult.data(), mmValues); + _mm_store_ps(sseResult.data(), mmSums); for (auto sseValue: sseResult) result += sseValue; @@ -631,5 +631,42 @@ float mean(absl::Span vector) noexcept while (value < sentinel) result += *value++; + return result / static_cast(vector.size()); +} + +template <> +float meanSquared(absl::Span vector) noexcept +{ + float result { 0.0 }; + if (vector.size() == 0) + return result; + + auto* value = vector.begin(); + auto* sentinel = vector.end(); + const auto* lastAligned = prevAligned(sentinel); + + while (unaligned(value)) { + result += (*value) * (*value); + value++; + } + + auto mmSums = _mm_setzero_ps(); + while(value < lastAligned) { + const auto mmValues = _mm_load_ps(value); + mmSums = _mm_add_ps(mmSums, _mm_mul_ps(mmValues, mmValues)); + value += TypeAlignment; + } + + std::array sseResult; + _mm_store_ps(sseResult.data(), mmSums); + + for (auto sseValue: sseResult) + result += sseValue; + + while (value < sentinel) { + result += (*value) * (*value); + value++; + } + return result / static_cast(vector.size()); } \ No newline at end of file diff --git a/sfizz/Synth.cpp b/sfizz/Synth.cpp index 4ccc0281..29c2f4f7 100644 --- a/sfizz/Synth.cpp +++ b/sfizz/Synth.cpp @@ -22,6 +22,7 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Synth.h" +#include "Config.h" #include "Debug.h" #include "ScopedFTZ.h" #include "StringViewHelpers.h" @@ -34,6 +35,7 @@ sfz::Synth::Synth() { for (int i = 0; i < config::numVoices; ++i) voices.push_back(std::make_unique(ccState)); + voiceViewArray.reserve(config::numVoices); } void sfz::Synth::callback(std::string_view header, const std::vector& members) @@ -235,11 +237,28 @@ bool sfz::Synth::loadSfzFile(const std::filesystem::path& filename) sfz::Voice* sfz::Synth::findFreeVoice() noexcept { auto freeVoice = absl::c_find_if(voices, [](const auto& voice) { return voice->isFree(); }); - if (freeVoice == voices.end()) { - DBG("Voices are overloaded, can't start a new note"); - return {}; + if (freeVoice != voices.end()) + return freeVoice->get(); + + // Find voices that can be stolen + DBG("No free voice, trying to steal"); + voiceViewArray.clear(); + for (auto& voice: voices) + if (voice->canBeStolen()) + voiceViewArray.push_back(voice.get()); + absl::c_sort(voices, [](const auto& lhs, const auto& rhs) { return lhs->getSourcePosition() > rhs->getSourcePosition(); }); + + for (auto* voice: voiceViewArray) { + DBG("Average voice power: " << voice->getMeanSquaredAverage()); + if (voice->getMeanSquaredAverage() < config::voiceStealingThreshold) { + DBG("Stealing voice..."); + voice->reset(); + return voice; + } } - return freeVoice->get(); + + DBG("Voices are overloaded, can't start a new note"); + return {}; } void sfz::Synth::getNumActiveVoices() const noexcept diff --git a/sfizz/Synth.h b/sfizz/Synth.h index fc5f4f69..ab1dcb7e 100644 --- a/sfizz/Synth.h +++ b/sfizz/Synth.h @@ -86,8 +86,10 @@ private: std::optional defaultSwitch; std::set unknownOpcodes; using RegionPtrVector = std::vector; + using VoicePtrVector = std::vector; std::vector> regions; std::vector> voices; + VoicePtrVector voiceViewArray; std::array noteActivationLists; std::array ccActivationLists; diff --git a/sfizz/Voice.cpp b/sfizz/Voice.cpp index fdf69b3a..6ff161d9 100644 --- a/sfizz/Voice.cpp +++ b/sfizz/Voice.cpp @@ -48,7 +48,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number, if (delay < 0) delay = 0; - DBG("Starting voice with " << region->sample); + // DBG("Starting voice with " << region->sample); state = State::playing; speedRatio = static_cast(region->sampleRate / this->sampleRate); @@ -59,7 +59,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number, if (region->volumeCC) volumedB += normalizeCC(ccState[region->volumeCC->first]) * region->volumeCC->second; volumeEnvelope.reset(db2mag(volumedB)); - DBG("Base volume: " << baseVolumedB << " dB - with modifier: " << volumedB << " dB"); + // DBG("Base volume: " << baseVolumedB << " dB - with modifier: " << volumedB << " dB"); baseGain = region->getBaseGain(); baseGain *= region->getCrossfadeGain(ccState); @@ -70,32 +70,32 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number, if (region->amplitudeCC) gain *= normalizeCC(ccState[region->amplitudeCC->first]) * normalizePercents(region->amplitudeCC->second); amplitudeEnvelope.reset(gain); - DBG("Base gain: " << baseGain << " - with modifier: " << gain); + // DBG("Base gain: " << baseGain << " - with modifier: " << gain); basePan = normalizeNegativePercents(region->pan); auto pan = basePan; if (region->panCC) pan += normalizeCC(ccState[region->panCC->first]) * normalizeNegativePercents(region->panCC->second); panEnvelope.reset(pan); - DBG("Base pan: " << basePan << " - with modifier: " << pan); + // DBG("Base pan: " << basePan << " - with modifier: " << pan); basePosition = normalizeNegativePercents(region->position); auto position = basePosition; if (region->positionCC) position += normalizeCC(ccState[region->positionCC->first]) * normalizeNegativePercents(region->positionCC->second); positionEnvelope.reset(position); - DBG("Base position: " << basePosition << " - with modifier: " << position); + // DBG("Base position: " << basePosition << " - with modifier: " << position); baseWidth = normalizeNegativePercents(region->width); auto width = baseWidth; if (region->widthCC) width += normalizeCC(ccState[region->widthCC->first]) * normalizeNegativePercents(region->widthCC->second); widthEnvelope.reset(width); - DBG("Base width: " << baseWidth << " - with modifier: " << width); + // DBG("Base width: " << baseWidth << " - with modifier: " << width); sourcePosition = region->getOffset(); floatPosition = static_cast(sourcePosition); - DBG("Offset: " << floatPosition); + // DBG("Offset: " << floatPosition); initialDelay = delay + region->getDelay(); baseFrequency = midiNoteFrequency(number) * pitchRatio; prepareEGEnvelope(delay, value); @@ -230,8 +230,10 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept ASSERT(static_cast(buffer.getNumFrames()) <= samplesPerBlock); buffer.fill(0.0f); - if (state == State::idle || region == nullptr) + if (state == State::idle || region == nullptr) { + powerHistory.push(0.0); return; + } if (region->isGenerator()) fillWithGenerator(buffer); @@ -245,6 +247,8 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept if (!egEnvelope.isSmoothing()) reset(); + + powerHistory.push(buffer.meanSquared()); } void sfz::Voice::processMono(AudioSpan buffer) noexcept @@ -471,4 +475,19 @@ void sfz::Voice::garbageCollect() noexcept void sfz::Voice::expectFileData(unsigned ticket) { this->ticket = ticket; -} \ No newline at end of file +} + +float sfz::Voice::getMeanSquaredAverage() const noexcept +{ + return powerHistory.getAverage(); +} + +bool sfz::Voice::canBeStolen() const noexcept +{ + return state == State::release; +} + +float sfz::Voice::getSourcePosition() const noexcept +{ + return floatPosition; +} diff --git a/sfizz/Voice.h b/sfizz/Voice.h index 9dc7119f..8bdac781 100644 --- a/sfizz/Voice.h +++ b/sfizz/Voice.h @@ -25,6 +25,7 @@ #include "ADSREnvelope.h" #include "Config.h" #include "LinearEnvelope.h" +#include "HistoricalBuffer.h" #include "Region.h" #include "AudioBuffer.h" #include "AudioSpan.h" @@ -59,6 +60,7 @@ public: void renderBlock(AudioSpan buffer) noexcept; bool isFree() const noexcept; + bool canBeStolen() const noexcept; int getTriggerNumber() const noexcept; int getTriggerChannel() const noexcept; uint8_t getTriggerValue() const noexcept; @@ -66,6 +68,9 @@ public: void reset() noexcept; void garbageCollect() noexcept; + + float getMeanSquaredAverage() const noexcept; + float getSourcePosition() const noexcept; private: void fillWithData(AudioSpan buffer) noexcept; void fillWithGenerator(AudioSpan buffer) noexcept; @@ -126,6 +131,7 @@ private: LinearEnvelope positionEnvelope; LinearEnvelope widthEnvelope; + HistoricalBuffer powerHistory { config::powerHistoryLength }; LEAK_DETECTOR(Voice); }; diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 7b21fffb..c991c58d 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -696,5 +696,19 @@ TEST_CASE("[Helpers] Mean (SIMD vs scalar)") { std::vector input(bigBufferSize); absl::c_iota(input, 0.0); - REQUIRE(mean(input) == mean(input)); + REQUIRE(mean(input) == Approx(mean(input)).margin(0.001)); +} + +TEST_CASE("[Helpers] Mean Squared") +{ + std::array input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f }; + REQUIRE(meanSquared(input) == 38.5f); + REQUIRE(meanSquared(input) == 38.5f); +} + +TEST_CASE("[Helpers] Mean Squared (SIMD vs scalar)") +{ + std::vector input(medBufferSize); + absl::c_iota(input, 0.0); + REQUIRE(meanSquared(input) == meanSquared(input)); } \ No newline at end of file