Merge pull request #313 from paulfd/xfade-smoothers

Crossfade smoothers
This commit is contained in:
Paul Ferrand 2020-07-07 14:49:28 +02:00 committed by GitHub
commit f2ffad44bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 92 additions and 21 deletions

View file

@ -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

View file

@ -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 };

View file

@ -25,6 +25,8 @@ class OnePoleFilter {
public:
OnePoleFilter() = default;
Type current() const { return state; }
void setGain(Type gain)
{
G = gain / (1 + gain);

View file

@ -25,12 +25,22 @@ void Smoother::reset(float value)
filter.reset(value);
}
void Smoother::process(absl::Span<const float> input, absl::Span<float> output)
void Smoother::process(absl::Span<const float> input, absl::Span<float> 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<float>(input, output);
filter.reset(input.back());
} else if (smoothing) {
filter.processLowpass(input, output);
else
} else if (input.data() != output.data()) {
copy<float>(input, output);
}
}
}

View file

@ -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<const float> input, absl::Span<float> output);
void process(absl::Span<const float> input, absl::Span<float> output, bool canShortcut = false);
float current() const { return filter.current(); }
private:
bool smoothing { false };
OnePoleFilter<float> filter {};

View file

@ -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<float> buffer) noexcept
#endif
}
void sfz::Voice::amplitudeEnvelope(absl::Span<float> 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<float> 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<float>(*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<float>(*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<float>(*tempSpan, *xfadeSpan);
}
xfadeSmoother.process(*xfadeSpan, *xfadeSpan, canShortcut);
applyGain<float>(*xfadeSpan, modulationSpan);
}
void sfz::Voice::amplitudeEnvelope(absl::Span<float> 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<float> modulationSpan) noexcept
applyGain<float>(*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<float>(*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<float>(*tempSpan, modulationSpan);
}
// Volume envelope
applyGain1<float>(db2mag(baseVolumedB), modulationSpan);
forEachWithSmoother(Mod::volume, [&](const CCData<Modifier>& mod, Smoother& smoother) {
@ -373,6 +414,7 @@ void sfz::Voice::ampStageMono(AudioSpan<float> buffer) noexcept
return;
amplitudeEnvelope(*modulationSpan);
applyCrossfades(*modulationSpan);
applyGain<float>(*modulationSpan, leftBuffer);
}
@ -386,6 +428,7 @@ void sfz::Voice::ampStageStereo(AudioSpan<float> buffer) noexcept
return;
amplitudeEnvelope(*modulationSpan);
applyCrossfades(*modulationSpan);
buffer.applyGain(*modulationSpan);
}

View file

@ -340,6 +340,15 @@ private:
* @param modulationSpan
*/
void amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept;
/**
* @brief Apply the crossfade envelope to a span.
*
* @param modulationSpan
*/
void applyCrossfades(absl::Span<float> modulationSpan) noexcept;
void resetCrossfades() noexcept;
/**
* @brief Amplitude stage for a mono source
*
@ -473,6 +482,7 @@ private:
ModifierArray<std::vector<Smoother>> modifierSmoothers;
Smoother gainSmoother;
Smoother bendSmoother;
Smoother xfadeSmoother;
void resetSmoothers() noexcept;
std::array<OnePoleFilter<float>, 2> channelEnvelopeFilters;