diff --git a/sources/Region.cpp b/sources/Region.cpp index e783b2ef..15f0d18a 100644 --- a/sources/Region.cpp +++ b/sources/Region.cpp @@ -75,8 +75,10 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("lobend"): setRangeStartFromOpcode(opcode, bendRange, Default::bendRange); break; case hash("hibend"): setRangeEndFromOpcode(opcode, bendRange, Default::bendRange); break; case hash("locc"): - if (opcode.parameter) - setRangeStartFromOpcode(opcode, ccConditions[*opcode.parameter], Default::ccRange); + if (opcode.parameter) + { + setRangeStartFromOpcode(opcode, ccConditions[*opcode.parameter], Default::ccRange); + } break; case hash("hicc"): if (opcode.parameter) @@ -84,10 +86,19 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; case hash("sw_lokey"): setRangeStartFromOpcode(opcode, keyswitchRange, Default::keyRange); break; case hash("sw_hikey"): setRangeEndFromOpcode(opcode, keyswitchRange, Default::keyRange); break; - case hash("sw_last"): setValueFromOpcode(opcode, keyswitch, Default::keyRange); break; - case hash("sw_down"): setValueFromOpcode(opcode, keyswitchDown, Default::keyRange); break; + case hash("sw_last"): + setValueFromOpcode(opcode, keyswitch, Default::keyRange); + keySwitched = false; + break; + case hash("sw_down"): + setValueFromOpcode(opcode, keyswitchDown, Default::keyRange); + keySwitched = false; + break; case hash("sw_up"): setValueFromOpcode(opcode, keyswitchUp, Default::keyRange); break; - case hash("sw_previous"): setValueFromOpcode(opcode, previousNote, Default::keyRange); break; + case hash("sw_previous"): + setValueFromOpcode(opcode, previousNote, Default::keyRange); + previousKeySwitched = false; + break; case hash("sw_vel"): switch(hash(opcode.value)) { @@ -109,7 +120,10 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("lorand"): setRangeStartFromOpcode(opcode, randRange, Default::randRange); break; case hash("hirand"): setRangeEndFromOpcode(opcode, randRange, Default::randRange); break; case hash("seq_length"): setValueFromOpcode(opcode, sequenceLength, Default::sequenceRange); break; - case hash("seq_position"): setValueFromOpcode(opcode, sequencePosition, Default::sequenceRange); break; + case hash("seq_position"): + setValueFromOpcode(opcode, sequencePosition, Default::sequenceRange); + sequenceSwitched = (opcode.value == "1"); + break; // Region logic: triggers case hash("trigger"): switch(hash(opcode.value)) diff --git a/sources/Region.h b/sources/Region.h index fcd6d1f1..f6e612c4 100644 --- a/sources/Region.h +++ b/sources/Region.h @@ -30,7 +30,6 @@ struct Region void registerPitchWheel(int channel, int pitch); void registerAftertouch(int channel, uint8_t aftertouch); void registerTempo(float secondsPerQuarter); - bool prepare(); bool isStereo() const noexcept; bool parseOpcode(const Opcode& opcode); // Sound source: sample playback @@ -99,7 +98,6 @@ struct Region SfzCrossfadeCurve crossfadeKeyCurve { Default::crossfadeKeyCurve }; SfzCrossfadeCurve crossfadeVelCurve { Default::crossfadeVelCurve }; - // Performance parameters: pitch uint8_t pitchKeycenter{Default::pitchKeycenter}; // pitch_keycenter int pitchKeytrack{ Default::pitchKeytrack }; // pitch_keytrack diff --git a/sources/Synth.cpp b/sources/Synth.cpp index 5a0324b3..851d3440 100644 --- a/sources/Synth.cpp +++ b/sources/Synth.cpp @@ -155,6 +155,7 @@ bool sfz::Synth::loadSfzFile(const std::filesystem::path& filename) for (auto cc = region->keyRange.getStart(); cc <= region->keyRange.getEnd(); cc++) ccActivationLists[cc].push_back(*currentRegion); + // Defaults for (int ccIndex = 1; ccIndex < 128; ccIndex++) region->registerCC(region->channelRange.getStart(), ccIndex, ccState[ccIndex]); @@ -163,6 +164,10 @@ bool sfz::Synth::loadSfzFile(const std::filesystem::path& filename) region->registerNoteOn(region->channelRange.getStart(), *defaultSwitch, 127, 1.0); region->registerNoteOff(region->channelRange.getStart(), *defaultSwitch, 0, 1.0); } + region->registerPitchWheel(region->channelRange.getStart(), 0); + region->registerAftertouch(region->channelRange.getStart(), 0); + region->registerTempo(2.0f); + currentRegion++; } diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp new file mode 100644 index 00000000..b89a987d --- /dev/null +++ b/tests/RegionActivationT.cpp @@ -0,0 +1,268 @@ +#include "catch2/catch.hpp" +#include "../sources/Region.h" +using namespace Catch::literals; + +TEST_CASE("Region activation", "Region tests") +{ + sfz::Region region { }; + region.parseOpcode({ "sample", "*sine" }); + SECTION("Basic state") + { + region.registerCC(1, 4, 0); + REQUIRE( region.isSwitchedOn() ); + } + + SECTION("Single CC range") + { + region.parseOpcode({ "locc4", "56" }); + region.parseOpcode({ "hicc4", "59" }); + region.registerCC(1, 4, 0); + REQUIRE( !region.isSwitchedOn() ); + region.registerCC(1, 4, 57); + REQUIRE( region.isSwitchedOn() ); + region.registerCC(1, 4, 56); + REQUIRE( region.isSwitchedOn() ); + region.registerCC(1, 4, 59); + REQUIRE( region.isSwitchedOn() ); + region.registerCC(1, 4, 43); + REQUIRE( !region.isSwitchedOn() ); + region.registerCC(1, 4, 65); + REQUIRE( !region.isSwitchedOn() ); + region.registerCC(1, 6, 57); + REQUIRE( !region.isSwitchedOn() ); + } + + SECTION("Multiple CC ranges") + { + region.parseOpcode({ "locc4", "56" }); + region.parseOpcode({ "hicc4", "59" }); + region.parseOpcode({ "locc54", "18" }); + region.parseOpcode({ "hicc54", "27" }); + region.registerCC(1, 4, 0); + region.registerCC(1, 54, 0); + REQUIRE( !region.isSwitchedOn() ); + region.registerCC(1, 4, 57); + REQUIRE( !region.isSwitchedOn() ); + region.registerCC(1, 54, 19); + REQUIRE( region.isSwitchedOn() ); + region.registerCC(1, 54, 18); + REQUIRE( region.isSwitchedOn() ); + region.registerCC(1, 54, 27); + REQUIRE( region.isSwitchedOn() ); + region.registerCC(1, 4, 56); + REQUIRE( region.isSwitchedOn() ); + region.registerCC(1, 4, 59); + REQUIRE( region.isSwitchedOn() ); + region.registerCC(1, 54, 2); + REQUIRE( !region.isSwitchedOn() ); + region.registerCC(1, 54, 26); + REQUIRE( region.isSwitchedOn() ); + region.registerCC(1, 4, 65); + REQUIRE( !region.isSwitchedOn() ); + } + + SECTION("Bend ranges") + { + region.parseOpcode({ "lobend", "56" }); + region.parseOpcode({ "hibend", "243" }); + region.registerPitchWheel(1, 0); + REQUIRE( !region.isSwitchedOn() ); + region.registerPitchWheel(1, 56); + REQUIRE( region.isSwitchedOn() ); + region.registerPitchWheel(1, 243); + REQUIRE( region.isSwitchedOn() ); + region.registerPitchWheel(1, 245); + REQUIRE( !region.isSwitchedOn() ); + } + + SECTION("Aftertouch ranges") + { + region.parseOpcode({ "lochanaft", "56" }); + region.parseOpcode({ "hichanaft", "68" }); + region.registerAftertouch(1, 0); + REQUIRE( !region.isSwitchedOn() ); + region.registerAftertouch(1, 56); + REQUIRE( region.isSwitchedOn() ); + region.registerAftertouch(1, 68); + REQUIRE( region.isSwitchedOn() ); + region.registerAftertouch(1, 98); + REQUIRE( !region.isSwitchedOn() ); + } + + SECTION("BPM ranges") + { + region.parseOpcode({ "lobpm", "56" }); + region.parseOpcode({ "hibpm", "68" }); + region.registerTempo(2.0f); + REQUIRE( !region.isSwitchedOn() ); + region.registerTempo(0.90f); + REQUIRE( region.isSwitchedOn() ); + region.registerTempo(1.01f); + REQUIRE( region.isSwitchedOn() ); + region.registerTempo(1.1f); + REQUIRE( !region.isSwitchedOn() ); + } + + // TODO: add keyswitches + SECTION("Keyswitches: sw_last") + { + region.parseOpcode({ "sw_last", "40" }); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOn(1, 41, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 41, 0, 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(1, 60, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 60, 0, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOn(1, 60, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOff(1, 60, 0, 0.5f); + region.registerNoteOn(1, 41, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 41, 0, 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(1, 60, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 60, 0, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOn(1, 60, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 60, 0, 0.5f); + region.registerNoteOn(1, 41, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 41, 0, 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(1, 40, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOn(1, 41, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + region.registerNoteOff(1, 41, 0, 0.5f); + REQUIRE( region.isSwitchedOn() ); + } + + SECTION("Keyswitches: sw_previous") + { + region.parseOpcode({ "sw_previous", "40" }); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOn(1, 41, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + region.registerNoteOff(1, 41, 0, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOn(1, 41, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 41, 0, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + } + + SECTION("Sequences: length 2, default position") + { + region.parseOpcode({ "seq_length", "2" }); + region.parseOpcode({ "seq_position", "1" }); + region.parseOpcode({ "key", "40" }); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + + } + SECTION("Sequences: length 2, position 2") + { + region.parseOpcode({ "seq_length", "2" }); + region.parseOpcode({ "seq_position", "2" }); + region.parseOpcode({ "key", "40" }); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( region.isSwitchedOn() ); + + } + SECTION("Sequences: length 3, position 2") + { + region.parseOpcode({ "seq_length", "3" }); + region.parseOpcode({ "seq_position", "2" }); + region.parseOpcode({ "key", "40" }); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( !region.isSwitchedOn() ); + region.registerNoteOn(1, 40, 64, 0.5f); + REQUIRE( region.isSwitchedOn() ); + region.registerNoteOff(1, 40, 0, 0.5f); + REQUIRE( region.isSwitchedOn() ); + } +} +