sfizz/src/sfizz/Voice.cpp
2020-08-09 23:17:31 +02:00

892 lines
28 KiB
C++

// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "Voice.h"
#include "Macros.h"
#include "Defaults.h"
#include "ModifierHelpers.h"
#include "MathHelpers.h"
#include "SIMDHelpers.h"
#include "Panning.h"
#include "SfzHelpers.h"
#include "modulations/ModId.h"
#include "modulations/ModKey.h"
#include "modulations/ModMatrix.h"
#include "Interpolators.h"
#include "absl/algorithm/container.h"
sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources)
: id{voiceNumber}, stateListener(nullptr), resources(resources)
{
filters.reserve(config::filtersPerVoice);
equalizers.reserve(config::eqsPerVoice);
for (WavetableOscillator& osc : waveOscillators)
osc.init(sampleRate);
gainSmoother.setSmoothing(config::gainSmoothing, sampleRate);
xfadeSmoother.setSmoothing(config::xfadeSmoothing, sampleRate);
for (auto & filter : channelEnvelopeFilters)
filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate));
}
void sfz::Voice::startVoice(Region* region, int delay, int number, float value, sfz::Voice::TriggerType triggerType) noexcept
{
ASSERT(value >= 0.0f && value <= 1.0f);
if (triggerType == TriggerType::CC)
number = region->pitchKeycenter;
this->triggerType = triggerType;
triggerNumber = number;
triggerValue = value;
this->region = region;
switchState(State::playing);
ASSERT(delay >= 0);
if (delay < 0)
delay = 0;
if (region->isGenerator()) {
const WavetableMulti* wave = nullptr;
switch (hash(region->sampleId.filename())) {
default:
case hash("*silence"):
break;
case hash("*sine"):
wave = resources.wavePool.getWaveSin();
break;
case hash("*triangle"): // fallthrough
case hash("*tri"):
wave = resources.wavePool.getWaveTriangle();
break;
case hash("*square"):
wave = resources.wavePool.getWaveSquare();
break;
case hash("*saw"):
wave = resources.wavePool.getWaveSaw();
break;
}
const float phase = region->getPhase();
const int quality = region->oscillatorQuality.value_or(Default::oscillatorQuality);
for (WavetableOscillator& osc : waveOscillators) {
osc.setWavetable(wave);
osc.setPhase(phase);
osc.setQuality(quality);
}
setupOscillatorUnison();
} else if (region->oscillator) {
const WavetableMulti* wave = resources.wavePool.getFileWave(region->sampleId.filename());
const float phase = region->getPhase();
const int quality = region->oscillatorQuality.value_or(Default::oscillatorQuality);
for (WavetableOscillator& osc : waveOscillators) {
osc.setWavetable(wave);
osc.setPhase(phase);
osc.setQuality(quality);
}
setupOscillatorUnison();
} else {
currentPromise = resources.filePool.getFilePromise(region->sampleId);
if (currentPromise == nullptr) {
switchState(State::cleanMeUp);
return;
}
speedRatio = static_cast<float>(currentPromise->sampleRate / this->sampleRate);
}
// do Scala retuning and reconvert the frequency into a 12TET key number
const float numberRetuned = resources.tuning.getKeyFractional12TET(number);
pitchRatio = region->getBasePitchVariation(numberRetuned, value);
// apply stretch tuning if set
if (resources.stretch)
pitchRatio *= resources.stretch->getRatioForFractionalKey(numberRetuned);
baseVolumedB = region->getBaseVolumedB(number);
baseGain = region->getBaseGain();
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());
ASSERT((equalizers.capacity() - equalizers.size()) >= region->equalizers.size());
const unsigned numChannels = region->isStereo() ? 2 : 1;
for (auto& filter: region->filters) {
auto newFilter = resources.filterPool.getFilter(filter, numChannels, number, value);
if (newFilter)
filters.push_back(newFilter);
}
for (auto& eq: region->equalizers) {
auto newEQ = resources.eqPool.getEQ(eq, numChannels, value);
if (newEQ)
equalizers.push_back(newEQ);
}
sourcePosition = region->getOffset();
triggerDelay = delay;
initialDelay = delay + static_cast<int>(region->getDelay() * sampleRate);
baseFrequency = resources.tuning.getFrequencyOfKey(number);
bendStepFactor = centsFactor(region->bendStep);
bendSmoother.setSmoothing(region->bendSmooth, sampleRate);
bendSmoother.reset(centsFactor(region->getBendInCents(resources.midiState.getPitchBend())));
egEnvelope.reset(region->amplitudeEG, *region, resources.midiState, delay, value, sampleRate);
resources.modMatrix.initVoice(id, region->getId());
}
int sfz::Voice::getCurrentSampleQuality() const noexcept
{
return (region && region->sampleQuality) ?
*region->sampleQuality : resources.synthConfig.currentSampleQuality();
}
bool sfz::Voice::isFree() const noexcept
{
return (state == State::idle);
}
void sfz::Voice::release(int delay, bool fastRelease) noexcept
{
if (state != State::playing)
return;
if (egEnvelope.getRemainingDelay() > delay) {
switchState(State::cleanMeUp);
} else {
egEnvelope.startRelease(delay, fastRelease);
}
}
void sfz::Voice::registerNoteOff(int delay, int noteNumber, float velocity) noexcept
{
ASSERT(velocity >= 0.0 && velocity <= 1.0);
UNUSED(velocity);
if (region == nullptr)
return;
if (state != State::playing)
return;
if (triggerNumber == noteNumber) {
noteIsOff = true;
if (region->loopMode == SfzLoopMode::one_shot)
return;
if (!region->checkSustain || resources.midiState.getCCValue(region->sustainCC) < region->sustainThreshold)
release(delay);
}
}
void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept
{
ASSERT(ccValue >= 0.0 && ccValue <= 1.0);
if (region == nullptr)
return;
if (state != State::playing)
return;
if (region->checkSustain && noteIsOff && ccNumber == region->sustainCC && ccValue < region->sustainThreshold)
release(delay);
}
void sfz::Voice::registerPitchWheel(int delay, float pitch) noexcept
{
if (state != State::playing)
return;
UNUSED(delay);
UNUSED(pitch);
}
void sfz::Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept
{
// TODO
UNUSED(delay);
UNUSED(aftertouch);
}
void sfz::Voice::registerTempo(int delay, float secondsPerQuarter) noexcept
{
// TODO
UNUSED(delay);
UNUSED(secondsPerQuarter);
}
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));
for (WavetableOscillator& osc : waveOscillators)
osc.init(sampleRate);
}
void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
{
this->samplesPerBlock = samplesPerBlock;
this->minEnvelopeDelay = samplesPerBlock / 2;
}
void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
{
ASSERT(static_cast<int>(buffer.getNumFrames()) <= samplesPerBlock);
buffer.fill(0.0f);
if (region == nullptr)
return;
const auto delay = min(static_cast<size_t>(initialDelay), buffer.getNumFrames());
auto delayed_buffer = buffer.subspan(delay);
initialDelay -= static_cast<int>(delay);
{ // Fill buffer with raw data
ScopedTiming logger { dataDuration };
if (region->isGenerator() || region->oscillator)
fillWithGenerator(delayed_buffer);
else
fillWithData(delayed_buffer);
}
if (region->isStereo()) {
ampStageStereo(buffer);
panStageStereo(buffer);
filterStageStereo(buffer);
} else {
ampStageMono(buffer);
filterStageMono(buffer);
panStageMono(buffer);
}
if (!egEnvelope.isSmoothing())
switchState(State::cleanMeUp);
updateChannelPowers(buffer);
age += buffer.getNumFrames();
if (triggerDelay) {
// Should be OK but just in case;
age = min(age - *triggerDelay, 0);
triggerDelay = absl::nullopt;
}
#if 0
ASSERT(!hasNanInf(buffer.getConstSpan(0)));
ASSERT(!hasNanInf(buffer.getConstSpan(1)));
SFIZZ_CHECK(isReasonableAudio(buffer.getConstSpan(0)));
SFIZZ_CHECK(isReasonableAudio(buffer.getConstSpan(1)));
#endif
}
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();
ModMatrix& mm = resources.modMatrix;
const ModKey volumeKey = ModKey::createNXYZ(ModId::Volume, region->getId());
const ModKey amplitudeKey = ModKey::createNXYZ(ModId::Amplitude, region->getId());
// AmpEG envelope
egEnvelope.getBlock(modulationSpan);
// Amplitude envelope
applyGain1<float>(baseGain, modulationSpan);
if (float* mod = mm.getModulationByKey(amplitudeKey)) {
for (size_t i = 0; i < numSamples; ++i)
modulationSpan[i] *= normalizePercents(mod[i]);
}
// Volume envelope
applyGain1<float>(db2mag(baseVolumedB), modulationSpan);
if (float* mod = mm.getModulationByKey(volumeKey)) {
for (size_t i = 0; i < numSamples; ++i)
modulationSpan[i] *= db2mag(mod[i]);
}
// Smooth the gain transitions
gainSmoother.process(modulationSpan, modulationSpan);
}
void sfz::Voice::ampStageMono(AudioSpan<float> buffer) noexcept
{
ScopedTiming logger { amplitudeDuration };
const auto numSamples = buffer.getNumFrames();
const auto leftBuffer = buffer.getSpan(0);
auto modulationSpan = resources.bufferPool.getBuffer(numSamples);
if (!modulationSpan)
return;
amplitudeEnvelope(*modulationSpan);
applyCrossfades(*modulationSpan);
applyGain<float>(*modulationSpan, leftBuffer);
}
void sfz::Voice::ampStageStereo(AudioSpan<float> buffer) noexcept
{
ScopedTiming logger { amplitudeDuration };
const auto numSamples = buffer.getNumFrames();
auto modulationSpan = resources.bufferPool.getBuffer(numSamples);
if (!modulationSpan)
return;
amplitudeEnvelope(*modulationSpan);
applyCrossfades(*modulationSpan);
buffer.applyGain(*modulationSpan);
}
void sfz::Voice::panStageMono(AudioSpan<float> buffer) noexcept
{
ScopedTiming logger { panningDuration };
const auto numSamples = buffer.getNumFrames();
const auto leftBuffer = buffer.getSpan(0);
const auto rightBuffer = buffer.getSpan(1);
auto modulationSpan = resources.bufferPool.getBuffer(numSamples);
if (!modulationSpan)
return;
ModMatrix& mm = resources.modMatrix;
const ModKey panKey = ModKey::createNXYZ(ModId::Pan, region->getId());
// Prepare for stereo output
copy<float>(leftBuffer, rightBuffer);
// Apply panning
fill(*modulationSpan, region->pan);
if (float* mod = mm.getModulationByKey(panKey)) {
for (size_t i = 0; i < numSamples; ++i)
(*modulationSpan)[i] += normalizePercents(mod[i]);
}
pan(*modulationSpan, leftBuffer, rightBuffer);
}
void sfz::Voice::panStageStereo(AudioSpan<float> buffer) noexcept
{
ScopedTiming logger { panningDuration };
const auto numSamples = buffer.getNumFrames();
const auto leftBuffer = buffer.getSpan(0);
const auto rightBuffer = buffer.getSpan(1);
auto modulationSpan = resources.bufferPool.getBuffer(numSamples);
if (!modulationSpan)
return;
ModMatrix& mm = resources.modMatrix;
const ModKey panKey = ModKey::createNXYZ(ModId::Pan, region->getId());
const ModKey widthKey = ModKey::createNXYZ(ModId::Width, region->getId());
const ModKey positionKey = ModKey::createNXYZ(ModId::Position, region->getId());
// Apply panning
fill(*modulationSpan, region->pan);
if (float* mod = mm.getModulationByKey(panKey)) {
for (size_t i = 0; i < numSamples; ++i)
(*modulationSpan)[i] += normalizePercents(mod[i]);
}
pan(*modulationSpan, leftBuffer, rightBuffer);
// Apply the width/position process
fill(*modulationSpan, region->width);
if (float* mod = mm.getModulationByKey(widthKey)) {
for (size_t i = 0; i < numSamples; ++i)
(*modulationSpan)[i] += normalizePercents(mod[i]);
}
width(*modulationSpan, leftBuffer, rightBuffer);
fill(*modulationSpan, region->position);
if (float* mod = mm.getModulationByKey(positionKey)) {
for (size_t i = 0; i < numSamples; ++i)
(*modulationSpan)[i] += normalizePercents(mod[i]);
}
pan(*modulationSpan, leftBuffer, rightBuffer);
}
void sfz::Voice::filterStageMono(AudioSpan<float> buffer) noexcept
{
ScopedTiming logger { filterDuration };
const auto numSamples = buffer.getNumFrames();
const auto leftBuffer = buffer.getSpan(0);
const float* inputChannel[1] { leftBuffer.data() };
float* outputChannel[1] { leftBuffer.data() };
for (auto& filter : filters) {
filter->process(inputChannel, outputChannel, numSamples);
}
for (auto& eq : equalizers) {
eq->process(inputChannel, outputChannel, numSamples);
}
}
void sfz::Voice::filterStageStereo(AudioSpan<float> buffer) noexcept
{
ScopedTiming logger { filterDuration };
const auto numSamples = buffer.getNumFrames();
const auto leftBuffer = buffer.getSpan(0);
const auto rightBuffer = buffer.getSpan(1);
const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() };
float* outputChannels[2] { leftBuffer.data(), rightBuffer.data() };
for (auto& filter : filters) {
filter->process(inputChannels, outputChannels, numSamples);
}
for (auto& eq : equalizers) {
eq->process(inputChannels, outputChannels, numSamples);
}
}
void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
{
const auto numSamples = buffer.getNumFrames();
if (numSamples == 0)
return;
if (currentPromise == nullptr) {
DBG("[Voice] Missing promise during fillWithData");
return;
}
auto source = currentPromise->getData();
auto jumps = resources.bufferPool.getBuffer(numSamples);
auto coeffs = resources.bufferPool.getBuffer(numSamples);
auto indices = resources.bufferPool.getIndexBuffer(numSamples);
if (!jumps || !indices || !coeffs)
return;
fill(*jumps, pitchRatio * speedRatio);
pitchEnvelope(*jumps);
jumps->front() += floatPositionOffset;
cumsum<float>(*jumps, *jumps);
sfzInterpolationCast<float>(*jumps, *indices, *coeffs);
add1<int>(sourcePosition, *indices);
if (region->shouldLoop() && region->loopEnd(currentPromise->oversamplingFactor) <= source.getNumFrames()) {
const auto loopEnd = static_cast<int>(region->loopEnd(currentPromise->oversamplingFactor));
const auto offset = loopEnd - static_cast<int>(region->loopStart(currentPromise->oversamplingFactor)) + 1;
for (auto* index = indices->begin(); index < indices->end(); ++index) {
if (*index > loopEnd) {
const auto remainingElements = static_cast<size_t>(std::distance(index, indices->end()));
subtract1<int>(offset, { index, remainingElements });
}
}
} else {
const auto sampleEnd = min(
static_cast<int>(region->trueSampleEnd(currentPromise->oversamplingFactor)),
static_cast<int>(source.getNumFrames())
) - 1;
for (unsigned i = 0; i < indices->size(); ++i) {
if ((*indices)[i] >= sampleEnd) {
#ifndef NDEBUG
// Check for underflow
if (source.getNumFrames() - 1 < region->trueSampleEnd(currentPromise->oversamplingFactor)) {
DBG("[sfizz] Underflow: source available samples "
<< source.getNumFrames() << "/"
<< region->trueSampleEnd(currentPromise->oversamplingFactor)
<< " for sample " << region->sampleId);
}
#endif
egEnvelope.startRelease(i, true);
fill<int>(indices->subspan(i), sampleEnd);
fill<float>(coeffs->subspan(i), 1.0f);
break;
}
}
}
const int quality = getCurrentSampleQuality();
switch (quality) {
default:
if (quality > 2)
goto high; // TODO sinc, not implemented
// fall through
case 1:
fillInterpolated<kInterpolatorLinear>(source, buffer, *indices, *coeffs);
break;
case 2: high:
#if 1
// B-spline response has faster decay of aliasing, but not zero-crossings at integer positions
fillInterpolated<kInterpolatorBspline3>(source, buffer, *indices, *coeffs);
#else
// Hermite polynomial
fillInterpolated<kInterpolatorHermite3>(source, buffer, *indices, *coeffs);
#endif
break;
}
sourcePosition = indices->back();
floatPositionOffset = coeffs->back();
#if 0
ASSERT(!hasNanInf(buffer.getConstSpan(0)));
ASSERT(!hasNanInf(buffer.getConstSpan(1)));
SFIZZ_CHECK(isReasonableAudio(buffer.getConstSpan(0)));
SFIZZ_CHECK(isReasonableAudio(buffer.getConstSpan(1)));
#endif
}
template <sfz::InterpolatorModel M>
void sfz::Voice::fillInterpolated(
const sfz::AudioSpan<const float>& source, sfz::AudioSpan<float>& dest,
absl::Span<const int> indices, absl::Span<const float> coeffs)
{
auto ind = indices.data();
auto coeff = coeffs.data();
auto leftSource = source.getConstSpan(0);
auto left = dest.getChannel(0);
if (source.getNumChannels() == 1) {
while (ind < indices.end()) {
*left = sfz::interpolate<M>(&leftSource[*ind], *coeff);
incrementAll(ind, left, coeff);
}
} else {
auto right = dest.getChannel(1);
auto rightSource = source.getConstSpan(1);
while (ind < indices.end()) {
*left = sfz::interpolate<M>(&leftSource[*ind], *coeff);
*right = sfz::interpolate<M>(&rightSource[*ind], *coeff);
incrementAll(ind, left, right, coeff);
}
}
}
void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
{
const auto leftSpan = buffer.getSpan(0);
const auto rightSpan = buffer.getSpan(1);
if (region->sampleId.filename() == "*noise") {
auto gen = [&]() {
return uniformNoiseDist(Random::randomGenerator);
};
absl::c_generate(leftSpan, gen);
absl::c_generate(rightSpan, gen);
} else if (region->sampleId.filename() == "*gnoise") {
// You need to wrap in a lambda, otherwise generate will
// make a copy of the gaussian distribution *along with its state*
// leading to periodic behavior....
auto gen = [&]() {
return gaussianNoiseDist();
};
absl::c_generate(leftSpan, gen);
absl::c_generate(rightSpan, gen);
} else {
const auto numFrames = buffer.getNumFrames();
auto frequencies = resources.bufferPool.getBuffer(numFrames);
if (!frequencies)
return;
float keycenterFrequency = midiNoteFrequency(region->pitchKeycenter);
fill(*frequencies, pitchRatio * keycenterFrequency);
pitchEnvelope(*frequencies);
if (waveUnisonSize == 1) {
WavetableOscillator& osc = waveOscillators[0];
osc.processModulated(frequencies->data(), 1.0, leftSpan.data(), buffer.getNumFrames());
copy<float>(leftSpan, rightSpan);
}
else {
buffer.fill(0.0f);
auto tempSpan = resources.bufferPool.getBuffer(numFrames);
if (!tempSpan)
return;
for (unsigned i = 0, n = waveUnisonSize; i < n; ++i) {
WavetableOscillator& osc = waveOscillators[i];
osc.processModulated(frequencies->data(), waveDetuneRatio[i], tempSpan->data(), numFrames);
multiplyAdd1<float>(waveLeftGain[i], *tempSpan, leftSpan);
multiplyAdd1<float>(waveRightGain[i], *tempSpan, rightSpan);
}
}
}
#if 0
ASSERT(!hasNanInf(buffer.getConstSpan(0)));
ASSERT(!hasNanInf(buffer.getConstSpan(1)));
SFIZZ_CHECK(isReasonableAudio(buffer.getConstSpan(0)));
SFIZZ_CHECK(isReasonableAudio(buffer.getConstSpan(1)));
#endif
}
bool sfz::Voice::checkOffGroup(int delay, uint32_t group) noexcept
{
if (region == nullptr)
return false;
if (delay <= this->triggerDelay)
return false;
if (triggerType == TriggerType::NoteOn && region->offBy == group) {
release(delay, region->offMode == SfzOffMode::fast);
return true;
}
return false;
}
void sfz::Voice::reset() noexcept
{
switchState(State::idle);
region = nullptr;
currentPromise.reset();
sourcePosition = 0;
age = 0;
floatPositionOffset = 0.0f;
noteIsOff = false;
for (auto& f : channelEnvelopeFilters)
f.reset();
for (auto& p : smoothedChannelEnvelopes)
p = 0.0f;
filters.clear();
equalizers.clear();
removeVoiceFromRing();
}
void sfz::Voice::setNextSisterVoice(Voice* voice) noexcept
{
// Should never be null
ASSERT(voice);
nextSisterVoice = voice;
}
void sfz::Voice::setPreviousSisterVoice(Voice* voice) noexcept
{
// Should never be null
ASSERT(voice);
previousSisterVoice = voice;
}
void sfz::Voice::removeVoiceFromRing() noexcept
{
previousSisterVoice->setNextSisterVoice(nextSisterVoice);
nextSisterVoice->setPreviousSisterVoice(previousSisterVoice);
previousSisterVoice = this;
nextSisterVoice = this;
}
float sfz::Voice::getAverageEnvelope() const noexcept
{
return max(smoothedChannelEnvelopes[0], smoothedChannelEnvelopes[1]);
}
bool sfz::Voice::releasedOrFree() const noexcept
{
return state != State::playing || egEnvelope.isReleased();
}
uint32_t sfz::Voice::getSourcePosition() const noexcept
{
return sourcePosition;
}
void sfz::Voice::setMaxFiltersPerVoice(size_t numFilters)
{
// There are filters in there, this call is unexpected
ASSERT(filters.size() == 0);
filters.reserve(numFilters);
}
void sfz::Voice::setMaxEQsPerVoice(size_t numFilters)
{
// There are filters in there, this call is unexpected
ASSERT(equalizers.size() == 0);
equalizers.reserve(numFilters);
}
void sfz::Voice::setupOscillatorUnison()
{
int m = region->oscillatorMulti;
float d = region->oscillatorDetune;
// 3-9: unison mode, 1: normal/RM, 2: PM/FM
// TODO(jpc) RM/FM/PM synthesis
if (m < 3) {
waveUnisonSize = 1;
waveDetuneRatio[0] = 1.0;
waveLeftGain[0] = 1.0;
waveRightGain[0] = 1.0;
return;
}
// oscillator count, aka. unison size
waveUnisonSize = m;
// detune (cents)
float detunes[config::oscillatorsPerVoice];
detunes[0] = 0.0;
detunes[1] = -d;
detunes[2] = +d;
for (int i = 3; i < m; ++i) {
int n = (i - 1) / 2;
detunes[i] = d * ((i & 1) ? -0.25f : +0.25f) * float(n);
}
// detune (ratio)
for (int i = 0; i < m; ++i)
waveDetuneRatio[i] = std::exp2(detunes[i] * (0.01f / 12.0f));
// gains
waveLeftGain[0] = 0.0;
waveRightGain[m - 1] = 0.0;
for (int i = 0; i < m - 1; ++i) {
float g = 1.0f - float(i) / float(m - 1);
waveLeftGain[m - 1 - i] = g;
waveRightGain[i] = g;
}
#if 0
fprintf(stderr, "\n");
fprintf(stderr, "# Left:\n");
for (int i = m - 1; i >= 0; --i) {
if (waveLeftGain[i] != 0)
fprintf(stderr, "[%d] %10g cents, %10g dB\n", i, detunes[i], 20.0f * std::log10(waveLeftGain[i]));
}
fprintf(stderr, "\n");
fprintf(stderr, "# Right:\n");
for (int i = 0; i < m; ++i) {
if (waveRightGain[i] != 0)
fprintf(stderr, "[%d] %10g cents, %10g dB\n", i, detunes[i], 20.0f * std::log10(waveRightGain[i]));
}
#endif
}
void sfz::Voice::updateChannelPowers(AudioSpan<float> buffer)
{
assert(smoothedChannelEnvelopes.size() == channelEnvelopeFilters.size());
assert(buffer.getNumChannels() <= channelEnvelopeFilters.size());
if (buffer.getNumFrames() == 0)
return;
for (unsigned i = 0; i < smoothedChannelEnvelopes.size(); ++i) {
const auto input = buffer.getConstSpan(i);
for (unsigned s = 0; s < buffer.getNumFrames(); ++s)
smoothedChannelEnvelopes[i] =
channelEnvelopeFilters[i].tickLowpass(std::abs(input[s]));
}
}
void sfz::Voice::switchState(State s)
{
if (s != state) {
state = s;
if (stateListener)
stateListener->onVoiceStateChanged(id, s);
}
}
void sfz::Voice::pitchEnvelope(absl::Span<float> pitchSpan) noexcept
{
const auto numFrames = pitchSpan.size();
auto bends = resources.bufferPool.getBuffer(numFrames);
if (!bends)
return;
const auto events = resources.midiState.getPitchEvents();
const auto bendLambda = [this](float bend) {
return centsFactor(region->getBendInCents(bend));
};
if (region->bendStep > 1)
pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor);
else
pitchBendEnvelope(events, *bends, bendLambda);
bendSmoother.process(*bends, *bends);
applyGain<float>(*bends, pitchSpan);
ModMatrix& mm = resources.modMatrix;
const ModKey pitchKey = ModKey::createNXYZ(ModId::Pitch, region->getId());
if (float* mod = mm.getModulationByKey(pitchKey)) {
for (size_t i = 0; i < numFrames; ++i)
pitchSpan[i] *= centsFactor(mod[i]);
}
}
void sfz::Voice::resetSmoothers() noexcept
{
bendSmoother.reset(1.0f);
gainSmoother.reset(0.0f);
}