Differentiate between releasing and "offed"

The goal is to be able to off voices in their release stage, possibly shortening the release tail
This commit is contained in:
Paul Fd 2021-06-25 17:59:48 +02:00
parent 30ff993de1
commit 92f741687e
11 changed files with 93 additions and 62 deletions

View file

@ -44,7 +44,7 @@ Int32Spec oscillatorQuality { 1, {0, 3}, 0 };
UInt32Spec group { 0, {0, uint32_t_max}, 0 }; UInt32Spec group { 0, {0, uint32_t_max}, 0 };
FloatSpec offTime { 6e-3f, {0.0f, 100.0f}, kPermissiveBounds }; FloatSpec offTime { 6e-3f, {0.0f, 100.0f}, kPermissiveBounds };
UInt32Spec polyphony { config::maxVoices, {0, config::maxVoices}, kEnforceBounds }; UInt32Spec polyphony { config::maxVoices, {0, config::maxVoices}, kEnforceBounds };
UInt32Spec notePolyphony { config::maxVoices, {0, config::maxVoices}, kEnforceBounds }; UInt32Spec notePolyphony { config::maxVoices, {1, config::maxVoices}, kEnforceBounds };
UInt8Spec key { 60, {0, 127}, kCanBeNote }; UInt8Spec key { 60, {0, 127}, kCanBeNote };
UInt8Spec loKey { 0, {0, 127}, kCanBeNote }; UInt8Spec loKey { 0, {0, 127}, kCanBeNote };
UInt8Spec hiKey { 127, {0, 127}, kCanBeNote }; UInt8Spec hiKey { 127, {0, 127}, kCanBeNote };

View file

@ -30,6 +30,6 @@ void sfz::PolyphonyGroup::removeAllVoices() noexcept
unsigned sfz::PolyphonyGroup::numPlayingVoices() const noexcept unsigned sfz::PolyphonyGroup::numPlayingVoices() const noexcept
{ {
return absl::c_count_if(voices, [](const Voice* v) { return absl::c_count_if(voices, [](const Voice* v) {
return !v->releasedOrFree(); return !v->offedOrFree();
}); });
} }

View file

@ -57,7 +57,7 @@ void sfz::RegionSet::removeVoiceFromHierarchy(const Region* region, const Voice*
unsigned sfz::RegionSet::numPlayingVoices() const noexcept unsigned sfz::RegionSet::numPlayingVoices() const noexcept
{ {
return absl::c_count_if(voices, [](const Voice* v) { return absl::c_count_if(voices, [](const Voice* v) {
return !v->releasedOrFree(); return !v->offedOrFree();
}); });
} }

View file

@ -221,6 +221,7 @@ struct Voice::Impl
State state_ { State::idle }; State state_ { State::idle };
bool noteIsOff_ { false }; bool noteIsOff_ { false };
bool offed_ { false };
enum class SustainState { Up, Sustaining }; enum class SustainState { Up, Sustaining };
SustainState sustainState_ { SustainState::Up }; SustainState sustainState_ { SustainState::Up };
enum class SostenutoState { Up, Sustaining, PreviouslyDown }; enum class SostenutoState { Up, Sustaining, PreviouslyDown };
@ -605,6 +606,7 @@ void Voice::Impl::off(int delay, bool fast) noexcept
// TODO(jpc): Flex AmpEG // TODO(jpc): Flex AmpEG
} }
offed_ = true;
release(delay); release(delay);
} }
@ -1637,8 +1639,18 @@ void Voice::Impl::fillWithGenerator(AudioSpan<float> buffer) noexcept
#endif #endif
} }
bool Voice::released() const noexcept
{
Impl& impl = *impl_;
return impl.released();
}
bool Voice::Impl::released() const noexcept bool Voice::Impl::released() const noexcept
{ {
if (!region_ || state_ != State::playing)
return true;
if (!region_->flexAmpEG) if (!region_->flexAmpEG)
return egAmplitude_.isReleased(); return egAmplitude_.isReleased();
else else
@ -1678,6 +1690,7 @@ void Voice::reset() noexcept
impl.floatPositionOffset_ = 0.0f; impl.floatPositionOffset_ = 0.0f;
impl.noteIsOff_ = false; impl.noteIsOff_ = false;
impl.sostenutoState_ = Impl::SostenutoState::Up; impl.sostenutoState_ = Impl::SostenutoState::Up;
impl.offed_ = false;
impl.resetLoopInformation(); impl.resetLoopInformation();
@ -1756,13 +1769,13 @@ float Voice::getAveragePower() const noexcept
return 0.0f; return 0.0f;
} }
bool Voice::releasedOrFree() const noexcept bool Voice::offedOrFree() const noexcept
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
if (impl.state_ != State::playing) if (impl.state_ != State::playing)
return true; return true;
return impl.released(); return impl.offed_;
} }
void Voice::setMaxFiltersPerVoice(size_t numFilters) void Voice::setMaxFiltersPerVoice(size_t numFilters)

View file

@ -206,12 +206,19 @@ public:
*/ */
bool isFree() const noexcept; bool isFree() const noexcept;
/** /**
* @brief Can the voice be reused (i.e. is it releasing or free) * @brief Is the voice released?
* *
* @return true * @return true
* @return false * @return false
*/ */
bool releasedOrFree() const noexcept; bool released() const noexcept;
/**
* @brief Can the voice be reused (i.e. is it releasing after being killed or free)
*
* @return true
* @return false
*/
bool offedOrFree() const noexcept;
/** /**
* @brief Get the event that triggered the voice * @brief Get the event that triggered the voice
* *

View file

@ -164,6 +164,7 @@ void VoiceManager::requireNumVoices(int numVoices, Resources& resources)
clear(); clear();
list_.reserve(numEffectiveVoices); list_.reserve(numEffectiveVoices);
temp_.reserve(numEffectiveVoices);
activeVoices_.reserve(numEffectiveVoices); activeVoices_.reserve(numEffectiveVoices);
for (int i = 0; i < numEffectiveVoices; ++i) { for (int i = 0; i < numEffectiveVoices; ++i) {
@ -185,33 +186,42 @@ void VoiceManager::checkNotePolyphony(const Region* region, int delay, const Tri
return; return;
unsigned notePolyphonyCounter { 0 }; unsigned notePolyphonyCounter { 0 };
Voice* selfMaskCandidate { nullptr }; temp_.clear();
for (Voice* voice : activeVoices_) { for (Voice* voice : activeVoices_) {
const TriggerEvent& voiceTriggerEvent = voice->getTriggerEvent(); const TriggerEvent& voiceTriggerEvent = voice->getTriggerEvent();
if (!voice->releasedOrFree() if (!voice->offedOrFree()
&& voice->getRegion()->group == region->group && voice->getRegion()->group == region->group
&& voiceTriggerEvent.number == triggerEvent.number) { && voiceTriggerEvent.number == triggerEvent.number) {
notePolyphonyCounter += 1; notePolyphonyCounter += 1;
switch (region->selfMask) { if (region->selfMask == SelfMask::dontMask || voiceTriggerEvent.value <= triggerEvent.value)
case SelfMask::mask: temp_.push_back(voice);
if (voiceTriggerEvent.value <= triggerEvent.value) {
if (!selfMaskCandidate
|| selfMaskCandidate->getTriggerEvent().value > voiceTriggerEvent.value) {
selfMaskCandidate = voice;
}
}
break;
case SelfMask::dontMask:
if (!selfMaskCandidate || selfMaskCandidate->getAge() < voice->getAge())
selfMaskCandidate = voice;
break;
}
} }
} }
if (notePolyphonyCounter >= *region->notePolyphony) { if (region->selfMask == SelfMask::mask) {
SisterVoiceRing::offAllSisters(selfMaskCandidate, delay); absl::c_sort(temp_, [](const Voice* lhs, const Voice* rhs) {
const auto lhsTrigger = lhs->getTriggerEvent();
const auto rhsTrigger = rhs->getTriggerEvent();
return lhsTrigger.value < rhsTrigger.value;
});
} else if (region->selfMask == SelfMask::dontMask) {
absl::c_sort(temp_, [](const Voice* lhs, const Voice* rhs) {
return lhs->getAge() > rhs->getAge();
});
} else {
ASSERTFALSE;
}
auto it = temp_.begin();
unsigned targetPolyphony { *region->notePolyphony - 1 };
while (notePolyphonyCounter > targetPolyphony && it < temp_.end()) {
Voice* voice = *it;
if (!voice->offedOrFree())
SisterVoiceRing::offAllSisters(voice, delay);
notePolyphonyCounter--;
it++;
} }
} }

View file

@ -136,6 +136,7 @@ private:
int getNumEffectiveVoices() const noexcept { return config::calculateActualVoices(numRequiredVoices_); } int getNumEffectiveVoices() const noexcept { return config::calculateActualVoices(numRequiredVoices_); }
std::vector<Voice> list_; std::vector<Voice> list_;
std::vector<Voice*> activeVoices_; std::vector<Voice*> activeVoices_;
std::vector<Voice*> temp_;
// These are the `group=` groups where you can off voices // These are the `group=` groups where you can off voices
std::vector<PolyphonyGroup> polyphonyGroups_; std::vector<PolyphonyGroup> polyphonyGroups_;
std::unique_ptr<VoiceStealer> stealer_ { absl::make_unique<OldestStealer>() }; std::unique_ptr<VoiceStealer> stealer_ { absl::make_unique<OldestStealer>() };

View file

@ -42,7 +42,7 @@ Voice* genericPolyphonyCheck(absl::Span<Voice*> candidates, unsigned polyphony,
*/ */
constexpr bool ignoreVoice(const Voice* voice) constexpr bool ignoreVoice(const Voice* voice)
{ {
return (voice == nullptr || voice->releasedOrFree()); return (voice == nullptr || voice->offedOrFree());
} }
Voice* FirstStealer::checkRegionPolyphony(const Region* region, absl::Span<Voice*> candidates) Voice* FirstStealer::checkRegionPolyphony(const Region* region, absl::Span<Voice*> candidates)

View file

@ -229,11 +229,11 @@ TEST_CASE("[Polyphony] Self-masking")
REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing
REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( numPlayingVoices(synth) == 2 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); REQUIRE(!synth.getVoiceView(0)->offedOrFree());
REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm);
REQUIRE( synth.getVoiceView(1)->releasedOrFree()); // The lowest velocity voice is the masking candidate REQUIRE( synth.getVoiceView(1)->offedOrFree()); // The lowest velocity voice is the masking candidate
REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 64_norm); REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 64_norm);
REQUIRE(!synth.getVoiceView(2)->releasedOrFree()); REQUIRE(!synth.getVoiceView(2)->offedOrFree());
} }
TEST_CASE("[Polyphony] Not self-masking") TEST_CASE("[Polyphony] Not self-masking")
@ -250,11 +250,11 @@ TEST_CASE("[Polyphony] Not self-masking")
REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing
REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( numPlayingVoices(synth) == 2 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
REQUIRE( synth.getVoiceView(0)->releasedOrFree()); REQUIRE( synth.getVoiceView(0)->offedOrFree());
REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm);
REQUIRE(!synth.getVoiceView(1)->releasedOrFree()); REQUIRE(!synth.getVoiceView(1)->offedOrFree());
REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 64_norm); REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 64_norm);
REQUIRE(!synth.getVoiceView(2)->releasedOrFree()); REQUIRE(!synth.getVoiceView(2)->offedOrFree());
} }
TEST_CASE("[Polyphony] Self-masking with the exact same velocity") TEST_CASE("[Polyphony] Self-masking with the exact same velocity")
@ -271,11 +271,11 @@ TEST_CASE("[Polyphony] Self-masking with the exact same velocity")
REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing
REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( numPlayingVoices(synth) == 2 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 64_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 64_norm);
REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); REQUIRE(!synth.getVoiceView(0)->offedOrFree());
REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 63_norm);
REQUIRE( synth.getVoiceView(1)->releasedOrFree()); // The first one is the masking candidate since they have the same velocity REQUIRE( synth.getVoiceView(1)->offedOrFree()); // The first one is the masking candidate since they have the same velocity
REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 63_norm);
REQUIRE(!synth.getVoiceView(2)->releasedOrFree()); REQUIRE(!synth.getVoiceView(2)->offedOrFree());
} }
TEST_CASE("[Polyphony] Self-masking only works from low to high") TEST_CASE("[Polyphony] Self-masking only works from low to high")
@ -291,9 +291,9 @@ TEST_CASE("[Polyphony] Self-masking only works from low to high")
REQUIRE( synth.getNumActiveVoices() == 2 ); // Both notes are playing REQUIRE( synth.getNumActiveVoices() == 2 ); // Both notes are playing
REQUIRE( numPlayingVoices(synth) == 2 ); // id REQUIRE( numPlayingVoices(synth) == 2 ); // id
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); REQUIRE(!synth.getVoiceView(0)->offedOrFree());
REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm);
REQUIRE(!synth.getVoiceView(1)->releasedOrFree()); REQUIRE(!synth.getVoiceView(1)->offedOrFree());
} }
TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same polyphony group (default)") TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same polyphony group (default)")
@ -309,13 +309,13 @@ TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same po
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( numPlayingVoices(synth) == 1 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 62_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 62_norm);
REQUIRE( synth.getVoiceView(0)->releasedOrFree()); // got killed REQUIRE( synth.getVoiceView(0)->offedOrFree()); // got killed
REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm);
REQUIRE( synth.getVoiceView(1)->releasedOrFree()); // got killed REQUIRE( synth.getVoiceView(1)->offedOrFree()); // got killed
REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 63_norm);
REQUIRE( synth.getVoiceView(2)->releasedOrFree()); // got killed REQUIRE( synth.getVoiceView(2)->offedOrFree()); // got killed
REQUIRE( synth.getVoiceView(3)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(3)->getTriggerEvent().value == 63_norm);
REQUIRE(!synth.getVoiceView(3)->releasedOrFree()); REQUIRE(!synth.getVoiceView(3)->offedOrFree());
} }
TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same polyphony group (default, with keyswitches)") TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same polyphony group (default, with keyswitches)")
@ -338,9 +338,9 @@ TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same po
REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( synth.getNumActiveVoices() == 2 );
REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( numPlayingVoices(synth) == 1 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
REQUIRE( synth.getVoiceView(0)->releasedOrFree()); REQUIRE( synth.getVoiceView(0)->offedOrFree());
REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 64_norm); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 64_norm);
REQUIRE(!synth.getVoiceView(1)->releasedOrFree()); REQUIRE(!synth.getVoiceView(1)->offedOrFree());
} }
@ -358,13 +358,13 @@ TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups")
REQUIRE( synth.getNumActiveVoices() == 4); // Both notes are playing REQUIRE( synth.getNumActiveVoices() == 4); // Both notes are playing
REQUIRE(numPlayingVoices(synth) == 2 ); REQUIRE(numPlayingVoices(synth) == 2 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 62_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 62_norm);
REQUIRE( synth.getVoiceView(0)->releasedOrFree()); // got killed REQUIRE( synth.getVoiceView(0)->offedOrFree()); // got killed
REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm);
REQUIRE( synth.getVoiceView(1)->releasedOrFree()); // got killed REQUIRE( synth.getVoiceView(1)->offedOrFree()); // got killed
REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 63_norm);
REQUIRE(!synth.getVoiceView(2)->releasedOrFree()); REQUIRE(!synth.getVoiceView(2)->offedOrFree());
REQUIRE( synth.getVoiceView(3)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(3)->getTriggerEvent().value == 63_norm);
REQUIRE(!synth.getVoiceView(3)->releasedOrFree()); REQUIRE(!synth.getVoiceView(3)->offedOrFree());
} }
TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups (with keyswitches)") TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups (with keyswitches)")
@ -387,9 +387,9 @@ TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups (wi
REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( synth.getNumActiveVoices() == 2 );
REQUIRE(numPlayingVoices(synth) == 2 ); REQUIRE(numPlayingVoices(synth) == 2 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); REQUIRE(!synth.getVoiceView(0)->offedOrFree());
REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 64_norm); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 64_norm);
REQUIRE(!synth.getVoiceView(1)->releasedOrFree()); REQUIRE(!synth.getVoiceView(1)->offedOrFree());
} }
TEST_CASE("[Polyphony] Note polyphony operates on release voices") TEST_CASE("[Polyphony] Note polyphony operates on release voices")
@ -409,9 +409,9 @@ TEST_CASE("[Polyphony] Note polyphony operates on release voices")
REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( synth.getNumActiveVoices() == 2 );
REQUIRE(numPlayingVoices(synth) == 1 ); REQUIRE(numPlayingVoices(synth) == 1 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
REQUIRE( synth.getVoiceView(0)->releasedOrFree()); REQUIRE( synth.getVoiceView(0)->offedOrFree());
REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 65_norm); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 65_norm);
REQUIRE(!synth.getVoiceView(1)->releasedOrFree()); REQUIRE(!synth.getVoiceView(1)->offedOrFree());
} }
TEST_CASE("[Polyphony] Note polyphony operates on release voices (masking works from low to high but takes into account the replaced velocity)") TEST_CASE("[Polyphony] Note polyphony operates on release voices (masking works from low to high but takes into account the replaced velocity)")
@ -432,9 +432,9 @@ TEST_CASE("[Polyphony] Note polyphony operates on release voices (masking works
REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( synth.getNumActiveVoices() == 2 );
REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( numPlayingVoices(synth) == 2 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); REQUIRE(!synth.getVoiceView(0)->offedOrFree());
REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 61_norm); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 61_norm);
REQUIRE(!synth.getVoiceView(1)->releasedOrFree()); REQUIRE(!synth.getVoiceView(1)->offedOrFree());
} }
TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain pedal") TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain pedal")
@ -459,8 +459,8 @@ TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain ped
synth.renderBlock(buffer); synth.renderBlock(buffer);
std::vector<std::string> expectedSamples2 { "*saw" }; std::vector<std::string> expectedSamples2 { "*saw" };
std::vector<float> expectedVelocities { 63_norm }; std::vector<float> expectedVelocities { 63_norm };
REQUIRE( playingSamples(synth) == expectedSamples2 );
REQUIRE( playingVelocities(synth) == expectedVelocities ); REQUIRE( playingVelocities(synth) == expectedVelocities );
REQUIRE( playingSamples(synth) == expectedSamples2 );
} }
TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain pedal (masking)") TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain pedal (masking)")

View file

@ -205,7 +205,7 @@ TEST_CASE("[Synth] Trigger=release and an envelope properly kills the voice at t
synth.renderBlock(buffer); // Decay (0.02) synth.renderBlock(buffer); // Decay (0.02)
synth.renderBlock(buffer); synth.renderBlock(buffer);
synth.renderBlock(buffer); // Release (0.1) synth.renderBlock(buffer); // Release (0.1)
REQUIRE(synth.getVoiceView(0)->releasedOrFree()); REQUIRE(synth.getVoiceView(0)->offedOrFree());
// Release is 0.1s // Release is 0.1s
for (int i = 0; i < 10; ++i) for (int i = 0; i < 10; ++i)
synth.renderBlock(buffer); synth.renderBlock(buffer);
@ -232,7 +232,7 @@ TEST_CASE("[Synth] Trigger=release_key and an envelope properly kills the voice
synth.renderBlock(buffer); // Decay (0.02) synth.renderBlock(buffer); // Decay (0.02)
synth.renderBlock(buffer); synth.renderBlock(buffer);
synth.renderBlock(buffer); // Release (0.1) synth.renderBlock(buffer); // Release (0.1)
REQUIRE(synth.getVoiceView(0)->releasedOrFree()); REQUIRE(synth.getVoiceView(0)->released());
// Release is 0.1s // Release is 0.1s
for (int i = 0; i < 10; ++i) for (int i = 0; i < 10; ++i)
synth.renderBlock(buffer); synth.renderBlock(buffer);
@ -259,7 +259,7 @@ TEST_CASE("[Synth] loopmode=one_shot and an envelope properly kills the voice at
synth.renderBlock(buffer); // Decay (0.02) synth.renderBlock(buffer); // Decay (0.02)
synth.renderBlock(buffer); synth.renderBlock(buffer);
synth.renderBlock(buffer); // Release (0.1) synth.renderBlock(buffer); // Release (0.1)
REQUIRE(synth.getVoiceView(0)->releasedOrFree()); REQUIRE(synth.getVoiceView(0)->released());
// Release is 0.1s // Release is 0.1s
for (int i = 0; i < 10; ++i) for (int i = 0; i < 10; ++i)
synth.renderBlock(buffer); synth.renderBlock(buffer);
@ -1934,7 +1934,7 @@ TEST_CASE("[Synth] Sustain cancels release is off by default")
synth.cc(0, 64, 127); synth.cc(0, 64, 127);
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( playingSamples(synth) == std::vector<std::string> { } ); REQUIRE( playingSamples(synth) == std::vector<std::string> { } );
} }
TEST_CASE("[Synth] Resets all controllers to default values") TEST_CASE("[Synth] Resets all controllers to default values")
{ {

View file

@ -69,7 +69,7 @@ const std::vector<const sfz::Voice*> getPlayingVoices(const sfz::Synth& synth)
std::vector<const sfz::Voice*> playingVoices; std::vector<const sfz::Voice*> playingVoices;
for (int i = 0; i < synth.getNumVoices(); ++i) { for (int i = 0; i < synth.getNumVoices(); ++i) {
const auto* voice = synth.getVoiceView(i); const auto* voice = synth.getVoiceView(i);
if (!voice->releasedOrFree()) if (!voice->released())
playingVoices.push_back(voice); playingVoices.push_back(voice);
} }
return playingVoices; return playingVoices;
@ -78,7 +78,7 @@ const std::vector<const sfz::Voice*> getPlayingVoices(const sfz::Synth& synth)
unsigned numPlayingVoices(const sfz::Synth& synth) unsigned numPlayingVoices(const sfz::Synth& synth)
{ {
return absl::c_count_if(getActiveVoices(synth), [](const sfz::Voice* v) { return absl::c_count_if(getActiveVoices(synth), [](const sfz::Voice* v) {
return !v->releasedOrFree(); return !v->released();
}); });
} }
@ -87,7 +87,7 @@ const std::vector<std::string> playingSamples(const sfz::Synth& synth)
std::vector<std::string> samples; std::vector<std::string> samples;
for (int i = 0; i < synth.getNumVoices(); ++i) { for (int i = 0; i < synth.getNumVoices(); ++i) {
const auto* voice = synth.getVoiceView(i); const auto* voice = synth.getVoiceView(i);
if (!voice->releasedOrFree()) { if (!voice->released()) {
if (auto region = voice->getRegion()) if (auto region = voice->getRegion())
samples.push_back(region->sampleId->filename()); samples.push_back(region->sampleId->filename());
} }
@ -100,7 +100,7 @@ const std::vector<float> playingVelocities(const sfz::Synth& synth)
std::vector<float> velocities; std::vector<float> velocities;
for (int i = 0; i < synth.getNumVoices(); ++i) { for (int i = 0; i < synth.getNumVoices(); ++i) {
const auto* voice = synth.getVoiceView(i); const auto* voice = synth.getVoiceView(i);
if (!voice->releasedOrFree()) if (!voice->released())
velocities.push_back(voice->getTriggerEvent().value); velocities.push_back(voice->getTriggerEvent().value);
} }
return velocities; return velocities;