Skip to content

Commit

Permalink
If the region spans multiple keys they can all fire on pedal up
Browse files Browse the repository at this point in the history
  • Loading branch information
paulfd committed Aug 7, 2020
1 parent 443e89c commit a4cfdd1
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 27 deletions.
38 changes: 27 additions & 11 deletions src/sfizz/Region.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;

Expand Down
4 changes: 3 additions & 1 deletion src/sfizz/Region.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand All @@ -384,7 +387,6 @@ struct Region {
bool pitchSwitched { true };
bool bpmSwitched { true };
bool aftertouchSwitched { true };
bool noteIsOff { false };
std::bitset<config::numCCs> ccSwitched;
absl::string_view defaultPath { "" };

Expand Down
29 changes: 21 additions & 8 deletions src/sfizz/Synth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down Expand Up @@ -992,18 +995,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);
Expand Down
47 changes: 40 additions & 7 deletions tests/RegionT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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")
{
Expand All @@ -1765,30 +1766,62 @@ 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" });
midiState.ccEvent(0, 64, 0.0f);
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 );
}
}
54 changes: 54 additions & 0 deletions tests/SynthT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
}

0 comments on commit a4cfdd1

Please sign in to comment.