Move the keyswitch logic in the synth rather than the regions
This commit is contained in:
parent
c27cb69a83
commit
6bfc9074d7
5 changed files with 106 additions and 68 deletions
|
|
@ -1588,20 +1588,11 @@ bool sfz::Region::registerNoteOn(int noteNumber, float velocity, float randValue
|
||||||
{
|
{
|
||||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
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);
|
const bool keyOk = keyRange.containsWithEnd(noteNumber);
|
||||||
if (keyOk) {
|
if (keyOk) {
|
||||||
// Sequence activation
|
// Sequence activation
|
||||||
sequenceSwitched =
|
sequenceSwitched =
|
||||||
((sequenceCounter++ % sequenceLength) == sequencePosition - 1);
|
((sequenceCounter++ % sequenceLength) == sequencePosition - 1);
|
||||||
|
|
||||||
if (previousNote)
|
|
||||||
previousKeySwitched = (*previousNote == noteNumber);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSwitchedOn())
|
if (!isSwitchedOn())
|
||||||
|
|
@ -1610,9 +1601,6 @@ bool sfz::Region::registerNoteOn(int noteNumber, float velocity, float randValue
|
||||||
if (!triggerOnNote)
|
if (!triggerOnNote)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (previousNote && !(previousKeySwitched && noteNumber != *previousNote))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const bool velOk = velocityRange.containsWithEnd(velocity);
|
const bool velOk = velocityRange.containsWithEnd(velocity);
|
||||||
const bool randOk = randRange.contains(randValue) || (randValue == 1.0f && randRange.getEnd() == 1.0f);
|
const bool randOk = randRange.contains(randValue) || (randValue == 1.0f && randRange.getEnd() == 1.0f);
|
||||||
const bool firstLegatoNote = (trigger == SfzTrigger::first && midiState.getActiveNotes() == 1);
|
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);
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
|
|
||||||
if (keyswitchDown && *keyswitchDown == noteNumber)
|
|
||||||
keySwitched = false;
|
|
||||||
|
|
||||||
if (keyswitchUp && *keyswitchUp == noteNumber)
|
|
||||||
keySwitched = true;
|
|
||||||
|
|
||||||
if (!isSwitchedOn())
|
if (!isSwitchedOn())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,6 @@ struct Resources
|
||||||
absl::optional<StretchTuning> stretch;
|
absl::optional<StretchTuning> stretch;
|
||||||
ModMatrix modMatrix;
|
ModMatrix modMatrix;
|
||||||
|
|
||||||
std::vector<uint8_t> keyswitches;
|
|
||||||
|
|
||||||
void setSampleRate(float samplerate)
|
void setSampleRate(float samplerate)
|
||||||
{
|
{
|
||||||
midiState.setSampleRate(samplerate);
|
midiState.setSampleRate(samplerate);
|
||||||
|
|
@ -56,7 +54,6 @@ struct Resources
|
||||||
logger.clear();
|
logger.clear();
|
||||||
midiState.reset();
|
midiState.reset();
|
||||||
modMatrix.clear();
|
modMatrix.clear();
|
||||||
keyswitches.clear();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -175,11 +175,17 @@ void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
|
||||||
if (octaveOffset != 0 || noteOffset != 0)
|
if (octaveOffset != 0 || noteOffset != 0)
|
||||||
lastRegion->offsetAllKeys(octaveOffset * 12 + noteOffset);
|
lastRegion->offsetAllKeys(octaveOffset * 12 + noteOffset);
|
||||||
|
|
||||||
if (lastRegion->keyswitch) {
|
if (lastRegion->keyswitch)
|
||||||
auto it = absl::c_find(resources.keyswitches, *lastRegion->keyswitch);
|
lastKeyswitchLists[*lastRegion->keyswitch].push_back(lastRegion.get());
|
||||||
if (it == resources.keyswitches.end())
|
|
||||||
resources.keyswitches.push_back(*lastRegion->keyswitch);
|
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
|
// 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)
|
if (lastRegion->group != Default::group && lastRegion->polyphony != config::maxVoices)
|
||||||
|
|
@ -203,10 +209,17 @@ void sfz::Synth::clear()
|
||||||
|
|
||||||
for (auto& voice : voices)
|
for (auto& voice : voices)
|
||||||
voice->reset();
|
voice->reset();
|
||||||
|
for (auto& list : lastKeyswitchLists)
|
||||||
|
list.clear();
|
||||||
|
for (auto& list : downKeyswitchLists)
|
||||||
|
list.clear();
|
||||||
|
for (auto& list : upKeyswitchLists)
|
||||||
|
list.clear();
|
||||||
for (auto& list : noteActivationLists)
|
for (auto& list : noteActivationLists)
|
||||||
list.clear();
|
list.clear();
|
||||||
for (auto& list : ccActivationLists)
|
for (auto& list : ccActivationLists)
|
||||||
list.clear();
|
list.clear();
|
||||||
|
previousKeyswitchLists.clear();
|
||||||
|
|
||||||
currentSet = nullptr;
|
currentSet = nullptr;
|
||||||
sets.clear();
|
sets.clear();
|
||||||
|
|
@ -220,7 +233,7 @@ void sfz::Synth::clear()
|
||||||
resources.clear();
|
resources.clear();
|
||||||
numGroups = 0;
|
numGroups = 0;
|
||||||
numMasters = 0;
|
numMasters = 0;
|
||||||
defaultSwitch = absl::nullopt;
|
currentSwitch = absl::nullopt;
|
||||||
defaultPath = "";
|
defaultPath = "";
|
||||||
resources.midiState.reset();
|
resources.midiState.reset();
|
||||||
resources.filePool.clear();
|
resources.filePool.clear();
|
||||||
|
|
@ -262,7 +275,7 @@ void sfz::Synth::handleMasterOpcodes(const std::vector<Opcode>& members)
|
||||||
currentSet->setPolyphonyLimit(*value);
|
currentSet->setPolyphonyLimit(*value);
|
||||||
break;
|
break;
|
||||||
case hash("sw_default"):
|
case hash("sw_default"):
|
||||||
setValueFromOpcode(member, defaultSwitch, Default::keyRange);
|
setValueFromOpcode(member, currentSwitch, Default::keyRange);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +293,7 @@ void sfz::Synth::handleGlobalOpcodes(const std::vector<Opcode>& members)
|
||||||
currentSet->setPolyphonyLimit(*value);
|
currentSet->setPolyphonyLimit(*value);
|
||||||
break;
|
break;
|
||||||
case hash("sw_default"):
|
case hash("sw_default"):
|
||||||
setValueFromOpcode(member, defaultSwitch, Default::keyRange);
|
setValueFromOpcode(member, currentSwitch, Default::keyRange);
|
||||||
break;
|
break;
|
||||||
case hash("volume"):
|
case hash("volume"):
|
||||||
// FIXME : Probably best not to mess with this and let the host control the 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);
|
setValueFromOpcode(member, maxPolyphony, Default::polyphonyRange);
|
||||||
break;
|
break;
|
||||||
case hash("sw_default"):
|
case hash("sw_default"):
|
||||||
setValueFromOpcode(member, defaultSwitch, Default::keyRange);
|
setValueFromOpcode(member, currentSwitch, Default::keyRange);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -535,7 +548,6 @@ void sfz::Synth::finalizeSfzLoad()
|
||||||
bool haveFilterEG { false };
|
bool haveFilterEG { false };
|
||||||
|
|
||||||
FlexEGs::clearUnusedCurves();
|
FlexEGs::clearUnusedCurves();
|
||||||
absl::c_sort(resources.keyswitches);
|
|
||||||
|
|
||||||
while (currentRegionIndex < currentRegionCount) {
|
while (currentRegionIndex < currentRegionCount) {
|
||||||
auto region = regions[currentRegionIndex].get();
|
auto region = regions[currentRegionIndex].get();
|
||||||
|
|
@ -603,8 +615,8 @@ void sfz::Synth::finalizeSfzLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (region->keyswitch) {
|
if (region->keyswitch) {
|
||||||
if (defaultSwitch)
|
if (currentSwitch)
|
||||||
region->keySwitched = (*defaultSwitch == *region->keyswitch);
|
region->keySwitched = (*currentSwitch == *region->keyswitch);
|
||||||
|
|
||||||
if (region->keyswitchLabel)
|
if (region->keyswitchLabel)
|
||||||
insertPairUniquely(keyswitchLabels, *region->keyswitch, *region->keyswitchLabel);
|
insertPairUniquely(keyswitchLabels, *region->keyswitch, *region->keyswitchLabel);
|
||||||
|
|
@ -617,15 +629,7 @@ void sfz::Synth::finalizeSfzLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto note = 0; note < 128; note++) {
|
for (auto note = 0; note < 128; note++) {
|
||||||
bool noteIsKeyswitch = (absl::c_binary_search(resources.keyswitches, note));
|
if (region->keyRange.containsWithEnd(note))
|
||||||
|
|
||||||
if (
|
|
||||||
region->keyRange.containsWithEnd(note)
|
|
||||||
|| (region->keyswitch && noteIsKeyswitch)
|
|
||||||
|| (region->keyswitchDown && *region->keyswitchDown == note)
|
|
||||||
|| (region->keyswitchUp && *region->keyswitchUp == note)
|
|
||||||
|| (region->previousNote && *region->previousNote == note)
|
|
||||||
)
|
|
||||||
noteActivationLists[note].push_back(region);
|
noteActivationLists[note].push_back(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1032,6 +1036,12 @@ void sfz::Synth::noteOffDispatch(int delay, int noteNumber, float velocity) noex
|
||||||
SisterVoiceRingBuilder ring;
|
SisterVoiceRingBuilder ring;
|
||||||
const TriggerEvent triggerEvent { TriggerEventType::NoteOff, noteNumber, velocity };
|
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]) {
|
for (auto& region : noteActivationLists[noteNumber]) {
|
||||||
if (region->registerNoteOff(noteNumber, velocity, randValue)) {
|
if (region->registerNoteOff(noteNumber, velocity, randValue)) {
|
||||||
if (region->trigger == SfzTrigger::release && !region->rtDead && !playingAttackVoice(region))
|
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;
|
SisterVoiceRingBuilder ring;
|
||||||
const TriggerEvent triggerEvent { TriggerEventType::NoteOn, noteNumber, velocity };
|
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]) {
|
for (auto& region : noteActivationLists[noteNumber]) {
|
||||||
if (noteIsKeyswitch && region->keyswitch)
|
|
||||||
region->keySwitched = (*region->keyswitch == noteNumber);
|
|
||||||
|
|
||||||
if (region->registerNoteOn(noteNumber, velocity, randValue)) {
|
if (region->registerNoteOn(noteNumber, velocity, randValue)) {
|
||||||
for (auto& voice : voices) {
|
for (auto& voice : voices) {
|
||||||
|
|
@ -1159,6 +1182,9 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc
|
||||||
startVoice(region, delay, triggerEvent, ring);
|
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
|
void sfz::Synth::startDelayedReleaseVoices(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept
|
||||||
|
|
|
||||||
|
|
@ -803,8 +803,8 @@ private:
|
||||||
std::vector<NoteNamePair> keyLabels;
|
std::vector<NoteNamePair> keyLabels;
|
||||||
std::vector<NoteNamePair> keyswitchLabels;
|
std::vector<NoteNamePair> keyswitchLabels;
|
||||||
|
|
||||||
// Default active switch if multiple keyswitchable regions are present
|
// Set as sw_default if present in the file
|
||||||
absl::optional<uint8_t> defaultSwitch;
|
absl::optional<uint8_t> currentSwitch;
|
||||||
std::vector<std::string> unknownOpcodes;
|
std::vector<std::string> unknownOpcodes;
|
||||||
using RegionViewVector = std::vector<Region*>;
|
using RegionViewVector = std::vector<Region*>;
|
||||||
using VoiceViewVector = std::vector<Voice*>;
|
using VoiceViewVector = std::vector<Voice*>;
|
||||||
|
|
@ -899,6 +899,10 @@ private:
|
||||||
*/
|
*/
|
||||||
bool playingAttackVoice(const Region* releaseRegion) noexcept;
|
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, 128> noteActivationLists;
|
||||||
std::array<RegionViewVector, config::numCCs> ccActivationLists;
|
std::array<RegionViewVector, config::numCCs> ccActivationLists;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,27 +114,6 @@ TEST_CASE("Region activation", "Region tests")
|
||||||
REQUIRE(!region.isSwitchedOn());
|
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")
|
SECTION("Sequences: length 2, default position")
|
||||||
{
|
{
|
||||||
region.parseOpcode({ "seq_length", "2" });
|
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(2)->isSwitchedOn() );
|
||||||
REQUIRE( synth.getRegionView(3)->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());
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue