Merge pull request #298 from paulfd/release-key
Add support for "sustain_cc=" and "trigger=release_key"
This commit is contained in:
commit
bcff0aa3b6
8 changed files with 148 additions and 10 deletions
|
|
@ -45,7 +45,6 @@ namespace config {
|
||||||
constexpr uint8_t gainSmoothing { 0 };
|
constexpr uint8_t gainSmoothing { 0 };
|
||||||
constexpr unsigned powerTableSizeExponent { 11 };
|
constexpr unsigned powerTableSizeExponent { 11 };
|
||||||
constexpr int maxFilePromises { maxVoices };
|
constexpr int maxFilePromises { maxVoices };
|
||||||
constexpr int sustainCC { 64 };
|
|
||||||
constexpr int allSoundOffCC { 120 };
|
constexpr int allSoundOffCC { 120 };
|
||||||
constexpr int resetCC { 121 };
|
constexpr int resetCC { 121 };
|
||||||
constexpr int allNotesOffCC { 123 };
|
constexpr int allNotesOffCC { 123 };
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,7 @@ namespace Default
|
||||||
constexpr float vel2release { 0.0f };
|
constexpr float vel2release { 0.0f };
|
||||||
constexpr float start { 0.0 };
|
constexpr float start { 0.0 };
|
||||||
constexpr float sustain { 100.0 };
|
constexpr float sustain { 100.0 };
|
||||||
|
constexpr uint16_t sustainCC { 64 };
|
||||||
constexpr float vel2sustain { 0.0 };
|
constexpr float vel2sustain { 0.0 };
|
||||||
constexpr int depth { 0 };
|
constexpr int depth { 0 };
|
||||||
constexpr Range<float> egTimeRange { 0.0, 100.0 };
|
constexpr Range<float> egTimeRange { 0.0, 100.0 };
|
||||||
|
|
|
||||||
|
|
@ -278,6 +278,9 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case hash("sustain_cc"):
|
||||||
|
setValueFromOpcode(opcode, sustainCC, Default::ccNumberRange);
|
||||||
|
break;
|
||||||
case hash("sustain_sw"):
|
case hash("sustain_sw"):
|
||||||
checkSustain = readBooleanFromOpcode(opcode).value_or(Default::checkSustain);
|
checkSustain = readBooleanFromOpcode(opcode).value_or(Default::checkSustain);
|
||||||
break;
|
break;
|
||||||
|
|
@ -1033,7 +1036,13 @@ bool sfz::Region::registerNoteOff(int noteNumber, float velocity, float randValu
|
||||||
|
|
||||||
const bool velOk = velocityRange.containsWithEnd(velocity);
|
const bool velOk = velocityRange.containsWithEnd(velocity);
|
||||||
const bool randOk = randRange.contains(randValue);
|
const bool randOk = randRange.contains(randValue);
|
||||||
const bool releaseTrigger = (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key);
|
bool releaseTrigger = (trigger == SfzTrigger::release_key);
|
||||||
|
if (trigger == SfzTrigger::release) {
|
||||||
|
if (midiState.getCCValue(sustainCC) < config::halfCCThreshold)
|
||||||
|
releaseTrigger = true;
|
||||||
|
else
|
||||||
|
noteIsOff = true;
|
||||||
|
}
|
||||||
return keyOk && velOk && randOk && releaseTrigger;
|
return keyOk && velOk && randOk && releaseTrigger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1048,13 +1057,18 @@ bool sfz::Region::registerCC(int ccNumber, float ccValue) noexcept
|
||||||
if (!isSwitchedOn())
|
if (!isSwitchedOn())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (sustainCC == ccNumber && ccValue < config::halfCCThreshold && noteIsOff) {
|
||||||
|
noteIsOff = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!triggerOnCC)
|
if (!triggerOnCC)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (ccTriggers.contains(ccNumber) && ccTriggers[ccNumber].containsWithEnd(ccValue))
|
if (ccTriggers.contains(ccNumber) && ccTriggers[ccNumber].containsWithEnd(ccValue))
|
||||||
return true;
|
return true;
|
||||||
else
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Region::registerPitchWheel(float pitch) noexcept
|
void sfz::Region::registerPitchWheel(float pitch) noexcept
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,7 @@ struct Region {
|
||||||
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
|
||||||
|
uint16_t sustainCC { Default::sustainCC }; // sustain_cc
|
||||||
|
|
||||||
// Region logic: internal conditions
|
// Region logic: internal conditions
|
||||||
Range<uint8_t> aftertouchRange { Default::aftertouchRange }; // hichanaft and lochanaft
|
Range<uint8_t> aftertouchRange { Default::aftertouchRange }; // hichanaft and lochanaft
|
||||||
|
|
@ -369,6 +370,9 @@ struct Region {
|
||||||
// Modifiers
|
// Modifiers
|
||||||
ModifierArray<CCMap<Modifier>> modifiers;
|
ModifierArray<CCMap<Modifier>> modifiers;
|
||||||
|
|
||||||
|
bool triggerOnCC { false }; // whether the region triggers on CC events or note events
|
||||||
|
bool triggerOnNote { true };
|
||||||
|
|
||||||
// Parent
|
// Parent
|
||||||
RegionSet* parent { nullptr };
|
RegionSet* parent { nullptr };
|
||||||
private:
|
private:
|
||||||
|
|
@ -379,9 +383,8 @@ private:
|
||||||
bool pitchSwitched { true };
|
bool pitchSwitched { true };
|
||||||
bool bpmSwitched { true };
|
bool bpmSwitched { true };
|
||||||
bool aftertouchSwitched { true };
|
bool aftertouchSwitched { true };
|
||||||
|
bool noteIsOff { false };
|
||||||
std::bitset<config::numCCs> ccSwitched;
|
std::bitset<config::numCCs> ccSwitched;
|
||||||
bool triggerOnCC { false };
|
|
||||||
bool triggerOnNote { true };
|
|
||||||
absl::string_view defaultPath { "" };
|
absl::string_view defaultPath { "" };
|
||||||
|
|
||||||
int sequenceCounter { 0 };
|
int sequenceCounter { 0 };
|
||||||
|
|
|
||||||
|
|
@ -518,7 +518,9 @@ void sfz::Synth::finalizeSfzLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int cc = 0; cc < config::numCCs; cc++) {
|
for (int cc = 0; cc < config::numCCs; cc++) {
|
||||||
if (region->ccTriggers.contains(cc) || region->ccConditions.contains(cc))
|
if (region->ccTriggers.contains(cc)
|
||||||
|
|| region->ccConditions.contains(cc)
|
||||||
|
|| (cc == region->sustainCC && region->trigger == SfzTrigger::release))
|
||||||
ccActivationLists[cc].push_back(region);
|
ccActivationLists[cc].push_back(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -991,7 +993,14 @@ void sfz::Synth::hdcc(int delay, int ccNumber, float normValue) noexcept
|
||||||
if (voice == nullptr)
|
if (voice == nullptr)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
voice->startVoice(region, delay, ccNumber, normValue, Voice::TriggerType::CC);
|
if (!region->triggerOnCC) {
|
||||||
|
// This is a sustain trigger
|
||||||
|
const auto replacedVelocity = resources.midiState.getNoteVelocity(region->pitchKeycenter);
|
||||||
|
voice->startVoice(region, delay, region->pitchKeycenter, replacedVelocity, Voice::TriggerType::NoteOff);
|
||||||
|
} else {
|
||||||
|
voice->startVoice(region, delay, ccNumber, normValue, Voice::TriggerType::CC);
|
||||||
|
}
|
||||||
|
|
||||||
ring.addVoiceToRing(voice);
|
ring.addVoiceToRing(voice);
|
||||||
RegionSet::registerVoiceInHierarchy(region, voice);
|
RegionSet::registerVoiceInHierarchy(region, voice);
|
||||||
polyphonyGroups[region->group].registerVoice(voice);
|
polyphonyGroups[region->group].registerVoice(voice);
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ void sfz::Voice::registerNoteOff(int delay, int noteNumber, float velocity) noex
|
||||||
if (region->loopMode == SfzLoopMode::one_shot)
|
if (region->loopMode == SfzLoopMode::one_shot)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!region->checkSustain || resources.midiState.getCCValue(config::sustainCC) < config::halfCCThreshold)
|
if (!region->checkSustain || resources.midiState.getCCValue(region->sustainCC) < config::halfCCThreshold)
|
||||||
release(delay);
|
release(delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +220,7 @@ void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept
|
||||||
if (state != State::playing)
|
if (state != State::playing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (region->checkSustain && noteIsOff && ccNumber == config::sustainCC && ccValue < config::halfCCThreshold)
|
if (region->checkSustain && noteIsOff && ccNumber == region->sustainCC && ccValue < config::halfCCThreshold)
|
||||||
release(delay);
|
release(delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1207,6 +1207,15 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
REQUIRE(region.checkSostenuto);
|
REQUIRE(region.checkSostenuto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("sustain_cc")
|
||||||
|
{
|
||||||
|
REQUIRE(region.sustainCC == 64);
|
||||||
|
region.parseOpcode({ "sustain_cc", "63" });
|
||||||
|
REQUIRE(region.sustainCC == 63);
|
||||||
|
region.parseOpcode({ "sustain_cc", "-1" });
|
||||||
|
REQUIRE(region.sustainCC == 0);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("Filter stacking and cutoffs")
|
SECTION("Filter stacking and cutoffs")
|
||||||
{
|
{
|
||||||
REQUIRE(region.filters.empty());
|
REQUIRE(region.filters.empty());
|
||||||
|
|
@ -1721,3 +1730,52 @@ TEST_CASE("[Region] Non-conforming floating point values in integer opcodes")
|
||||||
region.parseOpcode({ "pitch_keytrack", "-2.1" });
|
region.parseOpcode({ "pitch_keytrack", "-2.1" });
|
||||||
REQUIRE(region.pitchKeytrack == -2);
|
REQUIRE(region.pitchKeytrack == -2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("[Region] Release and release key")
|
||||||
|
{
|
||||||
|
MidiState midiState;
|
||||||
|
Region region { 0, midiState };
|
||||||
|
region.parseOpcode({ "key", "63" });
|
||||||
|
region.parseOpcode({ "sample", "*sine" });
|
||||||
|
SECTION("Release key without sustain")
|
||||||
|
{
|
||||||
|
region.parseOpcode({ "trigger", "release_key" });
|
||||||
|
midiState.ccEvent(0, 64, 0.0f);
|
||||||
|
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||||
|
REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||||
|
}
|
||||||
|
SECTION("Release key with sustain")
|
||||||
|
{
|
||||||
|
region.parseOpcode({ "trigger", "release_key" });
|
||||||
|
midiState.ccEvent(0, 64, 1.0f);
|
||||||
|
REQUIRE( !region.registerCC(64, 1.0f) );
|
||||||
|
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||||
|
REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||||
|
midiState.ccEvent(0, 64, 0.0f);
|
||||||
|
REQUIRE( !region.registerCC(64, 0.0f) );
|
||||||
|
}
|
||||||
|
SECTION("Release without sustain")
|
||||||
|
{
|
||||||
|
region.parseOpcode({ "trigger", "release" });
|
||||||
|
midiState.ccEvent(0, 64, 0.0f);
|
||||||
|
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||||
|
REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||||
|
}
|
||||||
|
SECTION("Release with sustain")
|
||||||
|
{
|
||||||
|
region.parseOpcode({ "trigger", "release" });
|
||||||
|
midiState.ccEvent(0, 64, 1.0f);
|
||||||
|
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||||
|
REQUIRE( !region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||||
|
}
|
||||||
|
SECTION("Release with sustain")
|
||||||
|
{
|
||||||
|
region.parseOpcode({ "trigger", "release" });
|
||||||
|
midiState.ccEvent(0, 64, 1.0f);
|
||||||
|
REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) );
|
||||||
|
REQUIRE( !region.registerNoteOff(63, 0.5f, 0.0f) );
|
||||||
|
midiState.ccEvent(0, 64, 0.0f);
|
||||||
|
REQUIRE( region.registerCC(64, 0.0f) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -609,3 +609,57 @@ TEST_CASE("[Synth] Sisters and off-by")
|
||||||
REQUIRE( synth.getNumActiveVoices() == 2 );
|
REQUIRE( synth.getNumActiveVoices() == 2 );
|
||||||
REQUIRE( sfz::SisterVoiceRing::countSisterVoices(synth.getVoiceView(0)) == 1 );
|
REQUIRE( sfz::SisterVoiceRing::countSisterVoices(synth.getVoiceView(0)) == 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Release key")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzString(fs::current_path(), R"(
|
||||||
|
<region> key=62 sample=*sine trigger=release_key
|
||||||
|
)");
|
||||||
|
synth.noteOn(0, 62, 85);
|
||||||
|
synth.cc(0, 64, 127);
|
||||||
|
synth.noteOff(0, 62, 85);
|
||||||
|
REQUIRE( synth.getNumActiveVoices() == 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Release")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzString(fs::current_path(), R"(
|
||||||
|
<region> key=62 sample=*sine trigger=release
|
||||||
|
)");
|
||||||
|
synth.noteOn(0, 62, 85);
|
||||||
|
synth.cc(0, 64, 127);
|
||||||
|
synth.noteOff(0, 62, 85);
|
||||||
|
REQUIRE( synth.getNumActiveVoices() == 0 );
|
||||||
|
synth.cc(0, 64, 0);
|
||||||
|
REQUIRE( synth.getNumActiveVoices() == 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Release key (Different sustain CC)")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzString(fs::current_path(), R"(
|
||||||
|
<global>sustain_cc=54
|
||||||
|
<region> key=62 sample=*sine trigger=release_key
|
||||||
|
)");
|
||||||
|
synth.noteOn(0, 62, 85);
|
||||||
|
synth.cc(0, 54, 127);
|
||||||
|
synth.noteOff(0, 62, 85);
|
||||||
|
REQUIRE( synth.getNumActiveVoices() == 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Release (Different sustain CC)")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzString(fs::current_path(), R"(
|
||||||
|
<global>sustain_cc=54
|
||||||
|
<region> key=62 sample=*sine trigger=release
|
||||||
|
)");
|
||||||
|
synth.noteOn(0, 62, 85);
|
||||||
|
synth.cc(0, 54, 127);
|
||||||
|
synth.noteOff(0, 62, 85);
|
||||||
|
REQUIRE( synth.getNumActiveVoices() == 0 );
|
||||||
|
synth.cc(0, 54, 0);
|
||||||
|
REQUIRE( synth.getNumActiveVoices() == 1 );
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue