If the region spans multiple keys they can all fire on pedal up
This commit is contained in:
parent
1d3346012f
commit
6086284efe
5 changed files with 145 additions and 27 deletions
|
|
@ -1031,24 +1031,45 @@ bool sfz::Region::registerNoteOff(int noteNumber, float velocity, float randValu
|
|||
keySwitched = true;
|
||||
}
|
||||
|
||||
const bool keyOk = keyRange.containsWithEnd(noteNumber);
|
||||
|
||||
if (!isSwitchedOn())
|
||||
return false;
|
||||
|
||||
if (!triggerOnNote)
|
||||
return false;
|
||||
|
||||
// Prerequisites
|
||||
|
||||
const bool keyOk = keyRange.containsWithEnd(noteNumber);
|
||||
const bool velOk = velocityRange.containsWithEnd(velocity);
|
||||
const bool randOk = randRange.contains(randValue);
|
||||
bool releaseTrigger = (trigger == SfzTrigger::release_key);
|
||||
|
||||
if (!(velOk && keyOk && randOk))
|
||||
return false;
|
||||
|
||||
// Release logic
|
||||
|
||||
if (trigger == SfzTrigger::release_key)
|
||||
return true;
|
||||
|
||||
if (trigger == SfzTrigger::release) {
|
||||
if (midiState.getCCValue(sustainCC) < sustainThreshold)
|
||||
releaseTrigger = true;
|
||||
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
|
||||
|
||||
const auto sameNoteTest = [noteNumber](const std::pair<int, float>& noteAndValue) {
|
||||
return noteAndValue.first == noteNumber;
|
||||
};
|
||||
|
||||
auto it = absl::c_find_if(delayedReleases, sameNoteTest);
|
||||
if (it == delayedReleases.end())
|
||||
delayedReleases.emplace_back(noteNumber, midiState.getNoteVelocity(noteNumber));
|
||||
else
|
||||
noteIsOff = true;
|
||||
it->second = velocity;
|
||||
}
|
||||
return keyOk && velOk && randOk && releaseTrigger;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sfz::Region::registerCC(int ccNumber, float ccValue) noexcept
|
||||
|
|
@ -1062,11 +1083,6 @@ bool sfz::Region::registerCC(int ccNumber, float ccValue) noexcept
|
|||
if (!isSwitchedOn())
|
||||
return false;
|
||||
|
||||
if (sustainCC == ccNumber && ccValue < sustainThreshold && noteIsOff) {
|
||||
noteIsOff = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!triggerOnCC)
|
||||
return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -376,6 +376,9 @@ struct Region {
|
|||
|
||||
// Parent
|
||||
RegionSet* parent { nullptr };
|
||||
|
||||
// Started notes
|
||||
std::vector<std::pair<int, float>> delayedReleases;
|
||||
private:
|
||||
const MidiState& midiState;
|
||||
bool keySwitched { true };
|
||||
|
|
@ -384,7 +387,6 @@ private:
|
|||
bool pitchSwitched { true };
|
||||
bool bpmSwitched { true };
|
||||
bool aftertouchSwitched { true };
|
||||
bool noteIsOff { false };
|
||||
std::bitset<config::numCCs> ccSwitched;
|
||||
absl::string_view defaultPath { "" };
|
||||
|
||||
|
|
|
|||
|
|
@ -161,6 +161,9 @@ void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
|
|||
lastRegion->parent = currentSet;
|
||||
currentSet->addRegion(lastRegion.get());
|
||||
|
||||
// Adapt the size of the delayed releases to avoid allocating later on
|
||||
lastRegion->delayedReleases.reserve(lastRegion->keyRange.length());
|
||||
|
||||
regions.push_back(std::move(lastRegion));
|
||||
}
|
||||
|
||||
|
|
@ -998,18 +1001,28 @@ void sfz::Synth::hdcc(int delay, int ccNumber, float normValue) noexcept
|
|||
SisterVoiceRingBuilder ring;
|
||||
|
||||
for (auto& region : ccActivationLists[ccNumber]) {
|
||||
if (region->registerCC(ccNumber, normValue)) {
|
||||
if (ccNumber == region->sustainCC) {
|
||||
for (auto& note: region->delayedReleases) {
|
||||
// FIXME: we really need to have some form of common method to find and start voices...
|
||||
auto voice = findFreeVoice();
|
||||
if (voice == nullptr)
|
||||
continue;
|
||||
|
||||
voice->startVoice(region, delay, note.first, note.second, Voice::TriggerType::NoteOff);
|
||||
|
||||
ring.addVoiceToRing(voice);
|
||||
RegionSet::registerVoiceInHierarchy(region, voice);
|
||||
polyphonyGroups[region->group].registerVoice(voice);
|
||||
}
|
||||
|
||||
region->delayedReleases.clear();
|
||||
} else if (region->registerCC(ccNumber, normValue)) {
|
||||
auto voice = findFreeVoice();
|
||||
if (voice == nullptr)
|
||||
continue;
|
||||
|
||||
if (!region->triggerOnCC) {
|
||||
// This is a sustain trigger
|
||||
const auto replacedVelocity = resources.midiState.getNoteVelocity(region->pitchKeycenter);
|
||||
voice->startVoice(region, delay, region->pitchKeycenter, replacedVelocity, Voice::TriggerType::NoteOff);
|
||||
} else {
|
||||
voice->startVoice(region, delay, ccNumber, normValue, Voice::TriggerType::CC);
|
||||
}
|
||||
|
||||
voice->startVoice(region, delay, ccNumber, normValue, Voice::TriggerType::CC);
|
||||
|
||||
ring.addVoiceToRing(voice);
|
||||
RegionSet::registerVoiceInHierarchy(region, voice);
|
||||
|
|
|
|||
|
|
@ -1749,7 +1749,8 @@ TEST_CASE("[Region] Release and release key")
|
|||
{
|
||||
MidiState midiState;
|
||||
Region region { 0, midiState };
|
||||
region.parseOpcode({ "key", "63" });
|
||||
region.parseOpcode({ "lokey", "63" });
|
||||
region.parseOpcode({ "hikey", "65" });
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
SECTION("Release key without sustain")
|
||||
{
|
||||
|
|
@ -1765,9 +1766,8 @@ TEST_CASE("[Region] Release and release key")
|
|||
REQUIRE( !region.registerCC(64, 1.0f) );
|
||||
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||
REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||
midiState.ccEvent(0, 64, 0.0f);
|
||||
REQUIRE( !region.registerCC(64, 0.0f) );
|
||||
}
|
||||
|
||||
SECTION("Release without sustain")
|
||||
{
|
||||
region.parseOpcode({ "trigger", "release" });
|
||||
|
|
@ -1775,20 +1775,53 @@ TEST_CASE("[Region] Release and release key")
|
|||
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||
REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||
}
|
||||
|
||||
SECTION("Release with sustain")
|
||||
{
|
||||
region.parseOpcode({ "trigger", "release" });
|
||||
midiState.ccEvent(0, 64, 1.0f);
|
||||
midiState.noteOnEvent(0, 63, 0.5f);
|
||||
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||
REQUIRE( !region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||
REQUIRE( region.delayedReleases.size() == 1 );
|
||||
std::vector<std::pair<int, float>> expected = {
|
||||
{ 63, 0.5f }
|
||||
};
|
||||
REQUIRE( region.delayedReleases == expected );
|
||||
}
|
||||
SECTION("Release with sustain")
|
||||
|
||||
SECTION("Release with sustain and 2 notes")
|
||||
{
|
||||
region.parseOpcode({ "trigger", "release" });
|
||||
midiState.ccEvent(0, 64, 1.0f);
|
||||
midiState.noteOnEvent(0, 63, 0.5f);
|
||||
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||
REQUIRE( !region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||
midiState.ccEvent(0, 64, 0.0f);
|
||||
REQUIRE( region.registerCC(64, 0.0f) );
|
||||
midiState.noteOnEvent(0, 64, 0.6f);
|
||||
REQUIRE( !region.registerNoteOn(64, 0.6f, 0.0f) );
|
||||
REQUIRE( !region.registerNoteOff(63, 0.0f, 0.0f) );
|
||||
REQUIRE( !region.registerNoteOff(64, 0.2f, 0.0f) );
|
||||
REQUIRE( region.delayedReleases.size() == 2 );
|
||||
std::vector<std::pair<int, float>> expected = {
|
||||
{ 63, 0.5f },
|
||||
{ 64, 0.6f }
|
||||
};
|
||||
REQUIRE( region.delayedReleases == expected );
|
||||
}
|
||||
|
||||
SECTION("Release with sustain and 2 notes but 1 outside")
|
||||
{
|
||||
region.parseOpcode({ "trigger", "release" });
|
||||
midiState.ccEvent(0, 64, 1.0f);
|
||||
midiState.noteOnEvent(0, 63, 0.5f);
|
||||
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||
midiState.noteOnEvent(0, 66, 0.6f);
|
||||
REQUIRE( !region.registerNoteOn(66, 0.6f, 0.0f) );
|
||||
REQUIRE( !region.registerNoteOff(63, 0.0f, 0.0f) );
|
||||
REQUIRE( !region.registerNoteOff(66, 0.2f, 0.0f) );
|
||||
REQUIRE( region.delayedReleases.size() == 1 );
|
||||
std::vector<std::pair<int, float>> expected = {
|
||||
{ 63, 0.5f }
|
||||
};
|
||||
REQUIRE( region.delayedReleases == expected );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -695,3 +695,57 @@ TEST_CASE("[Synth] Sustain threshold")
|
|||
synth.noteOff(0, 62, 85);
|
||||
REQUIRE( synth.getNumActiveVoices(true) == 2 );
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Release (Multiple notes)")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> lokey=62 hikey=64 sample=*sine trigger=release
|
||||
)");
|
||||
synth.noteOn(0, 62, 85);
|
||||
synth.noteOn(0, 63, 78);
|
||||
synth.noteOn(0, 64, 34);
|
||||
synth.cc(0, 64, 127);
|
||||
synth.noteOff(0, 64, 0);
|
||||
synth.noteOff(0, 63, 2);
|
||||
synth.noteOff(0, 62, 85);
|
||||
REQUIRE( synth.getNumActiveVoices() == 0 );
|
||||
synth.cc(0, 64, 0);
|
||||
REQUIRE( synth.getNumActiveVoices() == 3 );
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Release (Multiple notes, release_key ignores the pedal)")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> lokey=62 hikey=64 sample=*sine trigger=release_key
|
||||
)");
|
||||
synth.noteOn(0, 62, 85);
|
||||
synth.noteOn(0, 63, 78);
|
||||
synth.noteOn(0, 64, 34);
|
||||
synth.cc(0, 64, 127);
|
||||
synth.noteOff(0, 64, 0);
|
||||
synth.noteOff(0, 63, 2);
|
||||
synth.noteOff(0, 62, 85);
|
||||
REQUIRE( synth.getNumActiveVoices() == 3 );
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Release (Multiple notes, cleared the delayed voices after)")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> lokey=62 hikey=64 sample=*sine trigger=release
|
||||
loopmode=one_shot ampeg_attack=0.02 ampeg_release=0.1
|
||||
)");
|
||||
synth.noteOn(0, 62, 85);
|
||||
synth.noteOn(0, 63, 78);
|
||||
synth.noteOn(0, 64, 34);
|
||||
synth.cc(0, 64, 127);
|
||||
synth.noteOff(0, 64, 0);
|
||||
synth.noteOff(0, 63, 2);
|
||||
synth.noteOff(0, 62, 85);
|
||||
REQUIRE( synth.getNumActiveVoices() == 0 );
|
||||
synth.cc(0, 64, 0);
|
||||
REQUIRE( synth.getNumActiveVoices() == 3 );
|
||||
REQUIRE( synth.getRegionView(0)->delayedReleases.empty() );
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue