diff --git a/src/sfizz/Layer.cpp b/src/sfizz/Layer.cpp index a90261da..b06c2f71 100644 --- a/src/sfizz/Layer.cpp +++ b/src/sfizz/Layer.cpp @@ -129,7 +129,7 @@ bool Layer::registerNoteOff(int noteNumber, float velocity, float randValue) noe return false; } -bool Layer::registerCC(int ccNumber, float ccValue, bool dontTrigger) noexcept +void Layer::updateCCState(int ccNumber, float ccValue) noexcept { const Region& region = region_; @@ -151,8 +151,21 @@ bool Layer::registerCC(int ccNumber, float ccValue, bool dontTrigger) noexcept ccSwitched_.set(ccNumber, true); else ccSwitched_.set(ccNumber, false); +} - if (dontTrigger || !region.triggerOnCC) +bool Layer::registerCC(int ccNumber, float ccValue, float randValue) noexcept +{ + const Region& region = region_; + + updateCCState(ccNumber, ccValue); + + if (!region.triggerOnCC) + return false; + + const bool randOk = region.randRange.contains(randValue) + || (randValue >= 1.0f && region.randRange.isValid() && region.randRange.getEnd() >= 1.0f); + + if (!randOk) return false; if (auto triggerRange = region.ccTriggers.get(ccNumber)) { diff --git a/src/sfizz/Layer.h b/src/sfizz/Layer.h index ec7a7806..d049e2c0 100644 --- a/src/sfizz/Layer.h +++ b/src/sfizz/Layer.h @@ -80,15 +80,26 @@ public: */ bool registerNoteOff(int noteNumber, float velocity, float randValue) noexcept; /** - * @brief Register a new CC event. The region may be switched on or off using CCs so - * this function checks if it indeeds need to activate or not. + * @brief Update the internal state of the layer with respect to CC events (sustain, CC + * switch, etc). * * @param ccNumber * @param ccValue - * @return true if the region should trigger on this event * @return false */ - bool registerCC(int ccNumber, float ccValue, bool dontTrigger = false) noexcept; + void updateCCState(int ccNumber, float ccValue) noexcept; + /** + * @brief Register a new CC event,. This method updates the internal CC state with respect + * to CC events (sustain, CC switch, etc) and checks if the region should trigger on this + * event. + * + * @param ccNumber + * @param ccValue + * @param randValue + * @return true if the region should trigger on this event + * @return false otherwise + */ + bool registerCC(int ccNumber, float ccValue, float randValue) noexcept; /** * @brief Register a new pitch wheel event. * diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 055ec82d..049af524 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -727,7 +727,7 @@ void Synth::Impl::finalizeSfzLoad() // Defaults MidiState& midiState = resources_.getMidiState(); for (int cc = 0; cc < config::numCCs; cc++) { - layer.registerCC(cc, midiState.getCCValue(cc), true); + layer.updateCCState(cc, midiState.getCCValue(cc)); } @@ -1273,6 +1273,7 @@ void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept { SisterVoiceRingBuilder ring; TriggerEvent triggerEvent { TriggerEventType::CC, ccNumber, value }; + const auto randValue = randNoteDistribution_(Random::randomGenerator); for (Layer* layer : ccActivationLists_[ccNumber]) { const Region& region = layer->getRegion(); @@ -1290,7 +1291,7 @@ void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept } } - if (layer->registerCC(ccNumber, value)) { + if (layer->registerCC(ccNumber, value, randValue)) { checkOffGroups(®ion, delay, ccNumber); startVoice(layer, delay, triggerEvent, ring); } @@ -1952,7 +1953,7 @@ void Synth::Impl::resetAllControllers(int delay) noexcept for (const LayerPtr& layerPtr : layers_) { Layer& layer = *layerPtr; for (int cc = 0; cc < config::numCCs; ++cc) - layer.registerCC(cc, defaultCCValues_[cc], true); + layer.updateCCState(cc, defaultCCValues_[cc]); } } diff --git a/tests/DirectRegionT.cpp b/tests/DirectRegionT.cpp index f54bbfdc..b86ff5f4 100644 --- a/tests/DirectRegionT.cpp +++ b/tests/DirectRegionT.cpp @@ -43,7 +43,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") Layer layer { region, midiState }; layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 0.0f); - layer.registerCC(64, 0.0f); + layer.updateCCState(64, 0.0f); REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); REQUIRE( layer.registerNoteOff(63, 0.5f, 0.0f) ); } @@ -53,8 +53,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") Layer layer { region, midiState }; layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 1.0f); - layer.registerCC(64, 1.0f); - REQUIRE( !layer.registerCC(64, 1.0f) ); + layer.updateCCState(64, 1.0f); REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); REQUIRE( layer.registerNoteOff(63, 0.5f, 0.0f) ); } @@ -65,7 +64,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") Layer layer { region, midiState }; layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 0.0f); - layer.registerCC(64, 0.0f); + layer.updateCCState(64, 0.0f); REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); REQUIRE( layer.registerNoteOff(63, 0.5f, 0.0f) ); } @@ -76,7 +75,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") Layer layer { region, midiState }; layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 1.0f); - layer.registerCC(64, 1.0f); + layer.updateCCState(64, 1.0f); midiState.noteOnEvent(0, 63, 0.5f); REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); REQUIRE( !layer.registerNoteOff(63, 0.5f, 0.0f) ); @@ -93,7 +92,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") Layer layer { region, midiState }; layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 1.0f); - layer.registerCC(64, 1.0f); + layer.updateCCState(64, 1.0f); midiState.noteOnEvent(0, 63, 0.5f); REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); midiState.noteOnEvent(0, 64, 0.6f); @@ -114,7 +113,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") Layer layer { region, midiState }; layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 1.0f); - layer.registerCC(64, 1.0f); + layer.updateCCState(64, 1.0f); midiState.noteOnEvent(0, 63, 0.5f); REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); midiState.noteOnEvent(0, 66, 0.6f); diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp index fb69c984..b8ae8218 100644 --- a/tests/RegionActivationT.cpp +++ b/tests/RegionActivationT.cpp @@ -21,7 +21,7 @@ TEST_CASE("Region activation", "Region tests") SECTION("Basic state") { sfz::Layer layer { region, midiState }; - layer.registerCC(4, 0_norm); + layer.updateCCState(4, 0_norm); REQUIRE(layer.isSwitchedOn()); } @@ -30,19 +30,19 @@ TEST_CASE("Region activation", "Region tests") region.parseOpcode({ "locc4", "56" }); region.parseOpcode({ "hicc4", "59" }); sfz::Layer layer { region, midiState }; - layer.registerCC(4, 0_norm); + layer.updateCCState(4, 0_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(4, 57_norm); + layer.updateCCState(4, 57_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(4, 56_norm); + layer.updateCCState(4, 56_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(4, 59_norm); + layer.updateCCState(4, 59_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(4, 43_norm); + layer.updateCCState(4, 43_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(4, 65_norm); + layer.updateCCState(4, 65_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(6, 57_norm); + layer.updateCCState(6, 57_norm); REQUIRE(!layer.isSwitchedOn()); } @@ -53,26 +53,26 @@ TEST_CASE("Region activation", "Region tests") region.parseOpcode({ "locc54", "18" }); region.parseOpcode({ "hicc54", "27" }); sfz::Layer layer { region, midiState }; - layer.registerCC(4, 0_norm); - layer.registerCC(54, 0_norm); + layer.updateCCState(4, 0_norm); + layer.updateCCState(54, 0_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(4, 57_norm); + layer.updateCCState(4, 57_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(54, 19_norm); + layer.updateCCState(54, 19_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(54, 17_norm); + layer.updateCCState(54, 17_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(54, 27_norm); + layer.updateCCState(54, 27_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(4, 56_norm); + layer.updateCCState(4, 56_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(4, 59_norm); + layer.updateCCState(4, 59_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(54, 2_norm); + layer.updateCCState(54, 2_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(54, 26_norm); + layer.updateCCState(54, 26_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(4, 65_norm); + layer.updateCCState(4, 65_norm); REQUIRE(!layer.isSwitchedOn()); } diff --git a/tests/RegionTriggersT.cpp b/tests/RegionTriggersT.cpp index 5e5931a3..039fe4bb 100644 --- a/tests/RegionTriggersT.cpp +++ b/tests/RegionTriggersT.cpp @@ -27,7 +27,7 @@ TEST_CASE("Basic triggers", "Region triggers") REQUIRE(layer.registerNoteOn(40, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOff(40, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOn(41, 64_norm, 0.5f)); - REQUIRE(!layer.registerCC(63, 64_norm)); + REQUIRE(!layer.registerCC(63, 64_norm, 0.0f)); } SECTION("lokey and hikey") { @@ -42,7 +42,7 @@ TEST_CASE("Basic triggers", "Region triggers") REQUIRE(!layer.registerNoteOn(43, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOff(42, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOff(42, 64_norm, 0.5f)); - REQUIRE(!layer.registerCC(63, 64_norm)); + REQUIRE(!layer.registerCC(63, 64_norm, 0.0f)); } SECTION("key and release trigger") { @@ -53,7 +53,7 @@ TEST_CASE("Basic triggers", "Region triggers") REQUIRE(layer.registerNoteOff(40, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOn(41, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOff(41, 64_norm, 0.5f)); - REQUIRE(!layer.registerCC(63, 64_norm)); + REQUIRE(!layer.registerCC(63, 64_norm, 0.0f)); } SECTION("key and release_key trigger") { @@ -64,7 +64,7 @@ TEST_CASE("Basic triggers", "Region triggers") REQUIRE(layer.registerNoteOff(40, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOn(41, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOff(41, 64_norm, 0.5f)); - REQUIRE(!layer.registerCC(63, 64_norm)); + REQUIRE(!layer.registerCC(63, 64_norm, 0.0f)); } // TODO: first and legato triggers SECTION("lovel and hivel") @@ -130,18 +130,36 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "on_locc47", "64" }); region.parseOpcode({ "on_hicc47", "68" }); Layer layer1 { region, midiState }; - REQUIRE(!layer1.registerCC(47, 63_norm)); - REQUIRE(layer1.registerCC(47, 64_norm)); - REQUIRE(layer1.registerCC(47, 65_norm)); + REQUIRE(!layer1.registerCC(47, 63_norm, 0.0f)); + REQUIRE(layer1.registerCC(47, 64_norm, 0.0f)); + REQUIRE(layer1.registerCC(47, 65_norm, 0.0f)); region.parseOpcode({ "hikey", "-1" }); Layer layer2 { region, midiState }; - REQUIRE(layer2.registerCC(47, 64_norm)); - REQUIRE(layer2.registerCC(47, 65_norm)); - REQUIRE(layer2.registerCC(47, 66_norm)); - REQUIRE(layer2.registerCC(47, 67_norm)); - REQUIRE(layer2.registerCC(47, 68_norm)); - REQUIRE(!layer2.registerCC(47, 69_norm)); - REQUIRE(!layer2.registerCC(40, 64_norm)); + REQUIRE(layer2.registerCC(47, 64_norm, 0.0f)); + REQUIRE(layer2.registerCC(47, 65_norm, 0.0f)); + REQUIRE(layer2.registerCC(47, 66_norm, 0.0f)); + REQUIRE(layer2.registerCC(47, 67_norm, 0.0f)); + REQUIRE(layer2.registerCC(47, 68_norm, 0.0f)); + REQUIRE(!layer2.registerCC(47, 69_norm, 0.0f)); + REQUIRE(!layer2.registerCC(40, 64_norm, 0.0f)); + } + + SECTION("lorand and hirand with CC triggers") + { + region.parseOpcode({ "on_locc47", "64" }); + region.parseOpcode({ "on_hicc47", "68" }); + region.parseOpcode({ "hikey", "-1" }); + region.parseOpcode({ "lorand", "0.35" }); + region.parseOpcode({ "hirand", "0.40" }); + Layer layer { region, midiState }; + REQUIRE(!layer.registerCC(47, 64_norm, 0.0f)); + REQUIRE(!layer.registerCC(47, 64_norm, 0.34f)); + REQUIRE(layer.registerCC(47, 64_norm, 0.35f)); + REQUIRE(layer.registerCC(47, 64_norm, 0.36f)); + REQUIRE(layer.registerCC(47, 64_norm, 0.37f)); + REQUIRE(layer.registerCC(47, 64_norm, 0.38f)); + REQUIRE(layer.registerCC(47, 64_norm, 0.39f)); + REQUIRE(!layer.registerCC(47, 64_norm, 0.40f)); } SECTION("on_loccN does not disable key triggering") @@ -150,9 +168,9 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "on_locc1", "127" }); region.parseOpcode({ "on_hicc1", "127" }); Layer layer { region, midiState }; - REQUIRE(!layer.registerCC(1, 126_norm)); - REQUIRE(!layer.registerCC(2, 127_norm)); - REQUIRE(layer.registerCC(1, 127_norm)); + REQUIRE(!layer.registerCC(1, 126_norm, 0.0f)); + REQUIRE(!layer.registerCC(2, 127_norm, 0.0f)); + REQUIRE(layer.registerCC(1, 127_norm, 0.0f)); REQUIRE(layer.registerNoteOn(64, 127_norm, 0.5f)); } @@ -163,8 +181,8 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "on_hicc1", "127" }); region.parseOpcode({ "key", "-1" }); Layer layer { region, midiState }; - REQUIRE(!layer.registerCC(1, 126_norm)); - REQUIRE(layer.registerCC(1, 127_norm)); + REQUIRE(!layer.registerCC(1, 126_norm, 0.0f)); + REQUIRE(layer.registerCC(1, 127_norm, 0.0f)); REQUIRE(!layer.registerNoteOn(64, 127_norm, 0.5f)); } @@ -175,9 +193,9 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "on_hicc1", "127" }); region.parseOpcode({ "hikey", "-1" }); Layer layer { region, midiState }; - REQUIRE(!layer.registerCC(1, 126_norm)); - REQUIRE(!layer.registerCC(2, 127_norm)); - REQUIRE(layer.registerCC(1, 127_norm)); + REQUIRE(!layer.registerCC(1, 126_norm, 0.0f)); + REQUIRE(!layer.registerCC(2, 127_norm, 0.0f)); + REQUIRE(layer.registerCC(1, 127_norm, 0.0f)); REQUIRE(!layer.registerNoteOn(64, 127_norm, 0.5f)); } } @@ -371,3 +389,22 @@ TEST_CASE("[Triggers] sw_vel, consider the previous velocity for triggers") REQUIRE(messageList == expected); } } + + +TEST_CASE("[Triggers] Honor lorand/hirand on CC triggers") +{ + Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_vel.sfz", R"( + sample=*sine hikey=-1 start_locc64=63 start_hicc64=127 lorand=0 hirand=0.5 + sample=*saw hikey=-1 start_locc64=63 start_hicc64=127 lorand=0.5 hirand=1 + )"); + + synth.hdcc(0, 64, 10_norm); + REQUIRE(numPlayingVoices(synth) == 0); + synth.hdcc(0, 64, 100_norm); + REQUIRE(numPlayingVoices(synth) == 1); +}