From f874ad3db56a7bc0556c766f944d4e3fd4081d30 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 5 Jul 2020 15:25:24 +0200 Subject: [PATCH 1/5] Add short paths for the smoothers When the input is basically constant in particular --- src/sfizz/OnePoleFilter.h | 2 ++ src/sfizz/Smoothers.cpp | 19 +++++++++++++++++-- src/sfizz/Smoothers.h | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/sfizz/OnePoleFilter.h b/src/sfizz/OnePoleFilter.h index 9781b39e..603363a5 100644 --- a/src/sfizz/OnePoleFilter.h +++ b/src/sfizz/OnePoleFilter.h @@ -25,6 +25,8 @@ class OnePoleFilter { public: OnePoleFilter() = default; + Type current() const { return state; } + void setGain(Type gain) { G = gain / (1 + gain); diff --git a/src/sfizz/Smoothers.cpp b/src/sfizz/Smoothers.cpp index 252d1f50..c94d670a 100644 --- a/src/sfizz/Smoothers.cpp +++ b/src/sfizz/Smoothers.cpp @@ -27,10 +27,25 @@ void Smoother::reset(float value) void Smoother::process(absl::Span input, absl::Span output) { - if (smoothing) + CHECK_SPAN_SIZES(input, output); + if (input.size() == 0) + return; + + const auto midValue = input[input.size() / 2]; + const bool shortcut = ( + input.front() == input.back() + && input.front() == midValue + && input.front() == current() + ); + + if (smoothing && !shortcut) { filter.processLowpass(input, output); - else + } + else if (input.data() != output.data()) { copy(input, output); + } else { + // Nothing to do + } } } diff --git a/src/sfizz/Smoothers.h b/src/sfizz/Smoothers.h index 20eb4e3a..e2dadb8b 100644 --- a/src/sfizz/Smoothers.h +++ b/src/sfizz/Smoothers.h @@ -45,6 +45,7 @@ public: */ void process(absl::Span input, absl::Span output); + float current() const { return filter.current(); } private: bool smoothing { false }; OnePoleFilter filter {}; From 4ee4bbe9c1cb6aed1b15c02cdf55eb529e4ff371 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 5 Jul 2020 15:25:57 +0200 Subject: [PATCH 2/5] Smooth the crossfades --- src/sfizz/Config.h | 1 + src/sfizz/Voice.cpp | 56 +++++++++++++++++++++++++++++++-------------- src/sfizz/Voice.h | 9 ++++++++ 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 0c2ae266..ad5d0b87 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -41,6 +41,7 @@ namespace config { constexpr int numVoices { 64 }; constexpr unsigned maxVoices { 256 }; constexpr unsigned smoothingSteps { 512 }; + constexpr uint8_t xfadeSmoothing { 5 }; constexpr uint8_t gainSmoothing { 0 }; constexpr unsigned powerTableSizeExponent { 11 }; constexpr int maxFilePromises { maxVoices }; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 4f23fd5d..e0799d86 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -25,6 +25,7 @@ sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources) osc.init(sampleRate); gainSmoother.setSmoothing(config::gainSmoothing, sampleRate); + xfadeSmoother.setSmoothing(config::xfadeSmoothing, sampleRate); for (auto & filter : channelEnvelopeFilters) filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate)); @@ -245,6 +246,7 @@ void sfz::Voice::setSampleRate(float sampleRate) noexcept { this->sampleRate = sampleRate; gainSmoother.setSmoothing(config::gainSmoothing, sampleRate); + xfadeSmoother.setSmoothing(config::xfadeSmoothing, sampleRate); for (auto & filter : channelEnvelopeFilters) filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate)); @@ -308,11 +310,45 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept #endif } -void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept +void sfz::Voice::applyCrossfades(absl::Span modulationSpan) noexcept { const auto numSamples = modulationSpan.size(); const auto xfCurve = region->crossfadeCCCurve; + auto tempSpan = resources.bufferPool.getBuffer(numSamples); + if (!tempSpan) + return; + + bool smoothOutput = false; + for (const auto& mod : region->crossfadeCCInRange) { + const auto events = resources.midiState.getCCEvents(mod.cc); + smoothOutput |= (events.size() > 1); + linearEnvelope(events, *tempSpan, [&](float x) { + return crossfadeIn(mod.data, x, xfCurve); + }); + applyGain(*tempSpan, modulationSpan); + } + + for (const auto& mod : region->crossfadeCCOutRange) { + const auto events = resources.midiState.getCCEvents(mod.cc); + smoothOutput |= (events.size() > 1); + linearEnvelope(events, *tempSpan, [&](float x) { + return crossfadeOut(mod.data, x, xfCurve); + }); + applyGain(*tempSpan, modulationSpan); + } + + if (smoothOutput) { + xfadeSmoother.reset(modulationSpan.front()); + xfadeSmoother.process(modulationSpan, modulationSpan); + } +} + + +void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept +{ + const auto numSamples = modulationSpan.size(); + auto tempSpan = resources.bufferPool.getBuffer(numSamples); if (!tempSpan) return; @@ -328,22 +364,6 @@ void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept applyGain(*tempSpan, modulationSpan); }); - // Crossfade envelopes - for (const auto& mod : region->crossfadeCCInRange) { - const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&](float x) { - return crossfadeIn(mod.data, x, xfCurve); - }); - applyGain(*tempSpan, modulationSpan); - } - for (const auto& mod : region->crossfadeCCOutRange) { - const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&](float x) { - return crossfadeOut(mod.data, x, xfCurve); - }); - applyGain(*tempSpan, modulationSpan); - } - // Volume envelope applyGain1(db2mag(baseVolumedB), modulationSpan); forEachWithSmoother(Mod::volume, [&](const CCData& mod, Smoother& smoother) { @@ -370,6 +390,7 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept return; amplitudeEnvelope(*modulationSpan); + applyCrossfades(*modulationSpan); applyGain(*modulationSpan, leftBuffer); } @@ -383,6 +404,7 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept return; amplitudeEnvelope(*modulationSpan); + applyCrossfades(*modulationSpan); buffer.applyGain(*modulationSpan); } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 40accaf1..03c83337 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -340,6 +340,14 @@ private: * @param modulationSpan */ void amplitudeEnvelope(absl::Span modulationSpan) noexcept; + + /** + * @brief Apply the crossfade envelope to a span. + * + * @param modulationSpan + */ + void applyCrossfades(absl::Span modulationSpan) noexcept; + /** * @brief Amplitude stage for a mono source * @@ -473,6 +481,7 @@ private: ModifierArray> modifierSmoothers; Smoother gainSmoother; Smoother bendSmoother; + Smoother xfadeSmoother; void resetSmoothers() noexcept; std::array, 2> channelEnvelopeFilters; From 18d4240e586a924046a8c05889f22d6e10c31163 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 5 Jul 2020 17:40:03 +0200 Subject: [PATCH 3/5] Further separate the crossfades and initialize them properly --- src/sfizz/Voice.cpp | 35 ++++++++++++++++++++++++++++------- src/sfizz/Voice.h | 1 + 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index e0799d86..293857b1 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -107,6 +107,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, if (triggerType != TriggerType::CC) baseGain *= region->getNoteGain(number, value); gainSmoother.reset(); + resetCrossfades(); // Check that we can handle the number of filters; filters should be cleared here ASSERT((filters.capacity() - filters.size()) >= region->filters.size()); @@ -310,15 +311,37 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept #endif } +void sfz::Voice::resetCrossfades() noexcept +{ + float xfadeValue { 1.0f }; + const auto xfCurve = region->crossfadeCCCurve; + + for (const auto& mod : region->crossfadeCCInRange) { + const auto value = resources.midiState.getCCValue(mod.cc); + xfadeValue *= crossfadeIn(mod.data, value, xfCurve); + } + + for (const auto& mod : region->crossfadeCCOutRange) { + const auto value = resources.midiState.getCCValue(mod.cc); + xfadeValue *= crossfadeOut(mod.data, value, xfCurve); + } + + xfadeSmoother.reset(xfadeValue); +} + void sfz::Voice::applyCrossfades(absl::Span modulationSpan) noexcept { const auto numSamples = modulationSpan.size(); const auto xfCurve = region->crossfadeCCCurve; auto tempSpan = resources.bufferPool.getBuffer(numSamples); - if (!tempSpan) + auto xfadeSpan = resources.bufferPool.getBuffer(numSamples); + + if (!tempSpan || !xfadeSpan) return; + fill(*xfadeSpan, 1.0f); + bool smoothOutput = false; for (const auto& mod : region->crossfadeCCInRange) { const auto events = resources.midiState.getCCEvents(mod.cc); @@ -326,7 +349,7 @@ void sfz::Voice::applyCrossfades(absl::Span modulationSpan) noexcept linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.data, x, xfCurve); }); - applyGain(*tempSpan, modulationSpan); + applyGain(*tempSpan, *xfadeSpan); } for (const auto& mod : region->crossfadeCCOutRange) { @@ -335,13 +358,11 @@ void sfz::Voice::applyCrossfades(absl::Span modulationSpan) noexcept linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.data, x, xfCurve); }); - applyGain(*tempSpan, modulationSpan); + applyGain(*tempSpan, *xfadeSpan); } - if (smoothOutput) { - xfadeSmoother.reset(modulationSpan.front()); - xfadeSmoother.process(modulationSpan, modulationSpan); - } + xfadeSmoother.process(*xfadeSpan, *xfadeSpan); + applyGain(*xfadeSpan, modulationSpan); } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 03c83337..74fa080c 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -347,6 +347,7 @@ private: * @param modulationSpan */ void applyCrossfades(absl::Span modulationSpan) noexcept; + void resetCrossfades() noexcept; /** * @brief Amplitude stage for a mono source From dda4a94a1b0cd3187eba42b7fcbeaab5fe5e3962 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 7 Jul 2020 11:17:53 +0200 Subject: [PATCH 4/5] The process call in the smoother now takes an external canShortcut parameter If the filter can shortcut, it will check that the first input of the span is within a reasonable range of the current value of the filter, and if so it resets it to this value and proceeds as if it were not smoothing --- src/sfizz/Smoothers.cpp | 19 +++++++------------ src/sfizz/Smoothers.h | 5 ++++- src/sfizz/Voice.cpp | 8 ++++---- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/sfizz/Smoothers.cpp b/src/sfizz/Smoothers.cpp index c94d670a..59fcc5b9 100644 --- a/src/sfizz/Smoothers.cpp +++ b/src/sfizz/Smoothers.cpp @@ -25,26 +25,21 @@ void Smoother::reset(float value) filter.reset(value); } -void Smoother::process(absl::Span input, absl::Span output) +void Smoother::process(absl::Span input, absl::Span output, bool canShortcut) { CHECK_SPAN_SIZES(input, output); if (input.size() == 0) return; - const auto midValue = input[input.size() / 2]; - const bool shortcut = ( - input.front() == input.back() - && input.front() == midValue - && input.front() == current() - ); + if (canShortcut && std::abs(input.front() - current()) < config::virtuallyZero) { + if (input.data() != output.data()) + copy(input, output); - if (smoothing && !shortcut) { + filter.reset(input.back()); + } else if (smoothing) { filter.processLowpass(input, output); - } - else if (input.data() != output.data()) { + } else if (input.data() != output.data()) { copy(input, output); - } else { - // Nothing to do } } diff --git a/src/sfizz/Smoothers.h b/src/sfizz/Smoothers.h index e2dadb8b..598dd203 100644 --- a/src/sfizz/Smoothers.h +++ b/src/sfizz/Smoothers.h @@ -42,8 +42,11 @@ public: * * @param input * @param output + * @param canShortcut whether we can have a fast path if the filter is within + * a reasonable range around the first value of the input + * span. */ - void process(absl::Span input, absl::Span output); + void process(absl::Span input, absl::Span output, bool canShortcut = false); float current() const { return filter.current(); } private: diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 293857b1..b2412cc8 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -342,10 +342,10 @@ void sfz::Voice::applyCrossfades(absl::Span modulationSpan) noexcept fill(*xfadeSpan, 1.0f); - bool smoothOutput = false; + bool canShortcut = true; for (const auto& mod : region->crossfadeCCInRange) { const auto events = resources.midiState.getCCEvents(mod.cc); - smoothOutput |= (events.size() > 1); + canShortcut &= (events.size() == 1); linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.data, x, xfCurve); }); @@ -354,14 +354,14 @@ void sfz::Voice::applyCrossfades(absl::Span modulationSpan) noexcept for (const auto& mod : region->crossfadeCCOutRange) { const auto events = resources.midiState.getCCEvents(mod.cc); - smoothOutput |= (events.size() > 1); + canShortcut &= (events.size() == 1); linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.data, x, xfCurve); }); applyGain(*tempSpan, *xfadeSpan); } - xfadeSmoother.process(*xfadeSpan, *xfadeSpan); + xfadeSmoother.process(*xfadeSpan, *xfadeSpan, canShortcut); applyGain(*xfadeSpan, modulationSpan); } From 11106602f697202529f6537647dbdea240b0d94b Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 7 Jul 2020 13:22:59 +0200 Subject: [PATCH 5/5] Trying to solve the macos CI problem --- .travis/install_osx.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis/install_osx.sh b/.travis/install_osx.sh index 63e882a8..b6accc45 100755 --- a/.travis/install_osx.sh +++ b/.travis/install_osx.sh @@ -4,6 +4,7 @@ set -ex sudo ln -s /usr/local /opt/local brew update +brew upgrade python || brew link --overwrite python brew upgrade cmake brew install jack brew install dylibbundler