From 3ceee27c0afabc199648f640e791a9d33f3daebd Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 22:55:14 +0200 Subject: [PATCH] Move the modifiers in the midistate --- src/sfizz/EQPool.cpp | 18 ++- src/sfizz/FilterPool.cpp | 20 ++- src/sfizz/MidiState.h | 60 ++++++++- src/sfizz/SfzHelpers.h | 30 ++--- src/sfizz/Voice.cpp | 260 ++++++--------------------------------- src/sfizz/Voice.h | 6 - 6 files changed, 125 insertions(+), 269 deletions(-) diff --git a/src/sfizz/EQPool.cpp b/src/sfizz/EQPool.cpp index c3883124..81821e1e 100644 --- a/src/sfizz/EQPool.cpp +++ b/src/sfizz/EQPool.cpp @@ -28,9 +28,12 @@ void sfz::EQHolder::setup(const EQDescription& description, unsigned numChannels baseGain = description.gain + velocity * description.vel2gain; // Setup the modulated values - lastFrequency = midiState.modulate(baseFrequency, description.frequencyCC, Default::eqFrequencyRange); - lastBandwidth = midiState.modulate(baseBandwidth, description.bandwidthCC, Default::eqBandwidthRange); - lastGain = midiState.modulate(baseGain, description.gainCC, Default::eqGainRange); + lastFrequency = baseFrequency + midiState.fastAdditiveModifiers(description.frequencyCC); + lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency); + lastBandwidth = baseBandwidth + midiState.fastAdditiveModifiers(description.bandwidthCC); + lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth); + lastGain = baseGain + midiState.fastAdditiveModifiers(description.gainCC); + lastGain = Default::eqGainRange.clamp(lastGain); // Initialize the EQ eq.prepare(lastFrequency, lastBandwidth, lastGain); @@ -50,9 +53,12 @@ void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numF // TODO: Once the midistate envelopes are done, add modulation in there! // For now we take the last value - lastFrequency = midiState.modulate(baseFrequency, description->frequencyCC, Default::eqFrequencyRange); - lastBandwidth = midiState.modulate(baseBandwidth, description->bandwidthCC, Default::eqBandwidthRange); - lastGain = midiState.modulate(baseGain, description->gainCC, Default::eqGainRange); + lastFrequency = baseFrequency + midiState.fastAdditiveModifiers(description->frequencyCC); + lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency); + lastBandwidth = baseBandwidth + midiState.fastAdditiveModifiers(description->bandwidthCC); + lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth); + lastGain = baseGain + midiState.fastAdditiveModifiers(description->gainCC); + lastGain = Default::eqGainRange.clamp(lastGain); if (lastGain == 0.0f) { justCopy(); diff --git a/src/sfizz/FilterPool.cpp b/src/sfizz/FilterPool.cpp index 96f97308..4e89e802 100644 --- a/src/sfizz/FilterPool.cpp +++ b/src/sfizz/FilterPool.cpp @@ -40,9 +40,12 @@ void sfz::FilterHolder::setup(const FilterDescription& description, unsigned num baseResonance = description.resonance; // Setup the modulated values - lastCutoff = midiState.modulate(baseCutoff, description.cutoffCC, Default::filterCutoffRange, multiplyByCents); - lastResonance = midiState.modulate(baseResonance, description.resonanceCC, Default::filterResonanceRange); - lastGain = midiState.modulate(baseGain, description.gainCC, Default::filterGainRange); + lastCutoff = baseCutoff * midiState.fastMultiplicativeModifiers(description.cutoffCC, multiplyByCentsModifier); + lastCutoff = Default::filterCutoffRange.clamp(lastCutoff); + lastResonance = baseResonance + midiState.fastAdditiveModifiers(description.resonanceCC); + lastResonance = Default::filterResonanceRange.clamp(lastResonance); + lastGain = baseGain + midiState.fastAdditiveModifiers(description.gainCC); + lastGain = Default::filterGainRange.clamp(lastGain); // Initialize the filter filter.prepare(lastCutoff, lastResonance, lastGain); @@ -59,9 +62,14 @@ void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned // TODO: Once the midistate envelopes are done, add modulation in there! // For now we take the last value // TODO: the template deduction could be automatic here? - lastCutoff = midiState.modulate(baseCutoff, description->cutoffCC, Default::filterCutoffRange, multiplyByCents); - lastResonance = midiState.modulate(baseResonance, description->resonanceCC, Default::filterResonanceRange); - baseGain = midiState.modulate(baseGain, description->gainCC, Default::filterGainRange); + lastCutoff = baseCutoff * midiState.fastMultiplicativeModifiers(description->cutoffCC, [](const int& modifier, float value) -> float { + return value * centsFactor(modifier); + }); + lastCutoff = Default::filterCutoffRange.clamp(lastCutoff); + lastResonance = baseResonance + midiState.fastAdditiveModifiers(description->resonanceCC); + lastResonance = Default::filterResonanceRange.clamp(lastResonance); + lastGain = baseGain + midiState.fastAdditiveModifiers(description->gainCC); + lastGain = Default::filterGainRange.clamp(lastGain); filter.process(inputs, outputs, lastCutoff, lastResonance, lastGain, numFrames); } diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index d2f0ff4f..d7b528e3 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -8,6 +8,8 @@ #include #include "CCMap.h" #include "Range.h" +#include "absl/types/span.h" +#include "SIMDHelpers.h" namespace sfz { @@ -132,18 +134,68 @@ public: * @param lambda the function to apply for each modifier * @return T */ - template - T modulate(T value, const CCMap& modifiers, const Range& validRange, const modFunction& lambda = addToBase) const noexcept + template)> + float fastAdditiveModifiers(const CCMap& modifiers, F&& lambda = gainModifier) const noexcept { + float returnedValue { 0.0f }; for (auto& mod: modifiers) { - lambda(value, getCCValue(mod.cc) * mod.value); + returnedValue += lambda(getCCValue(mod.cc), mod.value); } - return validRange.clamp(value); + return returnedValue; } + template)> + float fastMultiplicativeModifiers(const CCMap& modifiers, F&& lambda = gainModifier) const noexcept + { + float returnedValue { 1.0f }; + for (auto& mod: modifiers) { + returnedValue *= lambda(getCCValue(mod.cc), mod.value); + } + return returnedValue; + } + + template)> + void additiveModifiers(const CCMap& modifiers, absl::Span output, absl::Span temp, F&& lambda = gainModifier) + { + fill(output, 0.0f); + for (auto& mod : modifiers) { + linearEnvelope(mod, temp, lambda); + add(temp, output); + } + } + + template)> + void multiplicativeModifiers(const CCMap& modifiers, absl::Span output, absl::Span temp, F&& lambda = gainModifier) + { + for (auto& mod : modifiers) { + linearEnvelope(mod, temp, lambda); + applyGain(temp, output); + } + } + + const EventVector& getEvents(int ccIdx) const noexcept; private: + + template + void linearEnvelope(T&& modifier, absl::Span envelope, F&& lambda) const + { + const auto eventList = getEvents(modifier.cc); + ASSERT(eventList.size() > 0); + ASSERT(eventList[0].delay == 0); + + auto lastValue = lambda(modifier.value, eventList[0].value); + auto lastDelay = eventList[0].delay; + for (unsigned i = 1; i < eventList.size(); ++ i) { + const auto event = eventList[i]; + const auto length = event.delay - lastDelay; + const auto step = (lambda(modifier.value, event.value) - lastValue)/ length; + lastValue = linearRamp(envelope.subspan(lastDelay, length), lastValue, step); + lastDelay += length; + } + fill(envelope.subspan(lastDelay), lastValue); + } int activeNotes { 0 }; /** diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 0d18a465..d991e795 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -274,26 +274,6 @@ bool findDefine(absl::string_view line, absl::string_view& variable, absl::strin */ bool findInclude(absl::string_view line, std::string& path); -/** - * @brief Defines a function that modulates a base value with another one - * - * @tparam T - */ -template -using modFunction = std::function; - -/** - * @brief Modulation helper that adds the modifier to the base value - * - * @tparam T - * @param base the base value - * @param modifier the modifier value - */ -template -inline CXX14_CONSTEXPR void addToBase(T& base, T modifier) -{ - base += modifier; -} /** * @brief multiply a value by a factor, in cents. To be used for pitch variations. @@ -301,9 +281,15 @@ inline CXX14_CONSTEXPR void addToBase(T& base, T modifier) * @param base * @param modifier */ -inline CXX14_CONSTEXPR void multiplyByCents(float& base, int modifier) +inline CXX14_CONSTEXPR float multiplyByCentsModifier(int modifier, float base) { - base *= centsFactor(modifier); + return base * centsFactor(modifier); +} + +template +inline CXX14_CONSTEXPR float gainModifier(T modifier, float value) +{ + return value * modifier; } diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index f10679df..2307e05d 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -252,221 +252,6 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept this->triggerDelay = absl::nullopt; } -template -void getLinearEnvelope(const sfz::CCMap& ccMods, const sfz::MidiState& state, absl::Span output, absl::Span temp, std::function function) -{ - for (auto& mod : ccMods) { - const auto eventList = state.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = function(mod.value, eventList[0].value); - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (function(mod.value, event.value) - lastValue)/ length; - lastValue = sfz::linearRamp(temp.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - sfz::fill(temp.subspan(lastDelay), lastValue); - sfz::applyGain(temp, output); - } -} - -void sfz::Voice::amplitudeModulation(absl::Span modulationSpan) noexcept -{ - fill(modulationSpan, 1.0f); - if (!region) - return; - - const auto numSamples = modulationSpan.size(); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!tempBuffer) - return; - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); - - for (auto& mod : region->amplitudeCC) { - const auto eventList = resources.midiState.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = mod.value * eventList[0].value; - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (Default::amplitudeRange.clamp(mod.value * event.value) - lastValue)/ length; - lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(tempSpan.subspan(lastDelay), lastValue); - applyGain(tempSpan, modulationSpan); - } - applyGain(baseGain, modulationSpan); -} - -void sfz::Voice::crossfadeModulation(absl::Span modulationSpan) noexcept -{ - fill(modulationSpan, 1.0f); - if (!region) - return; - - const auto numSamples = modulationSpan.size(); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!tempBuffer) - return; - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); - - for (auto& mod : region->crossfadeCCInRange) { - const auto eventList = resources.midiState.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = crossfadeIn(mod.value, eventList[0].value, region->crossfadeCCCurve); - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (crossfadeIn(mod.value, event.value, region->crossfadeCCCurve) - lastValue)/ length; - lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(tempSpan.subspan(lastDelay), lastValue); - applyGain(tempSpan, modulationSpan); - } - - for (auto& mod : region->crossfadeCCOutRange) { - const auto eventList = resources.midiState.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = crossfadeOut(mod.value, eventList[0].value, region->crossfadeCCCurve); - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (crossfadeOut(mod.value, event.value, region->crossfadeCCCurve) - lastValue)/ length; - lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(tempSpan.subspan(lastDelay), lastValue); - applyGain(tempSpan, modulationSpan); - } -} - -void sfz::Voice::panningModulation(absl::Span modulationSpan) noexcept -{ - if (!region) - return; - - if (region->panCC.empty()) { - fill(modulationSpan, region->pan); - return; - } - - fill(modulationSpan, 1.0f); - const auto numSamples = modulationSpan.size(); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!tempBuffer) - return; - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); - - for (auto& mod : region->panCC) { - const auto eventList = resources.midiState.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = mod.value * eventList[0].value; - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (Default::panRange.clamp(mod.value * event.value) - lastValue)/ length; - lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(tempSpan.subspan(lastDelay), lastValue); - applyGain(tempSpan, modulationSpan); - } - - add(region->pan, modulationSpan); -} - -void sfz::Voice::widthModulation(absl::Span modulationSpan) noexcept -{ - if (!region) - return; - - if (region->widthCC.empty()) { - fill(modulationSpan, region->width); - return; - } - - fill(modulationSpan, 1.0f); - const auto numSamples = modulationSpan.size(); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!tempBuffer) - return; - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); - - for (auto& mod : region->widthCC) { - const auto eventList = resources.midiState.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = mod.value * eventList[0].value; - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (Default::widthRange.clamp(mod.value * event.value) - lastValue)/ length; - lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(tempSpan.subspan(lastDelay), lastValue); - applyGain(tempSpan, modulationSpan); - } - add(region->width, modulationSpan); -} - -void sfz::Voice::positionModulation(absl::Span modulationSpan) noexcept -{ - if (!region) - return; - - if (region->positionCC.empty()) { - fill(modulationSpan, region->position); - return; - } - - fill(modulationSpan, 1.0f); - const auto numSamples = modulationSpan.size(); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!tempBuffer) - return; - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); - - for (auto& mod : region->positionCC) { - const auto eventList = resources.midiState.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = mod.value * eventList[0].value; - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (Default::positionRange.clamp(mod.value * event.value) - lastValue)/ length; - lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(tempSpan.subspan(lastDelay), lastValue); - applyGain(tempSpan, modulationSpan); - } - add(region->position, modulationSpan); -} - void sfz::Voice::processMono(AudioSpan buffer) noexcept { const auto numSamples = buffer.getNumFrames(); @@ -474,21 +259,29 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept auto rightBuffer = buffer.getSpan(1); auto modulationBuffer = resources.bufferPool.getBuffer(numSamples); - if (!modulationBuffer) + auto tempBuffer = resources.bufferPool.getBuffer(numSamples); + if (!modulationBuffer || !tempBuffer) return; auto modulationSpan = absl::MakeSpan(*modulationBuffer).first(numSamples); + auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); + using namespace std::placeholders; + const auto xfinBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); + const auto xfoutBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); { // Amplitude processing ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - amplitudeModulation(modulationSpan); + fill(modulationSpan, baseGain); + resources.midiState.multiplicativeModifiers(region->amplitudeCC, modulationSpan, tempSpan); DBG("Final gain: " << modulationSpan.back()); applyGain(modulationSpan, leftBuffer); // Crossfade envelopes // crossfadeEnvelope.getBlock(modulationSpan); - crossfadeModulation(modulationSpan); + fill(modulationSpan, 1.0f); + resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, modulationSpan, tempSpan, xfinBind); + resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, modulationSpan, tempSpan, xfoutBind); DBG("XF: " << modulationSpan.back()); applyGain(modulationSpan, leftBuffer); @@ -522,7 +315,8 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept copy(leftBuffer, rightBuffer); // Apply panning - panningModulation(modulationSpan); + fill(modulationSpan, region->pan); + resources.midiState.additiveModifiers(region->panCC, modulationSpan, tempSpan); DBG("Pan: " << modulationSpan.back()); pan(modulationSpan, leftBuffer, rightBuffer); } @@ -535,19 +329,29 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept auto rightBuffer = buffer.getSpan(1); auto modulationBuffer = resources.bufferPool.getBuffer(numSamples); - if (!modulationBuffer) + auto tempBuffer = resources.bufferPool.getBuffer(numSamples); + if (!modulationBuffer || !tempBuffer) return; auto modulationSpan = absl::MakeSpan(*modulationBuffer).first(numSamples); + auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); + + using namespace std::placeholders; + const auto xfinBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); + const auto xfoutBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); { // Amplitude processing ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - amplitudeModulation(modulationSpan); + fill(modulationSpan, baseGain); + resources.midiState.multiplicativeModifiers(region->amplitudeCC, modulationSpan, tempSpan); + DBG("Final gain: " << modulationSpan.back()); buffer.applyGain(modulationSpan); // Crossfade envelopes - crossfadeModulation(modulationSpan); + fill(modulationSpan, 1.0f); + resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, modulationSpan, tempSpan, xfinBind); + resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, modulationSpan, tempSpan, xfoutBind); buffer.applyGain(modulationSpan); // Volume envelope @@ -563,14 +367,20 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept ScopedTiming logger { panningDuration }; // Apply panning - panningModulation(modulationSpan); + // panningModulation(modulationSpan); + fill(modulationSpan, region->pan); + resources.midiState.additiveModifiers(region->panCC, modulationSpan, tempSpan); pan(modulationSpan, leftBuffer, rightBuffer); // Apply the width/position process - widthModulation(modulationSpan); + // widthModulation(modulationSpan); + fill(modulationSpan, region->width); + resources.midiState.additiveModifiers(region->widthCC, modulationSpan, tempSpan); width(modulationSpan, leftBuffer, rightBuffer); - positionModulation(modulationSpan); + // positionModulation(modulationSpan); + fill(modulationSpan, region->position); + resources.midiState.additiveModifiers(region->positionCC, modulationSpan, tempSpan); pan(modulationSpan, leftBuffer, rightBuffer); } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 996bcab2..e8154546 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -251,12 +251,6 @@ private: */ void processStereo(AudioSpan buffer) noexcept; - void amplitudeModulation(absl::Span modulationSpan) noexcept; - void crossfadeModulation(absl::Span modulationSpan) noexcept; - void panningModulation(absl::Span modulationSpan) noexcept; - void widthModulation(absl::Span modulationSpan) noexcept; - void positionModulation(absl::Span modulationSpan) noexcept; - Region* region { nullptr }; enum class State {