Normalize CCs internally in Synth and region

This commit is contained in:
Paul Fd 2020-03-25 23:48:17 +01:00 committed by Paul Ferrand
parent 0e1e65135e
commit 601126db0d
6 changed files with 82 additions and 44 deletions

View file

@ -68,14 +68,13 @@ namespace Default
// Region logic: key mapping
constexpr Range<uint8_t> keyRange { 0, 127 };
constexpr Range<float> velocityRange { 0.0f, 1.0f };
constexpr auto velocityRange = normalizedRange;
// Region logic: MIDI conditions
constexpr Range<uint8_t> channelRange { 1, 16 };
constexpr Range<uint8_t> midiChannelRange { 0, 15 };
constexpr Range<uint16_t> ccNumberRange { 0, config::numCCs };
constexpr Range<uint8_t> ccValueRange { 0, 127 };
constexpr uint8_t cc { 0 };
constexpr auto ccValueRange = normalizedRange;
constexpr Range<int> bendRange { -8192, 8192 };
constexpr int bend { 0 };
constexpr SfzVelocityOverride velocityOverride { SfzVelocityOverride::current };
@ -92,7 +91,7 @@ namespace Default
// Region logic: Triggers
constexpr SfzTrigger trigger { SfzTrigger::attack };
constexpr Range<uint8_t> ccTriggerValueRange{ 0, 127 };
constexpr Range<float> ccTriggerValueRange = normalizedRange;
// Performance parameters: amplifier
constexpr float globalVolume { -7.35f };
@ -123,8 +122,8 @@ namespace Default
constexpr Range<uint8_t> crossfadeKeyOutRange { 127, 127 };
constexpr Range<float> crossfadeVelInRange { 0.0f, 0.0f };
constexpr Range<float> crossfadeVelOutRange { 1.0f, 1.0f };
constexpr Range<uint8_t> crossfadeCCInRange { 0, 0 };
constexpr Range<uint8_t> crossfadeCCOutRange { 127, 127 };
constexpr Range<float> crossfadeCCInRange { 0.0f, 0.0f };
constexpr Range<float> crossfadeCCOutRange { 1.0f, 1.0f };
constexpr SfzCrossfadeCurve crossfadeKeyCurve { SfzCrossfadeCurve::power };
constexpr SfzCrossfadeCurve crossfadeVelCurve { SfzCrossfadeCurve::power };
constexpr SfzCrossfadeCurve crossfadeCCCurve { SfzCrossfadeCurve::power };

View file

@ -155,10 +155,16 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
setRangeEndFromOpcode(opcode, bendRange, Default::bendRange);
break;
case hash("locc&"):
setRangeStartFromOpcode(opcode, ccConditions[opcode.parameters.back()], Default::ccValueRange);
if (opcode.parameters.back() > config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
ccConditions[opcode.parameters.back()].setStart(normalizeCC(*value));
break;
case hash("hicc&"):
setRangeEndFromOpcode(opcode, ccConditions[opcode.parameters.back()], Default::ccValueRange);
if (opcode.parameters.back() > config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
ccConditions[opcode.parameters.back()].setEnd(normalizeCC(*value));
break;
case hash("sw_lokey"):
setRangeStartFromOpcode(opcode, keyswitchRange, Default::keyRange);
@ -250,11 +256,17 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
break;
case hash("on_locc&"): // fallthrough
case hash("start_locc&"):
setRangeStartFromOpcode(opcode, ccTriggers[opcode.parameters.back()], Default::ccTriggerValueRange);
if (opcode.parameters.back() > config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
ccTriggers[opcode.parameters.back()].setStart(normalizeCC(*value));
break;
case hash("on_hicc&"): // fallthrough
case hash("start_hicc&"):
setRangeEndFromOpcode(opcode, ccTriggers[opcode.parameters.back()], Default::ccTriggerValueRange);
if (opcode.parameters.back() > config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
ccTriggers[opcode.parameters.back()].setEnd(normalizeCC(*value));
break;
// Performance parameters: amplifier
@ -366,16 +378,28 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
}
break;
case hash("xfin_locc&"):
setRangeStartFromOpcode(opcode, crossfadeCCInRange[opcode.parameters.back()], Default::ccValueRange);
if (opcode.parameters.back() > config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
crossfadeCCInRange[opcode.parameters.back()].setStart(normalizeCC(*value));
break;
case hash("xfin_hicc&"):
setRangeEndFromOpcode(opcode, crossfadeCCInRange[opcode.parameters.back()], Default::ccValueRange);
if (opcode.parameters.back() > config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
crossfadeCCInRange[opcode.parameters.back()].setEnd(normalizeCC(*value));
break;
case hash("xfout_locc&"):
setRangeStartFromOpcode(opcode, crossfadeCCOutRange[opcode.parameters.back()], Default::ccValueRange);
if (opcode.parameters.back() > config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
crossfadeCCOutRange[opcode.parameters.back()].setStart(normalizeCC(*value));
break;
case hash("xfout_hicc&"):
setRangeEndFromOpcode(opcode, crossfadeCCOutRange[opcode.parameters.back()], Default::ccValueRange);
if (opcode.parameters.back() > config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
crossfadeCCOutRange[opcode.parameters.back()].setEnd(normalizeCC(*value));
break;
case hash("xf_cccurve"):
switch (hash(opcode.value)) {
@ -872,6 +896,11 @@ bool sfz::Region::registerNoteOffNormalized(int noteNumber, float velocity, floa
}
bool sfz::Region::registerCC(int ccNumber, uint8_t ccValue) noexcept
{
return registerCCNormalized(ccNumber, normalizeCC(ccValue));
}
bool sfz::Region::registerCCNormalized(int ccNumber, float ccValue) noexcept
{
if (ccConditions.getWithDefault(ccNumber).containsWithEnd(ccValue))
ccSwitched.set(ccNumber, true);
@ -1063,13 +1092,13 @@ float sfz::Region::getCrossfadeGain() const noexcept
// Crossfades due to CC states
for (const auto& valuePair : crossfadeCCInRange) {
const auto ccValue = midiState.getCCValue(valuePair.cc);
const auto ccValue = midiState.getCCValueNormalized(valuePair.cc);
const auto crossfadeRange = valuePair.value;
gain *= crossfadeIn(crossfadeRange, ccValue, crossfadeCCCurve);
}
for (const auto& valuePair : crossfadeCCOutRange) {
const auto ccValue = midiState.getCCValue(valuePair.cc);
const auto ccValue = midiState.getCCValueNormalized(valuePair.cc);
const auto crossfadeRange = valuePair.value;
gain *= crossfadeOut(crossfadeRange, ccValue, crossfadeCCCurve);
}

View file

@ -132,6 +132,16 @@ struct Region {
* @return false
*/
bool registerCC(int ccNumber, uint8_t ccValue) 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.
*
* @param ccNumber
* @param ccValue
* @return true if the region should trigger on this event
* @return false
*/
bool registerCCNormalized(int ccNumber, float ccValue) noexcept;
/**
* @brief Register a new pitch wheel event.
*
@ -295,7 +305,7 @@ struct Region {
// Region logic: MIDI conditions
Range<int> bendRange { Default::bendRange }; // hibend and lobend
CCMap<Range<uint8_t>> ccConditions { Default::ccValueRange };
CCMap<Range<float>> ccConditions { Default::ccValueRange };
Range<uint8_t> keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey
absl::optional<uint8_t> keyswitch {}; // sw_last
absl::optional<uint8_t> keyswitchUp {}; // sw_up
@ -314,7 +324,7 @@ struct Region {
// Region logic: triggers
SfzTrigger trigger { Default::trigger }; // trigger
CCMap<Range<uint8_t>> ccTriggers { Default::ccTriggerValueRange }; // on_loccN on_hiccN
CCMap<Range<float>> ccTriggers { Default::ccTriggerValueRange }; // on_loccN on_hiccN
// Performance parameters: amplifier
float volume { Default::volume }; // volume
@ -339,8 +349,8 @@ struct Region {
SfzCrossfadeCurve crossfadeKeyCurve { Default::crossfadeKeyCurve };
SfzCrossfadeCurve crossfadeVelCurve { Default::crossfadeVelCurve };
SfzCrossfadeCurve crossfadeCCCurve { Default::crossfadeCCCurve };
CCMap<Range<uint8_t>> crossfadeCCInRange { Default::crossfadeCCInRange }; // xfin_loccN xfin_hiccN
CCMap<Range<uint8_t>> crossfadeCCOutRange { Default::crossfadeCCOutRange }; // xfout_loccN xfout_hiccN
CCMap<Range<float>> crossfadeCCInRange { Default::crossfadeCCInRange }; // xfin_loccN xfin_hiccN
CCMap<Range<float>> crossfadeCCOutRange { Default::crossfadeCCOutRange }; // xfout_loccN xfout_hiccN
float rtDecay { Default::rtDecay }; // rt_decay
// Filters and EQs

View file

@ -181,7 +181,7 @@ void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& members)
case hash("Set_cc&"): // fallthrough
case hash("set_cc&"):
if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) {
const auto ccValue = readOpcode(member.value, Default::ccValueRange).value_or(0);
const auto ccValue = readOpcode(member.value, Default::midi7Range).value_or(0);
resources.midiState.ccEvent(0, member.parameters.back(), ccValue);
}
break;

View file

@ -236,7 +236,7 @@ TEST_CASE("[Files] Pizz basic")
REQUIRE(synth.getRegionView(i)->keyRange == sfz::Range<uint8_t>(12, 22));
REQUIRE(synth.getRegionView(i)->velocityRange == sfz::Range<float>(97_norm, 127_norm));
REQUIRE(synth.getRegionView(i)->pitchKeycenter == 21);
REQUIRE(synth.getRegionView(i)->ccConditions.getWithDefault(107) == sfz::Range<uint8_t>(0, 13));
REQUIRE(synth.getRegionView(i)->ccConditions.getWithDefault(107) == sfz::Range<float>(0_norm, 13_norm));
}
REQUIRE(synth.getRegionView(0)->randRange == sfz::Range<float>(0, 0.25));
REQUIRE(synth.getRegionView(1)->randRange == sfz::Range<float>(0.25, 0.5));

View file

@ -240,16 +240,16 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("locc, hicc")
{
REQUIRE(region.ccConditions.getWithDefault(0) == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.ccConditions[127] == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.ccConditions.getWithDefault(0) == sfz::Range<float>(0_norm, 127_norm));
REQUIRE(region.ccConditions[127] == sfz::Range<float>(0_norm, 127_norm));
region.parseOpcode({ "locc6", "4" });
REQUIRE(region.ccConditions[6] == sfz::Range<uint8_t>(4, 127));
REQUIRE(region.ccConditions[6] == sfz::Range<float>(4_norm, 127_norm));
region.parseOpcode({ "locc12", "-128" });
REQUIRE(region.ccConditions[12] == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.ccConditions[12] == sfz::Range<float>(0_norm, 127_norm));
region.parseOpcode({ "hicc65", "39" });
REQUIRE(region.ccConditions[65] == sfz::Range<uint8_t>(0, 39));
REQUIRE(region.ccConditions[65] == sfz::Range<float>(0_norm, 39_norm));
region.parseOpcode({ "hicc127", "135" });
REQUIRE(region.ccConditions[127] == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.ccConditions[127] == sfz::Range<float>(0_norm, 127_norm));
}
SECTION("sw_lokey, sw_hikey")
@ -427,10 +427,10 @@ TEST_CASE("[Region] Parsing opcodes")
}
region.parseOpcode({ "on_locc45", "15" });
REQUIRE(region.ccTriggers.contains(45));
REQUIRE(region.ccTriggers[45] == sfz::Range<uint8_t>(15, 127));
REQUIRE(region.ccTriggers[45] == sfz::Range<float>(15_norm, 127_norm));
region.parseOpcode({ "on_hicc4", "47" });
REQUIRE(region.ccTriggers.contains(45));
REQUIRE(region.ccTriggers[4] == sfz::Range<uint8_t>(0, 47));
REQUIRE(region.ccTriggers[4] == sfz::Range<float>(0_norm, 47_norm));
}
SECTION("volume")
@ -650,38 +650,38 @@ TEST_CASE("[Region] Parsing opcodes")
{
REQUIRE(!region.crossfadeCCInRange.contains(4));
region.parseOpcode({ "xfin_locc4", "4" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<uint8_t>(4, 4));
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(4_norm, 4_norm));
region.parseOpcode({ "xfin_locc4", "128" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<uint8_t>(127, 127));
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(127_norm, 127_norm));
region.parseOpcode({ "xfin_locc4", "59" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<uint8_t>(59, 127));
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(59_norm, 127_norm));
region.parseOpcode({ "xfin_hicc4", "59" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<uint8_t>(59, 59));
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(59_norm, 59_norm));
region.parseOpcode({ "xfin_hicc4", "128" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<uint8_t>(59, 127));
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(59_norm, 127_norm));
region.parseOpcode({ "xfin_hicc4", "0" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(0_norm, 0_norm));
region.parseOpcode({ "xfin_hicc4", "-1" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(0_norm, 0_norm));
}
SECTION("xfout_locc, xfout_hicc")
{
REQUIRE(!region.crossfadeCCOutRange.contains(4));
region.parseOpcode({ "xfout_locc4", "4" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<uint8_t>(4, 127));
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(4_norm, 127_norm));
region.parseOpcode({ "xfout_locc4", "128" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<uint8_t>(127, 127));
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(127_norm, 127_norm));
region.parseOpcode({ "xfout_locc4", "59" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<uint8_t>(59, 127));
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(59_norm, 127_norm));
region.parseOpcode({ "xfout_hicc4", "59" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<uint8_t>(59, 59));
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(59_norm, 59_norm));
region.parseOpcode({ "xfout_hicc4", "128" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<uint8_t>(59, 127));
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(59_norm, 127_norm));
region.parseOpcode({ "xfout_hicc4", "0" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(0_norm, 0_norm));
region.parseOpcode({ "xfout_hicc4", "-1" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(0_norm, 0_norm));
}
SECTION("xf_keycurve")