Merge pull request #526 from paulfd/keyswitches

Build a set of "effective" keyswitches and use that instead of sw_lokey/sw_hikey
This commit is contained in:
Paul Ferrand 2020-10-28 09:23:17 +01:00 committed by GitHub
commit 06157c7f64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 550 additions and 286 deletions

View file

@ -292,28 +292,49 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
if (auto value = readOpcode(opcode.value, Default::normalizedRange)) if (auto value = readOpcode(opcode.value, Default::normalizedRange))
ccConditions[opcode.parameters.back()].setEnd(*value); ccConditions[opcode.parameters.back()].setEnd(*value);
break; break;
case hash("sw_lokey"): case hash("sw_lokey"): // fallthrough
setRangeStartFromOpcode(opcode, keyswitchRange, Default::keyRange);
break;
case hash("sw_hikey"): case hash("sw_hikey"):
setRangeEndFromOpcode(opcode, keyswitchRange, Default::keyRange);
break; break;
case hash("sw_last"): case hash("sw_last"):
setValueFromOpcode(opcode, keyswitch, Default::keyRange); if (!lastKeyswitchRange) {
keySwitched = false; 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; break;
case hash("sw_label"): case hash("sw_label"):
keyswitchLabel = opcode.value; keyswitchLabel = opcode.value;
break; break;
case hash("sw_down"): case hash("sw_down"):
setValueFromOpcode(opcode, keyswitchDown, Default::keyRange); setValueFromOpcode(opcode, downKeyswitch, Default::keyRange);
keySwitched = false; keySwitched = false;
break; break;
case hash("sw_up"): case hash("sw_up"):
setValueFromOpcode(opcode, keyswitchUp, Default::keyRange); setValueFromOpcode(opcode, upKeyswitch, Default::keyRange);
break; break;
case hash("sw_previous"): case hash("sw_previous"):
setValueFromOpcode(opcode, previousNote, Default::keyRange); setValueFromOpcode(opcode, previousKeyswitch, Default::keyRange);
previousKeySwitched = false; previousKeySwitched = false;
break; break;
case hash("sw_vel"): case hash("sw_vel"):
@ -1591,25 +1612,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 (keyswitchRange.containsWithEnd(noteNumber)) {
if (keyswitch)
keySwitched = (*keyswitch == noteNumber);
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())
@ -1618,9 +1625,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);
@ -1634,14 +1638,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 (keyswitchRange.containsWithEnd(noteNumber)) {
if (keyswitchDown && *keyswitchDown == noteNumber)
keySwitched = false;
if (keyswitchUp && *keyswitchUp == noteNumber)
keySwitched = true;
}
if (!isSwitchedOn()) if (!isSwitchedOn())
return false; return false;
@ -1860,57 +1856,40 @@ float sfz::Region::velocityCurve(float velocity) const noexcept
return gain; return gain;
} }
uint8_t offsetAndClamp(uint8_t key, int offset, sfz::Range<uint8_t> range)
{
const int offsetKey { key + offset };
if (offsetKey > std::numeric_limits<uint8_t>::max())
return range.getEnd();
if (offsetKey < std::numeric_limits<uint8_t>::min())
return range.getStart();
return range.clamp(static_cast<uint8_t>(offsetKey));
}
void sfz::Region::offsetAllKeys(int offset) noexcept void sfz::Region::offsetAllKeys(int offset) noexcept
{ {
// Offset key range // Offset key range
if (keyRange != Default::keyRange) { if (keyRange != Default::keyRange) {
const auto start = keyRange.getStart(); const auto start = keyRange.getStart();
const auto end = keyRange.getEnd(); const auto end = keyRange.getEnd();
keyRange.setStart(offsetAndClamp(start, offset, Default::keyRange)); keyRange.setStart(offsetAndClampKey(start, offset, Default::keyRange));
keyRange.setEnd(offsetAndClamp(end, 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 // Offset key switches
if (keyswitchRange != Default::keyRange) { if (upKeyswitch)
const auto start = keyswitchRange.getStart(); upKeyswitch = offsetAndClampKey(*upKeyswitch, offset, Default::keyRange);
const auto end = keyswitchRange.getEnd(); if (lastKeyswitch)
keyswitchRange.setStart(offsetAndClamp(start, offset, Default::keyRange)); lastKeyswitch = offsetAndClampKey(*lastKeyswitch, offset, Default::keyRange);
keyswitchRange.setEnd(offsetAndClamp(end, offset, Default::keyRange)); if (downKeyswitch)
} downKeyswitch = offsetAndClampKey(*downKeyswitch, offset, Default::keyRange);
if (keyswitchUp) if (previousKeyswitch)
keyswitchUp = offsetAndClamp(*keyswitchUp, offset, Default::keyRange); previousKeyswitch = offsetAndClampKey(*previousKeyswitch, offset, Default::keyRange);
if (keyswitch)
keyswitch = offsetAndClamp(*keyswitch, offset, Default::keyRange);
if (keyswitchDown)
keyswitchDown = offsetAndClamp(*keyswitchDown, offset, Default::keyRange);
if (previousNote)
previousNote = offsetAndClamp(*previousNote, offset, Default::keyRange);
// Offset crossfade ranges // Offset crossfade ranges
if (crossfadeKeyInRange != Default::crossfadeKeyInRange) { if (crossfadeKeyInRange != Default::crossfadeKeyInRange) {
const auto start = crossfadeKeyInRange.getStart(); const auto start = crossfadeKeyInRange.getStart();
const auto end = crossfadeKeyInRange.getEnd(); const auto end = crossfadeKeyInRange.getEnd();
crossfadeKeyInRange.setStart(offsetAndClamp(start, offset, Default::keyRange)); crossfadeKeyInRange.setStart(offsetAndClampKey(start, offset, Default::keyRange));
crossfadeKeyInRange.setEnd(offsetAndClamp(end, offset, Default::keyRange)); crossfadeKeyInRange.setEnd(offsetAndClampKey(end, offset, Default::keyRange));
} }
if (crossfadeKeyOutRange != Default::crossfadeKeyOutRange) { if (crossfadeKeyOutRange != Default::crossfadeKeyOutRange) {
const auto start = crossfadeKeyOutRange.getStart(); const auto start = crossfadeKeyOutRange.getStart();
const auto end = crossfadeKeyOutRange.getEnd(); const auto end = crossfadeKeyOutRange.getEnd();
crossfadeKeyOutRange.setStart(offsetAndClamp(start, offset, Default::keyRange)); crossfadeKeyOutRange.setStart(offsetAndClampKey(start, offset, Default::keyRange));
crossfadeKeyOutRange.setEnd(offsetAndClamp(end, offset, Default::keyRange)); crossfadeKeyOutRange.setEnd(offsetAndClampKey(end, offset, Default::keyRange));
} }
} }

View file

@ -292,8 +292,6 @@ struct Region {
uint32_t loopStart(Oversampling factor = Oversampling::x1) const noexcept; uint32_t loopStart(Oversampling factor = Oversampling::x1) const noexcept;
uint32_t loopEnd(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 * @brief Get the gain this region contributes into the input of the Nth
* effect bus * effect bus
@ -349,12 +347,12 @@ struct Region {
// Region logic: MIDI conditions // Region logic: MIDI conditions
Range<float> bendRange { Default::bendValueRange }; // hibend and lobend Range<float> bendRange { Default::bendValueRange }; // hibend and lobend
CCMap<Range<float>> ccConditions { Default::ccValueRange }; CCMap<Range<float>> ccConditions { Default::ccValueRange };
Range<uint8_t> keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey absl::optional<uint8_t> lastKeyswitch {}; // sw_last
absl::optional<uint8_t> keyswitch {}; // sw_last absl::optional<Range<uint8_t>> lastKeyswitchRange {}; // sw_last
absl::optional<std::string> keyswitchLabel {}; absl::optional<std::string> keyswitchLabel {};
absl::optional<uint8_t> keyswitchUp {}; // sw_up absl::optional<uint8_t> upKeyswitch {}; // sw_up
absl::optional<uint8_t> keyswitchDown {}; // sw_down absl::optional<uint8_t> downKeyswitch {}; // sw_down
absl::optional<uint8_t> previousNote {}; // sw_previous absl::optional<uint8_t> previousKeyswitch {}; // sw_previous
SfzVelocityOverride velocityOverride { Default::velocityOverride }; // sw_vel SfzVelocityOverride velocityOverride { Default::velocityOverride }; // sw_vel
bool checkSustain { Default::checkSustain }; // sustain_sw bool checkSustain { Default::checkSustain }; // sustain_sw
bool checkSostenuto { Default::checkSostenuto }; // sostenuto_sw bool checkSostenuto { Default::checkSostenuto }; // sostenuto_sw
@ -455,7 +453,7 @@ struct Region {
// Started notes // Started notes
std::vector<std::pair<int, float>> delayedReleases; std::vector<std::pair<int, float>> delayedReleases;
private:
const MidiState& midiState; const MidiState& midiState;
bool keySwitched { true }; bool keySwitched { true };
bool previousKeySwitched { true }; bool previousKeySwitched { true };

View file

@ -177,6 +177,25 @@ constexpr float normalizeBend(float bendValue)
return clamp(bendValue, -8191.0f, 8191.0f) / 8191.0f; 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<uint8_t> range)
{
const int offsetKey { key + offset };
if (offsetKey > std::numeric_limits<uint8_t>::max())
return range.getEnd();
if (offsetKey < std::numeric_limits<uint8_t>::min())
return range.getStart();
return range.clamp(static_cast<uint8_t>(offsetKey));
}
namespace literals { namespace literals {
inline float operator""_norm(unsigned long long int value) inline float operator""_norm(unsigned long long int value)
{ {

View file

@ -175,6 +175,24 @@ 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->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());
if (lastRegion->downKeyswitch)
downKeyswitchLists[*lastRegion->downKeyswitch].push_back(lastRegion.get());
if (lastRegion->previousKeyswitch)
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)
setGroupPolyphony(lastRegion->group, lastRegion->polyphony); setGroupPolyphony(lastRegion->group, lastRegion->polyphony);
@ -197,10 +215,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();
@ -214,7 +239,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();
@ -256,7 +281,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;
} }
} }
@ -274,7 +299,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
@ -300,7 +325,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;
} }
}; };
@ -595,8 +620,24 @@ void sfz::Synth::finalizeSfzLoad()
} }
} }
if (region->keyswitchLabel && region->keyswitch) if (region->lastKeyswitch) {
insertPairUniquely(keyswitchLabels, *region->keyswitch, *region->keyswitchLabel); if (currentSwitch)
region->keySwitched = (*currentSwitch == *region->lastKeyswitch);
if (region->keyswitchLabel)
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 // Some regions had group number but no "group-level" opcodes handled the polyphony
while (polyphonyGroups.size() <= region->group) { while (polyphonyGroups.size() <= region->group) {
@ -605,7 +646,7 @@ void sfz::Synth::finalizeSfzLoad()
} }
for (auto note = 0; note < 128; note++) { for (auto note = 0; note < 128; note++) {
if (region->keyRange.containsWithEnd(note) || (region->hasKeyswitches() && region->keyswitchRange.containsWithEnd(note))) if (region->keyRange.containsWithEnd(note))
noteActivationLists[note].push_back(region); noteActivationLists[note].push_back(region);
} }
@ -621,10 +662,6 @@ void sfz::Synth::finalizeSfzLoad()
region->registerCC(cc, resources.midiState.getCCValue(cc)); 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 // Set the default frequencies on equalizers if needed
if (region->equalizers.size() > 0 if (region->equalizers.size() > 0
@ -1016,6 +1053,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))
@ -1126,6 +1169,23 @@ 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 };
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 (region->registerNoteOn(noteNumber, velocity, randValue)) { if (region->registerNoteOn(noteNumber, velocity, randValue)) {
for (auto& voice : voices) { for (auto& voice : voices) {
@ -1138,6 +1198,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->previousKeyswitch == noteNumber);
} }
void sfz::Synth::startDelayedReleaseVoices(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept 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> 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;

View file

@ -356,46 +356,6 @@ TEST_CASE("[Files] Channels (channels_multi.sfz)")
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::Auto); 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") TEST_CASE("[Files] wrong (overlapping) replacement for defines")
{ {
Synth synth; Synth synth;
@ -491,7 +451,6 @@ TEST_CASE("[Files] Note and octave offsets")
REQUIRE(synth.getRegionView(0)->keyRange == Range<uint8_t>(64, 64)); REQUIRE(synth.getRegionView(0)->keyRange == Range<uint8_t>(64, 64));
REQUIRE( synth.getRegionView(0)->pitchKeycenter == 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)->crossfadeKeyInRange == Default::crossfadeKeyInRange);
REQUIRE(synth.getRegionView(0)->crossfadeKeyOutRange == Default::crossfadeKeyOutRange); REQUIRE(synth.getRegionView(0)->crossfadeKeyOutRange == Default::crossfadeKeyOutRange);
@ -504,15 +463,14 @@ TEST_CASE("[Files] Note and octave offsets")
REQUIRE(synth.getRegionView(2)->crossfadeKeyOutRange == Range<uint8_t>(45, 49)); REQUIRE(synth.getRegionView(2)->crossfadeKeyOutRange == Range<uint8_t>(45, 49));
REQUIRE(synth.getRegionView(3)->keyRange == Range<uint8_t>(62, 62)); REQUIRE(synth.getRegionView(3)->keyRange == Range<uint8_t>(62, 62));
REQUIRE(synth.getRegionView(3)->keyswitchRange == Range<uint8_t>(23, 27)); REQUIRE( synth.getRegionView(3)->lastKeyswitch );
REQUIRE( synth.getRegionView(3)->keyswitch ); REQUIRE( *synth.getRegionView(3)->lastKeyswitch == 24 );
REQUIRE( *synth.getRegionView(3)->keyswitch == 24 ); REQUIRE( synth.getRegionView(3)->upKeyswitch );
REQUIRE( synth.getRegionView(3)->keyswitchUp ); REQUIRE( *synth.getRegionView(3)->upKeyswitch == 24 );
REQUIRE( *synth.getRegionView(3)->keyswitchUp == 24 ); REQUIRE( synth.getRegionView(3)->downKeyswitch );
REQUIRE( synth.getRegionView(3)->keyswitchDown ); REQUIRE( *synth.getRegionView(3)->downKeyswitch == 24 );
REQUIRE( *synth.getRegionView(3)->keyswitchDown == 24 ); REQUIRE( synth.getRegionView(3)->previousKeyswitch );
REQUIRE( synth.getRegionView(3)->previousNote ); REQUIRE( *synth.getRegionView(3)->previousKeyswitch == 61 );
REQUIRE( *synth.getRegionView(3)->previousNote == 61 );
REQUIRE(synth.getRegionView(4)->keyRange == Range<uint8_t>(76, 76)); REQUIRE(synth.getRegionView(4)->keyRange == Range<uint8_t>(76, 76));
REQUIRE( synth.getRegionView(4)->pitchKeycenter == 76 ); REQUIRE( synth.getRegionView(4)->pitchKeycenter == 76 );

View file

@ -5,6 +5,7 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "sfizz/Region.h" #include "sfizz/Region.h"
#include "sfizz/Synth.h"
#include "sfizz/SfzHelpers.h" #include "sfizz/SfzHelpers.h"
#include "catch2/catch.hpp" #include "catch2/catch.hpp"
using namespace Catch::literals; using namespace Catch::literals;
@ -113,104 +114,6 @@ TEST_CASE("Region activation", "Region tests")
REQUIRE(!region.isSwitchedOn()); 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" });
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" });
@ -273,3 +176,324 @@ TEST_CASE("Region activation", "Region tests")
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
} }
} }
TEST_CASE("[Keyswitches] Normal lastKeyswitch range")
{
sfz::Synth synth;
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
<global> sw_lokey=40 sw_hikey=42 sw_default=40
<region> sw_last=40 key=60 sample=*sine
<region> 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 lastKeyswitch range")
{
sfz::Synth synth;
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
<region> sw_last=40 key=60 sample=*sine
<region> 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 lastKeyswitch range")
{
sfz::Synth synth;
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
<global> sw_lokey=40 sw_hikey=42 sw_default=40
<region> sw_last=40 key=60 sample=*sine
<region> 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 lastKeyswitch range")
{
sfz::Synth synth;
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
<global> sw_lokey=1 sw_hikey=127 sw_default=40
<region> sw_last=40 key=60 sample=*sine
<region> 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"(
<global> sw_lokey=1 sw_hikey=127 sw_default=40
<region> 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"(
<global> sw_lokey=1 sw_hikey=10 sw_default=40
<region> 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"(
<global> sw_lokey=1 sw_hikey=127 sw_default=40
<region> 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"(
<global> sw_lokey=1 sw_hikey=127 sw_default=40
<region> 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"(
<global> sw_lokey=30 sw_hikey=50 sw_default=40
<region> sw_last=41 key=51 sample=*sine
<region> sw_last=40 key=52 sample=*sine
<region> sw_last=41 key=53 sample=*sine
<region> 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"(
<global> sw_lokey=30 sw_hikey=50 sw_default=40
<region> sw_last=41 key=51 sample=*sine
<region> sw_last=40 key=52 sample=*sine
<region> sw_last=41 key=53 sample=*sine
<region> 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() );
}
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());
}
TEST_CASE("[Keyswitches] sw_lolast and sw_hilast")
{
sfz::Synth synth;
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
<region> sw_lolast=57 sw_hilast=59 key=70 sample=*saw
<region> 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")
{
sfz::Synth synth;
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
<region> sw_last=40 sw_lolast=57 sw_hilast=59 key=70 sample=*saw
<region> 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());
}
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"(
<global> sw_default=58
<region> sw_lolast=57 sw_hilast=59 key=70 sample=*saw
<region> sw_lolast=60 sw_hilast=62 key=72 sample=*sine
)");
REQUIRE(synth.getRegionView(0)->isSwitchedOn());
REQUIRE(!synth.getRegionView(1)->isSwitchedOn());
}

View file

@ -339,23 +339,6 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.ccConditions[125] == sfz::Range<float>(0.0f, 1.0f)); REQUIRE(region.ccConditions[125] == sfz::Range<float>(0.0f, 1.0f));
} }
SECTION("sw_lokey, sw_hikey")
{
REQUIRE(region.keyswitchRange == Range<uint8_t>(0, 127));
region.parseOpcode({ "sw_lokey", "4" });
REQUIRE(region.keyswitchRange == Range<uint8_t>(4, 127));
region.parseOpcode({ "sw_lokey", "128" });
REQUIRE(region.keyswitchRange == Range<uint8_t>(127, 127));
region.parseOpcode({ "sw_lokey", "0" });
REQUIRE(region.keyswitchRange == Range<uint8_t>(0, 127));
region.parseOpcode({ "sw_hikey", "39" });
REQUIRE(region.keyswitchRange == Range<uint8_t>(0, 39));
region.parseOpcode({ "sw_hikey", "135" });
REQUIRE(region.keyswitchRange == Range<uint8_t>(0, 127));
region.parseOpcode({ "sw_hikey", "-1" });
REQUIRE(region.keyswitchRange == Range<uint8_t>(0, 0));
}
SECTION("sw_label") SECTION("sw_label")
{ {
REQUIRE(!region.keyswitchLabel); REQUIRE(!region.keyswitchLabel);
@ -367,58 +350,100 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("sw_last") SECTION("sw_last")
{ {
REQUIRE(!region.keyswitch); REQUIRE(!region.lastKeyswitch);
region.parseOpcode({ "sw_last", "4" }); region.parseOpcode({ "sw_last", "4" });
REQUIRE(region.keyswitch); REQUIRE(region.lastKeyswitch);
REQUIRE(*region.keyswitch == 4); REQUIRE(*region.lastKeyswitch == 4);
region.parseOpcode({ "sw_last", "128" }); region.parseOpcode({ "sw_last", "128" });
REQUIRE(region.keyswitch); REQUIRE(region.lastKeyswitch);
REQUIRE(*region.keyswitch == 127); REQUIRE(*region.lastKeyswitch == 127);
region.parseOpcode({ "sw_last", "-1" }); region.parseOpcode({ "sw_last", "-1" });
REQUIRE(region.keyswitch); REQUIRE(region.lastKeyswitch);
REQUIRE(*region.keyswitch == 0); REQUIRE(*region.lastKeyswitch == 0);
}
SECTION("sw_lolast/hilast")
{
REQUIRE(!region.lastKeyswitchRange);
region.parseOpcode({ "sw_lolast", "4" });
REQUIRE(region.lastKeyswitchRange);
REQUIRE(*region.lastKeyswitchRange == Range<uint8_t>(4, 4));
region.parseOpcode({ "sw_hilast", "128" });
REQUIRE(*region.lastKeyswitchRange == Range<uint8_t>(4, 127));
region.parseOpcode({ "sw_hilast", "63" });
REQUIRE(*region.lastKeyswitchRange == Range<uint8_t>(4, 63));
region.parseOpcode({ "sw_lolast", "64" });
REQUIRE(*region.lastKeyswitchRange == Range<uint8_t>(64, 64));
region.parseOpcode({ "sw_lolast", "-1" });
REQUIRE(*region.lastKeyswitchRange == Range<uint8_t>(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") SECTION("sw_up")
{ {
REQUIRE(!region.keyswitchUp); REQUIRE(!region.upKeyswitch);
region.parseOpcode({ "sw_up", "4" }); region.parseOpcode({ "sw_up", "4" });
REQUIRE(region.keyswitchUp); REQUIRE(region.upKeyswitch);
REQUIRE(*region.keyswitchUp == 4); REQUIRE(*region.upKeyswitch == 4);
region.parseOpcode({ "sw_up", "128" }); region.parseOpcode({ "sw_up", "128" });
REQUIRE(region.keyswitchUp); REQUIRE(region.upKeyswitch);
REQUIRE(*region.keyswitchUp == 127); REQUIRE(*region.upKeyswitch == 127);
region.parseOpcode({ "sw_up", "-1" }); region.parseOpcode({ "sw_up", "-1" });
REQUIRE(region.keyswitchUp); REQUIRE(region.upKeyswitch);
REQUIRE(*region.keyswitchUp == 0); REQUIRE(*region.upKeyswitch == 0);
} }
SECTION("sw_down") SECTION("sw_down")
{ {
REQUIRE(!region.keyswitchDown); REQUIRE(!region.downKeyswitch);
region.parseOpcode({ "sw_down", "4" }); region.parseOpcode({ "sw_down", "4" });
REQUIRE(region.keyswitchDown); REQUIRE(region.downKeyswitch);
REQUIRE(*region.keyswitchDown == 4); REQUIRE(*region.downKeyswitch == 4);
region.parseOpcode({ "sw_down", "128" }); region.parseOpcode({ "sw_down", "128" });
REQUIRE(region.keyswitchDown); REQUIRE(region.downKeyswitch);
REQUIRE(*region.keyswitchDown == 127); REQUIRE(*region.downKeyswitch == 127);
region.parseOpcode({ "sw_down", "-1" }); region.parseOpcode({ "sw_down", "-1" });
REQUIRE(region.keyswitchDown); REQUIRE(region.downKeyswitch);
REQUIRE(*region.keyswitchDown == 0); REQUIRE(*region.downKeyswitch == 0);
} }
SECTION("sw_previous") SECTION("sw_previous")
{ {
REQUIRE(!region.previousNote); REQUIRE(!region.previousKeyswitch);
region.parseOpcode({ "sw_previous", "4" }); region.parseOpcode({ "sw_previous", "4" });
REQUIRE(region.previousNote); REQUIRE(region.previousKeyswitch);
REQUIRE(*region.previousNote == 4); REQUIRE(*region.previousKeyswitch == 4);
region.parseOpcode({ "sw_previous", "128" }); region.parseOpcode({ "sw_previous", "128" });
REQUIRE(region.previousNote); REQUIRE(region.previousKeyswitch);
REQUIRE(*region.previousNote == 127); REQUIRE(*region.previousKeyswitch == 127);
region.parseOpcode({ "sw_previous", "-1" }); region.parseOpcode({ "sw_previous", "-1" });
REQUIRE(region.previousNote); REQUIRE(region.previousKeyswitch);
REQUIRE(*region.previousNote == 0); REQUIRE(*region.previousKeyswitch == 0);
} }
SECTION("sw_vel") SECTION("sw_vel")

View file

@ -7,7 +7,6 @@
#include "sfizz/Defaults.h" #include "sfizz/Defaults.h"
#include "sfizz/Region.h" #include "sfizz/Region.h"
#include "sfizz/SfzHelpers.h" #include "sfizz/SfzHelpers.h"
#include "sfizz/MidiState.h"
#include "catch2/catch.hpp" #include "catch2/catch.hpp"
#include <chrono> #include <chrono>
#include <thread> #include <thread>

View file

@ -1,5 +0,0 @@
<global> sw_lokey=30 sw_hikey=50 sw_default=40
<region> sw_last=41 key=51 sample=*silence
<region> sw_last=40 key=52 sample=*silence
<region> sw_last=41 key=53 sample=*silence
<region> sw_last=40 key=54 sample=*silence