diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 29dae245..f065a4a7 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -309,6 +309,15 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } } break; + MATCH("/region&/use_timer_range", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.useTimerRange) { + client.receive<'T'>(delay, path, {}); + } else { + client.receive<'F'>(delay, path, {}); + } + } break; + MATCH("/region&/count", "") { GET_REGION_OR_BREAK(indices[0]) if (region.sampleCount) diff --git a/tests/RegionTriggersT.cpp b/tests/RegionTriggersT.cpp index b2548683..a53a700a 100644 --- a/tests/RegionTriggersT.cpp +++ b/tests/RegionTriggersT.cpp @@ -426,3 +426,127 @@ TEST_CASE("[Triggers] Offed voices with CC triggers do not activate release trig synth.hdcc(10, 64, 10_norm); REQUIRE(playingSamples(synth) == std::vector { "*saw" }); } + +TEST_CASE("[Triggers] lotimer") +{ + Synth synth; + synth.setSampleRate(48000); + synth.setSamplesPerBlock(480); + sfz::AudioBuffer buffer { 2, 480 }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_vel.sfz", R"( + key=40 lotimer=0.1 sample=kick.wav loop_mode=one_shot + )"); + + // Advance time, t = 0.2s + for (int i = 0; i < 20; ++i) + synth.renderBlock(buffer); + + synth.noteOn(0, 40, 100); + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + + // Advance time by 0.02s and send a note off + synth.renderBlock(buffer); + synth.noteOff(0, 40, 100); + synth.renderBlock(buffer); + + // t = 0.22s, not allowed to retrigger + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + synth.noteOn(0, 40, 100); + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + + // Advance time by 0.08s and send a note off + synth.renderBlock(buffer); + synth.noteOff(0, 40, 100); + for (int i = 0; i < 7; ++i) + synth.renderBlock(buffer); + + // t = 0.3s, retriggering OK + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + synth.noteOn(0, 40, 100); + REQUIRE(playingSamples(synth) == std::vector { "kick.wav", "kick.wav" }); +} + +TEST_CASE("[Triggers] lotimer on CC") +{ + Synth synth; + synth.setSampleRate(48000); + synth.setSamplesPerBlock(480); + sfz::AudioBuffer buffer { 2, 480 }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_vel.sfz", R"( + start_locc4=100 start_hicc4=100 lotimer=0.1 sample=kick.wav loop_mode=one_shot + )"); + + // Advance time, t = 0.2s + for (int i = 0; i < 20; ++i) + synth.renderBlock(buffer); + + REQUIRE(playingSamples(synth) == std::vector { }); + synth.hdcc(0, 4, 100_norm); + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + + // Advance time by 0.02s and reset CC + synth.renderBlock(buffer); + synth.hdcc(0, 4, 0); + synth.renderBlock(buffer); + + // t = 0.22s, not allowed to retrigger + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + synth.hdcc(0, 4, 100_norm); + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + + // Advance time by 0.08s and reset CC + synth.renderBlock(buffer); + synth.hdcc(0, 4, 0); + for (int i = 0; i < 7; ++i) + synth.renderBlock(buffer); + + // t = 0.3s, retriggering OK + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + synth.hdcc(0, 4, 100_norm); + REQUIRE(playingSamples(synth) == std::vector { "kick.wav", "kick.wav" }); +} + +TEST_CASE("[Triggers] hitimer (with group)") +{ + Synth synth; + synth.setSampleRate(48000); + synth.setSamplesPerBlock(480); + sfz::AudioBuffer buffer { 2, 480 }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_vel.sfz", R"( + group=1 key=40 sample=snare.wav loop_mode=one_shot + group=1 key=41 hitimer=0.1 sample=kick.wav loop_mode=one_shot + )"); + + // Advance time to 0.1s + for (int i = 0; i < 10; ++i) + synth.renderBlock(buffer); + + // Not triggering before snare + synth.noteOn(0, 41, 100); + REQUIRE(playingSamples(synth) == std::vector { }); + synth.noteOn(0, 40, 100); + REQUIRE(playingSamples(synth) == std::vector { "snare.wav" }); + + // OK to trigger now + REQUIRE(playingSamples(synth) == std::vector { "snare.wav" }); + synth.noteOn(0, 41, 100); + REQUIRE(playingSamples(synth) == std::vector { "snare.wav", "kick.wav" }); + + // Advance time + for (int i = 0; i < 5; ++i) + synth.renderBlock(buffer); + + // t = 0.15s, still OK to trigger now + REQUIRE(playingSamples(synth) == std::vector { "snare.wav", "kick.wav" }); + synth.noteOn(0, 41, 100); + REQUIRE(playingSamples(synth) == std::vector { "snare.wav", "kick.wav", "kick.wav" }); + + // Advance time (it has to be 0.1s because playing the kick resets the timer) + for (int i = 0; i < 10; ++i) + synth.renderBlock(buffer); + + // t = 0.25s, not triggering a new kick + REQUIRE(playingSamples(synth) == std::vector { "snare.wav", "kick.wav", "kick.wav" }); + synth.noteOn(0, 41, 100); + REQUIRE(playingSamples(synth) == std::vector { "snare.wav", "kick.wav", "kick.wav" }); +} diff --git a/tests/RegionValuesT.cpp b/tests/RegionValuesT.cpp index f6bb21ba..57dcb543 100644 --- a/tests/RegionValuesT.cpp +++ b/tests/RegionValuesT.cpp @@ -1106,6 +1106,39 @@ TEST_CASE("[Values] Rand range") REQUIRE(messageList == expected); } +TEST_CASE("[Values] Timer range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav lotimer=0.2 hitimer=0.4 + sample=kick.wav lotimer=-0.1 hitimer=0.4 + sample=kick.wav lotimer=0.2 hitimer=-0.1 + )"); + synth.dispatchMessage(client, 0, "/region0/timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/use_timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/use_timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/use_timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/use_timer_range", "", nullptr); + std::vector expected { + "/region0/timer_range,ff : { 0, 3.40282e+38 }", + "/region0/use_timer_range,F : { }", + "/region1/timer_range,ff : { 0.2, 0.4 }", + "/region1/use_timer_range,T : { }", + "/region2/timer_range,ff : { 0, 0.4 }", + "/region2/use_timer_range,T : { }", + "/region3/timer_range,ff : { 0.2, 3.40282e+38 }", + "/region3/use_timer_range,T : { }", + }; + REQUIRE(messageList == expected); +} + TEST_CASE("[Values] Sequence length") { Synth synth;