From 3e58a36b61cb0b626b4a666f04aa3b63f2c2ffca Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 9 Feb 2020 22:52:52 +0100 Subject: [PATCH 01/21] Multiply pan on the channels --- src/sfizz/SIMDHelpers.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index 9ece0217..0fada489 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -755,8 +755,8 @@ namespace _internals { return panData[index]; }; - *left++ = lookUp(p); - *right++ = lookUp(1 - p); + *left++ *= lookUp(p); + *right++ *= lookUp(1 - p); } } From 7e665f28a12685efe95ec8340de6ba74cc73cef0 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 9 Feb 2020 22:54:53 +0100 Subject: [PATCH 02/21] Disable the SIMD accelerator on pan --- src/sfizz/Config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 92765c35..cd696ac8 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -77,7 +77,7 @@ namespace SIMDConfig { constexpr bool subtract { false }; constexpr bool multiplyAdd { false }; constexpr bool copy { false }; - constexpr bool pan { true }; + constexpr bool pan { false }; constexpr bool cumsum { true }; constexpr bool diff { false }; constexpr bool sfzInterpolationCast { true }; From e8b6692d778dd4ad0f0dc8429604363d43d5d663 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 9 Feb 2020 23:02:19 +0100 Subject: [PATCH 03/21] Changed to the pan helper --- src/sfizz/Voice.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index d4a3e6f0..4ba8f016 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -294,15 +294,8 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept copy(leftBuffer, rightBuffer); panEnvelope.getBlock(span1); - // We assume that the pan envelope is already normalized between -1 and 1 - // Check bm_pan for your architecture to check if it's interesting to use the pan helper instead - fill(span2, 1.0f); - add(span1, span2); - applyGain(piFour, span2); - cos(span2, span1); - sin(span2, span2); - applyGain(span1, leftBuffer); - applyGain(span2, rightBuffer); + copy(leftBuffer, rightBuffer); + pan(span1, leftBuffer, rightBuffer); } void sfz::Voice::processStereo(AudioSpan buffer) noexcept From 992d20782fd7ee7a6248462b3c6c941ebd446156 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 9 Feb 2020 23:05:02 +0100 Subject: [PATCH 04/21] Changed the way the pan CC is computed --- src/sfizz/Voice.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 4ba8f016..b18416f5 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -177,17 +177,17 @@ void sfz::Voice::registerCC(int delay, int ccNumber, uint8_t ccValue) noexcept } if (region->panCC && ccNumber == region->panCC->first) { - const float newPan { basePan + normalizeCC(ccValue) * normalizeNegativePercents(region->panCC->second) }; + const float newPan { basePan + normalizeCC(ccValue) * region->panCC->second / 100.0f }; panEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPan)); } if (region->positionCC && ccNumber == region->positionCC->first) { - const float newPosition { basePosition + normalizeCC(ccValue) * normalizeNegativePercents(region->positionCC->second) }; + const float newPosition { basePosition + normalizeCC(ccValue) * region->panCC->second / 100.0f }; positionEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPosition)); } if (region->widthCC && ccNumber == region->widthCC->first) { - const float newWidth { baseWidth + normalizeCC(ccValue) * normalizeNegativePercents(region->widthCC->second) }; + const float newWidth { baseWidth + normalizeCC(ccValue) * region->panCC->second / 100.0f }; widthEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newWidth)); } From 9783e393da99b15530a67569386554b5d4b0c04f Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 9 Feb 2020 23:38:50 +0100 Subject: [PATCH 05/21] Added a width snippet that uses the pan LUT --- src/sfizz/SIMDHelpers.h | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index 0fada489..fea1404e 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -742,21 +742,30 @@ namespace _internals { return pan; }(); + template + inline T panLookup(T pan) + { + // reduce range, round to nearest + int index = static_cast(T{0.5} + pan * (panSize - 1)); + return panData[index]; + } + template inline void snippetPan(const T*& pan, T*& left, T*& right) { T p = ((*pan++) + T{1.0}) * T{0.5}; p = clamp(p, 0, 1); + *left++ *= panLookup(p); + *right++ *= panLookup(1 - p); + } - auto lookUp = [](T pan) -> T - { - // reduce range, round to nearest - int index = static_cast(T{0.5} + pan * (panSize - 1)); - return panData[index]; - }; - - *left++ *= lookUp(p); - *right++ *= lookUp(1 - p); + template + inline void snippetWidth(const T*& pan, T*& mid, T*& side) + { + T p = std::abs(*pan); + p = clamp(p, 0, 1); + *mid++ *= panLookup(p); + *side++ *= *pan++ > 0 ? panLookup(1 - p) : -panLookup(1 - p); } } @@ -787,6 +796,19 @@ void pan(absl::Span panEnvelope, absl::Span leftBuffer, absl::Span void pan(absl::Span panEnvelope, absl::Span leftBuffer, absl::Span rightBuffer) noexcept; +template +void width(absl::Span widthEnvelope, absl::Span midBuffer, absl::Span sideBuffer) noexcept +{ + ASSERT(midBuffer.size() >= widthEnvelope.size()); + ASSERT(sideBuffer.size() >= widthEnvelope.size()); + auto* width = widthEnvelope.begin(); + auto* mid = midBuffer.begin(); + auto* side = sideBuffer.begin(); + auto* sentinel = width + min(widthEnvelope.size(), midBuffer.size(), sideBuffer.size()); + while (width < sentinel) + _internals::snippetWidth(width, mid, side); +} + /** * @brief Computes the mean of a span * From 06de58830898e375419b6e1cae48aba779e72c87 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 9 Feb 2020 23:39:33 +0100 Subject: [PATCH 06/21] Changed the M/S processing to use the pan/width helpers --- src/sfizz/Voice.cpp | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index b18416f5..45e6ab4e 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -327,33 +327,28 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept copy(rightBuffer, span1); add(leftBuffer, rightBuffer); subtract(span1, leftBuffer); - applyGain(sqrtTwoInv, leftBuffer); - applyGain(sqrtTwoInv, rightBuffer); + + // Add const aliases to be slightly more readable + const auto midBuffer = leftBuffer; + const auto sideBuffer = rightBuffer; + applyGain(sqrtTwoInv, midBuffer); + applyGain(sqrtTwoInv, sideBuffer); // Apply the width process widthEnvelope.getBlock(span1); - fill(span2, 1.0f); - add(span1, span2); - applyGain(piFour, span2); - cos(span2, span1); - sin(span2, span2); - applyGain(span1, leftBuffer); - applyGain(span2, rightBuffer); + width(span1, midBuffer, sideBuffer); - // Apply a position to the "left" channel which is supposed to be our mid channel - // TODO: add panning here too? + // Copy the mid channel into another span + const auto midBufferRight = span2; + copy(midBuffer, midBufferRight); positionEnvelope.getBlock(span1); - fill(span2, 1.0f); - add(span1, span2); - applyGain(piFour, span2); - cos(span2, span1); - sin(span2, span2); - copy(leftBuffer, span3); - copy(rightBuffer, leftBuffer); - multiplyAdd(span1, span3, leftBuffer); - multiplyAdd(span2, span3, rightBuffer); - applyGain(sqrtTwoInv, leftBuffer); - applyGain(sqrtTwoInv, rightBuffer); + pan(span1, midBuffer, midBufferRight); + + // Rebuild left/right + add(sideBuffer, midBuffer); + applyGain(sqrtTwoInv, leftBuffer); + add(midBufferRight, sideBuffer); + applyGain(sqrtTwoInv, rightBuffer); } void sfz::Voice::fillWithData(AudioSpan buffer) noexcept From 58a5e87df533ba2add21d5512c804c64fd27c4fa Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 9 Feb 2020 23:46:00 +0100 Subject: [PATCH 07/21] Position and width correctly map their own cc modulation --- src/sfizz/Voice.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 45e6ab4e..56e7fcea 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -182,12 +182,12 @@ void sfz::Voice::registerCC(int delay, int ccNumber, uint8_t ccValue) noexcept } if (region->positionCC && ccNumber == region->positionCC->first) { - const float newPosition { basePosition + normalizeCC(ccValue) * region->panCC->second / 100.0f }; + const float newPosition { basePosition + normalizeCC(ccValue) * region->positionCC->second / 100.0f }; positionEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPosition)); } if (region->widthCC && ccNumber == region->widthCC->first) { - const float newWidth { baseWidth + normalizeCC(ccValue) * region->panCC->second / 100.0f }; + const float newWidth { baseWidth + normalizeCC(ccValue) * region->widthCC->second / 100.0f }; widthEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newWidth)); } From 3c14e15a1614be198228daf42171040b2de73baf Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 9 Feb 2020 23:51:23 +0100 Subject: [PATCH 08/21] Use the CC switch on the initial width and position with proper scaling --- src/sfizz/Voice.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 56e7fcea..d97eb6a8 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -63,19 +63,19 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value basePan = normalizeNegativePercents(region->pan); auto pan { basePan }; if (region->panCC) - pan += normalizeCC(midiState.getCCValue(region->panCC->first)) * normalizeNegativePercents(region->panCC->second); + pan += normalizeCC(midiState.getCCValue(region->panCC->first)) * region->panCC->second / 100.0f; panEnvelope.reset(Default::symmetricNormalizedRange.clamp(pan)); basePosition = normalizeNegativePercents(region->position); auto position { basePosition }; if (region->positionCC) - position += normalizeCC(midiState.getCCValue(region->positionCC->first)) * normalizeNegativePercents(region->positionCC->second); + position += normalizeCC(midiState.getCCValue(region->positionCC->first)) * region->positionCC->second / 100.0f; positionEnvelope.reset(Default::symmetricNormalizedRange.clamp(position)); baseWidth = normalizeNegativePercents(region->width); auto width { baseWidth }; if (region->widthCC) - width += normalizeCC(midiState.getCCValue(region->widthCC->first)) * normalizeNegativePercents(region->widthCC->second); + width += normalizeCC(midiState.getCCValue(region->widthCC->first)) * region->widthCC->second / 100.0f; widthEnvelope.reset(Default::symmetricNormalizedRange.clamp(width)); pitchBendEnvelope.setFunction([region](float pitchValue){ From 04779965b8dca0ba36f651d751cb572c7a5b8953 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 9 Feb 2020 23:52:30 +0100 Subject: [PATCH 09/21] Remove unused spans --- src/sfizz/Voice.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index d97eb6a8..4ef3e8b0 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -302,8 +302,6 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept { const auto numSamples = buffer.getNumFrames(); auto span1 = tempSpan1.first(numSamples); - auto span2 = tempSpan2.first(numSamples); - auto span3 = tempSpan3.first(numSamples); auto leftBuffer = buffer.getSpan(0); auto rightBuffer = buffer.getSpan(1); @@ -339,7 +337,7 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept width(span1, midBuffer, sideBuffer); // Copy the mid channel into another span - const auto midBufferRight = span2; + const auto midBufferRight = tempSpan2.first(numSamples); copy(midBuffer, midBufferRight); positionEnvelope.getBlock(span1); pan(span1, midBuffer, midBufferRight); From ccbcf4a4c7edf1e2bd667eb01999fd6fa1e286d7 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 10 Feb 2020 00:18:11 +0100 Subject: [PATCH 10/21] Remove a helper; no clamping for normalizePercent --- src/sfizz/SfzHelpers.h | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 37cf3dda..71d3c4f1 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -71,7 +71,7 @@ constexpr float normalizeVelocity(T velocity) template constexpr float normalizePercents(T percentValue) { - return std::min(std::max(static_cast(percentValue), 0.0f), 100.0f) / 100.0f; + return percentValue * 0.01f; } /** @@ -86,19 +86,6 @@ constexpr float normalizeBend(float bendValue) return std::min(std::max(bendValue, -8191.0f), 8191.0f) / 8191.0f; } -/** - * @brief Normalize a possibly negative percentage between -1 and 1 - * - * @tparam T - * @param percentValue - * @return constexpr float - */ -template -constexpr float normalizeNegativePercents(T percentValue) -{ - return std::min(std::max(static_cast(percentValue), -100.0f), 100.0f) / 100.0f; -} - /** * @brief If a cc switch exists for the value, returns the value with the CC modifier, otherwise returns the value alone. * From c55e578c15b9e007067481c70062fa6df7f9be1d Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 10 Feb 2020 00:18:36 +0100 Subject: [PATCH 11/21] Use the helper with a multiplication --- src/sfizz/Voice.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 4ef3e8b0..80717efd 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -60,22 +60,22 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value float crossfadeGain { region->getCrossfadeGain(midiState.getCCArray()) }; crossfadeEnvelope.reset(Default::normalizedRange.clamp(crossfadeGain)); - basePan = normalizeNegativePercents(region->pan); + basePan = normalizePercents(region->pan); auto pan { basePan }; if (region->panCC) - pan += normalizeCC(midiState.getCCValue(region->panCC->first)) * region->panCC->second / 100.0f; + pan += normalizeCC(midiState.getCCValue(region->panCC->first)) * normalizePercents(region->panCC->second); panEnvelope.reset(Default::symmetricNormalizedRange.clamp(pan)); - basePosition = normalizeNegativePercents(region->position); + basePosition = normalizePercents(region->position); auto position { basePosition }; if (region->positionCC) - position += normalizeCC(midiState.getCCValue(region->positionCC->first)) * region->positionCC->second / 100.0f; + position += normalizeCC(midiState.getCCValue(region->positionCC->first)) * normalizePercents(region->positionCC->second); positionEnvelope.reset(Default::symmetricNormalizedRange.clamp(position)); - baseWidth = normalizeNegativePercents(region->width); + baseWidth = normalizePercents(region->width); auto width { baseWidth }; if (region->widthCC) - width += normalizeCC(midiState.getCCValue(region->widthCC->first)) * region->widthCC->second / 100.0f; + width += normalizeCC(midiState.getCCValue(region->widthCC->first)) * normalizePercents(region->widthCC->second); widthEnvelope.reset(Default::symmetricNormalizedRange.clamp(width)); pitchBendEnvelope.setFunction([region](float pitchValue){ @@ -177,17 +177,17 @@ void sfz::Voice::registerCC(int delay, int ccNumber, uint8_t ccValue) noexcept } if (region->panCC && ccNumber == region->panCC->first) { - const float newPan { basePan + normalizeCC(ccValue) * region->panCC->second / 100.0f }; + const float newPan { basePan + normalizeCC(ccValue) * normalizePercents(region->panCC->second) }; panEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPan)); } if (region->positionCC && ccNumber == region->positionCC->first) { - const float newPosition { basePosition + normalizeCC(ccValue) * region->positionCC->second / 100.0f }; + const float newPosition { basePosition + normalizeCC(ccValue) * normalizePercents(region->positionCC->second) }; positionEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPosition)); } if (region->widthCC && ccNumber == region->widthCC->first) { - const float newWidth { baseWidth + normalizeCC(ccValue) * region->widthCC->second / 100.0f }; + const float newWidth { baseWidth + normalizeCC(ccValue) * normalizePercents(region->widthCC->second) }; widthEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newWidth)); } From 25068e51bfbde5fd6249b337d2e74f3ac303c0f3 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 10 Feb 2020 00:40:09 +0100 Subject: [PATCH 12/21] Missed a normalization --- src/sfizz/SIMDHelpers.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index fea1404e..7aa93750 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -760,12 +760,12 @@ namespace _internals { } template - inline void snippetWidth(const T*& pan, T*& mid, T*& side) + inline void snippetWidth(const T*& width, T*& mid, T*& side) { - T p = std::abs(*pan); - p = clamp(p, 0, 1); - *mid++ *= panLookup(p); - *side++ *= *pan++ > 0 ? panLookup(1 - p) : -panLookup(1 - p); + T w = std::abs(*width) * T{0.5}; + w = clamp(w, 0, 1); + *mid++ *= panLookup(w); + *side++ *= *width++ > 0 ? panLookup(1 - w) : -panLookup(1 - w); } } From 67e549b14bddfe2c41d6d018d1ddfd30d9b385a9 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 10 Feb 2020 00:51:31 +0100 Subject: [PATCH 13/21] Removed an unused span --- src/sfizz/Voice.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 80717efd..fc569cf3 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -272,7 +272,6 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept auto rightBuffer = buffer.getSpan(1); auto span1 = tempSpan1.first(numSamples); - auto span2 = tempSpan2.first(numSamples); // Amplitude envelope amplitudeEnvelope.getBlock(span1); From 33cc4a65181b1907153e50c7bec65613a9c8a5e6 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 10 Feb 2020 14:16:21 +0100 Subject: [PATCH 14/21] Changes to the mono and stereo processes - Corrected possible issues in the computation - Removed an unnecessary copy in the mono process - Trying to make the processes clearer --- src/sfizz/Voice.cpp | 74 +++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index d200a1c9..fbe2674e 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -290,23 +290,23 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept auto leftBuffer = buffer.getSpan(0); auto rightBuffer = buffer.getSpan(1); - auto span1 = tempSpan1.first(numSamples); + auto modulationSpan = tempSpan1.first(numSamples); // Amplitude envelope - amplitudeEnvelope.getBlock(span1); - applyGain(span1, leftBuffer); + amplitudeEnvelope.getBlock(modulationSpan); + applyGain(modulationSpan, leftBuffer); // Crossfade envelope - crossfadeEnvelope.getBlock(span1); - applyGain(span1, leftBuffer); + crossfadeEnvelope.getBlock(modulationSpan); + applyGain(modulationSpan, leftBuffer); // Volume envelope - volumeEnvelope.getBlock(span1); - applyGain(span1, leftBuffer); + volumeEnvelope.getBlock(modulationSpan); + applyGain(modulationSpan, leftBuffer); // AmpEG envelope - egEnvelope.getBlock(span1); - applyGain(span1, leftBuffer); + egEnvelope.getBlock(modulationSpan); + applyGain(modulationSpan, leftBuffer); // Filtering and EQ const float* inputChannel[1] { leftBuffer.data() }; @@ -322,59 +322,67 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept // Prepare for stereo output copy(leftBuffer, rightBuffer); - panEnvelope.getBlock(span1); - copy(leftBuffer, rightBuffer); - pan(span1, leftBuffer, rightBuffer); + // Apply panning + panEnvelope.getBlock(modulationSpan); + pan(modulationSpan, leftBuffer, rightBuffer); } void sfz::Voice::processStereo(AudioSpan buffer) noexcept { const auto numSamples = buffer.getNumFrames(); - auto span1 = tempSpan1.first(numSamples); + auto modulationSpan = tempSpan1.first(numSamples); auto leftBuffer = buffer.getSpan(0); auto rightBuffer = buffer.getSpan(1); // Amplitude envelope - amplitudeEnvelope.getBlock(span1); - buffer.applyGain(span1); + amplitudeEnvelope.getBlock(modulationSpan); + buffer.applyGain(modulationSpan); // Crossfade envelope - crossfadeEnvelope.getBlock(span1); - buffer.applyGain(span1); + crossfadeEnvelope.getBlock(modulationSpan); + buffer.applyGain(modulationSpan); // Volume envelope - volumeEnvelope.getBlock(span1); - buffer.applyGain(span1); + volumeEnvelope.getBlock(modulationSpan); + buffer.applyGain(modulationSpan); // AmpEG envelope - egEnvelope.getBlock(span1); - buffer.applyGain(span1); + egEnvelope.getBlock(modulationSpan); + buffer.applyGain(modulationSpan); // Create mid/side from left/right in the output buffer - copy(rightBuffer, span1); - add(leftBuffer, rightBuffer); - subtract(span1, leftBuffer); - // Add const aliases to be slightly more readable + const auto leftBufferCopy = tempSpan2.first(numSamples); + copy(leftBuffer, leftBufferCopy); + const auto midBuffer = leftBuffer; + add(rightBuffer, midBuffer); + const auto sideBuffer = rightBuffer; + applyGain(-1.0f, sideBuffer); + add(leftBufferCopy, sideBuffer); + applyGain(sqrtTwoInv, midBuffer); applyGain(sqrtTwoInv, sideBuffer); // Apply the width process - widthEnvelope.getBlock(span1); - width(span1, midBuffer, sideBuffer); + widthEnvelope.getBlock(modulationSpan); + width(modulationSpan, midBuffer, sideBuffer); // Copy the mid channel into another span - const auto midBufferRight = tempSpan2.first(numSamples); - copy(midBuffer, midBufferRight); - positionEnvelope.getBlock(span1); - pan(span1, midBuffer, midBufferRight); + const auto midBufferCopy = tempSpan3.first(numSamples); + copy(midBuffer, midBufferCopy); + positionEnvelope.getBlock(modulationSpan); + pan(modulationSpan, midBuffer, midBufferCopy); // Rebuild left/right - add(sideBuffer, midBuffer); + // Recall that midBuffer and leftBuffer point to the same buffer + add(sideBuffer, leftBuffer); applyGain(sqrtTwoInv, leftBuffer); - add(midBufferRight, sideBuffer); + + // Recall that sideBuffer and rightBuffer point to the same buffer + applyGain(-1.0f, sideBuffer); + add(midBufferCopy, sideBuffer); applyGain(sqrtTwoInv, rightBuffer); // Filtering and EQ From ef5a2fa546a3f257ca38301e761a8ec0b47ee01e Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 10 Feb 2020 16:16:15 +0100 Subject: [PATCH 15/21] Add the test program for stereo --- tests/CMakeLists.txt | 5 + tests/DemoStereo.cpp | 238 +++++++++++++++++++++++++++++++++++++++++++ tests/DemoStereo.ui | 98 ++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 tests/DemoStereo.cpp create mode 100644 tests/DemoStereo.ui diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d14ea8f1..2bb008a5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,6 +42,11 @@ if(JACK_FOUND AND TARGET Qt5::Widgets) target_include_directories(sfizz_demo_filters PRIVATE ${JACK_INCLUDE_DIRS}) target_link_libraries(sfizz_demo_filters PRIVATE sfizz::sfizz Qt5::Widgets ${JACK_LIBRARIES}) set_target_properties(sfizz_demo_filters PROPERTIES AUTOUIC ON) + + add_executable(sfizz_demo_stereo DemoStereo.cpp) + target_include_directories(sfizz_demo_stereo PRIVATE ${JACK_INCLUDE_DIRS}) + target_link_libraries(sfizz_demo_stereo PRIVATE sfizz::sfizz Qt5::Widgets ${JACK_LIBRARIES}) + set_target_properties(sfizz_demo_stereo PROPERTIES AUTOUIC ON) endif() file(COPY "." DESTINATION ${CMAKE_BINARY_DIR}/tests) diff --git a/tests/DemoStereo.cpp b/tests/DemoStereo.cpp new file mode 100644 index 00000000..0c6f47ee --- /dev/null +++ b/tests/DemoStereo.cpp @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "sfizz/SIMDHelpers.h" +#include "ui_DemoStereo.h" +#include +#include +#include +#include +#include +#include +#include + +/// +struct jack_delete { + void operator()(jack_client_t *x) const noexcept { jack_client_close(x); } +}; + +typedef std::unique_ptr jack_client_u; + +/// +class DemoApp : public QApplication { +public: + DemoApp(int &argc, char **argv); + bool initSound(); + void initWindow(); + +private: + static int processAudio(jack_nframes_t nframes, void *cbdata); + +private: + void valueChangedWidth(int value); + void valueChangedPan(int value); + +private: + QMainWindow *fWindow = nullptr; + Ui::DemoStereoWindow fUi; + + static constexpr int widthMin = -100; + static constexpr int widthMax = +100; + + static constexpr int panMin = -100; + static constexpr int panMax = +100; + + int fWidth = 100; + int fPan = 0; + + std::unique_ptr fTmpWidthEnvelope; + std::unique_ptr fTmpPositionEnvelope; + std::unique_ptr fTmpBuffer1; + + jack_client_u fClient; + jack_port_t *fPorts[4] = {}; +}; + +DemoApp::DemoApp(int &argc, char **argv) + : QApplication(argc, argv) +{ + setApplicationName(tr("Sfizz Stereo")); +} + +bool DemoApp::initSound() +{ + jack_client_t *client = jack_client_open( + applicationName().toUtf8().data(), JackNoStartServer, nullptr); + if (!client) { + QMessageBox::critical(nullptr, tr("Error"), tr("Cannot open JACK audio.")); + return false; + } + + fClient.reset(client); + + uint32_t bufsize = jack_get_buffer_size(client); + fTmpWidthEnvelope.reset(new float[bufsize]); + fTmpPositionEnvelope.reset(new float[bufsize]); + fTmpBuffer1.reset(new float[bufsize]); + + fPorts[0] = jack_port_register(client, "in_left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + fPorts[1] = jack_port_register(client, "in_right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + fPorts[2] = jack_port_register(client, "out_left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + fPorts[3] = jack_port_register(client, "out_right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + + if (!(fPorts[0] && fPorts[1] && fPorts[2] && fPorts[3])) { + QMessageBox::critical(nullptr, tr("Error"), tr("Cannot register JACK ports.")); + return false; + } + + jack_set_process_callback(client, &processAudio, this); + + if (jack_activate(client) != 0) { + QMessageBox::critical(nullptr, tr("Error"), tr("Cannot activate JACK client.")); + return false; + } + + return true; +} + +void DemoApp::initWindow() +{ + QMainWindow *window = new QMainWindow; + fWindow = window; + fUi.setupUi(window); + window->setWindowTitle(applicationDisplayName()); + + fUi.valWidth->setRange(widthMin, widthMax); + fUi.valPan->setRange(panMin, panMax); + fUi.spinWidth->setRange(widthMin, widthMax); + fUi.spinPan->setRange(panMin, panMax); + + fUi.valWidth->setValue(fWidth); + fUi.valPan->setValue(fPan); + fUi.spinWidth->setValue(fWidth); + fUi.spinPan->setValue(fPan); + + connect( + fUi.valWidth, &QSlider::valueChanged, + this, [this](int value) { valueChangedWidth(value); }); + connect( + fUi.spinWidth, QOverload::of(&QSpinBox::valueChanged), + this, [this](int value) { valueChangedWidth(value); }); + connect( + fUi.valPan, &QSlider::valueChanged, + this, [this](int value) { valueChangedPan(value); }); + connect( + fUi.spinPan, QOverload::of(&QSpinBox::valueChanged), + this, [this](int value) { valueChangedPan(value); }); + + window->adjustSize(); + window->setFixedSize(window->size()); + + window->show(); +} + +int DemoApp::processAudio(jack_nframes_t nframes, void *cbdata) +{ + DemoApp *self = reinterpret_cast(cbdata); + + absl::Span leftBuffer { + reinterpret_cast(jack_port_get_buffer(self->fPorts[2], nframes)), + nframes}; + absl::Span rightBuffer { + reinterpret_cast(jack_port_get_buffer(self->fPorts[3], nframes)), + nframes}; + + std::copy_n( + reinterpret_cast(jack_port_get_buffer(self->fPorts[0], nframes)), + nframes, leftBuffer.begin()); + std::copy_n( + reinterpret_cast(jack_port_get_buffer(self->fPorts[1], nframes)), + nframes, rightBuffer.begin()); + + absl::Span widthEnvelope{self->fTmpWidthEnvelope.get(), nframes}; + absl::Span positionEnvelope{self->fTmpPositionEnvelope.get(), nframes}; + absl::Span tempSpan1{self->fTmpBuffer1.get(), nframes}; + + std::fill(widthEnvelope.begin(), widthEnvelope.end(), self->fWidth * 0.01f); + std::fill(positionEnvelope.begin(), positionEnvelope.end(), self->fPan * 0.01f); + + using namespace sfz; + + /* TODO(jpc) have this code in common instead of copy-paste */ + + // Create mid/side from left/right in the output buffer + // Add const aliases to be slightly more readable + const auto leftBufferCopy = tempSpan1; + copy(leftBuffer, leftBufferCopy); + + const auto midBuffer = leftBuffer; + add(rightBuffer, midBuffer); + + const auto sideBuffer = rightBuffer; + applyGain(-1.0f, sideBuffer); + add(leftBufferCopy, sideBuffer); + + applyGain(sqrtTwoInv, midBuffer); + applyGain(sqrtTwoInv, sideBuffer); + + // Apply the width process + width(widthEnvelope, midBuffer, sideBuffer); + + // Copy the mid channel into another span + const auto midBufferCopy = tempSpan1; + copy(midBuffer, midBufferCopy); + pan(positionEnvelope, midBuffer, midBufferCopy); + + // Rebuild left/right + // Recall that midBuffer and leftBuffer point to the same buffer + add(sideBuffer, leftBuffer); + applyGain(sqrtTwoInv, leftBuffer); + + // Recall that sideBuffer and rightBuffer point to the same buffer + applyGain(-1.0f, sideBuffer); + add(midBufferCopy, sideBuffer); + applyGain(sqrtTwoInv, rightBuffer); + + return 0; +} + +void DemoApp::valueChangedWidth(int value) +{ + fUi.valWidth->blockSignals(true); + fUi.valWidth->setValue(value); + fUi.valWidth->blockSignals(false); + + fUi.spinWidth->blockSignals(true); + fUi.spinWidth->setValue(value); + fUi.spinWidth->blockSignals(false); + + fWidth = value; +} + +void DemoApp::valueChangedPan(int value) +{ + fUi.valPan->blockSignals(true); + fUi.valPan->setValue(value); + fUi.valPan->blockSignals(false); + + fUi.spinPan->blockSignals(true); + fUi.spinPan->setValue(value); + fUi.spinPan->blockSignals(false); + + fPan = value; +} + +int main(int argc, char *argv[]) +{ + DemoApp app(argc, argv); + + if (!app.initSound()) + return 1; + + app.initWindow(); + + return app.exec(); +} diff --git a/tests/DemoStereo.ui b/tests/DemoStereo.ui new file mode 100644 index 00000000..b69cd01d --- /dev/null +++ b/tests/DemoStereo.ui @@ -0,0 +1,98 @@ + + + DemoStereoWindow + + + + 0 + 0 + 615 + 72 + + + + + + + + Width + + + + + + + + 500 + 0 + + + + -100 + + + 100 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + -100 + + + 100 + + + + + + + Pan + + + + + + + + 500 + 0 + + + + -100 + + + 100 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + -100 + + + 100 + + + + + + + + + From d74777280abf4b73df3ac2c5ce0b23a6661dc9b0 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 10 Feb 2020 19:35:40 +0100 Subject: [PATCH 16/21] Changed the signature of the pan/width/position snippets --- src/sfizz/SIMDHelpers.h | 30 +++++++++++++++++------------- src/sfizz/SIMDSSE.cpp | 12 ++++++++---- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index 7aa93750..77c220e4 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -751,21 +751,21 @@ namespace _internals { } template - inline void snippetPan(const T*& pan, T*& left, T*& right) + inline void snippetPan(T pan, T& left, T& right) { - T p = ((*pan++) + T{1.0}) * T{0.5}; - p = clamp(p, 0, 1); - *left++ *= panLookup(p); - *right++ *= panLookup(1 - p); + pan = (pan + T{1.0}) * T{0.5}; + pan = clamp(pan, 0, 1); + left *= panLookup(pan); + right *= panLookup(1 - pan); } template - inline void snippetWidth(const T*& width, T*& mid, T*& side) + inline void snippetWidth(const T& width, T& mid, T& side) { - T w = std::abs(*width) * T{0.5}; + T w = std::abs(width) * T{0.5}; w = clamp(w, 0, 1); - *mid++ *= panLookup(w); - *side++ *= *width++ > 0 ? panLookup(1 - w) : -panLookup(1 - w); + mid *= panLookup(w); + side *= width > 0 ? panLookup(1 - w) : -panLookup(1 - w); } } @@ -789,8 +789,10 @@ void pan(absl::Span panEnvelope, absl::Span leftBuffer, absl::Span @@ -805,8 +807,10 @@ void width(absl::Span widthEnvelope, absl::Span midBuffer, absl::Spa auto* mid = midBuffer.begin(); auto* side = sideBuffer.begin(); auto* sentinel = width + min(widthEnvelope.size(), midBuffer.size(), sideBuffer.size()); - while (width < sentinel) - _internals::snippetWidth(width, mid, side); + while (width < sentinel) { + _internals::snippetWidth(*width, *mid, *side); + incrementAll(width, mid, side); + } } /** diff --git a/src/sfizz/SIMDSSE.cpp b/src/sfizz/SIMDSSE.cpp index 8b541657..3093fcdf 100644 --- a/src/sfizz/SIMDSSE.cpp +++ b/src/sfizz/SIMDSSE.cpp @@ -594,8 +594,10 @@ void sfz::pan(absl::Span panEnvelope, absl::Span); @@ -613,8 +615,10 @@ void sfz::pan(absl::Span panEnvelope, absl::Span(pan, left, right); } - while (pan < sentinel) - _internals::snippetPan(pan, left, right); + while (pan < sentinel){ + _internals::snippetPan(*pan, *left, *right); + incrementAll(pan, left, right); + } } template <> From 90d680f0c33cf669a2204af01c36ccfaaae3fb8f Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 10 Feb 2020 19:36:04 +0100 Subject: [PATCH 17/21] Added benchmarks and tests for width/pan --- benchmarks/BM_pan.cpp | 4 ++ benchmarks/BM_widthPos.cpp | 120 +++++++++++++++++++++++++++++++++++++ benchmarks/CMakeLists.txt | 2 + tests/SIMDHelpersT.cpp | 58 ++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 benchmarks/BM_widthPos.cpp diff --git a/benchmarks/BM_pan.cpp b/benchmarks/BM_pan.cpp index ff764cdd..b3f522e8 100644 --- a/benchmarks/BM_pan.cpp +++ b/benchmarks/BM_pan.cpp @@ -12,6 +12,7 @@ #include #include #include "Config.h" +#include "ScopedFTZ.h" #include "absl/types/span.h" class PanArray : public benchmark::Fixture { @@ -47,6 +48,7 @@ public: BENCHMARK_DEFINE_F(PanArray, Scalar)(benchmark::State& state) { + ScopedFTZ ftz; for (auto _ : state) { sfz::pan(pan, absl::MakeSpan(left), absl::MakeSpan(right)); @@ -54,6 +56,7 @@ BENCHMARK_DEFINE_F(PanArray, Scalar)(benchmark::State& state) { } BENCHMARK_DEFINE_F(PanArray, SIMD)(benchmark::State& state) { + ScopedFTZ ftz; for (auto _ : state) { sfz::pan(pan, absl::MakeSpan(left), absl::MakeSpan(right)); @@ -61,6 +64,7 @@ BENCHMARK_DEFINE_F(PanArray, SIMD)(benchmark::State& state) { } BENCHMARK_DEFINE_F(PanArray, BlockOps)(benchmark::State& state) { + ScopedFTZ ftz; for (auto _ : state) { sfz::fill(span2, 1.0f); diff --git a/benchmarks/BM_widthPos.cpp b/benchmarks/BM_widthPos.cpp new file mode 100644 index 00000000..b7f82a4d --- /dev/null +++ b/benchmarks/BM_widthPos.cpp @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "SIMDHelpers.h" +#include +#include +#include +#include +#include +#include +#include "Config.h" +#include "ScopedFTZ.h" +#include "absl/types/span.h" + +class WidthPosArray : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) { + std::random_device rd { }; + std::mt19937 gen { rd() }; + std::uniform_real_distribution dist { 0.001f, 1.0f }; + width = std::vector(state.range(0)); + position = std::vector(state.range(0)); + left = std::vector(state.range(0)); + right = std::vector(state.range(0)); + std::generate(width.begin(), width.end(), [&]() { return dist(gen); }); + std::generate(position.begin(), position.end(), [&]() { return dist(gen); }); + std::generate(right.begin(), right.end(), [&]() { return dist(gen); }); + std::generate(left.begin(), left.end(), [&]() { return dist(gen); }); + temp1 = std::vector(state.range(0)); + temp2 = std::vector(state.range(0)); + temp3 = std::vector(state.range(0)); + span1 = absl::MakeSpan(temp1); + span2 = absl::MakeSpan(temp2); + span3 = absl::MakeSpan(temp3); + } + + void TearDown(const ::benchmark::State& state [[maybe_unused]]) { + + } + + std::vector width; + std::vector position; + std::vector left; + std::vector right; + std::vector temp1; + std::vector temp2; + std::vector temp3; + absl::Span span1; + absl::Span span2; + absl::Span span3; +}; + +BENCHMARK_DEFINE_F(WidthPosArray, Scalar)(benchmark::State& state) { + ScopedFTZ ftz; + for (auto _ : state) + { + auto widthPtr = width.data(); + auto posPtr = position.data(); + auto leftPtr = left.data(); + auto rightPtr = right.data(); + const auto sentinel = width.data() + width.size(); + while(widthPtr < sentinel) + { + auto mid = (*leftPtr + *rightPtr) * sqrtTwoInv; + auto side = (*leftPtr - *rightPtr) * sqrtTwoInv; + sfz::_internals::snippetWidth(*widthPtr, mid, side); + auto midRight = mid; + sfz::_internals::snippetPan(*posPtr, mid, midRight); + *leftPtr = (mid + side) * sqrtTwoInv; + *rightPtr = (midRight - side) * sqrtTwoInv; + incrementAll(leftPtr, rightPtr, widthPtr, posPtr); + } + } +} + +BENCHMARK_DEFINE_F(WidthPosArray, BlockOps)(benchmark::State& state) { + ScopedFTZ ftz; + const auto leftBuffer = absl::MakeSpan(left); + const auto rightBuffer = absl::MakeSpan(right); + for (auto _ : state) + { + const auto leftBufferCopy = span2; + sfz::copy(left, leftBufferCopy); + + const auto midBuffer = leftBuffer; + sfz::add(rightBuffer, midBuffer); + + const auto sideBuffer = rightBuffer; + sfz::applyGain(-1.0f, sideBuffer); + sfz::add(leftBufferCopy, sideBuffer); + + sfz::applyGain(sqrtTwoInv, midBuffer); + sfz::applyGain(sqrtTwoInv, sideBuffer); + + // Apply the width process + sfz::width(width, midBuffer, sideBuffer); + + // Copy the mid channel into another span + const auto midBufferCopy = span3; + sfz::copy(midBuffer, midBufferCopy); + sfz::pan(position, midBuffer, midBufferCopy); + + // Rebuild left/right + // Recall that midBuffer and leftBuffer point to the same buffer + sfz::add(sideBuffer, leftBuffer); + sfz::applyGain(sqrtTwoInv, leftBuffer); + + // Recall that sideBuffer and rightBuffer point to the same buffer + sfz::applyGain(-1.0f, sideBuffer); + sfz::add(midBufferCopy, sideBuffer); + sfz::applyGain(sqrtTwoInv, rightBuffer); + } +} + +BENCHMARK_REGISTER_F(WidthPosArray, Scalar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(WidthPosArray, BlockOps)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_MAIN(); diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 7cff1d29..a73c12b5 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -51,6 +51,7 @@ sfizz_add_benchmark(bm_mean BM_mean.cpp) sfizz_add_benchmark(bm_meanSquared BM_meanSquared.cpp) sfizz_add_benchmark(bm_cumsum BM_cumsum.cpp) sfizz_add_benchmark(bm_diff BM_diff.cpp) +sfizz_add_benchmark(bm_widthPos BM_widthPos.cpp) sfizz_add_benchmark(bm_interpolationCast BM_interpolationCast.cpp) sfizz_add_benchmark(bm_pointerIterationOrOffsets BM_pointerIterationOrOffsets.cpp) @@ -106,6 +107,7 @@ add_dependencies(sfizz_benchmarks bm_resampleChunk bm_envelopes bm_wavfile + bm_widthPos bm_flacfile bm_filterModulation bm_filterStereoMono diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 1fff15b5..985be6e7 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -756,3 +756,61 @@ TEST_CASE("[Helpers] Diff (SIMD vs Scalar)") sfz::diff(input, absl::MakeSpan(outputSIMD)); REQUIRE(approxEqual(outputScalar, outputSIMD)); } + +TEST_CASE("[Helpers] Pan Scalar") +{ + std::array leftValue { 1.0f }; + std::array rightValue { 1.0f }; + auto left = absl::MakeSpan(leftValue); + auto right = absl::MakeSpan(rightValue); + SECTION("Pan = 0") + { + std::array pan { 0.0f }; + sfz::pan(pan, left, right); + REQUIRE(left[0] == Approx(0.70711f).margin(0.001f)); + REQUIRE(right[0] == Approx(0.70711f).margin(0.001f)); + } + SECTION("Pan = 1") + { + std::array pan { 1.0f }; + sfz::pan(pan, left, right); + REQUIRE(left[0] == Approx(0.0f).margin(0.001f)); + REQUIRE(right[0] == Approx(1.0f).margin(0.001f)); + } + SECTION("Pan = -1") + { + std::array pan { -1.0f }; + sfz::pan(pan, left, right); + REQUIRE(left[0] == Approx(1.0f).margin(0.001f)); + REQUIRE(right[0] == Approx(0.0f).margin(0.001f)); + } +} + +TEST_CASE("[Helpers] Width Scalar") +{ + std::array midValue { 1.0f }; + std::array sideValue { 1.0f }; + auto mid = absl::MakeSpan(midValue); + auto side = absl::MakeSpan(sideValue); + SECTION("width = 1") + { + std::array width { 1.0f }; + sfz::width(width, mid, side); + REQUIRE(mid[0] == Approx(0.70711f).margin(0.001f)); + REQUIRE(side[0] == Approx(0.70711f).margin(0.001f)); + } + SECTION("width = 0") + { + std::array width { 0.0f }; + sfz::width(width, mid, side); + REQUIRE(mid[0] == Approx(1.0f).margin(0.001f)); + REQUIRE(side[0] == Approx(0.0f).margin(0.001f)); + } + SECTION("width = -1") + { + std::array width { -1.0f }; + sfz::width(width, mid, side); + REQUIRE(mid[0] == Approx(0.70711f).margin(0.001f)); + REQUIRE(side[0] == Approx(-0.70711f).margin(0.001f)); + } +} From 754a7f8be79fd2ee31bf8d9edbb3c88919456313 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 10 Feb 2020 21:23:30 +0100 Subject: [PATCH 18/21] Changed the width helpers ; need SIMD version still --- benchmarks/BM_widthPos.cpp | 57 ++------------------------------------ src/sfizz/SIMDHelpers.h | 28 +++++++++++-------- tests/SIMDHelpersT.cpp | 26 ++++++++--------- 3 files changed, 32 insertions(+), 79 deletions(-) diff --git a/benchmarks/BM_widthPos.cpp b/benchmarks/BM_widthPos.cpp index b7f82a4d..b55e5628 100644 --- a/benchmarks/BM_widthPos.cpp +++ b/benchmarks/BM_widthPos.cpp @@ -54,67 +54,16 @@ public: }; BENCHMARK_DEFINE_F(WidthPosArray, Scalar)(benchmark::State& state) { - ScopedFTZ ftz; - for (auto _ : state) - { - auto widthPtr = width.data(); - auto posPtr = position.data(); - auto leftPtr = left.data(); - auto rightPtr = right.data(); - const auto sentinel = width.data() + width.size(); - while(widthPtr < sentinel) - { - auto mid = (*leftPtr + *rightPtr) * sqrtTwoInv; - auto side = (*leftPtr - *rightPtr) * sqrtTwoInv; - sfz::_internals::snippetWidth(*widthPtr, mid, side); - auto midRight = mid; - sfz::_internals::snippetPan(*posPtr, mid, midRight); - *leftPtr = (mid + side) * sqrtTwoInv; - *rightPtr = (midRight - side) * sqrtTwoInv; - incrementAll(leftPtr, rightPtr, widthPtr, posPtr); - } - } -} - -BENCHMARK_DEFINE_F(WidthPosArray, BlockOps)(benchmark::State& state) { ScopedFTZ ftz; const auto leftBuffer = absl::MakeSpan(left); const auto rightBuffer = absl::MakeSpan(right); for (auto _ : state) { - const auto leftBufferCopy = span2; - sfz::copy(left, leftBufferCopy); - - const auto midBuffer = leftBuffer; - sfz::add(rightBuffer, midBuffer); - - const auto sideBuffer = rightBuffer; - sfz::applyGain(-1.0f, sideBuffer); - sfz::add(leftBufferCopy, sideBuffer); - - sfz::applyGain(sqrtTwoInv, midBuffer); - sfz::applyGain(sqrtTwoInv, sideBuffer); - - // Apply the width process - sfz::width(width, midBuffer, sideBuffer); - - // Copy the mid channel into another span - const auto midBufferCopy = span3; - sfz::copy(midBuffer, midBufferCopy); - sfz::pan(position, midBuffer, midBufferCopy); - - // Rebuild left/right - // Recall that midBuffer and leftBuffer point to the same buffer - sfz::add(sideBuffer, leftBuffer); - sfz::applyGain(sqrtTwoInv, leftBuffer); - - // Recall that sideBuffer and rightBuffer point to the same buffer - sfz::applyGain(-1.0f, sideBuffer); - sfz::add(midBufferCopy, sideBuffer); - sfz::applyGain(sqrtTwoInv, rightBuffer); + sfz::width(width, leftBuffer, rightBuffer); + sfz::pan(position, leftBuffer, rightBuffer); } } + BENCHMARK_REGISTER_F(WidthPosArray, Scalar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); -BENCHMARK_REGISTER_F(WidthPosArray, BlockOps)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); BENCHMARK_MAIN(); diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index 77c220e4..355426d9 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -760,12 +760,16 @@ namespace _internals { } template - inline void snippetWidth(const T& width, T& mid, T& side) + inline void snippetWidth(T width, T& left, T& right) { - T w = std::abs(width) * T{0.5}; + T w = (width + T{1.0}) * T{0.5}; w = clamp(w, 0, 1); - mid *= panLookup(w); - side *= width > 0 ? panLookup(1 - w) : -panLookup(1 - w); + const auto coeff1 = panLookup(w); + const auto coeff2 = panLookup(1 - w); + const auto l = left; + const auto r = right; + left = l * coeff2 + r * coeff1; + right = l * coeff1 + r * coeff2; } } @@ -799,17 +803,17 @@ template <> void pan(absl::Span panEnvelope, absl::Span leftBuffer, absl::Span rightBuffer) noexcept; template -void width(absl::Span widthEnvelope, absl::Span midBuffer, absl::Span sideBuffer) noexcept +void width(absl::Span widthEnvelope, absl::Span leftBuffer, absl::Span rightBuffer) noexcept { - ASSERT(midBuffer.size() >= widthEnvelope.size()); - ASSERT(sideBuffer.size() >= widthEnvelope.size()); + ASSERT(leftBuffer.size() >= widthEnvelope.size()); + ASSERT(rightBuffer.size() >= widthEnvelope.size()); auto* width = widthEnvelope.begin(); - auto* mid = midBuffer.begin(); - auto* side = sideBuffer.begin(); - auto* sentinel = width + min(widthEnvelope.size(), midBuffer.size(), sideBuffer.size()); + auto* left = leftBuffer.begin(); + auto* right = rightBuffer.begin(); + auto* sentinel = width + min(widthEnvelope.size(), leftBuffer.size(), rightBuffer.size()); while (width < sentinel) { - _internals::snippetWidth(*width, *mid, *side); - incrementAll(width, mid, side); + _internals::snippetWidth(*width, *left, *right); + incrementAll(width, left, right); } } diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 985be6e7..51f9426c 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -788,29 +788,29 @@ TEST_CASE("[Helpers] Pan Scalar") TEST_CASE("[Helpers] Width Scalar") { - std::array midValue { 1.0f }; - std::array sideValue { 1.0f }; - auto mid = absl::MakeSpan(midValue); - auto side = absl::MakeSpan(sideValue); + std::array leftValue { 1.0f }; + std::array rightValue { 1.0f }; + auto left = absl::MakeSpan(leftValue); + auto right = absl::MakeSpan(rightValue); SECTION("width = 1") { std::array width { 1.0f }; - sfz::width(width, mid, side); - REQUIRE(mid[0] == Approx(0.70711f).margin(0.001f)); - REQUIRE(side[0] == Approx(0.70711f).margin(0.001f)); + sfz::width(width, left, right); + REQUIRE(left[0] == Approx(1.0f).margin(0.001f)); + REQUIRE(right[0] == Approx(1.0f).margin(0.001f)); } SECTION("width = 0") { std::array width { 0.0f }; - sfz::width(width, mid, side); - REQUIRE(mid[0] == Approx(1.0f).margin(0.001f)); - REQUIRE(side[0] == Approx(0.0f).margin(0.001f)); + sfz::width(width, left, right); + REQUIRE(left[0] == Approx(1.414f).margin(0.001f)); + REQUIRE(right[0] == Approx(1.414f).margin(0.001f)); } SECTION("width = -1") { std::array width { -1.0f }; - sfz::width(width, mid, side); - REQUIRE(mid[0] == Approx(0.70711f).margin(0.001f)); - REQUIRE(side[0] == Approx(-0.70711f).margin(0.001f)); + sfz::width(width, left, right); + REQUIRE(left[0] == Approx(1.0f).margin(0.001f)); + REQUIRE(right[0] == Approx(1.0f).margin(0.001f)); } } From 186c4f6b537cd4cca7221d6f247190099411a27d Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 10 Feb 2020 21:28:31 +0100 Subject: [PATCH 19/21] Changed the width/position process again --- src/sfizz/Voice.cpp | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index fbe2674e..636fd5ba 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -350,40 +350,11 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept egEnvelope.getBlock(modulationSpan); buffer.applyGain(modulationSpan); - // Create mid/side from left/right in the output buffer - // Add const aliases to be slightly more readable - const auto leftBufferCopy = tempSpan2.first(numSamples); - copy(leftBuffer, leftBufferCopy); - - const auto midBuffer = leftBuffer; - add(rightBuffer, midBuffer); - - const auto sideBuffer = rightBuffer; - applyGain(-1.0f, sideBuffer); - add(leftBufferCopy, sideBuffer); - - applyGain(sqrtTwoInv, midBuffer); - applyGain(sqrtTwoInv, sideBuffer); - - // Apply the width process + // Apply the width/position process widthEnvelope.getBlock(modulationSpan); - width(modulationSpan, midBuffer, sideBuffer); - - // Copy the mid channel into another span - const auto midBufferCopy = tempSpan3.first(numSamples); - copy(midBuffer, midBufferCopy); + width(modulationSpan, leftBuffer, rightBuffer); positionEnvelope.getBlock(modulationSpan); - pan(modulationSpan, midBuffer, midBufferCopy); - - // Rebuild left/right - // Recall that midBuffer and leftBuffer point to the same buffer - add(sideBuffer, leftBuffer); - applyGain(sqrtTwoInv, leftBuffer); - - // Recall that sideBuffer and rightBuffer point to the same buffer - applyGain(-1.0f, sideBuffer); - add(midBufferCopy, sideBuffer); - applyGain(sqrtTwoInv, rightBuffer); + pan(modulationSpan, leftBuffer, rightBuffer); // Filtering and EQ const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() }; From 3a0ae681117eea33a03bfdbff9836477cc97a959 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 10 Feb 2020 21:58:08 +0100 Subject: [PATCH 20/21] Updated the stereo demo --- tests/DemoStereo.cpp | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/tests/DemoStereo.cpp b/tests/DemoStereo.cpp index 0c6f47ee..2ef1558c 100644 --- a/tests/DemoStereo.cpp +++ b/tests/DemoStereo.cpp @@ -160,42 +160,8 @@ int DemoApp::processAudio(jack_nframes_t nframes, void *cbdata) std::fill(positionEnvelope.begin(), positionEnvelope.end(), self->fPan * 0.01f); using namespace sfz; - - /* TODO(jpc) have this code in common instead of copy-paste */ - - // Create mid/side from left/right in the output buffer - // Add const aliases to be slightly more readable - const auto leftBufferCopy = tempSpan1; - copy(leftBuffer, leftBufferCopy); - - const auto midBuffer = leftBuffer; - add(rightBuffer, midBuffer); - - const auto sideBuffer = rightBuffer; - applyGain(-1.0f, sideBuffer); - add(leftBufferCopy, sideBuffer); - - applyGain(sqrtTwoInv, midBuffer); - applyGain(sqrtTwoInv, sideBuffer); - - // Apply the width process - width(widthEnvelope, midBuffer, sideBuffer); - - // Copy the mid channel into another span - const auto midBufferCopy = tempSpan1; - copy(midBuffer, midBufferCopy); - pan(positionEnvelope, midBuffer, midBufferCopy); - - // Rebuild left/right - // Recall that midBuffer and leftBuffer point to the same buffer - add(sideBuffer, leftBuffer); - applyGain(sqrtTwoInv, leftBuffer); - - // Recall that sideBuffer and rightBuffer point to the same buffer - applyGain(-1.0f, sideBuffer); - add(midBufferCopy, sideBuffer); - applyGain(sqrtTwoInv, rightBuffer); - + width(widthEnvelope, leftBuffer, rightBuffer); + pan(positionEnvelope, leftBuffer, rightBuffer); return 0; } From 9da95b509288e984eaee933a4b76ee15640626bb Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 10 Feb 2020 22:25:17 +0100 Subject: [PATCH 21/21] Add the SSE version (to test but it's not faster) --- benchmarks/BM_widthPos.cpp | 11 ++++++++++ src/sfizz/SIMDHelpers.h | 15 +++++++++++++ src/sfizz/SIMDSSE.cpp | 43 +++++++++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/benchmarks/BM_widthPos.cpp b/benchmarks/BM_widthPos.cpp index b55e5628..312a029a 100644 --- a/benchmarks/BM_widthPos.cpp +++ b/benchmarks/BM_widthPos.cpp @@ -64,6 +64,17 @@ BENCHMARK_DEFINE_F(WidthPosArray, Scalar)(benchmark::State& state) { } } +BENCHMARK_DEFINE_F(WidthPosArray, SIMD)(benchmark::State& state) { + ScopedFTZ ftz; + const auto leftBuffer = absl::MakeSpan(left); + const auto rightBuffer = absl::MakeSpan(right); + for (auto _ : state) + { + sfz::width(width, leftBuffer, rightBuffer); + sfz::pan(position, leftBuffer, rightBuffer); + } +} BENCHMARK_REGISTER_F(WidthPosArray, Scalar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(WidthPosArray, SIMD)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); BENCHMARK_MAIN(); diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index 355426d9..f4c5a2d7 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -802,6 +802,18 @@ void pan(absl::Span panEnvelope, absl::Span leftBuffer, absl::Span void pan(absl::Span panEnvelope, absl::Span leftBuffer, absl::Span rightBuffer) noexcept; +/** + * @brief Controls the width of a stereo signal, setting it to mono when width = 0 and inverting the channels + * when width = -1. Width = 1 has no effect. + * + * The output size will be the minimum of the width envelope span and left and right buffer span sizes. + * + * @tparam T the underlying type + * @tparam SIMD use the SIMD version or the scalar version + * @param panEnvelope + * @param leftBuffer + * @param rightBuffer + */ template void width(absl::Span widthEnvelope, absl::Span leftBuffer, absl::Span rightBuffer) noexcept { @@ -817,6 +829,9 @@ void width(absl::Span widthEnvelope, absl::Span leftBuffer, absl::Sp } } +template <> +void width(absl::Span widthEnvelope, absl::Span leftBuffer, absl::Span rightBuffer) noexcept; + /** * @brief Computes the mean of a span * diff --git a/src/sfizz/SIMDSSE.cpp b/src/sfizz/SIMDSSE.cpp index 3093fcdf..ba98c12d 100644 --- a/src/sfizz/SIMDSSE.cpp +++ b/src/sfizz/SIMDSSE.cpp @@ -609,7 +609,7 @@ void sfz::pan(absl::Span panEnvelope, absl::Span(pan, left, right); @@ -621,6 +621,47 @@ void sfz::pan(absl::Span panEnvelope, absl::Span +void sfz::width(absl::Span widthEnvelope, absl::Span leftBuffer, absl::Span rightBuffer) noexcept +{ + ASSERT(leftBuffer.size() >= widthEnvelope.size()); + ASSERT(rightBuffer.size() >= widthEnvelope.size()); + auto* width = widthEnvelope.begin(); + auto* left = leftBuffer.begin(); + auto* right = rightBuffer.begin(); + auto* sentinel = width + min(widthEnvelope.size(), leftBuffer.size(), rightBuffer.size()); + const auto* lastAligned = prevAligned(sentinel); + + while (unaligned(width, left, right) && width < lastAligned) { + _internals::snippetWidth(*width, *left, *right); + incrementAll(width, left, right); + } + + const auto mmPiFour = _mm_set_ps1(piFour); + __m128 mmCos; + __m128 mmSin; + while (width < lastAligned) { + auto mmWidth = _mm_load_ps(width); + mmWidth = _mm_mul_ps(mmWidth, mmPiFour); + sincos_ps(mmWidth, &mmSin, &mmCos); + auto mmCosPlusSine = _mm_add_ps(mmCos, mmSin); + auto mmCosMinusSine = _mm_sub_ps(mmCos, mmSin); + auto mmLeft = _mm_load_ps(left); + auto mmRight = _mm_load_ps(right); + auto mmTemp = _mm_mul_ps(mmCosMinusSine, mmRight); + mmRight = _mm_add_ps(_mm_mul_ps(mmCosMinusSine, mmLeft), _mm_mul_ps(mmCosPlusSine, mmRight)); + mmLeft = _mm_add_ps(_mm_mul_ps(mmCosPlusSine, mmLeft), mmTemp); + _mm_store_ps(left, mmLeft); + _mm_store_ps(right, mmRight); + incrementAll(width, left, right); + } + + while (width < sentinel){ + _internals::snippetWidth(*width, *left, *right); + incrementAll(width, left, right); + } +} + template <> float sfz::mean(absl::Span vector) noexcept {