Move the keyswitch logic in the synth rather than the regions

This commit is contained in:
Paul Ferrand 2020-10-27 11:04:25 +01:00
parent c27cb69a83
commit 6bfc9074d7
5 changed files with 106 additions and 68 deletions

View file

@ -1588,20 +1588,11 @@ bool sfz::Region::registerNoteOn(int noteNumber, float velocity, float randValue
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
if (keyswitchDown && *keyswitchDown == noteNumber)
keySwitched = true;
if (keyswitchUp && *keyswitchUp == noteNumber)
keySwitched = false;
const bool keyOk = keyRange.containsWithEnd(noteNumber);
if (keyOk) {
// Sequence activation
sequenceSwitched =
((sequenceCounter++ % sequenceLength) == sequencePosition - 1);
if (previousNote)
previousKeySwitched = (*previousNote == noteNumber);
}
if (!isSwitchedOn())
@ -1610,9 +1601,6 @@ bool sfz::Region::registerNoteOn(int noteNumber, float velocity, float randValue
if (!triggerOnNote)
return false;
if (previousNote && !(previousKeySwitched && noteNumber != *previousNote))
return false;
const bool velOk = velocityRange.containsWithEnd(velocity);
const bool randOk = randRange.contains(randValue) || (randValue == 1.0f && randRange.getEnd() == 1.0f);
const bool firstLegatoNote = (trigger == SfzTrigger::first && midiState.getActiveNotes() == 1);
@ -1626,12 +1614,6 @@ bool sfz::Region::registerNoteOff(int noteNumber, float velocity, float randValu
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
if (keyswitchDown && *keyswitchDown == noteNumber)
keySwitched = false;
if (keyswitchUp && *keyswitchUp == noteNumber)
keySwitched = true;
if (!isSwitchedOn())
return false;

View file

@ -33,8 +33,6 @@ struct Resources
absl::optional<StretchTuning> stretch;
ModMatrix modMatrix;
std::vector<uint8_t> keyswitches;
void setSampleRate(float samplerate)
{
midiState.setSampleRate(samplerate);
@ -56,7 +54,6 @@ struct Resources
logger.clear();
midiState.reset();
modMatrix.clear();
keyswitches.clear();
}
};
}

View file

@ -175,11 +175,17 @@ void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
if (octaveOffset != 0 || noteOffset != 0)
lastRegion->offsetAllKeys(octaveOffset * 12 + noteOffset);
if (lastRegion->keyswitch) {
auto it = absl::c_find(resources.keyswitches, *lastRegion->keyswitch);
if (it == resources.keyswitches.end())
resources.keyswitches.push_back(*lastRegion->keyswitch);
}
if (lastRegion->keyswitch)
lastKeyswitchLists[*lastRegion->keyswitch].push_back(lastRegion.get());
if (lastRegion->keyswitchUp)
upKeyswitchLists[*lastRegion->keyswitchUp].push_back(lastRegion.get());
if (lastRegion->keyswitchDown)
downKeyswitchLists[*lastRegion->keyswitchDown].push_back(lastRegion.get());
if (lastRegion->previousNote)
previousKeyswitchLists.push_back(lastRegion.get());
// There was a combination of group= and polyphony= on a region, so set the group polyphony
if (lastRegion->group != Default::group && lastRegion->polyphony != config::maxVoices)
@ -203,10 +209,17 @@ void sfz::Synth::clear()
for (auto& voice : voices)
voice->reset();
for (auto& list : lastKeyswitchLists)
list.clear();
for (auto& list : downKeyswitchLists)
list.clear();
for (auto& list : upKeyswitchLists)
list.clear();
for (auto& list : noteActivationLists)
list.clear();
for (auto& list : ccActivationLists)
list.clear();
previousKeyswitchLists.clear();
currentSet = nullptr;
sets.clear();
@ -220,7 +233,7 @@ void sfz::Synth::clear()
resources.clear();
numGroups = 0;
numMasters = 0;
defaultSwitch = absl::nullopt;
currentSwitch = absl::nullopt;
defaultPath = "";
resources.midiState.reset();
resources.filePool.clear();
@ -262,7 +275,7 @@ void sfz::Synth::handleMasterOpcodes(const std::vector<Opcode>& members)
currentSet->setPolyphonyLimit(*value);
break;
case hash("sw_default"):
setValueFromOpcode(member, defaultSwitch, Default::keyRange);
setValueFromOpcode(member, currentSwitch, Default::keyRange);
break;
}
}
@ -280,7 +293,7 @@ void sfz::Synth::handleGlobalOpcodes(const std::vector<Opcode>& members)
currentSet->setPolyphonyLimit(*value);
break;
case hash("sw_default"):
setValueFromOpcode(member, defaultSwitch, Default::keyRange);
setValueFromOpcode(member, currentSwitch, Default::keyRange);
break;
case hash("volume"):
// FIXME : Probably best not to mess with this and let the host control the volume
@ -306,7 +319,7 @@ void sfz::Synth::handleGroupOpcodes(const std::vector<Opcode>& members, const st
setValueFromOpcode(member, maxPolyphony, Default::polyphonyRange);
break;
case hash("sw_default"):
setValueFromOpcode(member, defaultSwitch, Default::keyRange);
setValueFromOpcode(member, currentSwitch, Default::keyRange);
break;
}
};
@ -535,7 +548,6 @@ void sfz::Synth::finalizeSfzLoad()
bool haveFilterEG { false };
FlexEGs::clearUnusedCurves();
absl::c_sort(resources.keyswitches);
while (currentRegionIndex < currentRegionCount) {
auto region = regions[currentRegionIndex].get();
@ -603,8 +615,8 @@ void sfz::Synth::finalizeSfzLoad()
}
if (region->keyswitch) {
if (defaultSwitch)
region->keySwitched = (*defaultSwitch == *region->keyswitch);
if (currentSwitch)
region->keySwitched = (*currentSwitch == *region->keyswitch);
if (region->keyswitchLabel)
insertPairUniquely(keyswitchLabels, *region->keyswitch, *region->keyswitchLabel);
@ -617,15 +629,7 @@ void sfz::Synth::finalizeSfzLoad()
}
for (auto note = 0; note < 128; note++) {
bool noteIsKeyswitch = (absl::c_binary_search(resources.keyswitches, note));
if (
region->keyRange.containsWithEnd(note)
|| (region->keyswitch && noteIsKeyswitch)
|| (region->keyswitchDown && *region->keyswitchDown == note)
|| (region->keyswitchUp && *region->keyswitchUp == note)
|| (region->previousNote && *region->previousNote == note)
)
if (region->keyRange.containsWithEnd(note))
noteActivationLists[note].push_back(region);
}
@ -1032,6 +1036,12 @@ void sfz::Synth::noteOffDispatch(int delay, int noteNumber, float velocity) noex
SisterVoiceRingBuilder ring;
const TriggerEvent triggerEvent { TriggerEventType::NoteOff, noteNumber, velocity };
for (auto& region : upKeyswitchLists[noteNumber])
region->keySwitched = true;
for (auto& region : downKeyswitchLists[noteNumber])
region->keySwitched = false;
for (auto& region : noteActivationLists[noteNumber]) {
if (region->registerNoteOff(noteNumber, velocity, randValue)) {
if (region->trigger == SfzTrigger::release && !region->rtDead && !playingAttackVoice(region))
@ -1142,11 +1152,24 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc
SisterVoiceRingBuilder ring;
const TriggerEvent triggerEvent { TriggerEventType::NoteOn, noteNumber, velocity };
bool noteIsKeyswitch = absl::c_binary_search(resources.keyswitches, noteNumber);
if (!lastKeyswitchLists[noteNumber].empty()) {
if (currentSwitch && *currentSwitch != noteNumber) {
for (auto& region : lastKeyswitchLists[*currentSwitch])
region->keySwitched = false;
}
currentSwitch = noteNumber;
}
for (auto& region : lastKeyswitchLists[noteNumber])
region->keySwitched = true;
for (auto& region : upKeyswitchLists[noteNumber])
region->keySwitched = false;
for (auto& region : downKeyswitchLists[noteNumber])
region->keySwitched = true;
for (auto& region : noteActivationLists[noteNumber]) {
if (noteIsKeyswitch && region->keyswitch)
region->keySwitched = (*region->keyswitch == noteNumber);
if (region->registerNoteOn(noteNumber, velocity, randValue)) {
for (auto& voice : voices) {
@ -1159,6 +1182,9 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc
startVoice(region, delay, triggerEvent, ring);
}
}
for (auto& region : previousKeyswitchLists)
region->previousKeySwitched = (*region->previousNote == noteNumber);
}
void sfz::Synth::startDelayedReleaseVoices(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept

View file

@ -803,8 +803,8 @@ private:
std::vector<NoteNamePair> keyLabels;
std::vector<NoteNamePair> keyswitchLabels;
// Default active switch if multiple keyswitchable regions are present
absl::optional<uint8_t> defaultSwitch;
// Set as sw_default if present in the file
absl::optional<uint8_t> currentSwitch;
std::vector<std::string> unknownOpcodes;
using RegionViewVector = std::vector<Region*>;
using VoiceViewVector = std::vector<Voice*>;
@ -899,6 +899,10 @@ private:
*/
bool playingAttackVoice(const Region* releaseRegion) noexcept;
std::array<RegionViewVector, 128> lastKeyswitchLists;
std::array<RegionViewVector, 128> downKeyswitchLists;
std::array<RegionViewVector, 128> upKeyswitchLists;
RegionViewVector previousKeyswitchLists;
std::array<RegionViewVector, 128> noteActivationLists;
std::array<RegionViewVector, config::numCCs> ccActivationLists;

View file

@ -114,27 +114,6 @@ TEST_CASE("Region activation", "Region tests")
REQUIRE(!region.isSwitchedOn());
}
SECTION("Keyswitches: sw_previous")
{
region.parseOpcode({ "sw_previous", "40" });
REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(40, 64_norm, 0.5f);
REQUIRE(region.isSwitchedOn());
region.registerNoteOff(40, 0_norm, 0.5f);
REQUIRE(region.isSwitchedOn());
region.registerNoteOn(41, 64_norm, 0.5f);
REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(40, 64_norm, 0.5f);
REQUIRE(region.isSwitchedOn());
region.registerNoteOff(40, 0_norm, 0.5f);
region.registerNoteOff(41, 0_norm, 0.5f);
REQUIRE(region.isSwitchedOn());
region.registerNoteOn(41, 64_norm, 0.5f);
REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(41, 0_norm, 0.5f);
REQUIRE(!region.isSwitchedOn());
}
SECTION("Sequences: length 2, default position")
{
region.parseOpcode({ "seq_length", "2" });
@ -396,3 +375,53 @@ TEST_CASE("[Keyswitches] sw_default and playing with switches")
REQUIRE( !synth.getRegionView(2)->isSwitchedOn() );
REQUIRE( synth.getRegionView(3)->isSwitchedOn() );
}
TEST_CASE("[Keyswitches] sw_previous in range")
{
sfz::Synth synth;
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
<region> sample=*saw sw_previous=60 lokey=50 hikey=70
)");
// Note: sforzando seems to activate by default if sw_previous is indeed 60,
// but not any other value. As it does not seem really useful at this point
// the test assumes that sw_previous regions are disabled by default
REQUIRE(!synth.getRegionView(0)->isSwitchedOn());
synth.noteOn(0, 51, 64);
REQUIRE(synth.getNumActiveVoices(true) == 0);
synth.noteOn(0, 60, 64);
REQUIRE(synth.getNumActiveVoices(true) == 0);
REQUIRE(synth.getRegionView(0)->isSwitchedOn());
synth.noteOn(0, 51, 64);
REQUIRE(synth.getNumActiveVoices(true) == 1);
REQUIRE(!synth.getRegionView(0)->isSwitchedOn());
synth.noteOn(0, 60, 64);
REQUIRE(synth.getNumActiveVoices(true) == 1);
REQUIRE(synth.getRegionView(0)->isSwitchedOn());
synth.noteOn(0, 60, 64);
REQUIRE(synth.getNumActiveVoices(true) == 2);
REQUIRE(synth.getRegionView(0)->isSwitchedOn());
}
TEST_CASE("[Keyswitches] sw_previous out of range")
{
// The behavior is the same in this case, regardless of the keyrange
sfz::Synth synth;
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
<region> sample=*saw sw_previous=60 lokey=50 hikey=55
)");
REQUIRE(!synth.getRegionView(0)->isSwitchedOn());
synth.noteOn(0, 51, 64);
REQUIRE(synth.getNumActiveVoices(true) == 0);
synth.noteOn(0, 60, 64);
REQUIRE(synth.getNumActiveVoices(true) == 0);
REQUIRE(synth.getRegionView(0)->isSwitchedOn());
synth.noteOn(0, 51, 64);
REQUIRE(synth.getNumActiveVoices(true) == 1);
REQUIRE(!synth.getRegionView(0)->isSwitchedOn());
synth.noteOn(0, 60, 64);
REQUIRE(synth.getNumActiveVoices(true) == 1);
REQUIRE(synth.getRegionView(0)->isSwitchedOn());
synth.noteOn(0, 61, 64);
REQUIRE(!synth.getRegionView(0)->isSwitchedOn());
}