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 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/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..59fcc5b9 100644 --- a/src/sfizz/Smoothers.cpp +++ b/src/sfizz/Smoothers.cpp @@ -25,12 +25,22 @@ 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) { - if (smoothing) + CHECK_SPAN_SIZES(input, output); + if (input.size() == 0) + return; + + if (canShortcut && std::abs(input.front() - current()) < config::virtuallyZero) { + if (input.data() != output.data()) + copy(input, output); + + filter.reset(input.back()); + } else if (smoothing) { filter.processLowpass(input, output); - else + } else if (input.data() != output.data()) { copy(input, output); + } } } diff --git a/src/sfizz/Smoothers.h b/src/sfizz/Smoothers.h index 20eb4e3a..598dd203 100644 --- a/src/sfizz/Smoothers.h +++ b/src/sfizz/Smoothers.h @@ -42,9 +42,13 @@ 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: bool smoothing { false }; OnePoleFilter filter {}; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 144b680d..7709e360 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)); @@ -109,6 +110,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()); @@ -248,6 +250,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)); @@ -311,11 +314,65 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept #endif } -void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept +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); + auto xfadeSpan = resources.bufferPool.getBuffer(numSamples); + + if (!tempSpan || !xfadeSpan) + return; + + fill(*xfadeSpan, 1.0f); + + bool canShortcut = true; + for (const auto& mod : region->crossfadeCCInRange) { + const auto events = resources.midiState.getCCEvents(mod.cc); + canShortcut &= (events.size() == 1); + linearEnvelope(events, *tempSpan, [&](float x) { + return crossfadeIn(mod.data, x, xfCurve); + }); + applyGain(*tempSpan, *xfadeSpan); + } + + for (const auto& mod : region->crossfadeCCOutRange) { + const auto events = resources.midiState.getCCEvents(mod.cc); + canShortcut &= (events.size() == 1); + linearEnvelope(events, *tempSpan, [&](float x) { + return crossfadeOut(mod.data, x, xfCurve); + }); + applyGain(*tempSpan, *xfadeSpan); + } + + xfadeSmoother.process(*xfadeSpan, *xfadeSpan, canShortcut); + applyGain(*xfadeSpan, modulationSpan); +} + + +void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept +{ + const auto numSamples = modulationSpan.size(); + auto tempSpan = resources.bufferPool.getBuffer(numSamples); if (!tempSpan) return; @@ -331,22 +388,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) { @@ -373,6 +414,7 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept return; amplitudeEnvelope(*modulationSpan); + applyCrossfades(*modulationSpan); applyGain(*modulationSpan, leftBuffer); } @@ -386,6 +428,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..74fa080c 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -340,6 +340,15 @@ 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; + void resetCrossfades() noexcept; + /** * @brief Amplitude stage for a mono source * @@ -473,6 +482,7 @@ private: ModifierArray> modifierSmoothers; Smoother gainSmoother; Smoother bendSmoother; + Smoother xfadeSmoother; void resetSmoothers() noexcept; std::array, 2> channelEnvelopeFilters;