Working state
This commit is contained in:
parent
8539508e0a
commit
65ea4a27bf
8 changed files with 139 additions and 34 deletions
|
|
@ -23,6 +23,7 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex
|
||||||
noteOnTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
|
noteOnTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
|
||||||
lastNotePlayed = noteNumber;
|
lastNotePlayed = noteNumber;
|
||||||
activeNotes++;
|
activeNotes++;
|
||||||
|
noteStates[noteNumber] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -37,6 +38,7 @@ void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noe
|
||||||
noteOffTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
|
noteOffTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
|
||||||
if (activeNotes > 0)
|
if (activeNotes > 0)
|
||||||
activeNotes--;
|
activeNotes--;
|
||||||
|
noteStates[noteNumber] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -181,6 +183,7 @@ void sfz::MidiState::reset() noexcept
|
||||||
activeNotes = 0;
|
activeNotes = 0;
|
||||||
internalClock = 0;
|
internalClock = 0;
|
||||||
lastNotePlayed = 0;
|
lastNotePlayed = 0;
|
||||||
|
noteStates.reset();
|
||||||
absl::c_fill(noteOnTimes, 0);
|
absl::c_fill(noteOnTimes, 0);
|
||||||
absl::c_fill(noteOffTimes, 0);
|
absl::c_fill(noteOffTimes, 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <bitset>
|
||||||
#include "CCMap.h"
|
#include "CCMap.h"
|
||||||
#include "Range.h"
|
#include "Range.h"
|
||||||
|
|
||||||
|
|
@ -142,6 +143,15 @@ public:
|
||||||
*/
|
*/
|
||||||
void flushEvents() noexcept;
|
void flushEvents() noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a note is currently depressed
|
||||||
|
*
|
||||||
|
* @param noteNumber
|
||||||
|
* @return true
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
|
bool isNotePressed(int noteNumber) const noexcept { return noteStates[noteNumber]; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the CC value for CC number
|
* @brief Get the CC value for CC number
|
||||||
*
|
*
|
||||||
|
|
@ -191,6 +201,12 @@ private:
|
||||||
|
|
||||||
MidiNoteArray<unsigned> noteOffTimes { {} };
|
MidiNoteArray<unsigned> noteOffTimes { {} };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Store the note states
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
std::bitset<128> noteStates;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Stores the velocity of the note ons for currently
|
* @brief Stores the velocity of the note ons for currently
|
||||||
* depressed notes.
|
* depressed notes.
|
||||||
|
|
|
||||||
|
|
@ -1504,7 +1504,7 @@ bool sfz::Region::isSwitchedOn() const noexcept
|
||||||
return keySwitched && previousKeySwitched && sequenceSwitched && pitchSwitched && bpmSwitched && aftertouchSwitched && ccSwitched.all();
|
return keySwitched && previousKeySwitched && sequenceSwitched && pitchSwitched && bpmSwitched && aftertouchSwitched && ccSwitched.all();
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Region::delaySustainRelease(int noteNumber, float velocity)
|
void sfz::Region::delaySustainRelease(int noteNumber, float velocity) noexcept
|
||||||
{
|
{
|
||||||
if (delayedSustainReleases.size() == delayedSustainReleases.capacity())
|
if (delayedSustainReleases.size() == delayedSustainReleases.capacity())
|
||||||
return;
|
return;
|
||||||
|
|
@ -1512,7 +1512,7 @@ void sfz::Region::delaySustainRelease(int noteNumber, float velocity)
|
||||||
delayedSustainReleases.emplace_back(noteNumber, velocity);
|
delayedSustainReleases.emplace_back(noteNumber, velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Region::delaySostenutoRelease(int noteNumber, float velocity)
|
void sfz::Region::delaySostenutoRelease(int noteNumber, float velocity) noexcept
|
||||||
{
|
{
|
||||||
if (delayedSostenutoReleases.size() == delayedSostenutoReleases.capacity())
|
if (delayedSostenutoReleases.size() == delayedSostenutoReleases.capacity())
|
||||||
return;
|
return;
|
||||||
|
|
@ -1520,13 +1520,37 @@ void sfz::Region::delaySostenutoRelease(int noteNumber, float velocity)
|
||||||
delayedSostenutoReleases.emplace_back(noteNumber, velocity);
|
delayedSostenutoReleases.emplace_back(noteNumber, velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Region::removeFromSostenutoReleases(int noteNumber)
|
void sfz::Region::removeFromSostenutoReleases(int noteNumber) noexcept
|
||||||
{
|
{
|
||||||
swapAndPopFirst(delayedSostenutoReleases, [=](const std::pair<int, float>& p) {
|
swapAndPopFirst(delayedSostenutoReleases, [=](const std::pair<int, float>& p) {
|
||||||
return p.first == noteNumber;
|
return p.first == noteNumber;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sfz::Region::storeSostenutoNotes() noexcept
|
||||||
|
{
|
||||||
|
ASSERT(delayedSostenutoReleases.empty());
|
||||||
|
for (int note = keyRange.getStart(); note <= keyRange.getEnd(); ++note) {
|
||||||
|
if (midiState.isNotePressed(note))
|
||||||
|
delaySostenutoRelease(note, midiState.getNoteVelocity(note));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool sfz::Region::isNoteSustained(int noteNumber) const noexcept
|
||||||
|
{
|
||||||
|
return absl::c_find_if(delayedSustainReleases, [=](const std::pair<int, float>& p) {
|
||||||
|
return p.first == noteNumber;
|
||||||
|
}) != delayedSustainReleases.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sfz::Region::isNoteSostenutoed(int noteNumber) const noexcept
|
||||||
|
{
|
||||||
|
return absl::c_find_if(delayedSostenutoReleases, [=](const std::pair<int, float>& p) {
|
||||||
|
return p.first == noteNumber;
|
||||||
|
}) != delayedSostenutoReleases.end();
|
||||||
|
}
|
||||||
|
|
||||||
bool sfz::Region::registerNoteOn(int noteNumber, float velocity, float randValue) noexcept
|
bool sfz::Region::registerNoteOn(int noteNumber, float velocity, float randValue) noexcept
|
||||||
{
|
{
|
||||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
|
|
@ -1553,13 +1577,6 @@ bool sfz::Region::registerNoteOn(int noteNumber, float velocity, float randValue
|
||||||
const bool attackTrigger = (trigger == Trigger::attack);
|
const bool attackTrigger = (trigger == Trigger::attack);
|
||||||
const bool notFirstLegatoNote = (trigger == Trigger::legato && midiState.getActiveNotes() > 1);
|
const bool notFirstLegatoNote = (trigger == Trigger::legato && midiState.getActiveNotes() > 1);
|
||||||
|
|
||||||
if (trigger == Trigger::release &&
|
|
||||||
keyOk && velOk
|
|
||||||
&& checkSostenuto && midiState.getCCValue(sostenutoCC) < sostenutoThreshold) {
|
|
||||||
// This note on will possibly be "sostenutoed"
|
|
||||||
delaySostenutoRelease(noteNumber, velocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyOk && velOk && randOk && (attackTrigger || firstLegatoNote || notFirstLegatoNote);
|
return keyOk && velOk && randOk && (attackTrigger || firstLegatoNote || notFirstLegatoNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1588,22 +1605,21 @@ bool sfz::Region::registerNoteOff(int noteNumber, float velocity, float randValu
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (trigger == Trigger::release) {
|
if (trigger == Trigger::release) {
|
||||||
if (checkSostenuto && midiState.getCCValue(sostenutoCC) < sostenutoThreshold)
|
const bool sostenutoed = isNoteSostenutoed(noteNumber);
|
||||||
|
|
||||||
|
if (sostenutoed && !sostenutoPressed) {
|
||||||
removeFromSostenutoReleases(noteNumber);
|
removeFromSostenutoReleases(noteNumber);
|
||||||
|
if (sustainPressed)
|
||||||
|
delaySustainRelease(noteNumber, midiState.getNoteVelocity(noteNumber));
|
||||||
|
}
|
||||||
|
|
||||||
const bool shouldSustain = checkSustain && midiState.getCCValue(sustainCC) >= sustainThreshold;
|
if (sustainPressed) {
|
||||||
const bool shouldSostenuto =
|
if (!sostenutoPressed || !sostenutoed)
|
||||||
checkSostenuto && midiState.getCCValue(sostenutoCC) >= sostenutoThreshold
|
delaySustainRelease(noteNumber, midiState.getNoteVelocity(noteNumber));
|
||||||
&& absl::c_find_if(delayedSostenutoReleases, [=](const std::pair<int, float>& p) {
|
} else {
|
||||||
return p.first == noteNumber;
|
if (!sostenutoPressed || !sostenutoed)
|
||||||
}) != delayedSostenutoReleases.end();
|
return true;
|
||||||
|
}
|
||||||
if (!shouldSustain && !shouldSostenuto)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// If we reach this part, we're storing the notes to delay their release on CC up
|
|
||||||
// This is handled by the Synth object
|
|
||||||
delaySustainRelease(noteNumber, midiState.getNoteVelocity(noteNumber));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1613,6 +1629,20 @@ bool sfz::Region::registerCC(int ccNumber, float ccValue) noexcept
|
||||||
{
|
{
|
||||||
ASSERT(ccValue >= 0.0f && ccValue <= 1.0f);
|
ASSERT(ccValue >= 0.0f && ccValue <= 1.0f);
|
||||||
|
|
||||||
|
if (ccNumber == sustainCC)
|
||||||
|
sustainPressed = checkSustain && ccValue >= sustainThreshold;
|
||||||
|
|
||||||
|
if (ccNumber == sostenutoCC) {
|
||||||
|
const bool newState = checkSostenuto && ccValue >= sostenutoThreshold;
|
||||||
|
if (!sostenutoPressed && newState)
|
||||||
|
storeSostenutoNotes();
|
||||||
|
|
||||||
|
if (!newState && sostenutoPressed)
|
||||||
|
delayedSostenutoReleases.clear();
|
||||||
|
|
||||||
|
sostenutoPressed = newState;
|
||||||
|
}
|
||||||
|
|
||||||
if (ccConditions.getWithDefault(ccNumber).containsWithEnd(ccValue))
|
if (ccConditions.getWithDefault(ccNumber).containsWithEnd(ccValue))
|
||||||
ccSwitched.set(ccNumber, true);
|
ccSwitched.set(ccNumber, true);
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -508,11 +508,16 @@ struct Region {
|
||||||
RegionSet* parent { nullptr };
|
RegionSet* parent { nullptr };
|
||||||
|
|
||||||
// Started notes
|
// Started notes
|
||||||
|
bool sustainPressed { false };
|
||||||
|
bool sostenutoPressed { false };
|
||||||
std::vector<std::pair<int, float>> delayedSustainReleases;
|
std::vector<std::pair<int, float>> delayedSustainReleases;
|
||||||
std::vector<std::pair<int, float>> delayedSostenutoReleases;
|
std::vector<std::pair<int, float>> delayedSostenutoReleases;
|
||||||
void delaySustainRelease(int noteNumber, float velocity);
|
void delaySustainRelease(int noteNumber, float velocity) noexcept;
|
||||||
void delaySostenutoRelease(int noteNumber, float velocity);
|
void delaySostenutoRelease(int noteNumber, float velocity) noexcept;
|
||||||
void removeFromSostenutoReleases(int noteNumber);
|
void storeSostenutoNotes() noexcept;
|
||||||
|
void removeFromSostenutoReleases(int noteNumber) noexcept;
|
||||||
|
bool isNoteSustained(int noteNumber) const noexcept;
|
||||||
|
bool isNoteSostenutoed(int noteNumber) const noexcept;
|
||||||
|
|
||||||
const MidiState& midiState;
|
const MidiState& midiState;
|
||||||
bool keySwitched { true };
|
bool keySwitched { true };
|
||||||
|
|
|
||||||
|
|
@ -659,7 +659,8 @@ void Synth::Impl::finalizeSfzLoad()
|
||||||
for (int cc = 0; cc < config::numCCs; cc++) {
|
for (int cc = 0; cc < config::numCCs; cc++) {
|
||||||
if (region->ccTriggers.contains(cc)
|
if (region->ccTriggers.contains(cc)
|
||||||
|| region->ccConditions.contains(cc)
|
|| region->ccConditions.contains(cc)
|
||||||
|| (cc == region->sustainCC && region->trigger == Trigger::release))
|
|| (cc == region->sustainCC && region->trigger == Trigger::release)
|
||||||
|
|| (cc == region->sostenutoCC && region->trigger == Trigger::release))
|
||||||
ccActivationLists_[cc].push_back(region);
|
ccActivationLists_[cc].push_back(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1163,6 +1164,7 @@ void Synth::Impl::startDelayedSustainReleases(Region* region, int delay, SisterV
|
||||||
const TriggerEvent noteOffEvent { TriggerEventType::NoteOff, note.first, note.second };
|
const TriggerEvent noteOffEvent { TriggerEventType::NoteOff, note.first, note.second };
|
||||||
startVoice(region, delay, noteOffEvent, ring);
|
startVoice(region, delay, noteOffEvent, ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
region->delayedSustainReleases.clear();
|
region->delayedSustainReleases.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1191,11 +1193,19 @@ void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept
|
||||||
SisterVoiceRingBuilder ring;
|
SisterVoiceRingBuilder ring;
|
||||||
const TriggerEvent triggerEvent { TriggerEventType::CC, ccNumber, value };
|
const TriggerEvent triggerEvent { TriggerEventType::CC, ccNumber, value };
|
||||||
for (auto& region : ccActivationLists_[ccNumber]) {
|
for (auto& region : ccActivationLists_[ccNumber]) {
|
||||||
if (ccNumber == region->sustainCC && value < region->sustainThreshold)
|
if (region->checkSustain && ccNumber == region->sustainCC && value < region->sustainThreshold)
|
||||||
startDelayedSustainReleases(region, delay, ring);
|
startDelayedSustainReleases(region, delay, ring);
|
||||||
|
|
||||||
if (ccNumber == region->sostenutoCC && value < region->sostenutoThreshold)
|
if (region->checkSostenuto && ccNumber == region->sostenutoCC && value < region->sostenutoThreshold) {
|
||||||
startDelayedSostenutoReleases(region, delay, ring);
|
if (region->sustainPressed) {
|
||||||
|
for (const auto& v: region->delayedSostenutoReleases)
|
||||||
|
region->delaySustainRelease(v.first, v.second);
|
||||||
|
|
||||||
|
region->delayedSostenutoReleases.clear();
|
||||||
|
} else {
|
||||||
|
startDelayedSostenutoReleases(region, delay, ring);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (region->registerCC(ccNumber, value))
|
if (region->registerCC(ccNumber, value))
|
||||||
startVoice(region, delay, triggerEvent, ring);
|
startVoice(region, delay, triggerEvent, ring);
|
||||||
|
|
|
||||||
|
|
@ -599,12 +599,12 @@ void Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool sustainPedalReleaseCondition = !impl.region_->checkSustain
|
const bool sustainPedalReleaseCondition = !impl.region_->checkSustain
|
||||||
|| (impl.noteIsOff_ && (impl.sustainState_ != Impl::SustainState::Sustaining));
|
|| (impl.sustainState_ != Impl::SustainState::Sustaining);
|
||||||
|
|
||||||
const bool sostenutoPedalReleaseCondition = !impl.region_->checkSostenuto
|
const bool sostenutoPedalReleaseCondition = !impl.region_->checkSostenuto
|
||||||
|| (impl.noteIsOff_ && (impl.sostenutoState_ != Impl::SostenutoState::Sustaining));
|
|| (impl.sostenutoState_ != Impl::SostenutoState::Sustaining);
|
||||||
|
|
||||||
if (sostenutoPedalReleaseCondition && sustainPedalReleaseCondition)
|
if (impl.noteIsOff_ && sostenutoPedalReleaseCondition && sustainPedalReleaseCondition)
|
||||||
release(delay);
|
release(delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ TEST_CASE("[Direct Region Tests] Release and release key")
|
||||||
{
|
{
|
||||||
region.parseOpcode({ "trigger", "release_key" });
|
region.parseOpcode({ "trigger", "release_key" });
|
||||||
midiState.ccEvent(0, 64, 0.0f);
|
midiState.ccEvent(0, 64, 0.0f);
|
||||||
|
region.registerCC(64, 0.0f);
|
||||||
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||||
REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) );
|
REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||||
}
|
}
|
||||||
|
|
@ -49,6 +50,7 @@ TEST_CASE("[Direct Region Tests] Release and release key")
|
||||||
{
|
{
|
||||||
region.parseOpcode({ "trigger", "release_key" });
|
region.parseOpcode({ "trigger", "release_key" });
|
||||||
midiState.ccEvent(0, 64, 1.0f);
|
midiState.ccEvent(0, 64, 1.0f);
|
||||||
|
region.registerCC(64, 1.0f);
|
||||||
REQUIRE( !region.registerCC(64, 1.0f) );
|
REQUIRE( !region.registerCC(64, 1.0f) );
|
||||||
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||||
REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) );
|
REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||||
|
|
@ -58,6 +60,7 @@ TEST_CASE("[Direct Region Tests] Release and release key")
|
||||||
{
|
{
|
||||||
region.parseOpcode({ "trigger", "release" });
|
region.parseOpcode({ "trigger", "release" });
|
||||||
midiState.ccEvent(0, 64, 0.0f);
|
midiState.ccEvent(0, 64, 0.0f);
|
||||||
|
region.registerCC(64, 0.0f);
|
||||||
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||||
REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) );
|
REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||||
}
|
}
|
||||||
|
|
@ -66,6 +69,7 @@ TEST_CASE("[Direct Region Tests] Release and release key")
|
||||||
{
|
{
|
||||||
region.parseOpcode({ "trigger", "release" });
|
region.parseOpcode({ "trigger", "release" });
|
||||||
midiState.ccEvent(0, 64, 1.0f);
|
midiState.ccEvent(0, 64, 1.0f);
|
||||||
|
region.registerCC(64, 1.0f);
|
||||||
midiState.noteOnEvent(0, 63, 0.5f);
|
midiState.noteOnEvent(0, 63, 0.5f);
|
||||||
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||||
REQUIRE( !region.registerNoteOff(63, 0.5f, 0.0f) );
|
REQUIRE( !region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||||
|
|
@ -80,6 +84,7 @@ TEST_CASE("[Direct Region Tests] Release and release key")
|
||||||
{
|
{
|
||||||
region.parseOpcode({ "trigger", "release" });
|
region.parseOpcode({ "trigger", "release" });
|
||||||
midiState.ccEvent(0, 64, 1.0f);
|
midiState.ccEvent(0, 64, 1.0f);
|
||||||
|
region.registerCC(64, 1.0f);
|
||||||
midiState.noteOnEvent(0, 63, 0.5f);
|
midiState.noteOnEvent(0, 63, 0.5f);
|
||||||
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||||
midiState.noteOnEvent(0, 64, 0.6f);
|
midiState.noteOnEvent(0, 64, 0.6f);
|
||||||
|
|
@ -98,6 +103,7 @@ TEST_CASE("[Direct Region Tests] Release and release key")
|
||||||
{
|
{
|
||||||
region.parseOpcode({ "trigger", "release" });
|
region.parseOpcode({ "trigger", "release" });
|
||||||
midiState.ccEvent(0, 64, 1.0f);
|
midiState.ccEvent(0, 64, 1.0f);
|
||||||
|
region.registerCC(64, 1.0f);
|
||||||
midiState.noteOnEvent(0, 63, 0.5f);
|
midiState.noteOnEvent(0, 63, 0.5f);
|
||||||
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||||
midiState.noteOnEvent(0, 66, 0.6f);
|
midiState.noteOnEvent(0, 66, 0.6f);
|
||||||
|
|
|
||||||
|
|
@ -889,6 +889,41 @@ TEST_CASE("[Synth] Release (Different sostenuto CC)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Release (sustain + sostenuto)")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"(
|
||||||
|
<region> key=62 sample=*silence
|
||||||
|
<region> key=62 sample=*sine trigger=release
|
||||||
|
<region> key=64 sample=*silence
|
||||||
|
<region> key=64 sample=*sine trigger=release
|
||||||
|
)");
|
||||||
|
SECTION("Sustain up first")
|
||||||
|
{
|
||||||
|
synth.noteOn(0, 62, 85);
|
||||||
|
synth.cc(1, 66, 127);
|
||||||
|
synth.cc(1, 64, 127);
|
||||||
|
synth.noteOff(2, 62, 85);
|
||||||
|
REQUIRE( synth.getNumActiveVoices() == 1 );
|
||||||
|
synth.cc(3, 64, 0);
|
||||||
|
REQUIRE( synth.getNumActiveVoices() == 1 );
|
||||||
|
synth.cc(4, 66, 0);
|
||||||
|
REQUIRE( synth.getNumActiveVoices() == 2 );
|
||||||
|
}
|
||||||
|
SECTION("Sostenuto up first")
|
||||||
|
{
|
||||||
|
synth.noteOn(0, 62, 85);
|
||||||
|
synth.cc(1, 66, 127);
|
||||||
|
synth.cc(1, 64, 127);
|
||||||
|
synth.noteOff(2, 62, 85);
|
||||||
|
REQUIRE( synth.getNumActiveVoices() == 1 );
|
||||||
|
synth.cc(3, 66, 0);
|
||||||
|
REQUIRE( synth.getNumActiveVoices() == 1 );
|
||||||
|
synth.cc(4, 64, 0);
|
||||||
|
REQUIRE( synth.getNumActiveVoices() == 2 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("[Synth] Sustain threshold default")
|
TEST_CASE("[Synth] Sustain threshold default")
|
||||||
{
|
{
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue