Skip to content

Commit

Permalink
Merge pull request #393 from paulfd/polyphony-cleanups
Browse files Browse the repository at this point in the history
Polyphony cleanups
  • Loading branch information
paulfd authored Sep 9, 2020
2 parents f53074f + 863a08c commit afadb17
Show file tree
Hide file tree
Showing 9 changed files with 495 additions and 266 deletions.
11 changes: 3 additions & 8 deletions src/sfizz/SisterVoiceRing.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,6 @@ struct SisterVoiceRing {
*/
class SisterVoiceRingBuilder {
public:
~SisterVoiceRingBuilder() noexcept {
if (lastStartedVoice != nullptr) {
ASSERT(firstStartedVoice);
lastStartedVoice->setNextSisterVoice(firstStartedVoice);
firstStartedVoice->setPreviousSisterVoice(lastStartedVoice);
}
}

/**
* @brief Add a voice to the sister ring
*
Expand All @@ -145,6 +137,9 @@ class SisterVoiceRingBuilder {
if (firstStartedVoice == nullptr)
firstStartedVoice = voice;

firstStartedVoice->setPreviousSisterVoice(voice);
voice->setNextSisterVoice(firstStartedVoice);

if (lastStartedVoice != nullptr) {
voice->setPreviousSisterVoice(lastStartedVoice);
lastStartedVoice->setNextSisterVoice(voice);
Expand Down
337 changes: 174 additions & 163 deletions src/sfizz/Synth.cpp

Large diffs are not rendered by default.

93 changes: 91 additions & 2 deletions src/sfizz/Synth.h
Original file line number Diff line number Diff line change
Expand Up @@ -725,9 +725,33 @@ class Synth final : public Voice::StateListener, public Parser::Listener {

fs::file_time_type checkModificationTime();

/**
* @brief Check all regions and start voices for note on events
*
* @param delay
* @param noteNumber
* @param velocity
*/
void noteOnDispatch(int delay, int noteNumber, float velocity) noexcept;

/**
* @brief Check all regions and start voices for note off events
*
* @param delay
* @param noteNumber
* @param velocity
*/
void noteOffDispatch(int delay, int noteNumber, float velocity) noexcept;

/**
* @brief Check all regions and start voices for cc events
*
* @param delay
* @param ccNumber
* @param value
*/
void ccDispatch(int delay, int ccNumber, float value) noexcept;

template<class T>
static void updateUsedCCsFromCCMap(std::bitset<sfz::config::numCCs>& usedCCs, const CCMap<T> map)
{
Expand Down Expand Up @@ -765,18 +789,83 @@ class Synth final : public Voice::StateListener, public Parser::Listener {
using RegionSetPtr = std::unique_ptr<RegionSet>;
std::vector<RegionPtr> regions;
std::vector<VoicePtr> voices;

// These are more general "groups" than sfz and encapsulates the full hierarchy
RegionSet* currentSet;
OpcodeScope lastHeader { OpcodeScope::kOpcodeScopeGlobal };
std::vector<RegionSetPtr> sets;

// These are the `group=` groups where you can off voices
std::vector<PolyphonyGroup> polyphonyGroups;

// Views to speed up iteration over the regions and voices when events
// occur in the audio callback
VoiceViewVector regionPolyphonyArray;
VoiceViewVector tempPolyphonyArray;
VoiceViewVector voiceViewArray;
VoiceStealing stealer;

VoiceViewVector voiceViewArray;
/**
* @brief Check the region polyphony, releasing voices if necessary
*
* @param region
* @param delay
*/
void checkRegionPolyphony(const Region* region, int delay) noexcept;

/**
* @brief Check the note polyphony, releasing voices if necessary
*
* @param region
* @param delay
* @param triggerEvent
*/
void checkNotePolyphony(const Region* region, int delay, const TriggerEvent& triggerEvent) noexcept;

/**
* @brief Check the group polyphony, releasing voices if necessary
*
* @param region
* @param delay
*/
void checkGroupPolyphony(const Region* region, int delay) noexcept;

/**
* @brief Check the region set polyphony at all levels, releasing voices if necessary
*
* @param region
* @param delay
*/
void checkSetPolyphony(const Region* region, int delay) noexcept;

/**
* @brief Start a voice for a specific region.
* This will do the needed polyphony checks and voice stealing.
*
* @param region
* @param delay
* @param triggerEvent
* @param ring
*/
void startVoice(Region* region, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept;

/**
* @brief Start all delayed release voices of the region if necessary
*
* @param region
* @param delay
* @param ring
*/
void startDelayedReleaseVoices(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept;

/**
* @brief Check if a playing voice matches the release region
*
* @param releaseRegion
* @return true
* @return false
*/
bool playingAttackVoice(const Region* releaseRegion) noexcept;

std::array<RegionViewVector, 128> noteActivationLists;
std::array<RegionViewVector, config::numCCs> ccActivationLists;

Expand Down
24 changes: 24 additions & 0 deletions src/sfizz/TriggerEvent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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

#pragma once

namespace sfz
{
enum class TriggerEventType { NoteOn, NoteOff, CC };

/**
* @brief Encapsulate a midi event with normalized values
*
*/
struct TriggerEvent
{
TriggerEventType type;
int number;
float value;
};

}
44 changes: 21 additions & 23 deletions src/sfizz/Voice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,18 @@ sfz::Voice::~Voice()
{
}

void sfz::Voice::startVoice(Region* region, int delay, int number, float value, sfz::Voice::TriggerType triggerType) noexcept
void sfz::Voice::startVoice(Region* region, int delay, const TriggerEvent& event) noexcept
{
ASSERT(value >= 0.0f && value <= 1.0f);

if (triggerType == TriggerType::CC)
number = region->pitchKeycenter;

this->triggerType = triggerType;
triggerNumber = number;
triggerValue = value;
ASSERT(event.value >= 0.0f && event.value <= 1.0f);

this->region = region;

if (region->disabled())
return;

triggerEvent = event;
if (triggerEvent.type == TriggerEventType::CC)
triggerEvent.number = region->pitchKeycenter;

switchState(State::playing);

ASSERT(delay >= 0);
Expand Down Expand Up @@ -106,18 +102,18 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
}

// do Scala retuning and reconvert the frequency into a 12TET key number
const float numberRetuned = resources.tuning.getKeyFractional12TET(number);
const float numberRetuned = resources.tuning.getKeyFractional12TET(triggerEvent.number);

pitchRatio = region->getBasePitchVariation(numberRetuned, value);
pitchRatio = region->getBasePitchVariation(numberRetuned, triggerEvent.value);

// apply stretch tuning if set
if (resources.stretch)
pitchRatio *= resources.stretch->getRatioForFractionalKey(numberRetuned);

baseVolumedB = region->getBaseVolumedB(number);
baseVolumedB = region->getBaseVolumedB(triggerEvent.number);
baseGain = region->getBaseGain();
if (triggerType != TriggerType::CC)
baseGain *= region->getNoteGain(number, value);
if (triggerEvent.type != TriggerEventType::CC)
baseGain *= region->getNoteGain(triggerEvent.number, triggerEvent.value);
gainSmoother.reset();
resetCrossfades();

Expand All @@ -127,25 +123,25 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,

const unsigned numChannels = region->isStereo() ? 2 : 1;
for (auto& filter: region->filters) {
auto newFilter = resources.filterPool.getFilter(filter, numChannels, number, value);
auto newFilter = resources.filterPool.getFilter(filter, numChannels, triggerEvent.number, triggerEvent.value);
if (newFilter)
filters.push_back(newFilter);
}

for (auto& eq: region->equalizers) {
auto newEQ = resources.eqPool.getEQ(eq, numChannels, value);
auto newEQ = resources.eqPool.getEQ(eq, numChannels, triggerEvent.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);
baseFrequency = resources.tuning.getFrequencyOfKey(triggerEvent.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);
egEnvelope.reset(region->amplitudeEG, *region, resources.midiState, delay, triggerEvent.value, sampleRate);

resources.modMatrix.initVoice(id, region->getId(), delay);
}
Expand Down Expand Up @@ -197,7 +193,7 @@ void sfz::Voice::registerNoteOff(int delay, int noteNumber, float velocity) noex
if (state != State::playing)
return;

if (triggerNumber == noteNumber) {
if (triggerEvent.number == noteNumber && triggerEvent.type == TriggerEventType::NoteOn) {
noteIsOff = true;

if (region->loopMode == SfzLoopMode::one_shot)
Expand Down Expand Up @@ -712,12 +708,14 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
#endif
}

bool sfz::Voice::checkOffGroup(int delay, uint32_t group) noexcept
bool sfz::Voice::checkOffGroup(const Region* other, int delay, int noteNumber) noexcept
{
if (region == nullptr)
if (region == nullptr || other == nullptr)
return false;

if (triggerType == TriggerType::NoteOn && region->offBy == group) {
if (triggerEvent.type == TriggerEventType::NoteOn
&& region->offBy == other->group
&& noteNumber != triggerEvent.number) {
off(delay);
return true;
}
Expand Down
Loading

0 comments on commit afadb17

Please sign in to comment.