Move the modifiers in the midistate

This commit is contained in:
Paul Ferrand 2020-03-29 22:55:14 +02:00
parent 507ddc8e01
commit 3ceee27c0a
6 changed files with 125 additions and 269 deletions

View file

@ -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();

View file

@ -40,9 +40,12 @@ void sfz::FilterHolder::setup(const FilterDescription& description, unsigned num
baseResonance = description.resonance;
// Setup the modulated values
lastCutoff = midiState.modulate<float, int>(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<float, int>(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);
}

View file

@ -8,6 +8,8 @@
#include <array>
#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<class T, class U>
T modulate(T value, const CCMap<U>& modifiers, const Range<T>& validRange, const modFunction<T, U>& lambda = addToBase<T>) const noexcept
template<class T, class F = decltype(gainModifier<T>)>
float fastAdditiveModifiers(const CCMap<T>& modifiers, F&& lambda = gainModifier<T>) 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<class T, class F = decltype(gainModifier<T>)>
float fastMultiplicativeModifiers(const CCMap<T>& modifiers, F&& lambda = gainModifier<T>) const noexcept
{
float returnedValue { 1.0f };
for (auto& mod: modifiers) {
returnedValue *= lambda(getCCValue(mod.cc), mod.value);
}
return returnedValue;
}
template<class T, class F = decltype(gainModifier<T>)>
void additiveModifiers(const CCMap<T>& modifiers, absl::Span<float> output, absl::Span<float> temp, F&& lambda = gainModifier<T>)
{
fill<float>(output, 0.0f);
for (auto& mod : modifiers) {
linearEnvelope(mod, temp, lambda);
add<float>(temp, output);
}
}
template<class T, class F = decltype(gainModifier<T>)>
void multiplicativeModifiers(const CCMap<T>& modifiers, absl::Span<float> output, absl::Span<float> temp, F&& lambda = gainModifier<T>)
{
for (auto& mod : modifiers) {
linearEnvelope(mod, temp, lambda);
applyGain<float>(temp, output);
}
}
const EventVector& getEvents(int ccIdx) const noexcept;
private:
template<class T, class F>
void linearEnvelope(T&& modifier, absl::Span<float> 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<float>(envelope.subspan(lastDelay, length), lastValue, step);
lastDelay += length;
}
fill<float>(envelope.subspan(lastDelay), lastValue);
}
int activeNotes { 0 };
/**

View file

@ -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<class T, class U>
using modFunction = std::function<void(T&, U)>;
/**
* @brief Modulation helper that adds the modifier to the base value
*
* @tparam T
* @param base the base value
* @param modifier the modifier value
*/
template<class T>
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<class T>
inline CXX14_CONSTEXPR float gainModifier(T modifier, float value)
{
return value * modifier;
}

View file

@ -252,221 +252,6 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
this->triggerDelay = absl::nullopt;
}
template<class T>
void getLinearEnvelope(const sfz::CCMap<T>& ccMods, const sfz::MidiState& state, absl::Span<float> output, absl::Span<float> temp, std::function<float(const T&, float)> 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<float>(temp.subspan(lastDelay, length), lastValue, step);
lastDelay += length;
}
sfz::fill<float>(temp.subspan(lastDelay), lastValue);
sfz::applyGain<float>(temp, output);
}
}
void sfz::Voice::amplitudeModulation(absl::Span<float> modulationSpan) noexcept
{
fill<float>(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<float>(tempSpan.subspan(lastDelay, length), lastValue, step);
lastDelay += length;
}
fill<float>(tempSpan.subspan(lastDelay), lastValue);
applyGain<float>(tempSpan, modulationSpan);
}
applyGain<float>(baseGain, modulationSpan);
}
void sfz::Voice::crossfadeModulation(absl::Span<float> modulationSpan) noexcept
{
fill<float>(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<float>(tempSpan.subspan(lastDelay, length), lastValue, step);
lastDelay += length;
}
fill<float>(tempSpan.subspan(lastDelay), lastValue);
applyGain<float>(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<float>(tempSpan.subspan(lastDelay, length), lastValue, step);
lastDelay += length;
}
fill<float>(tempSpan.subspan(lastDelay), lastValue);
applyGain<float>(tempSpan, modulationSpan);
}
}
void sfz::Voice::panningModulation(absl::Span<float> modulationSpan) noexcept
{
if (!region)
return;
if (region->panCC.empty()) {
fill<float>(modulationSpan, region->pan);
return;
}
fill<float>(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<float>(tempSpan.subspan(lastDelay, length), lastValue, step);
lastDelay += length;
}
fill<float>(tempSpan.subspan(lastDelay), lastValue);
applyGain<float>(tempSpan, modulationSpan);
}
add<float>(region->pan, modulationSpan);
}
void sfz::Voice::widthModulation(absl::Span<float> modulationSpan) noexcept
{
if (!region)
return;
if (region->widthCC.empty()) {
fill<float>(modulationSpan, region->width);
return;
}
fill<float>(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<float>(tempSpan.subspan(lastDelay, length), lastValue, step);
lastDelay += length;
}
fill<float>(tempSpan.subspan(lastDelay), lastValue);
applyGain<float>(tempSpan, modulationSpan);
}
add<float>(region->width, modulationSpan);
}
void sfz::Voice::positionModulation(absl::Span<float> modulationSpan) noexcept
{
if (!region)
return;
if (region->positionCC.empty()) {
fill<float>(modulationSpan, region->position);
return;
}
fill<float>(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<float>(tempSpan.subspan(lastDelay, length), lastValue, step);
lastDelay += length;
}
fill<float>(tempSpan.subspan(lastDelay), lastValue);
applyGain<float>(tempSpan, modulationSpan);
}
add<float>(region->position, modulationSpan);
}
void sfz::Voice::processMono(AudioSpan<float> buffer) noexcept
{
const auto numSamples = buffer.getNumFrames();
@ -474,21 +259,29 @@ void sfz::Voice::processMono(AudioSpan<float> 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<float, float>, _1, _2, region->crossfadeCCCurve);
const auto xfoutBind = std::bind(crossfadeIn<float, float>, _1, _2, region->crossfadeCCCurve);
{ // Amplitude processing
ScopedTiming logger { amplitudeDuration };
// Amplitude envelope
amplitudeModulation(modulationSpan);
fill<float>(modulationSpan, baseGain);
resources.midiState.multiplicativeModifiers(region->amplitudeCC, modulationSpan, tempSpan);
DBG("Final gain: " << modulationSpan.back());
applyGain<float>(modulationSpan, leftBuffer);
// Crossfade envelopes
// crossfadeEnvelope.getBlock(modulationSpan);
crossfadeModulation(modulationSpan);
fill<float>(modulationSpan, 1.0f);
resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, modulationSpan, tempSpan, xfinBind);
resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, modulationSpan, tempSpan, xfoutBind);
DBG("XF: " << modulationSpan.back());
applyGain<float>(modulationSpan, leftBuffer);
@ -522,7 +315,8 @@ void sfz::Voice::processMono(AudioSpan<float> buffer) noexcept
copy<float>(leftBuffer, rightBuffer);
// Apply panning
panningModulation(modulationSpan);
fill<float>(modulationSpan, region->pan);
resources.midiState.additiveModifiers(region->panCC, modulationSpan, tempSpan);
DBG("Pan: " << modulationSpan.back());
pan<float>(modulationSpan, leftBuffer, rightBuffer);
}
@ -535,19 +329,29 @@ void sfz::Voice::processStereo(AudioSpan<float> 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<float, float>, _1, _2, region->crossfadeCCCurve);
const auto xfoutBind = std::bind(crossfadeIn<float, float>, _1, _2, region->crossfadeCCCurve);
{ // Amplitude processing
ScopedTiming logger { amplitudeDuration };
// Amplitude envelope
amplitudeModulation(modulationSpan);
fill<float>(modulationSpan, baseGain);
resources.midiState.multiplicativeModifiers(region->amplitudeCC, modulationSpan, tempSpan);
DBG("Final gain: " << modulationSpan.back());
buffer.applyGain(modulationSpan);
// Crossfade envelopes
crossfadeModulation(modulationSpan);
fill<float>(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<float> buffer) noexcept
ScopedTiming logger { panningDuration };
// Apply panning
panningModulation(modulationSpan);
// panningModulation(modulationSpan);
fill<float>(modulationSpan, region->pan);
resources.midiState.additiveModifiers(region->panCC, modulationSpan, tempSpan);
pan<float>(modulationSpan, leftBuffer, rightBuffer);
// Apply the width/position process
widthModulation(modulationSpan);
// widthModulation(modulationSpan);
fill<float>(modulationSpan, region->width);
resources.midiState.additiveModifiers(region->widthCC, modulationSpan, tempSpan);
width<float>(modulationSpan, leftBuffer, rightBuffer);
positionModulation(modulationSpan);
// positionModulation(modulationSpan);
fill<float>(modulationSpan, region->position);
resources.midiState.additiveModifiers(region->positionCC, modulationSpan, tempSpan);
pan<float>(modulationSpan, leftBuffer, rightBuffer);
}

View file

@ -251,12 +251,6 @@ private:
*/
void processStereo(AudioSpan<float> buffer) noexcept;
void amplitudeModulation(absl::Span<float> modulationSpan) noexcept;
void crossfadeModulation(absl::Span<float> modulationSpan) noexcept;
void panningModulation(absl::Span<float> modulationSpan) noexcept;
void widthModulation(absl::Span<float> modulationSpan) noexcept;
void positionModulation(absl::Span<float> modulationSpan) noexcept;
Region* region { nullptr };
enum class State {