diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 17f2ee50..43ce0cf8 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -8,6 +8,7 @@ #include "sfizz/SisterVoiceRing.h" #include "sfizz/SfzHelpers.h" #include "sfizz/NumericId.h" +#include #include "catch2/catch.hpp" using namespace Catch::literals; using namespace sfz::literals; @@ -626,14 +627,48 @@ TEST_CASE("[Synth] Release") { sfz::Synth synth; synth.loadSfzString(fs::current_path(), R"( + key=62 sample=*silence key=62 sample=*sine trigger=release )"); synth.noteOn(0, 62, 85); synth.cc(0, 64, 127); synth.noteOff(0, 62, 85); - REQUIRE( synth.getNumActiveVoices(true) == 0 ); - synth.cc(0, 64, 0); REQUIRE( synth.getNumActiveVoices(true) == 1 ); + synth.cc(0, 64, 0); + REQUIRE( synth.getNumActiveVoices(true) == 2 ); +} + +TEST_CASE("[Synth] Release (pedal was already down)") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path(), R"( + key=62 sample=*silence + key=62 sample=*sine trigger=release + )"); + synth.cc(0, 64, 127); + synth.noteOn(0, 62, 85); + synth.noteOff(0, 62, 85); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + synth.cc(0, 64, 0); + REQUIRE( synth.getNumActiveVoices(true) == 2 ); +} + + + +TEST_CASE("[Synth] Release samples don't play unless there is another playing region that matches") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path(), R"( + key=62 sample=*sine trigger=release + )"); + synth.noteOn(0, 62, 85); + synth.noteOff(0, 62, 0); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + synth.cc(0, 64, 127); + synth.noteOn(0, 62, 85); + synth.noteOff(0, 62, 0); + synth.cc(0, 64, 0); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); } TEST_CASE("[Synth] Release key (Different sustain CC)") @@ -646,7 +681,7 @@ TEST_CASE("[Synth] Release key (Different sustain CC)") synth.noteOn(0, 62, 85); synth.cc(0, 54, 127); synth.noteOff(0, 62, 85); - REQUIRE( synth.getNumActiveVoices(true) == 1 ); + REQUIRE( synth.getNumActiveVoices(true) == 2 ); } TEST_CASE("[Synth] Release (Different sustain CC)") @@ -654,14 +689,15 @@ TEST_CASE("[Synth] Release (Different sustain CC)") sfz::Synth synth; synth.loadSfzString(fs::current_path(), R"( sustain_cc=54 + key=62 sample=*silence key=62 sample=*sine trigger=release )"); synth.noteOn(0, 62, 85); synth.cc(0, 54, 127); synth.noteOff(0, 62, 85); - REQUIRE( synth.getNumActiveVoices(true) == 0 ); - synth.cc(0, 54, 0); REQUIRE( synth.getNumActiveVoices(true) == 1 ); + synth.cc(0, 54, 0); + REQUIRE( synth.getNumActiveVoices(true) == 2 ); } TEST_CASE("[Synth] Sustain threshold default") @@ -681,37 +717,45 @@ TEST_CASE("[Synth] Sustain threshold") sfz::Synth synth; synth.loadSfzString(fs::current_path(), R"( sustain_lo=63 + key=62 sample=*silence key=62 sample=*sine trigger=release )"); synth.noteOn(0, 62, 85); synth.cc(0, 64, 1); synth.noteOff(0, 62, 85); - REQUIRE( synth.getNumActiveVoices(true) == 1 ); - synth.noteOn(0, 62, 85); - synth.noteOff(0, 62, 85); REQUIRE( synth.getNumActiveVoices(true) == 2 ); synth.noteOn(0, 62, 85); + synth.noteOff(0, 62, 85); + REQUIRE( synth.getNumActiveVoices(true) == 4 ); + synth.noteOn(0, 62, 85); + REQUIRE( synth.getNumActiveVoices(true) == 5 ); synth.cc(0, 64, 64); synth.noteOff(0, 62, 85); - REQUIRE( synth.getNumActiveVoices(true) == 2 ); + REQUIRE( synth.getNumActiveVoices(true) == 5 ); } -TEST_CASE("[Synth] Release (Multiple notes)") +template +void sortAll(C& container) { - sfz::Synth synth; - synth.loadSfzString(fs::current_path(), R"( - lokey=62 hikey=64 sample=*sine trigger=release - )"); - synth.noteOn(0, 62, 85); - synth.noteOn(0, 63, 78); - synth.noteOn(0, 64, 34); - synth.cc(0, 64, 127); - synth.noteOff(0, 64, 0); - synth.noteOff(0, 63, 2); - synth.noteOff(0, 62, 85); - REQUIRE( synth.getNumActiveVoices(true) == 0 ); - synth.cc(0, 64, 0); - REQUIRE( synth.getNumActiveVoices(true) == 3 ); + std::sort(container.begin(), container.end()); +} + +template +void sortAll(C& container, Args&... others) +{ + std::sort(container.begin(), container.end()); + sortAll(others...); +} + +const std::vector getActiveVoices(const sfz::Synth& synth) +{ + std::vector activeVoices; + for (int i = 0; i < synth.getNumVoices(); ++i) { + const auto* voice = synth.getVoiceView(i); + if (!voice->isFree()) + activeVoices.push_back(voice); + } + return activeVoices; } TEST_CASE("[Synth] Release (Multiple notes, release_key ignores the pedal)") @@ -728,12 +772,21 @@ TEST_CASE("[Synth] Release (Multiple notes, release_key ignores the pedal)") synth.noteOff(0, 63, 2); synth.noteOff(0, 62, 85); REQUIRE( synth.getNumActiveVoices(true) == 3 ); + + std::vector requiredVelocities { 34_norm, 78_norm, 85_norm}; + std::vector actualVelocities; + for (auto* v: getActiveVoices(synth)) { + actualVelocities.push_back(v->getTriggerValue()); + } + sortAll(requiredVelocities, actualVelocities); + REQUIRE( requiredVelocities == actualVelocities ); } -TEST_CASE("[Synth] Release (Multiple notes, cleared the delayed voices after)") +TEST_CASE("[Synth] Release (Multiple notes, release, cleared the delayed voices after)") { sfz::Synth synth; synth.loadSfzString(fs::current_path(), R"( + lokey=62 hikey=64 sample=*silence lokey=62 hikey=64 sample=*sine trigger=release loopmode=one_shot ampeg_attack=0.02 ampeg_release=0.1 )"); @@ -744,8 +797,142 @@ TEST_CASE("[Synth] Release (Multiple notes, cleared the delayed voices after)") synth.noteOff(0, 64, 0); synth.noteOff(0, 63, 2); synth.noteOff(0, 62, 85); + REQUIRE( synth.getNumActiveVoices(true) == 3 ); + synth.cc(0, 64, 0); + REQUIRE( synth.getNumActiveVoices(true) == 6 ); + + std::vector requiredVelocities { 34_norm, 78_norm, 85_norm, 34_norm, 78_norm, 85_norm }; + std::vector actualVelocities; + for (auto* v: getActiveVoices(synth)) { + actualVelocities.push_back(v->getTriggerValue()); + } + sortAll(requiredVelocities, actualVelocities); + REQUIRE( requiredVelocities == actualVelocities ); + + REQUIRE( synth.getRegionView(1)->delayedReleases.empty() ); +} + +TEST_CASE("[Synth] Release (Multiple notes after pedal is down, release, cleared the delayed voices after)") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path(), R"( + lokey=62 hikey=64 sample=*silence + lokey=62 hikey=64 sample=*sine trigger=release + loopmode=one_shot ampeg_attack=0.02 ampeg_release=0.1 + )"); + synth.cc(0, 64, 127); + synth.noteOn(1, 62, 85); + synth.noteOn(1, 63, 78); + synth.noteOn(1, 64, 34); + synth.noteOff(2, 64, 0); + synth.noteOff(2, 63, 2); + synth.noteOff(2, 62, 3); + REQUIRE( synth.getNumActiveVoices(true) == 3 ); + synth.cc(3, 64, 0); + REQUIRE( synth.getNumActiveVoices(true) == 6 ); + + std::vector requiredVelocities { 34_norm, 78_norm, 85_norm, 34_norm, 78_norm, 85_norm }; + std::vector actualVelocities; + for (auto* v: getActiveVoices(synth)) { + actualVelocities.push_back(v->getTriggerValue()); + } + sortAll(requiredVelocities, actualVelocities); + REQUIRE( requiredVelocities == actualVelocities ); + + REQUIRE( synth.getRegionView(1)->delayedReleases.empty() ); +} + +TEST_CASE("[Synth] Release (Multiple note ons during pedal down)") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path(), R"( + lokey=62 hikey=64 sample=*silence + lokey=62 hikey=64 sample=*sine trigger=release + loopmode=one_shot ampeg_attack=0.02 ampeg_release=0.1 + )"); + synth.noteOn(0, 62, 85); + synth.cc(0, 64, 127); + synth.noteOff(0, 62, 0); + synth.noteOn(0, 62, 78); + synth.noteOff(0, 62, 2); + REQUIRE( synth.getNumActiveVoices(true) == 2 ); + synth.cc(0, 64, 0); + REQUIRE( synth.getNumActiveVoices(true) == 4 ); + + std::vector requiredVelocities { 78_norm, 85_norm, 78_norm, 85_norm }; + std::vector actualVelocities; + for (auto* v: getActiveVoices(synth)) { + actualVelocities.push_back(v->getTriggerValue()); + } + sortAll(requiredVelocities, actualVelocities); + REQUIRE( requiredVelocities == actualVelocities ); + REQUIRE( synth.getRegionView(1)->delayedReleases.empty() ); +} + +TEST_CASE("[Synth] No release sample after the main sample stopped sounding by default") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, 4096 }; + + synth.loadSfzString(fs::current_path(), R"( + lokey=62 hikey=64 sample=TestFiles/closedhat.wav loop_mode=one_shot + lokey=62 hikey=64 sample=*sine trigger=release + loopmode=one_shot ampeg_attack=0.02 ampeg_release=0.1 + )"); + synth.noteOn(0, 62, 85); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + for (unsigned i = 0; i < 100; ++i) { + synth.renderBlock(buffer); + } + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + synth.noteOff(0, 62, 0); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + + synth.noteOn(0, 62, 85); + synth.cc(0, 64, 127); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + for (unsigned i = 0; i < 100; ++i) { + synth.renderBlock(buffer); + } + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + synth.noteOff(0, 62, 0); REQUIRE( synth.getNumActiveVoices(true) == 0 ); synth.cc(0, 64, 0); - REQUIRE( synth.getNumActiveVoices(true) == 3 ); - REQUIRE( synth.getRegionView(0)->delayedReleases.empty() ); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + + REQUIRE( synth.getRegionView(1)->delayedReleases.empty() ); +} + +TEST_CASE("[Synth] If rt_dead is active the release sample can sound after the attack sample died") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, 4096 }; + + synth.loadSfzString(fs::current_path(), R"( + lokey=62 hikey=64 sample=TestFiles/closedhat.wav loop_mode=one_shot + lokey=62 hikey=64 sample=*sine trigger=release + loopmode=one_shot ampeg_attack=0.02 ampeg_release=0.1 + )"); + synth.noteOn(0, 62, 85); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + for (unsigned i = 0; i < 100; ++i) { + synth.renderBlock(buffer); + } + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + synth.noteOff(0, 62, 0); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + + synth.noteOn(0, 62, 85); + synth.cc(0, 64, 127); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + for (unsigned i = 0; i < 100; ++i) { + synth.renderBlock(buffer); + } + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + synth.noteOff(0, 62, 0); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + synth.cc(0, 64, 0); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + + REQUIRE( synth.getRegionView(1)->delayedReleases.empty() ); }