From c27cb69a838a832eccd1a1fd16477f8a36f1f6fb Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 26 Oct 2020 22:07:40 +0100 Subject: [PATCH 1/5] Build a set of "effective" keyswitches anduse it instead of sw_lokey/sw_hikey --- src/sfizz/Region.cpp | 67 +++---- src/sfizz/Region.h | 5 +- src/sfizz/Resources.h | 3 + src/sfizz/SfzHelpers.h | 19 ++ src/sfizz/Synth.cpp | 35 +++- tests/FilesT.cpp | 42 ----- tests/RegionActivationT.cpp | 277 +++++++++++++++++++++-------- tests/RegionT.cpp | 17 -- tests/RegionValueComputationsT.cpp | 1 - tests/TestFiles/sw_default.sfz | 5 - 10 files changed, 271 insertions(+), 200 deletions(-) delete mode 100644 tests/TestFiles/sw_default.sfz diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 71eb7ca6..601f2a48 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -292,11 +292,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) if (auto value = readOpcode(opcode.value, Default::normalizedRange)) ccConditions[opcode.parameters.back()].setEnd(*value); break; - case hash("sw_lokey"): - setRangeStartFromOpcode(opcode, keyswitchRange, Default::keyRange); - break; + case hash("sw_lokey"): // fallthrough case hash("sw_hikey"): - setRangeEndFromOpcode(opcode, keyswitchRange, Default::keyRange); break; case hash("sw_last"): setValueFromOpcode(opcode, keyswitch, Default::keyRange); @@ -1591,16 +1588,11 @@ bool sfz::Region::registerNoteOn(int noteNumber, float velocity, float randValue { ASSERT(velocity >= 0.0f && velocity <= 1.0f); - if (keyswitchRange.containsWithEnd(noteNumber)) { - if (keyswitch) - keySwitched = (*keyswitch == noteNumber); + if (keyswitchDown && *keyswitchDown == noteNumber) + keySwitched = true; - if (keyswitchDown && *keyswitchDown == noteNumber) - keySwitched = true; - - if (keyswitchUp && *keyswitchUp == noteNumber) - keySwitched = false; - } + if (keyswitchUp && *keyswitchUp == noteNumber) + keySwitched = false; const bool keyOk = keyRange.containsWithEnd(noteNumber); if (keyOk) { @@ -1634,13 +1626,11 @@ bool sfz::Region::registerNoteOff(int noteNumber, float velocity, float randValu { ASSERT(velocity >= 0.0f && velocity <= 1.0f); - if (keyswitchRange.containsWithEnd(noteNumber)) { - if (keyswitchDown && *keyswitchDown == noteNumber) - keySwitched = false; + if (keyswitchDown && *keyswitchDown == noteNumber) + keySwitched = false; - if (keyswitchUp && *keyswitchUp == noteNumber) - keySwitched = true; - } + if (keyswitchUp && *keyswitchUp == noteNumber) + keySwitched = true; if (!isSwitchedOn()) return false; @@ -1860,57 +1850,40 @@ float sfz::Region::velocityCurve(float velocity) const noexcept return gain; } -uint8_t offsetAndClamp(uint8_t key, int offset, sfz::Range range) -{ - const int offsetKey { key + offset }; - if (offsetKey > std::numeric_limits::max()) - return range.getEnd(); - if (offsetKey < std::numeric_limits::min()) - return range.getStart(); - - return range.clamp(static_cast(offsetKey)); -} - void sfz::Region::offsetAllKeys(int offset) noexcept { // Offset key range if (keyRange != Default::keyRange) { const auto start = keyRange.getStart(); const auto end = keyRange.getEnd(); - keyRange.setStart(offsetAndClamp(start, offset, Default::keyRange)); - keyRange.setEnd(offsetAndClamp(end, offset, Default::keyRange)); + keyRange.setStart(offsetAndClampKey(start, offset, Default::keyRange)); + keyRange.setEnd(offsetAndClampKey(end, offset, Default::keyRange)); } - pitchKeycenter = offsetAndClamp(pitchKeycenter, offset, Default::keyRange); + pitchKeycenter = offsetAndClampKey(pitchKeycenter, offset, Default::keyRange); // Offset key switches - if (keyswitchRange != Default::keyRange) { - const auto start = keyswitchRange.getStart(); - const auto end = keyswitchRange.getEnd(); - keyswitchRange.setStart(offsetAndClamp(start, offset, Default::keyRange)); - keyswitchRange.setEnd(offsetAndClamp(end, offset, Default::keyRange)); - } if (keyswitchUp) - keyswitchUp = offsetAndClamp(*keyswitchUp, offset, Default::keyRange); + keyswitchUp = offsetAndClampKey(*keyswitchUp, offset, Default::keyRange); if (keyswitch) - keyswitch = offsetAndClamp(*keyswitch, offset, Default::keyRange); + keyswitch = offsetAndClampKey(*keyswitch, offset, Default::keyRange); if (keyswitchDown) - keyswitchDown = offsetAndClamp(*keyswitchDown, offset, Default::keyRange); + keyswitchDown = offsetAndClampKey(*keyswitchDown, offset, Default::keyRange); if (previousNote) - previousNote = offsetAndClamp(*previousNote, offset, Default::keyRange); + previousNote = offsetAndClampKey(*previousNote, offset, Default::keyRange); // Offset crossfade ranges if (crossfadeKeyInRange != Default::crossfadeKeyInRange) { const auto start = crossfadeKeyInRange.getStart(); const auto end = crossfadeKeyInRange.getEnd(); - crossfadeKeyInRange.setStart(offsetAndClamp(start, offset, Default::keyRange)); - crossfadeKeyInRange.setEnd(offsetAndClamp(end, offset, Default::keyRange)); + crossfadeKeyInRange.setStart(offsetAndClampKey(start, offset, Default::keyRange)); + crossfadeKeyInRange.setEnd(offsetAndClampKey(end, offset, Default::keyRange)); } if (crossfadeKeyOutRange != Default::crossfadeKeyOutRange) { const auto start = crossfadeKeyOutRange.getStart(); const auto end = crossfadeKeyOutRange.getEnd(); - crossfadeKeyOutRange.setStart(offsetAndClamp(start, offset, Default::keyRange)); - crossfadeKeyOutRange.setEnd(offsetAndClamp(end, offset, Default::keyRange)); + crossfadeKeyOutRange.setStart(offsetAndClampKey(start, offset, Default::keyRange)); + crossfadeKeyOutRange.setEnd(offsetAndClampKey(end, offset, Default::keyRange)); } } diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 2f0f7f0b..a88ab965 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -292,8 +292,6 @@ struct Region { uint32_t loopStart(Oversampling factor = Oversampling::x1) const noexcept; uint32_t loopEnd(Oversampling factor = Oversampling::x1) const noexcept; - bool hasKeyswitches() const noexcept { return keyswitchDown || keyswitchUp || keyswitch || previousNote; } - /** * @brief Get the gain this region contributes into the input of the Nth * effect bus @@ -349,7 +347,6 @@ struct Region { // Region logic: MIDI conditions Range bendRange { Default::bendValueRange }; // hibend and lobend CCMap> ccConditions { Default::ccValueRange }; - Range keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey absl::optional keyswitch {}; // sw_last absl::optional keyswitchLabel {}; absl::optional keyswitchUp {}; // sw_up @@ -455,7 +452,7 @@ struct Region { // Started notes std::vector> delayedReleases; -private: + const MidiState& midiState; bool keySwitched { true }; bool previousKeySwitched { true }; diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index 577ff756..cf5d27b5 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -33,6 +33,8 @@ struct Resources absl::optional stretch; ModMatrix modMatrix; + std::vector keyswitches; + void setSampleRate(float samplerate) { midiState.setSampleRate(samplerate); @@ -54,6 +56,7 @@ struct Resources logger.clear(); midiState.reset(); modMatrix.clear(); + keyswitches.clear(); } }; } diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 416b2304..a2d2439e 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -177,6 +177,25 @@ constexpr float normalizeBend(float bendValue) return clamp(bendValue, -8191.0f, 8191.0f) / 8191.0f; } +/** + * @brief Offset a key and clamp it to a reasonable range + * + * @param key + * @param offset + * @param range + * @return uint8_t + */ +inline CXX14_CONSTEXPR uint8_t offsetAndClampKey(uint8_t key, int offset, sfz::Range range) +{ + const int offsetKey { key + offset }; + if (offsetKey > std::numeric_limits::max()) + return range.getEnd(); + if (offsetKey < std::numeric_limits::min()) + return range.getStart(); + + return range.clamp(static_cast(offsetKey)); +} + namespace literals { inline float operator""_norm(unsigned long long int value) { diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index f947331f..b52d538d 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -175,6 +175,12 @@ void sfz::Synth::buildRegion(const std::vector& 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); + } + // 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) setGroupPolyphony(lastRegion->group, lastRegion->polyphony); @@ -529,6 +535,7 @@ void sfz::Synth::finalizeSfzLoad() bool haveFilterEG { false }; FlexEGs::clearUnusedCurves(); + absl::c_sort(resources.keyswitches); while (currentRegionIndex < currentRegionCount) { auto region = regions[currentRegionIndex].get(); @@ -595,8 +602,13 @@ void sfz::Synth::finalizeSfzLoad() } } - if (region->keyswitchLabel && region->keyswitch) - insertPairUniquely(keyswitchLabels, *region->keyswitch, *region->keyswitchLabel); + if (region->keyswitch) { + if (defaultSwitch) + region->keySwitched = (*defaultSwitch == *region->keyswitch); + + if (region->keyswitchLabel) + insertPairUniquely(keyswitchLabels, *region->keyswitch, *region->keyswitchLabel); + } // Some regions had group number but no "group-level" opcodes handled the polyphony while (polyphonyGroups.size() <= region->group) { @@ -605,7 +617,15 @@ void sfz::Synth::finalizeSfzLoad() } for (auto note = 0; note < 128; note++) { - if (region->keyRange.containsWithEnd(note) || (region->hasKeyswitches() && region->keyswitchRange.containsWithEnd(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) + ) noteActivationLists[note].push_back(region); } @@ -621,10 +641,6 @@ void sfz::Synth::finalizeSfzLoad() region->registerCC(cc, resources.midiState.getCCValue(cc)); } - if (defaultSwitch) { - region->registerNoteOn(*defaultSwitch, 1.0f, 1.0f); - region->registerNoteOff(*defaultSwitch, 0.0f, 1.0f); - } // Set the default frequencies on equalizers if needed if (region->equalizers.size() > 0 @@ -1126,7 +1142,12 @@ 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); + for (auto& region : noteActivationLists[noteNumber]) { + if (noteIsKeyswitch && region->keyswitch) + region->keySwitched = (*region->keyswitch == noteNumber); + if (region->registerNoteOn(noteNumber, velocity, randValue)) { for (auto& voice : voices) { if (voice->checkOffGroup(region, delay, noteNumber)) { diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 5f524b64..8917e591 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -356,46 +356,6 @@ TEST_CASE("[Files] Channels (channels_multi.sfz)") REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::Auto); } -TEST_CASE("[Files] sw_default") -{ - Synth synth; - synth.loadSfzFile(fs::current_path() / "tests/TestFiles/sw_default.sfz"); - REQUIRE( synth.getNumRegions() == 4 ); - REQUIRE( !synth.getRegionView(0)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(1)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(3)->isSwitchedOn() ); -} - -TEST_CASE("[Files] sw_default and playing with switches") -{ - Synth synth; - synth.loadSfzFile(fs::current_path() / "tests/TestFiles/sw_default.sfz"); - REQUIRE( synth.getNumRegions() == 4 ); - REQUIRE( !synth.getRegionView(0)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(1)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(3)->isSwitchedOn() ); - synth.noteOn(0, 41, 64); - synth.noteOff(0, 41, 0); - REQUIRE( synth.getRegionView(0)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(1)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(2)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(3)->isSwitchedOn() ); - synth.noteOn(0, 42, 64); - synth.noteOff(0, 42, 0); - REQUIRE( !synth.getRegionView(0)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(1)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(3)->isSwitchedOn() ); - synth.noteOn(0, 40, 64); - synth.noteOff(0, 40, 64); - REQUIRE( !synth.getRegionView(0)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(1)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(3)->isSwitchedOn() ); -} - TEST_CASE("[Files] wrong (overlapping) replacement for defines") { Synth synth; @@ -491,7 +451,6 @@ TEST_CASE("[Files] Note and octave offsets") REQUIRE(synth.getRegionView(0)->keyRange == Range(64, 64)); REQUIRE( synth.getRegionView(0)->pitchKeycenter == 64 ); - REQUIRE(synth.getRegionView(0)->keyswitchRange == Default::keyRange); REQUIRE(synth.getRegionView(0)->crossfadeKeyInRange == Default::crossfadeKeyInRange); REQUIRE(synth.getRegionView(0)->crossfadeKeyOutRange == Default::crossfadeKeyOutRange); @@ -504,7 +463,6 @@ TEST_CASE("[Files] Note and octave offsets") REQUIRE(synth.getRegionView(2)->crossfadeKeyOutRange == Range(45, 49)); REQUIRE(synth.getRegionView(3)->keyRange == Range(62, 62)); - REQUIRE(synth.getRegionView(3)->keyswitchRange == Range(23, 27)); REQUIRE( synth.getRegionView(3)->keyswitch ); REQUIRE( *synth.getRegionView(3)->keyswitch == 24 ); REQUIRE( synth.getRegionView(3)->keyswitchUp ); diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp index e7957ffb..af5ee739 100644 --- a/tests/RegionActivationT.cpp +++ b/tests/RegionActivationT.cpp @@ -5,6 +5,7 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "sfizz/Region.h" +#include "sfizz/Synth.h" #include "sfizz/SfzHelpers.h" #include "catch2/catch.hpp" using namespace Catch::literals; @@ -113,83 +114,6 @@ TEST_CASE("Region activation", "Region tests") REQUIRE(!region.isSwitchedOn()); } - // TODO: add keyswitches - SECTION("Keyswitches: sw_last") - { - region.parseOpcode({ "sw_last", "40" }); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOn(40, 64_norm, 0.5f); - REQUIRE(region.isSwitchedOn()); - region.registerNoteOff(40, 64_norm, 0.5f); - REQUIRE(region.isSwitchedOn()); - region.registerNoteOn(41, 64_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOff(41, 0_norm, 0.5f); - } - - SECTION("Keyswitches: sw_last with non-default keyswitch range") - { - region.parseOpcode({ "sw_lokey", "30" }); - region.parseOpcode({ "sw_hikey", "50" }); - region.parseOpcode({ "sw_last", "40" }); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOn(60, 64_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOff(60, 0_norm, 0.5f); - 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(60, 64_norm, 0.5f); - REQUIRE(region.isSwitchedOn()); - region.registerNoteOff(60, 0_norm, 0.5f); - region.registerNoteOn(41, 64_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOff(41, 0_norm, 0.5f); - } - - SECTION("Keyswitches: sw_down with non-default keyswitch range") - { - region.parseOpcode({ "sw_lokey", "30" }); - region.parseOpcode({ "sw_hikey", "50" }); - region.parseOpcode({ "sw_down", "40" }); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOn(60, 64_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOff(60, 0_norm, 0.5f); - 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(60, 64_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOff(60, 0_norm, 0.5f); - region.registerNoteOn(41, 64_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOff(41, 0_norm, 0.5f); - } - - SECTION("Keyswitches: sw_up with non-default keyswitch range") - { - region.parseOpcode({ "sw_lokey", "30" }); - region.parseOpcode({ "sw_hikey", "50" }); - region.parseOpcode({ "sw_up", "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()); - } - SECTION("Keyswitches: sw_previous") { region.parseOpcode({ "sw_previous", "40" }); @@ -273,3 +197,202 @@ TEST_CASE("Region activation", "Region tests") REQUIRE(!region.isSwitchedOn()); } } + +TEST_CASE("[Keyswitches] Normal keyswitch range") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( + sw_lokey=40 sw_hikey=42 sw_default=40 + sw_last=40 key=60 sample=*sine + sw_last=41 key=62 sample=*saw + )"); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 62, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 41, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 62, 64); + REQUIRE(synth.getNumActiveVoices(true) == 2); +} + +TEST_CASE("[Keyswitches] No keyswitch range") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( + sw_last=40 key=60 sample=*sine + sw_last=41 key=62 sample=*saw + )"); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 0); + synth.noteOn(0, 62, 64); + REQUIRE(synth.getNumActiveVoices(true) == 0); + synth.noteOn(0, 40, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 62, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 41, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 62, 64); + REQUIRE(synth.getNumActiveVoices(true) == 2); +} + +TEST_CASE("[Keyswitches] Out of keyswitch range") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( + sw_lokey=40 sw_hikey=42 sw_default=40 + sw_last=40 key=60 sample=*sine + sw_last=43 key=62 sample=*saw + )"); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 62, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 43, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 62, 64); + REQUIRE(synth.getNumActiveVoices(true) == 2); +} + +TEST_CASE("[Keyswitches] Overlapping key and keyswitch range") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( + sw_lokey=1 sw_hikey=127 sw_default=40 + sw_last=40 key=60 sample=*sine + sw_last=41 key=62 sample=*saw + )"); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 62, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 41, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 62, 64); + REQUIRE(synth.getNumActiveVoices(true) == 2); + synth.noteOn(0, 43, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 2); + synth.noteOn(0, 62, 64); + REQUIRE(synth.getNumActiveVoices(true) == 3); +} + +TEST_CASE("[Keyswitches] sw_down, in range") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( + sw_lokey=1 sw_hikey=127 sw_default=40 + sw_down=40 key=60 sample=*sine + )"); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 0); + synth.noteOn(0, 40, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOff(0, 40, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); +} + +TEST_CASE("[Keyswitches] sw_down, out of range") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( + sw_lokey=1 sw_hikey=10 sw_default=40 + sw_down=40 key=60 sample=*sine + )"); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 0); + synth.noteOn(0, 40, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOff(0, 40, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); +} + +TEST_CASE("[Keyswitches] sw_up, in range") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( + sw_lokey=1 sw_hikey=127 sw_default=40 + sw_up=40 key=60 sample=*sine + )"); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 40, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOff(0, 40, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 2); +} + +TEST_CASE("[Keyswitches] sw_up, out of range") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( + sw_lokey=1 sw_hikey=127 sw_default=40 + sw_up=40 key=60 sample=*sine + )"); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOn(0, 40, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 1); + synth.noteOff(0, 40, 64); + synth.noteOn(0, 60, 64); + REQUIRE(synth.getNumActiveVoices(true) == 2); +} + +TEST_CASE("[Keyswitches] sw_default") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_default.sfz", R"( + sw_lokey=30 sw_hikey=50 sw_default=40 + sw_last=41 key=51 sample=*sine + sw_last=40 key=52 sample=*sine + sw_last=41 key=53 sample=*sine + sw_last=40 key=54 sample=*sine + )"); + REQUIRE( synth.getNumRegions() == 4 ); + REQUIRE( !synth.getRegionView(0)->isSwitchedOn() ); + REQUIRE( synth.getRegionView(1)->isSwitchedOn() ); + REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); + REQUIRE( synth.getRegionView(3)->isSwitchedOn() ); +} + +TEST_CASE("[Keyswitches] sw_default and playing with switches") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_default.sfz", R"( + sw_lokey=30 sw_hikey=50 sw_default=40 + sw_last=41 key=51 sample=*sine + sw_last=40 key=52 sample=*sine + sw_last=41 key=53 sample=*sine + sw_last=40 key=54 sample=*sine + )"); + REQUIRE( synth.getNumRegions() == 4 ); + REQUIRE( !synth.getRegionView(0)->isSwitchedOn() ); + REQUIRE( synth.getRegionView(1)->isSwitchedOn() ); + REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); + REQUIRE( synth.getRegionView(3)->isSwitchedOn() ); + synth.noteOn(0, 41, 64); + synth.noteOff(0, 41, 0); + REQUIRE( synth.getRegionView(0)->isSwitchedOn() ); + REQUIRE( !synth.getRegionView(1)->isSwitchedOn() ); + REQUIRE( synth.getRegionView(2)->isSwitchedOn() ); + REQUIRE( !synth.getRegionView(3)->isSwitchedOn() ); + synth.noteOn(0, 40, 64); + synth.noteOff(0, 40, 64); + REQUIRE( !synth.getRegionView(0)->isSwitchedOn() ); + REQUIRE( synth.getRegionView(1)->isSwitchedOn() ); + REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); + REQUIRE( synth.getRegionView(3)->isSwitchedOn() ); +} diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index f15bd286..2b1cc780 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -339,23 +339,6 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.ccConditions[125] == sfz::Range(0.0f, 1.0f)); } - SECTION("sw_lokey, sw_hikey") - { - REQUIRE(region.keyswitchRange == Range(0, 127)); - region.parseOpcode({ "sw_lokey", "4" }); - REQUIRE(region.keyswitchRange == Range(4, 127)); - region.parseOpcode({ "sw_lokey", "128" }); - REQUIRE(region.keyswitchRange == Range(127, 127)); - region.parseOpcode({ "sw_lokey", "0" }); - REQUIRE(region.keyswitchRange == Range(0, 127)); - region.parseOpcode({ "sw_hikey", "39" }); - REQUIRE(region.keyswitchRange == Range(0, 39)); - region.parseOpcode({ "sw_hikey", "135" }); - REQUIRE(region.keyswitchRange == Range(0, 127)); - region.parseOpcode({ "sw_hikey", "-1" }); - REQUIRE(region.keyswitchRange == Range(0, 0)); - } - SECTION("sw_label") { REQUIRE(!region.keyswitchLabel); diff --git a/tests/RegionValueComputationsT.cpp b/tests/RegionValueComputationsT.cpp index 50deb231..3b2d3e32 100644 --- a/tests/RegionValueComputationsT.cpp +++ b/tests/RegionValueComputationsT.cpp @@ -7,7 +7,6 @@ #include "sfizz/Defaults.h" #include "sfizz/Region.h" #include "sfizz/SfzHelpers.h" -#include "sfizz/MidiState.h" #include "catch2/catch.hpp" #include #include diff --git a/tests/TestFiles/sw_default.sfz b/tests/TestFiles/sw_default.sfz deleted file mode 100644 index 44d896c5..00000000 --- a/tests/TestFiles/sw_default.sfz +++ /dev/null @@ -1,5 +0,0 @@ - sw_lokey=30 sw_hikey=50 sw_default=40 - sw_last=41 key=51 sample=*silence - sw_last=40 key=52 sample=*silence - sw_last=41 key=53 sample=*silence - sw_last=40 key=54 sample=*silence \ No newline at end of file From 6bfc9074d723e87f083ef74b036c60ae8729df93 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 27 Oct 2020 11:04:25 +0100 Subject: [PATCH 2/5] Move the keyswitch logic in the synth rather than the regions --- src/sfizz/Region.cpp | 18 --------- src/sfizz/Resources.h | 3 -- src/sfizz/Synth.cpp | 74 +++++++++++++++++++++++++------------ src/sfizz/Synth.h | 8 +++- tests/RegionActivationT.cpp | 71 ++++++++++++++++++++++++----------- 5 files changed, 106 insertions(+), 68 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 601f2a48..f4be5582 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -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; diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index cf5d27b5..577ff756 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -33,8 +33,6 @@ struct Resources absl::optional stretch; ModMatrix modMatrix; - std::vector keyswitches; - void setSampleRate(float samplerate) { midiState.setSampleRate(samplerate); @@ -56,7 +54,6 @@ struct Resources logger.clear(); midiState.reset(); modMatrix.clear(); - keyswitches.clear(); } }; } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index b52d538d..143e22ba 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -175,11 +175,17 @@ void sfz::Synth::buildRegion(const std::vector& 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& 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& 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& 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 diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index d1db8a93..40b5012e 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -803,8 +803,8 @@ private: std::vector keyLabels; std::vector keyswitchLabels; - // Default active switch if multiple keyswitchable regions are present - absl::optional defaultSwitch; + // Set as sw_default if present in the file + absl::optional currentSwitch; std::vector unknownOpcodes; using RegionViewVector = std::vector; using VoiceViewVector = std::vector; @@ -899,6 +899,10 @@ private: */ bool playingAttackVoice(const Region* releaseRegion) noexcept; + std::array lastKeyswitchLists; + std::array downKeyswitchLists; + std::array upKeyswitchLists; + RegionViewVector previousKeyswitchLists; std::array noteActivationLists; std::array ccActivationLists; diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp index af5ee739..7a08ce76 100644 --- a/tests/RegionActivationT.cpp +++ b/tests/RegionActivationT.cpp @@ -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"( + 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"( + 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()); +} From 9c3de9daddf37fcfa430dae5936f2fb548cc9d0c Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 27 Oct 2020 11:19:12 +0100 Subject: [PATCH 3/5] Rename for consistency --- src/sfizz/Region.cpp | 24 ++++++++-------- src/sfizz/Region.h | 8 +++--- src/sfizz/Synth.cpp | 22 +++++++-------- tests/FilesT.cpp | 16 +++++------ tests/RegionActivationT.cpp | 8 +++--- tests/RegionT.cpp | 56 ++++++++++++++++++------------------- 6 files changed, 67 insertions(+), 67 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index f4be5582..d7826720 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -296,21 +296,21 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("sw_hikey"): break; case hash("sw_last"): - setValueFromOpcode(opcode, keyswitch, Default::keyRange); + setValueFromOpcode(opcode, lastKeyswitch, Default::keyRange); keySwitched = false; break; case hash("sw_label"): keyswitchLabel = opcode.value; break; case hash("sw_down"): - setValueFromOpcode(opcode, keyswitchDown, Default::keyRange); + setValueFromOpcode(opcode, downKeyswitch, Default::keyRange); keySwitched = false; break; case hash("sw_up"): - setValueFromOpcode(opcode, keyswitchUp, Default::keyRange); + setValueFromOpcode(opcode, upKeyswitch, Default::keyRange); break; case hash("sw_previous"): - setValueFromOpcode(opcode, previousNote, Default::keyRange); + setValueFromOpcode(opcode, previousKeyswitch, Default::keyRange); previousKeySwitched = false; break; case hash("sw_vel"): @@ -1844,14 +1844,14 @@ void sfz::Region::offsetAllKeys(int offset) noexcept pitchKeycenter = offsetAndClampKey(pitchKeycenter, offset, Default::keyRange); // Offset key switches - if (keyswitchUp) - keyswitchUp = offsetAndClampKey(*keyswitchUp, offset, Default::keyRange); - if (keyswitch) - keyswitch = offsetAndClampKey(*keyswitch, offset, Default::keyRange); - if (keyswitchDown) - keyswitchDown = offsetAndClampKey(*keyswitchDown, offset, Default::keyRange); - if (previousNote) - previousNote = offsetAndClampKey(*previousNote, offset, Default::keyRange); + if (upKeyswitch) + upKeyswitch = offsetAndClampKey(*upKeyswitch, offset, Default::keyRange); + if (lastKeyswitch) + lastKeyswitch = offsetAndClampKey(*lastKeyswitch, offset, Default::keyRange); + if (downKeyswitch) + downKeyswitch = offsetAndClampKey(*downKeyswitch, offset, Default::keyRange); + if (previousKeyswitch) + previousKeyswitch = offsetAndClampKey(*previousKeyswitch, offset, Default::keyRange); // Offset crossfade ranges if (crossfadeKeyInRange != Default::crossfadeKeyInRange) { diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index a88ab965..996eec4e 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -347,11 +347,11 @@ struct Region { // Region logic: MIDI conditions Range bendRange { Default::bendValueRange }; // hibend and lobend CCMap> ccConditions { Default::ccValueRange }; - absl::optional keyswitch {}; // sw_last + absl::optional lastKeyswitch {}; // sw_last absl::optional keyswitchLabel {}; - absl::optional keyswitchUp {}; // sw_up - absl::optional keyswitchDown {}; // sw_down - absl::optional previousNote {}; // sw_previous + absl::optional upKeyswitch {}; // sw_up + absl::optional downKeyswitch {}; // sw_down + absl::optional previousKeyswitch {}; // sw_previous SfzVelocityOverride velocityOverride { Default::velocityOverride }; // sw_vel bool checkSustain { Default::checkSustain }; // sustain_sw bool checkSostenuto { Default::checkSostenuto }; // sostenuto_sw diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 143e22ba..631a5b43 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -175,16 +175,16 @@ void sfz::Synth::buildRegion(const std::vector& regionOpcodes) if (octaveOffset != 0 || noteOffset != 0) lastRegion->offsetAllKeys(octaveOffset * 12 + noteOffset); - if (lastRegion->keyswitch) - lastKeyswitchLists[*lastRegion->keyswitch].push_back(lastRegion.get()); + if (lastRegion->lastKeyswitch) + lastKeyswitchLists[*lastRegion->lastKeyswitch].push_back(lastRegion.get()); - if (lastRegion->keyswitchUp) - upKeyswitchLists[*lastRegion->keyswitchUp].push_back(lastRegion.get()); + if (lastRegion->upKeyswitch) + upKeyswitchLists[*lastRegion->upKeyswitch].push_back(lastRegion.get()); - if (lastRegion->keyswitchDown) - downKeyswitchLists[*lastRegion->keyswitchDown].push_back(lastRegion.get()); + if (lastRegion->downKeyswitch) + downKeyswitchLists[*lastRegion->downKeyswitch].push_back(lastRegion.get()); - if (lastRegion->previousNote) + if (lastRegion->previousKeyswitch) previousKeyswitchLists.push_back(lastRegion.get()); // There was a combination of group= and polyphony= on a region, so set the group polyphony @@ -614,12 +614,12 @@ void sfz::Synth::finalizeSfzLoad() } } - if (region->keyswitch) { + if (region->lastKeyswitch) { if (currentSwitch) - region->keySwitched = (*currentSwitch == *region->keyswitch); + region->keySwitched = (*currentSwitch == *region->lastKeyswitch); if (region->keyswitchLabel) - insertPairUniquely(keyswitchLabels, *region->keyswitch, *region->keyswitchLabel); + insertPairUniquely(keyswitchLabels, *region->lastKeyswitch, *region->keyswitchLabel); } // Some regions had group number but no "group-level" opcodes handled the polyphony @@ -1184,7 +1184,7 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc } for (auto& region : previousKeyswitchLists) - region->previousKeySwitched = (*region->previousNote == noteNumber); + region->previousKeySwitched = (*region->previousKeyswitch == noteNumber); } void sfz::Synth::startDelayedReleaseVoices(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 8917e591..b0a19757 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -463,14 +463,14 @@ TEST_CASE("[Files] Note and octave offsets") REQUIRE(synth.getRegionView(2)->crossfadeKeyOutRange == Range(45, 49)); REQUIRE(synth.getRegionView(3)->keyRange == Range(62, 62)); - REQUIRE( synth.getRegionView(3)->keyswitch ); - REQUIRE( *synth.getRegionView(3)->keyswitch == 24 ); - REQUIRE( synth.getRegionView(3)->keyswitchUp ); - REQUIRE( *synth.getRegionView(3)->keyswitchUp == 24 ); - REQUIRE( synth.getRegionView(3)->keyswitchDown ); - REQUIRE( *synth.getRegionView(3)->keyswitchDown == 24 ); - REQUIRE( synth.getRegionView(3)->previousNote ); - REQUIRE( *synth.getRegionView(3)->previousNote == 61 ); + REQUIRE( synth.getRegionView(3)->lastKeyswitch ); + REQUIRE( *synth.getRegionView(3)->lastKeyswitch == 24 ); + REQUIRE( synth.getRegionView(3)->upKeyswitch ); + REQUIRE( *synth.getRegionView(3)->upKeyswitch == 24 ); + REQUIRE( synth.getRegionView(3)->downKeyswitch ); + REQUIRE( *synth.getRegionView(3)->downKeyswitch == 24 ); + REQUIRE( synth.getRegionView(3)->previousKeyswitch ); + REQUIRE( *synth.getRegionView(3)->previousKeyswitch == 61 ); REQUIRE(synth.getRegionView(4)->keyRange == Range(76, 76)); REQUIRE( synth.getRegionView(4)->pitchKeycenter == 76 ); diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp index 7a08ce76..0763b56f 100644 --- a/tests/RegionActivationT.cpp +++ b/tests/RegionActivationT.cpp @@ -177,7 +177,7 @@ TEST_CASE("Region activation", "Region tests") } } -TEST_CASE("[Keyswitches] Normal keyswitch range") +TEST_CASE("[Keyswitches] Normal lastKeyswitch range") { sfz::Synth synth; synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( @@ -196,7 +196,7 @@ TEST_CASE("[Keyswitches] Normal keyswitch range") REQUIRE(synth.getNumActiveVoices(true) == 2); } -TEST_CASE("[Keyswitches] No keyswitch range") +TEST_CASE("[Keyswitches] No lastKeyswitch range") { sfz::Synth synth; synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( @@ -219,7 +219,7 @@ TEST_CASE("[Keyswitches] No keyswitch range") REQUIRE(synth.getNumActiveVoices(true) == 2); } -TEST_CASE("[Keyswitches] Out of keyswitch range") +TEST_CASE("[Keyswitches] Out of lastKeyswitch range") { sfz::Synth synth; synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( @@ -238,7 +238,7 @@ TEST_CASE("[Keyswitches] Out of keyswitch range") REQUIRE(synth.getNumActiveVoices(true) == 2); } -TEST_CASE("[Keyswitches] Overlapping key and keyswitch range") +TEST_CASE("[Keyswitches] Overlapping key and lastKeyswitch range") { sfz::Synth synth; synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index 2b1cc780..7e20583c 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -350,58 +350,58 @@ TEST_CASE("[Region] Parsing opcodes") SECTION("sw_last") { - REQUIRE(!region.keyswitch); + REQUIRE(!region.lastKeyswitch); region.parseOpcode({ "sw_last", "4" }); - REQUIRE(region.keyswitch); - REQUIRE(*region.keyswitch == 4); + REQUIRE(region.lastKeyswitch); + REQUIRE(*region.lastKeyswitch == 4); region.parseOpcode({ "sw_last", "128" }); - REQUIRE(region.keyswitch); - REQUIRE(*region.keyswitch == 127); + REQUIRE(region.lastKeyswitch); + REQUIRE(*region.lastKeyswitch == 127); region.parseOpcode({ "sw_last", "-1" }); - REQUIRE(region.keyswitch); - REQUIRE(*region.keyswitch == 0); + REQUIRE(region.lastKeyswitch); + REQUIRE(*region.lastKeyswitch == 0); } SECTION("sw_up") { - REQUIRE(!region.keyswitchUp); + REQUIRE(!region.upKeyswitch); region.parseOpcode({ "sw_up", "4" }); - REQUIRE(region.keyswitchUp); - REQUIRE(*region.keyswitchUp == 4); + REQUIRE(region.upKeyswitch); + REQUIRE(*region.upKeyswitch == 4); region.parseOpcode({ "sw_up", "128" }); - REQUIRE(region.keyswitchUp); - REQUIRE(*region.keyswitchUp == 127); + REQUIRE(region.upKeyswitch); + REQUIRE(*region.upKeyswitch == 127); region.parseOpcode({ "sw_up", "-1" }); - REQUIRE(region.keyswitchUp); - REQUIRE(*region.keyswitchUp == 0); + REQUIRE(region.upKeyswitch); + REQUIRE(*region.upKeyswitch == 0); } SECTION("sw_down") { - REQUIRE(!region.keyswitchDown); + REQUIRE(!region.downKeyswitch); region.parseOpcode({ "sw_down", "4" }); - REQUIRE(region.keyswitchDown); - REQUIRE(*region.keyswitchDown == 4); + REQUIRE(region.downKeyswitch); + REQUIRE(*region.downKeyswitch == 4); region.parseOpcode({ "sw_down", "128" }); - REQUIRE(region.keyswitchDown); - REQUIRE(*region.keyswitchDown == 127); + REQUIRE(region.downKeyswitch); + REQUIRE(*region.downKeyswitch == 127); region.parseOpcode({ "sw_down", "-1" }); - REQUIRE(region.keyswitchDown); - REQUIRE(*region.keyswitchDown == 0); + REQUIRE(region.downKeyswitch); + REQUIRE(*region.downKeyswitch == 0); } SECTION("sw_previous") { - REQUIRE(!region.previousNote); + REQUIRE(!region.previousKeyswitch); region.parseOpcode({ "sw_previous", "4" }); - REQUIRE(region.previousNote); - REQUIRE(*region.previousNote == 4); + REQUIRE(region.previousKeyswitch); + REQUIRE(*region.previousKeyswitch == 4); region.parseOpcode({ "sw_previous", "128" }); - REQUIRE(region.previousNote); - REQUIRE(*region.previousNote == 127); + REQUIRE(region.previousKeyswitch); + REQUIRE(*region.previousKeyswitch == 127); region.parseOpcode({ "sw_previous", "-1" }); - REQUIRE(region.previousNote); - REQUIRE(*region.previousNote == 0); + REQUIRE(region.previousKeyswitch); + REQUIRE(*region.previousKeyswitch == 0); } SECTION("sw_vel") From 5ea4b876dfd207f2e2e9c57a0b4706610e46bfde Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 27 Oct 2020 14:50:31 +0100 Subject: [PATCH 4/5] Add sw_lolast/sw_hilast --- src/sfizz/Region.cpp | 28 ++++++++++++++-- src/sfizz/Region.h | 1 + src/sfizz/Synth.cpp | 6 ++++ tests/RegionActivationT.cpp | 64 ++++++++++++++++++++++++++++++++++++- tests/RegionT.cpp | 42 ++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 3 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index d7826720..42be5f79 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -296,8 +296,32 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("sw_hikey"): break; case hash("sw_last"): - setValueFromOpcode(opcode, lastKeyswitch, Default::keyRange); - keySwitched = false; + if (!lastKeyswitchRange) { + setValueFromOpcode(opcode, lastKeyswitch, Default::keyRange); + keySwitched = false; + } + break; + case hash("sw_lolast"): + if (auto value = readOpcode(opcode.value, Default::keyRange)) { + if (!lastKeyswitchRange) + lastKeyswitchRange.emplace(*value, *value); + else + lastKeyswitchRange->setStart(*value); + + keySwitched = false; + lastKeyswitch = absl::nullopt; + } + break; + case hash("sw_hilast"): + if (auto value = readOpcode(opcode.value, Default::keyRange)) { + if (!lastKeyswitchRange) + lastKeyswitchRange.emplace(*value, *value); + else + lastKeyswitchRange->setEnd(*value); + + keySwitched = false; + lastKeyswitch = absl::nullopt; + } break; case hash("sw_label"): keyswitchLabel = opcode.value; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 996eec4e..7fb0b695 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -348,6 +348,7 @@ struct Region { Range bendRange { Default::bendValueRange }; // hibend and lobend CCMap> ccConditions { Default::ccValueRange }; absl::optional lastKeyswitch {}; // sw_last + absl::optional> lastKeyswitchRange {}; // sw_last absl::optional keyswitchLabel {}; absl::optional upKeyswitch {}; // sw_up absl::optional downKeyswitch {}; // sw_down diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 631a5b43..ba5c109d 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -178,6 +178,12 @@ void sfz::Synth::buildRegion(const std::vector& regionOpcodes) if (lastRegion->lastKeyswitch) lastKeyswitchLists[*lastRegion->lastKeyswitch].push_back(lastRegion.get()); + if (lastRegion->lastKeyswitchRange) { + auto& range = *lastRegion->lastKeyswitchRange; + for (uint8_t note = range.getStart(), end = range.getEnd(); note <= end; note++) + lastKeyswitchLists[note].push_back(lastRegion.get()); + } + if (lastRegion->upKeyswitch) upKeyswitchLists[*lastRegion->upKeyswitch].push_back(lastRegion.get()); diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp index 0763b56f..6c5a3ed9 100644 --- a/tests/RegionActivationT.cpp +++ b/tests/RegionActivationT.cpp @@ -402,7 +402,6 @@ TEST_CASE("[Keyswitches] sw_previous in range") 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 @@ -425,3 +424,66 @@ TEST_CASE("[Keyswitches] sw_previous out of range") synth.noteOn(0, 61, 64); REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); } + +TEST_CASE("[Keyswitches] sw_lolast and sw_hilast") +{ + // 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"( + sw_lolast=57 sw_hilast=59 key=70 sample=*saw + sw_lolast=60 sw_hilast=62 key=72 sample=*sine + )"); + REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 51, 64); + REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 57, 64); + REQUIRE(synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 60, 64); + REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 58, 64); + REQUIRE(synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 61, 64); + REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 59, 64); + REQUIRE(synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 62, 64); + REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(synth.getRegionView(1)->isSwitchedOn()); +} + +TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_last") +{ + // 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"( + sw_last=40 sw_lolast=57 sw_hilast=59 key=70 sample=*saw + sw_lolast=60 sw_hilast=62 sw_last=41 key=72 sample=*sine + )"); + REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 40, 64); + REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 41, 64); + REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 57, 64); + REQUIRE(synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 41, 64); + REQUIRE(synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 60, 64); + REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(synth.getRegionView(1)->isSwitchedOn()); + synth.noteOn(0, 40, 64); + REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(synth.getRegionView(1)->isSwitchedOn()); +} diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index 7e20583c..312d17a0 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -362,6 +362,48 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(*region.lastKeyswitch == 0); } + SECTION("sw_lolast/hilast") + { + REQUIRE(!region.lastKeyswitchRange); + region.parseOpcode({ "sw_lolast", "4" }); + REQUIRE(region.lastKeyswitchRange); + REQUIRE(*region.lastKeyswitchRange == Range(4, 4)); + region.parseOpcode({ "sw_hilast", "128" }); + REQUIRE(*region.lastKeyswitchRange == Range(4, 127)); + region.parseOpcode({ "sw_hilast", "63" }); + REQUIRE(*region.lastKeyswitchRange == Range(4, 63)); + region.parseOpcode({ "sw_lolast", "64" }); + REQUIRE(*region.lastKeyswitchRange == Range(64, 64)); + region.parseOpcode({ "sw_lolast", "-1" }); + REQUIRE(*region.lastKeyswitchRange == Range(0, 64)); + } + + SECTION("sw_hilast disables sw_last") + { + REQUIRE(!region.lastKeyswitchRange); + REQUIRE(!region.lastKeyswitch); + region.parseOpcode({ "sw_last", "4" }); + REQUIRE(region.lastKeyswitch); + region.parseOpcode({ "sw_hilast", "63" }); + REQUIRE(region.lastKeyswitchRange); + REQUIRE(!region.lastKeyswitch); + region.parseOpcode({ "sw_last", "4" }); + REQUIRE(!region.lastKeyswitch); + } + + SECTION("sw_lolast disables sw_last") + { + REQUIRE(!region.lastKeyswitchRange); + REQUIRE(!region.lastKeyswitch); + region.parseOpcode({ "sw_last", "4" }); + REQUIRE(region.lastKeyswitch); + region.parseOpcode({ "sw_lolast", "63" }); + REQUIRE(region.lastKeyswitchRange); + REQUIRE(!region.lastKeyswitch); + region.parseOpcode({ "sw_last", "4" }); + REQUIRE(!region.lastKeyswitch); + } + SECTION("sw_up") { REQUIRE(!region.upKeyswitch); From 74a676b4930bc60baae7e55c08f3865037a572ab Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 27 Oct 2020 15:11:51 +0100 Subject: [PATCH 5/5] sw_default with sw_lolast and sw_hilast --- src/sfizz/Synth.cpp | 12 +++++++++++- tests/RegionActivationT.cpp | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index ba5c109d..b15e8adb 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -628,6 +628,17 @@ void sfz::Synth::finalizeSfzLoad() insertPairUniquely(keyswitchLabels, *region->lastKeyswitch, *region->keyswitchLabel); } + if (region->lastKeyswitchRange) { + auto& range = *region->lastKeyswitchRange; + if (currentSwitch) + region->keySwitched = range.containsWithEnd(*currentSwitch); + + if (region->keyswitchLabel) { + for (uint8_t note = range.getStart(), end = range.getEnd(); note <= end; note++) + insertPairUniquely(keyswitchLabels, note, *region->keyswitchLabel); + } + } + // Some regions had group number but no "group-level" opcodes handled the polyphony while (polyphonyGroups.size() <= region->group) { polyphonyGroups.emplace_back(); @@ -1176,7 +1187,6 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc region->keySwitched = true; for (auto& region : noteActivationLists[noteNumber]) { - if (region->registerNoteOn(noteNumber, velocity, randValue)) { for (auto& voice : voices) { if (voice->checkOffGroup(region, delay, noteNumber)) { diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp index 6c5a3ed9..be5c808c 100644 --- a/tests/RegionActivationT.cpp +++ b/tests/RegionActivationT.cpp @@ -427,7 +427,6 @@ TEST_CASE("[Keyswitches] sw_previous out of range") TEST_CASE("[Keyswitches] sw_lolast and sw_hilast") { - // 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"( sw_lolast=57 sw_hilast=59 key=70 sample=*saw @@ -460,7 +459,6 @@ TEST_CASE("[Keyswitches] sw_lolast and sw_hilast") TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_last") { - // 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"( sw_last=40 sw_lolast=57 sw_hilast=59 key=70 sample=*saw @@ -487,3 +485,15 @@ TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_last") REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); REQUIRE(synth.getRegionView(1)->isSwitchedOn()); } + +TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_default") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( + sw_default=58 + sw_lolast=57 sw_hilast=59 key=70 sample=*saw + sw_lolast=60 sw_hilast=62 key=72 sample=*sine + )"); + REQUIRE(synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); +}