Merge pull request #313 from paulfd/xfade-smoothers
Crossfade smoothers
This commit is contained in:
commit
f2ffad44bf
7 changed files with 92 additions and 21 deletions
|
|
@ -4,6 +4,7 @@ set -ex
|
||||||
|
|
||||||
sudo ln -s /usr/local /opt/local
|
sudo ln -s /usr/local /opt/local
|
||||||
brew update
|
brew update
|
||||||
|
brew upgrade python || brew link --overwrite python
|
||||||
brew upgrade cmake
|
brew upgrade cmake
|
||||||
brew install jack
|
brew install jack
|
||||||
brew install dylibbundler
|
brew install dylibbundler
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ namespace config {
|
||||||
constexpr int numVoices { 64 };
|
constexpr int numVoices { 64 };
|
||||||
constexpr unsigned maxVoices { 256 };
|
constexpr unsigned maxVoices { 256 };
|
||||||
constexpr unsigned smoothingSteps { 512 };
|
constexpr unsigned smoothingSteps { 512 };
|
||||||
|
constexpr uint8_t xfadeSmoothing { 5 };
|
||||||
constexpr uint8_t gainSmoothing { 0 };
|
constexpr uint8_t gainSmoothing { 0 };
|
||||||
constexpr unsigned powerTableSizeExponent { 11 };
|
constexpr unsigned powerTableSizeExponent { 11 };
|
||||||
constexpr int maxFilePromises { maxVoices };
|
constexpr int maxFilePromises { maxVoices };
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ class OnePoleFilter {
|
||||||
public:
|
public:
|
||||||
OnePoleFilter() = default;
|
OnePoleFilter() = default;
|
||||||
|
|
||||||
|
Type current() const { return state; }
|
||||||
|
|
||||||
void setGain(Type gain)
|
void setGain(Type gain)
|
||||||
{
|
{
|
||||||
G = gain / (1 + gain);
|
G = gain / (1 + gain);
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,22 @@ void Smoother::reset(float value)
|
||||||
filter.reset(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);
|
filter.processLowpass(input, output);
|
||||||
else
|
} else if (input.data() != output.data()) {
|
||||||
copy<float>(input, output);
|
copy<float>(input, output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,13 @@ public:
|
||||||
*
|
*
|
||||||
* @param input
|
* @param input
|
||||||
* @param output
|
* @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:
|
private:
|
||||||
bool smoothing { false };
|
bool smoothing { false };
|
||||||
OnePoleFilter<float> filter {};
|
OnePoleFilter<float> filter {};
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources)
|
||||||
osc.init(sampleRate);
|
osc.init(sampleRate);
|
||||||
|
|
||||||
gainSmoother.setSmoothing(config::gainSmoothing, sampleRate);
|
gainSmoother.setSmoothing(config::gainSmoothing, sampleRate);
|
||||||
|
xfadeSmoother.setSmoothing(config::xfadeSmoothing, sampleRate);
|
||||||
|
|
||||||
for (auto & filter : channelEnvelopeFilters)
|
for (auto & filter : channelEnvelopeFilters)
|
||||||
filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate));
|
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)
|
if (triggerType != TriggerType::CC)
|
||||||
baseGain *= region->getNoteGain(number, value);
|
baseGain *= region->getNoteGain(number, value);
|
||||||
gainSmoother.reset();
|
gainSmoother.reset();
|
||||||
|
resetCrossfades();
|
||||||
|
|
||||||
// Check that we can handle the number of filters; filters should be cleared here
|
// Check that we can handle the number of filters; filters should be cleared here
|
||||||
ASSERT((filters.capacity() - filters.size()) >= region->filters.size());
|
ASSERT((filters.capacity() - filters.size()) >= region->filters.size());
|
||||||
|
|
@ -248,6 +250,7 @@ void sfz::Voice::setSampleRate(float sampleRate) noexcept
|
||||||
{
|
{
|
||||||
this->sampleRate = sampleRate;
|
this->sampleRate = sampleRate;
|
||||||
gainSmoother.setSmoothing(config::gainSmoothing, sampleRate);
|
gainSmoother.setSmoothing(config::gainSmoothing, sampleRate);
|
||||||
|
xfadeSmoother.setSmoothing(config::xfadeSmoothing, sampleRate);
|
||||||
|
|
||||||
for (auto & filter : channelEnvelopeFilters)
|
for (auto & filter : channelEnvelopeFilters)
|
||||||
filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate));
|
filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate));
|
||||||
|
|
@ -311,11 +314,65 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
#endif
|
#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 numSamples = modulationSpan.size();
|
||||||
const auto xfCurve = region->crossfadeCCCurve;
|
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);
|
auto tempSpan = resources.bufferPool.getBuffer(numSamples);
|
||||||
if (!tempSpan)
|
if (!tempSpan)
|
||||||
return;
|
return;
|
||||||
|
|
@ -331,22 +388,6 @@ void sfz::Voice::amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept
|
||||||
applyGain<float>(*tempSpan, modulationSpan);
|
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
|
// Volume envelope
|
||||||
applyGain1<float>(db2mag(baseVolumedB), modulationSpan);
|
applyGain1<float>(db2mag(baseVolumedB), modulationSpan);
|
||||||
forEachWithSmoother(Mod::volume, [&](const CCData<Modifier>& mod, Smoother& smoother) {
|
forEachWithSmoother(Mod::volume, [&](const CCData<Modifier>& mod, Smoother& smoother) {
|
||||||
|
|
@ -373,6 +414,7 @@ void sfz::Voice::ampStageMono(AudioSpan<float> buffer) noexcept
|
||||||
return;
|
return;
|
||||||
|
|
||||||
amplitudeEnvelope(*modulationSpan);
|
amplitudeEnvelope(*modulationSpan);
|
||||||
|
applyCrossfades(*modulationSpan);
|
||||||
applyGain<float>(*modulationSpan, leftBuffer);
|
applyGain<float>(*modulationSpan, leftBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -386,6 +428,7 @@ void sfz::Voice::ampStageStereo(AudioSpan<float> buffer) noexcept
|
||||||
return;
|
return;
|
||||||
|
|
||||||
amplitudeEnvelope(*modulationSpan);
|
amplitudeEnvelope(*modulationSpan);
|
||||||
|
applyCrossfades(*modulationSpan);
|
||||||
buffer.applyGain(*modulationSpan);
|
buffer.applyGain(*modulationSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,15 @@ private:
|
||||||
* @param modulationSpan
|
* @param modulationSpan
|
||||||
*/
|
*/
|
||||||
void amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept;
|
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
|
* @brief Amplitude stage for a mono source
|
||||||
*
|
*
|
||||||
|
|
@ -473,6 +482,7 @@ private:
|
||||||
ModifierArray<std::vector<Smoother>> modifierSmoothers;
|
ModifierArray<std::vector<Smoother>> modifierSmoothers;
|
||||||
Smoother gainSmoother;
|
Smoother gainSmoother;
|
||||||
Smoother bendSmoother;
|
Smoother bendSmoother;
|
||||||
|
Smoother xfadeSmoother;
|
||||||
void resetSmoothers() noexcept;
|
void resetSmoothers() noexcept;
|
||||||
|
|
||||||
std::array<OnePoleFilter<float>, 2> channelEnvelopeFilters;
|
std::array<OnePoleFilter<float>, 2> channelEnvelopeFilters;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue