Merge pull request #101 from paulfd/parser-rework

Changed the way the parser handles multiple parameters in opcodes
This commit is contained in:
Paul Ferrand 2020-03-06 11:16:52 +01:00 committed by GitHub
commit 291920f263
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 234 additions and 275 deletions

View file

@ -20,15 +20,13 @@ sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue)
while (nextNumIndex != opcode.npos) { while (nextNumIndex != opcode.npos) {
const auto numLetters = nextNumIndex - nextCharIndex; const auto numLetters = nextNumIndex - nextCharIndex;
parameterPosition += numLetters; parameterPosition += numLetters;
lettersOnlyHash = hash(opcode.substr(nextCharIndex, numLetters), lettersOnlyHash); lettersOnlyHash = hashNoAmpersand(opcode.substr(nextCharIndex, numLetters), lettersOnlyHash);
nextCharIndex = opcode.find_first_not_of("1234567890", nextNumIndex); nextCharIndex = opcode.find_first_not_of("1234567890", nextNumIndex);
uint32_t returnedValue; uint32_t returnedValue;
hasBackParameter = (nextCharIndex == opcode.npos); const auto numDigits = (nextCharIndex == opcode.npos) ? opcode.npos : nextCharIndex - nextNumIndex;
const auto numDigits = hasBackParameter ? opcode.npos : nextCharIndex - nextNumIndex;
if (absl::SimpleAtoi(opcode.substr(nextNumIndex, numDigits), &returnedValue)) { if (absl::SimpleAtoi(opcode.substr(nextNumIndex, numDigits), &returnedValue)) {
// ASSERT(returnedValue < std::numeric_limits<uint8_t>::max()); lettersOnlyHash = hash("&", lettersOnlyHash);
parameterPositions.push_back(parameterPosition);
parameters.push_back(returnedValue); parameters.push_back(returnedValue);
} }
@ -36,35 +34,5 @@ sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue)
} }
if (nextCharIndex != opcode.npos) if (nextCharIndex != opcode.npos)
lettersOnlyHash = hash(opcode.substr(nextCharIndex), lettersOnlyHash); lettersOnlyHash = hashNoAmpersand(opcode.substr(nextCharIndex), lettersOnlyHash);
}
absl::optional<uint8_t> sfz::Opcode::backParameter() const noexcept
{
if (hasBackParameter && !parameters.empty())
return parameters.back();
return {};
}
absl::optional<uint8_t> sfz::Opcode::firstParameter() const noexcept
{
if (!hasBackParameter && !parameters.empty())
return parameters.front();
if (hasBackParameter && parameters.size() > 1)
return parameters.front();
return {};
}
absl::optional<uint8_t> sfz::Opcode::middleParameter() const noexcept
{
if (!hasBackParameter && parameters.size() > 1)
return parameters[1];
if (hasBackParameter && parameters.size() > 2)
return parameters[1];
return {};
} }

View file

@ -26,17 +26,12 @@ namespace sfz {
*/ */
struct Opcode { struct Opcode {
Opcode() = delete; Opcode() = delete;
absl::optional<uint8_t> backParameter() const noexcept;
absl::optional<uint8_t> firstParameter() const noexcept;
absl::optional<uint8_t> middleParameter() const noexcept;
Opcode(absl::string_view inputOpcode, absl::string_view inputValue); Opcode(absl::string_view inputOpcode, absl::string_view inputValue);
absl::string_view opcode {}; absl::string_view opcode {};
absl::string_view value {}; absl::string_view value {};
uint64_t lettersOnlyHash { Fnv1aBasis }; uint64_t lettersOnlyHash { Fnv1aBasis };
// This is to handle the integer parameters of some opcodes // This is to handle the integer parameters of some opcodes
std::vector<uint8_t> parameters; std::vector<uint16_t> parameters;
std::vector<int> parameterPositions;
bool hasBackParameter { false };
LEAK_DETECTOR(Opcode); LEAK_DETECTOR(Opcode);
}; };
@ -192,9 +187,8 @@ template <class ValueType>
inline void setCCPairFromOpcode(const Opcode& opcode, absl::optional<CCValuePair>& target, const Range<ValueType>& validRange) inline void setCCPairFromOpcode(const Opcode& opcode, absl::optional<CCValuePair>& target, const Range<ValueType>& validRange)
{ {
auto value = readOpcode(opcode.value, validRange); auto value = readOpcode(opcode.value, validRange);
const auto backParameter = opcode.backParameter(); if (value && Default::ccNumberRange.containsWithEnd(opcode.parameters.back()))
if (value && backParameter && Default::ccNumberRange.containsWithEnd(*backParameter)) target = std::make_pair(opcode.parameters.back(), *value);
target = std::make_pair(*backParameter, *value);
else else
target = {}; target = {};
} }

View file

@ -32,13 +32,6 @@ bool extendIfNecessary(std::vector<T>& vec, unsigned size, unsigned defaultCapac
bool sfz::Region::parseOpcode(const Opcode& opcode) bool sfz::Region::parseOpcode(const Opcode& opcode)
{ {
const auto backParameter = opcode.backParameter();
// Check that the parameter is well formed
if (backParameter && !sfz::Default::ccNumberRange.containsWithEnd(*backParameter)) {
DBG("Wrong parameter value (" << std::to_string(*backParameter) << ") for opcode " << opcode.opcode);
return false;
}
switch (opcode.lettersOnlyHash) { switch (opcode.lettersOnlyHash) {
// Sound source: sample playback // Sound source: sample playback
case hash("sample"): case hash("sample"):
@ -73,7 +66,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
case hash("count"): case hash("count"):
setValueFromOpcode(opcode, sampleCount, Default::sampleCountRange); setValueFromOpcode(opcode, sampleCount, Default::sampleCountRange);
break; break;
case hash("loopmode"): case hash("loopmode"): [[fallthrough]];
case hash("loop_mode"): case hash("loop_mode"):
switch (hash(opcode.value)) { switch (hash(opcode.value)) {
case hash("no_loop"): case hash("no_loop"):
@ -92,21 +85,21 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
DBG("Unkown loop mode:" << std::string(opcode.value)); DBG("Unkown loop mode:" << std::string(opcode.value));
} }
break; break;
case hash("loopend"): case hash("loopend"): [[fallthrough]];
case hash("loop_end"): case hash("loop_end"):
setRangeEndFromOpcode(opcode, loopRange, Default::loopRange); setRangeEndFromOpcode(opcode, loopRange, Default::loopRange);
break; break;
case hash("loopstart"): case hash("loopstart"): [[fallthrough]];
case hash("loop_start"): case hash("loop_start"):
setRangeStartFromOpcode(opcode, loopRange, Default::loopRange); setRangeStartFromOpcode(opcode, loopRange, Default::loopRange);
break; break;
// Instrument settings: voice lifecycle // Instrument settings: voice lifecycle
case hash("group"): case hash("group"): [[fallthrough]];
case hash("polyphony_group"): case hash("polyphony_group"):
setValueFromOpcode(opcode, group, Default::groupRange); setValueFromOpcode(opcode, group, Default::groupRange);
break; break;
case hash("offby"): case hash("offby"): [[fallthrough]];
case hash("off_by"): case hash("off_by"):
setValueFromOpcode(opcode, offBy, Default::groupRange); setValueFromOpcode(opcode, offBy, Default::groupRange);
break; break;
@ -150,14 +143,11 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
case hash("hibend"): case hash("hibend"):
setRangeEndFromOpcode(opcode, bendRange, Default::bendRange); setRangeEndFromOpcode(opcode, bendRange, Default::bendRange);
break; break;
case hash("locc"): case hash("locc&"):
if (backParameter) { setRangeStartFromOpcode(opcode, ccConditions[opcode.parameters.back()], Default::ccValueRange);
setRangeStartFromOpcode(opcode, ccConditions[*backParameter], Default::ccValueRange);
}
break; break;
case hash("hicc"): case hash("hicc&"):
if (backParameter) setRangeEndFromOpcode(opcode, ccConditions[opcode.parameters.back()], Default::ccValueRange);
setRangeEndFromOpcode(opcode, ccConditions[*backParameter], Default::ccValueRange);
break; break;
case hash("sw_lokey"): case hash("sw_lokey"):
setRangeStartFromOpcode(opcode, keyswitchRange, Default::keyRange); setRangeStartFromOpcode(opcode, keyswitchRange, Default::keyRange);
@ -247,49 +237,47 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
DBG("Unknown trigger mode: " << std::string(opcode.value)); DBG("Unknown trigger mode: " << std::string(opcode.value));
} }
break; break;
case hash("on_locc"): case hash("on_locc&"): [[fallthrough]];
case hash("start_locc"): case hash("start_locc&"):
if (backParameter) setRangeStartFromOpcode(opcode, ccTriggers[opcode.parameters.back()], Default::ccTriggerValueRange);
setRangeStartFromOpcode(opcode, ccTriggers[*backParameter], Default::ccTriggerValueRange);
break; break;
case hash("on_hicc"): case hash("on_hicc&"): [[fallthrough]];
case hash("start_hicc"): case hash("start_hicc&"):
if (backParameter) setRangeEndFromOpcode(opcode, ccTriggers[opcode.parameters.back()], Default::ccTriggerValueRange);
setRangeEndFromOpcode(opcode, ccTriggers[*backParameter], Default::ccTriggerValueRange);
break; break;
// Performance parameters: amplifier // Performance parameters: amplifier
case hash("volume"): case hash("volume"):
setValueFromOpcode(opcode, volume, Default::volumeRange); setValueFromOpcode(opcode, volume, Default::volumeRange);
break; break;
case hash("gain_cc"): case hash("gain_cc&"): [[fallthrough]];
case hash("gain_oncc"): case hash("gain_oncc&"): [[fallthrough]];
case hash("volume_oncc"): case hash("volume_oncc&"):
setCCPairFromOpcode(opcode, volumeCC, Default::volumeCCRange); setCCPairFromOpcode(opcode, volumeCC, Default::volumeCCRange);
break; break;
case hash("amplitude"): case hash("amplitude"):
setValueFromOpcode(opcode, amplitude, Default::amplitudeRange); setValueFromOpcode(opcode, amplitude, Default::amplitudeRange);
break; break;
case hash("amplitude_cc"): case hash("amplitude_cc&"): [[fallthrough]];
case hash("amplitude_oncc"): case hash("amplitude_oncc&"):
setCCPairFromOpcode(opcode, amplitudeCC, Default::amplitudeRange); setCCPairFromOpcode(opcode, amplitudeCC, Default::amplitudeRange);
break; break;
case hash("pan"): case hash("pan"):
setValueFromOpcode(opcode, pan, Default::panRange); setValueFromOpcode(opcode, pan, Default::panRange);
break; break;
case hash("pan_oncc"): case hash("pan_oncc&"):
setCCPairFromOpcode(opcode, panCC, Default::panCCRange); setCCPairFromOpcode(opcode, panCC, Default::panCCRange);
break; break;
case hash("position"): case hash("position"):
setValueFromOpcode(opcode, position, Default::positionRange); setValueFromOpcode(opcode, position, Default::positionRange);
break; break;
case hash("position_oncc"): case hash("position_oncc&"):
setCCPairFromOpcode(opcode, positionCC, Default::positionCCRange); setCCPairFromOpcode(opcode, positionCC, Default::positionCCRange);
break; break;
case hash("width"): case hash("width"):
setValueFromOpcode(opcode, width, Default::widthRange); setValueFromOpcode(opcode, width, Default::widthRange);
break; break;
case hash("width_oncc"): case hash("width_oncc&"):
setCCPairFromOpcode(opcode, widthCC, Default::widthCCRange); setCCPairFromOpcode(opcode, widthCC, Default::widthCCRange);
break; break;
case hash("amp_keycenter"): case hash("amp_keycenter"):
@ -305,11 +293,11 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
setValueFromOpcode(opcode, ampRandom, Default::ampRandomRange); setValueFromOpcode(opcode, ampRandom, Default::ampRandomRange);
volumeDistribution.param(std::uniform_real_distribution<float>::param_type(0, ampRandom)); volumeDistribution.param(std::uniform_real_distribution<float>::param_type(0, ampRandom));
break; break;
case hash("amp_velcurve_"): case hash("amp_velcurve_&"):
{ {
auto value = readOpcode(opcode.value, Default::ampVelcurveRange); auto value = readOpcode(opcode.value, Default::ampVelcurveRange);
if (value) if (value)
velocityPoints.emplace_back(*backParameter, *value); velocityPoints.emplace_back(opcode.parameters.back(), *value);
} }
break; break;
case hash("xfin_lokey"): case hash("xfin_lokey"):
@ -360,25 +348,17 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
DBG("Unknown crossfade power curve: " << std::string(opcode.value)); DBG("Unknown crossfade power curve: " << std::string(opcode.value));
} }
break; break;
case hash("xfin_locc"): case hash("xfin_locc&"):
if (backParameter) { setRangeStartFromOpcode(opcode, crossfadeCCInRange[opcode.parameters.back()], Default::ccValueRange);
setRangeStartFromOpcode(opcode, crossfadeCCInRange[*backParameter], Default::ccValueRange);
}
break; break;
case hash("xfin_hicc"): case hash("xfin_hicc&"):
if (backParameter) { setRangeEndFromOpcode(opcode, crossfadeCCInRange[opcode.parameters.back()], Default::ccValueRange);
setRangeEndFromOpcode(opcode, crossfadeCCInRange[*backParameter], Default::ccValueRange);
}
break; break;
case hash("xfout_locc"): case hash("xfout_locc&"):
if (backParameter) { setRangeStartFromOpcode(opcode, crossfadeCCOutRange[opcode.parameters.back()], Default::ccValueRange);
setRangeStartFromOpcode(opcode, crossfadeCCOutRange[*backParameter], Default::ccValueRange);
}
break; break;
case hash("xfout_hicc"): case hash("xfout_hicc&"):
if (backParameter) { setRangeEndFromOpcode(opcode, crossfadeCCOutRange[opcode.parameters.back()], Default::ccValueRange);
setRangeEndFromOpcode(opcode, crossfadeCCOutRange[*backParameter], Default::ccValueRange);
}
break; break;
case hash("xf_cccurve"): case hash("xf_cccurve"):
switch (hash(opcode.value)) { switch (hash(opcode.value)) {
@ -397,120 +377,124 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
break; break;
// Performance parameters: filters // Performance parameters: filters
case hash("cutoff"): case hash("cutoff"): [[fallthrough]];
case hash("cutoff&"):
{ {
const auto filterIndex { backParameter.value_or(1) - 1 }; const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.back() - 1);
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false; return false;
setValueFromOpcode(opcode, filters[filterIndex].cutoff, Default::filterCutoffRange); setValueFromOpcode(opcode, filters[filterIndex].cutoff, Default::filterCutoffRange);
} }
break; break;
case hash("resonance"): case hash("resonance"): [[fallthrough]];
case hash("resonance&"):
{ {
const auto filterIndex { backParameter.value_or(1) - 1 }; const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.back() - 1);
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false; return false;
setValueFromOpcode(opcode, filters[filterIndex].resonance, Default::filterResonanceRange); setValueFromOpcode(opcode, filters[filterIndex].resonance, Default::filterResonanceRange);
} }
break; break;
case hash("cutoff_oncc"): case hash("cutoff_oncc&"): [[fallthrough]];
case hash("cutoff_cc"): case hash("cutoff_cc&"): [[fallthrough]];
case hash("cutoff&_oncc&"): [[fallthrough]];
case hash("cutoff&_cc&"):
{ {
if (!backParameter) const auto filterIndex = opcode.parameters.size() == 1 ? 0 : (opcode.parameters.front() - 1);
return false;
const auto filterIndex { opcode.firstParameter().value_or(1) - 1 };
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false; return false;
setValueFromOpcode( setValueFromOpcode(
opcode, opcode,
filters[filterIndex].cutoffCC[*backParameter], filters[filterIndex].cutoffCC[opcode.parameters.back()],
Default::filterCutoffModRange Default::filterCutoffModRange
); );
} }
break; break;
case hash("resonance_oncc"): case hash("resonance&_oncc&"): [[fallthrough]];
case hash("resonance_cc"): case hash("resonance&_cc&"): [[fallthrough]];
case hash("resonance_oncc&"): [[fallthrough]];
case hash("resonance_cc&"):
{ {
if (!backParameter) const auto filterIndex = opcode.parameters.size() == 1 ? 0 : (opcode.parameters.front() - 1);
return false;
const auto filterIndex { opcode.firstParameter().value_or(1) - 1 };
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false; return false;
setValueFromOpcode( setValueFromOpcode(
opcode, opcode,
filters[filterIndex].resonanceCC[*backParameter], filters[filterIndex].resonanceCC[opcode.parameters.back()],
Default::filterResonanceModRange Default::filterResonanceModRange
); );
} }
break; break;
case hash("fil_keytrack"): case hash("fil_keytrack"): [[fallthrough]];
case hash("fil&_keytrack"):
{ {
const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.front() - 1);
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false; return false;
setValueFromOpcode(opcode, filters[filterIndex].keytrack, Default::filterKeytrackRange); setValueFromOpcode(opcode, filters[filterIndex].keytrack, Default::filterKeytrackRange);
} }
break; break;
case hash("fil_keycenter"): case hash("fil_keycenter"): [[fallthrough]];
case hash("fil&_keycenter"):
{ {
const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.front() - 1);
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false; return false;
setValueFromOpcode(opcode, filters[filterIndex].keycenter, Default::keyRange); setValueFromOpcode(opcode, filters[filterIndex].keycenter, Default::keyRange);
} }
break; break;
case hash("fil_veltrack"): case hash("fil_veltrack"): [[fallthrough]];
case hash("fil&_veltrack"):
{ {
const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.front() - 1);
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false; return false;
setValueFromOpcode(opcode, filters[filterIndex].veltrack, Default::filterVeltrackRange); setValueFromOpcode(opcode, filters[filterIndex].veltrack, Default::filterVeltrackRange);
} }
break; break;
case hash("fil_random"): case hash("fil_random"): [[fallthrough]];
case hash("fil&_random"):
{ {
const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.front() - 1);
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false; return false;
setValueFromOpcode(opcode, filters[filterIndex].random, Default::filterRandomRange); setValueFromOpcode(opcode, filters[filterIndex].random, Default::filterRandomRange);
} }
break; break;
case hash("fil_gain"): case hash("fil_gain"): [[fallthrough]];
case hash("fil&_gain"):
{ {
const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.front() - 1);
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false; return false;
setValueFromOpcode(opcode, filters[filterIndex].gain, Default::filterGainRange); setValueFromOpcode(opcode, filters[filterIndex].gain, Default::filterGainRange);
} }
break; break;
case hash("fil_gaincc"): case hash("fil_gaincc&"): [[fallthrough]];
case hash("fil&_gaincc&"):
{ {
if (!backParameter) const auto filterIndex = opcode.parameters.size() == 1 ? 0 : (opcode.parameters.front() - 1);
return false;
const auto filterIndex { opcode.firstParameter().value_or(1) - 1 };
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false; return false;
setValueFromOpcode( setValueFromOpcode(
opcode, opcode,
filters[filterIndex].gainCC[*backParameter], filters[filterIndex].gainCC[opcode.parameters.back()],
Default::filterGainModRange Default::filterGainModRange
); );
} }
break; break;
case hash("fil_type"): case hash("fil_type"): [[fallthrough]];
case hash("fil&_type"):
{ {
const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.front() - 1);
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false; return false;
@ -545,104 +529,96 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
break; break;
// Performance parameters: EQ // Performance parameters: EQ
case hash("eq_bw"): case hash("eq&_bw"):
{ {
const auto eqNumber = opcode.firstParameter(); const auto eqNumber = opcode.parameters.front();
if (!eqNumber || *eqNumber == 0) if (eqNumber == 0)
return false; return false;
if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
return false; return false;
setValueFromOpcode(opcode, equalizers[*eqNumber - 1].bandwidth, Default::eqBandwidthRange); setValueFromOpcode(opcode, equalizers[eqNumber - 1].bandwidth, Default::eqBandwidthRange);
} }
break; break;
case hash("eq_bw_oncc"): [[fallthrough]]; case hash("eq&_bw_oncc&"): [[fallthrough]];
case hash("eq_bwcc"): case hash("eq&_bwcc&"):
{ {
const auto eqNumber = opcode.firstParameter(); const auto eqNumber = opcode.parameters.front();
if (!eqNumber || *eqNumber == 0) if (eqNumber == 0)
return false; return false;
if (!backParameter) if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
return false;
if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs))
return false; return false;
setValueFromOpcode(opcode, equalizers[*eqNumber - 1].bandwidthCC[*backParameter], Default::eqBandwidthModRange); setValueFromOpcode(opcode, equalizers[eqNumber - 1].bandwidthCC[opcode.parameters.back()], Default::eqBandwidthModRange);
} }
break; break;
case hash("eq_freq"): case hash("eq&_freq"):
{ {
const auto eqNumber = opcode.firstParameter(); const auto eqNumber = opcode.parameters.front();
if (!eqNumber || *eqNumber == 0) if (eqNumber == 0)
return false; return false;
if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
return false; return false;
setValueFromOpcode(opcode, equalizers[*eqNumber - 1].frequency, Default::eqFrequencyRange); setValueFromOpcode(opcode, equalizers[eqNumber - 1].frequency, Default::eqFrequencyRange);
} }
break; break;
case hash("eq_freq_oncc"): [[fallthrough]]; case hash("eq&_freq_oncc&"): [[fallthrough]];
case hash("eq_freqcc"): case hash("eq&_freqcc&"):
{ {
const auto eqNumber = opcode.firstParameter(); const auto eqNumber = opcode.parameters.front();
if (!eqNumber || *eqNumber == 0) if (eqNumber == 0)
return false; return false;
if (!backParameter) if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
return false;
if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs))
return false; return false;
setValueFromOpcode(opcode, equalizers[*eqNumber - 1].frequencyCC[*backParameter], Default::eqFrequencyModRange); setValueFromOpcode(opcode, equalizers[eqNumber - 1].frequencyCC[opcode.parameters.back()], Default::eqFrequencyModRange);
} }
break; break;
case hash("eq_velfreq"): case hash("eq&_vel&freq"):
{ {
const auto eqNumber = opcode.firstParameter(); const auto eqNumber = opcode.parameters.front();
const auto check2 = opcode.middleParameter(); if (eqNumber == 0)
if (!eqNumber || *eqNumber == 0)
return false; return false;
if (!check2 || *check2 != 2 || opcode.parameterPositions[1] != 6) if (opcode.parameters[1] != 2)
return false; // was eqN_vel3freq or something else than eqN_vel2freq return false; // was eqN_vel3freq or something else than eqN_vel2freq
if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
return false; return false;
setValueFromOpcode(opcode, equalizers[*eqNumber - 1].vel2frequency, Default::eqFrequencyModRange); setValueFromOpcode(opcode, equalizers[eqNumber - 1].vel2frequency, Default::eqFrequencyModRange);
} }
break; break;
case hash("eq_gain"): case hash("eq&_gain"):
{ {
const auto eqNumber = opcode.firstParameter(); const auto eqNumber = opcode.parameters.front();
if (!eqNumber || *eqNumber == 0) if (eqNumber == 0)
return false; return false;
if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
return false; return false;
setValueFromOpcode(opcode, equalizers[*eqNumber - 1].gain, Default::eqGainRange); setValueFromOpcode(opcode, equalizers[eqNumber - 1].gain, Default::eqGainRange);
} }
break; break;
case hash("eq_gain_oncc"): [[fallthrough]]; case hash("eq&_gain_oncc&"): [[fallthrough]];
case hash("eq_gaincc"): case hash("eq&_gaincc&"):
{ {
const auto eqNumber = opcode.firstParameter(); const auto eqNumber = opcode.parameters.front();
if (!eqNumber || *eqNumber == 0) if (eqNumber == 0)
return false; return false;
if (!backParameter) if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
return false;
if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs))
return false; return false;
setValueFromOpcode(opcode, equalizers[*eqNumber - 1].gainCC[*backParameter], Default::eqGainModRange); setValueFromOpcode(opcode, equalizers[eqNumber - 1].gainCC[opcode.parameters.back()], Default::eqGainModRange);
} }
break; break;
case hash("eq_velgain"): case hash("eq&_vel&gain"):
{ {
const auto eqNumber = opcode.firstParameter(); const auto eqNumber = opcode.parameters.front();
const auto check2 = opcode.middleParameter(); if (eqNumber == 0)
if (!eqNumber || *eqNumber == 0)
return false; return false;
if (!check2 || *check2 != 2 || opcode.parameterPositions[1] != 6) if (opcode.parameters[1] != 2)
return false; // was eqN_vel3gain or something else than eqN_vel2gain return false; // was eqN_vel3gain or something else than eqN_vel2gain
if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
return false; return false;
setValueFromOpcode(opcode, equalizers[*eqNumber - 1].vel2gain, Default::eqGainModRange); setValueFromOpcode(opcode, equalizers[eqNumber - 1].vel2gain, Default::eqGainModRange);
} }
break; break;
// Performance parameters: pitch // Performance parameters: pitch
@ -662,7 +638,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
case hash("transpose"): case hash("transpose"):
setValueFromOpcode(opcode, transpose, Default::transposeRange); setValueFromOpcode(opcode, transpose, Default::transposeRange);
break; break;
case hash("tune"): case hash("tune"): [[fallthrough]];
case hash("pitch"): case hash("pitch"):
setValueFromOpcode(opcode, tune, Default::tuneRange); setValueFromOpcode(opcode, tune, Default::tuneRange);
break; break;
@ -698,56 +674,62 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
case hash("ampeg_sustain"): case hash("ampeg_sustain"):
setValueFromOpcode(opcode, amplitudeEG.sustain, Default::egPercentRange); setValueFromOpcode(opcode, amplitudeEG.sustain, Default::egPercentRange);
break; break;
case hash("ampeg_velattack"): case hash("ampeg_vel&attack"):
if (!opcode.parameters.empty() && opcode.parameters.front() == 2) if (opcode.parameters.front() != 2)
return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2attack, Default::egOnCCTimeRange); setValueFromOpcode(opcode, amplitudeEG.vel2attack, Default::egOnCCTimeRange);
break; break;
case hash("ampeg_veldecay"): case hash("ampeg_vel&decay"):
if (!opcode.parameters.empty() && opcode.parameters.front() == 2) if (opcode.parameters.front() != 2)
return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2decay, Default::egOnCCTimeRange); setValueFromOpcode(opcode, amplitudeEG.vel2decay, Default::egOnCCTimeRange);
break; break;
case hash("ampeg_veldelay"): case hash("ampeg_vel&delay"):
if (!opcode.parameters.empty() && opcode.parameters.front() == 2) if (opcode.parameters.front() != 2)
return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2delay, Default::egOnCCTimeRange); setValueFromOpcode(opcode, amplitudeEG.vel2delay, Default::egOnCCTimeRange);
break; break;
case hash("ampeg_velhold"): case hash("ampeg_vel&hold"):
if (!opcode.parameters.empty() && opcode.parameters.front() == 2) if (opcode.parameters.front() != 2)
return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2hold, Default::egOnCCTimeRange); setValueFromOpcode(opcode, amplitudeEG.vel2hold, Default::egOnCCTimeRange);
break; break;
case hash("ampeg_velrelease"): case hash("ampeg_vel&release"):
if (!opcode.parameters.empty() && opcode.parameters.front() == 2) if (opcode.parameters.front() != 2)
return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2release, Default::egOnCCTimeRange); setValueFromOpcode(opcode, amplitudeEG.vel2release, Default::egOnCCTimeRange);
break; break;
case hash("ampeg_velsustain"): case hash("ampeg_vel&sustain"):
if (!opcode.parameters.empty() && opcode.parameters.front() == 2) if (opcode.parameters.front() != 2)
return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2sustain, Default::egOnCCPercentRange); setValueFromOpcode(opcode, amplitudeEG.vel2sustain, Default::egOnCCPercentRange);
break; break;
case hash("ampeg_attackcc"): case hash("ampeg_attackcc&"): [[fallthrough]];
case hash("ampeg_attack_oncc"): case hash("ampeg_attack_oncc&"):
setCCPairFromOpcode(opcode, amplitudeEG.ccAttack, Default::egOnCCTimeRange); setCCPairFromOpcode(opcode, amplitudeEG.ccAttack, Default::egOnCCTimeRange);
break; break;
case hash("ampeg_decaycc"): case hash("ampeg_decaycc&"): [[fallthrough]];
case hash("ampeg_decay_oncc"): case hash("ampeg_decay_oncc&"):
setCCPairFromOpcode(opcode, amplitudeEG.ccDecay, Default::egOnCCTimeRange); setCCPairFromOpcode(opcode, amplitudeEG.ccDecay, Default::egOnCCTimeRange);
break; break;
case hash("ampeg_delaycc"): case hash("ampeg_delaycc&"): [[fallthrough]];
case hash("ampeg_delay_oncc"): case hash("ampeg_delay_oncc&"):
setCCPairFromOpcode(opcode, amplitudeEG.ccDelay, Default::egOnCCTimeRange); setCCPairFromOpcode(opcode, amplitudeEG.ccDelay, Default::egOnCCTimeRange);
break; break;
case hash("ampeg_holdcc"): case hash("ampeg_holdcc&"): [[fallthrough]];
case hash("ampeg_hold_oncc"): case hash("ampeg_hold_oncc&"):
setCCPairFromOpcode(opcode, amplitudeEG.ccHold, Default::egOnCCTimeRange); setCCPairFromOpcode(opcode, amplitudeEG.ccHold, Default::egOnCCTimeRange);
break; break;
case hash("ampeg_releasecc"): case hash("ampeg_releasecc&"): [[fallthrough]];
case hash("ampeg_release_oncc"): case hash("ampeg_release_oncc&"):
setCCPairFromOpcode(opcode, amplitudeEG.ccRelease, Default::egOnCCTimeRange); setCCPairFromOpcode(opcode, amplitudeEG.ccRelease, Default::egOnCCTimeRange);
break; break;
case hash("ampeg_startcc"): case hash("ampeg_startcc&"): [[fallthrough]];
case hash("ampeg_start_oncc"): case hash("ampeg_start_oncc&"):
setCCPairFromOpcode(opcode, amplitudeEG.ccStart, Default::egOnCCPercentRange); setCCPairFromOpcode(opcode, amplitudeEG.ccStart, Default::egOnCCPercentRange);
break; break;
case hash("ampeg_sustaincc"): case hash("ampeg_sustaincc&"): [[fallthrough]];
case hash("ampeg_sustain_oncc"): case hash("ampeg_sustain_oncc&"):
setCCPairFromOpcode(opcode, amplitudeEG.ccSustain, Default::egOnCCPercentRange); setCCPairFromOpcode(opcode, amplitudeEG.ccSustain, Default::egOnCCPercentRange);
break; break;
@ -755,7 +737,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
case hash("hichan"): case hash("hichan"):
case hash("lochan"): case hash("lochan"):
case hash("ampeg_depth"): case hash("ampeg_depth"):
case hash("ampeg_vel2depth"): case hash("ampeg_vel&depth"):
break; break;
default: default:
return false; return false;

View file

@ -66,3 +66,24 @@ constexpr uint64_t hash(absl::string_view s, uint64_t h = Fnv1aBasis)
return h; return h;
} }
/**
* @brief Same function as `hash()` but ignores ampersands (&)
*
* See e.g. the Region.cpp file
*
* @param s the input string to be hashed
* @param h the hashing seed to use
* @return uint64_t
*/
constexpr uint64_t hashNoAmpersand(absl::string_view s, uint64_t h = Fnv1aBasis)
{
if (s.length() > 0) {
if (s.front() == '&')
return hashNoAmpersand( { s.data() + 1, s.length() - 1 }, h );
else
return hashNoAmpersand( { s.data() + 1, s.length() - 1 }, (h ^ s.front()) * Fnv1aPrime );
}
return h;
}

View file

@ -153,21 +153,18 @@ void sfz::Synth::handleGlobalOpcodes(const std::vector<Opcode>& members)
void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& members) void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& members)
{ {
for (auto& member : members) { for (auto& member : members) {
const auto backParameter = member.backParameter();
switch (member.lettersOnlyHash) { switch (member.lettersOnlyHash) {
case hash("Set_cc"): case hash("Set_cc&"): [[fallthrough]];
[[fallthrough]]; case hash("set_cc&"):
case hash("set_cc"): if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) {
if (backParameter && Default::ccNumberRange.containsWithEnd(*backParameter)) {
const auto ccValue = readOpcode(member.value, Default::ccValueRange).value_or(0); const auto ccValue = readOpcode(member.value, Default::ccValueRange).value_or(0);
resources.midiState.ccEvent(*backParameter, ccValue); resources.midiState.ccEvent(member.parameters.back(), ccValue);
} }
break; break;
case hash("Label_cc"): case hash("Label_cc&"): [[fallthrough]];
[[fallthrough]]; case hash("label_cc&"):
case hash("label_cc"): if (Default::ccNumberRange.containsWithEnd(member.parameters.back()))
if (backParameter && Default::ccNumberRange.containsWithEnd(*backParameter)) ccNames.emplace_back(member.parameters.back(), std::string(member.value));
ccNames.emplace_back(*backParameter, std::string(member.value));
break; break;
case hash("Default_path"): case hash("Default_path"):
[[fallthrough]]; [[fallthrough]];

View file

@ -17,9 +17,6 @@ TEST_CASE("[Opcode] Construction")
REQUIRE(opcode.lettersOnlyHash == hash("sample")); REQUIRE(opcode.lettersOnlyHash == hash("sample"));
REQUIRE(opcode.parameters.empty()); REQUIRE(opcode.parameters.empty());
REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.value == "dummy");
REQUIRE(!opcode.backParameter());
REQUIRE(!opcode.firstParameter());
REQUIRE(!opcode.middleParameter());
} }
SECTION("Normal construction with underscore") SECTION("Normal construction with underscore")
@ -29,56 +26,69 @@ TEST_CASE("[Opcode] Construction")
REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore"));
REQUIRE(opcode.parameters.empty()); REQUIRE(opcode.parameters.empty());
REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.value == "dummy");
REQUIRE(!opcode.backParameter()); }
REQUIRE(!opcode.firstParameter());
REQUIRE(!opcode.middleParameter()); SECTION("Normal construction with ampersand")
{
sfz::Opcode opcode { "sample&_ampersand", "dummy" };
REQUIRE(opcode.opcode == "sample&_ampersand");
REQUIRE(opcode.lettersOnlyHash == hash("sample_ampersand"));
REQUIRE(opcode.parameters.empty());
REQUIRE(opcode.value == "dummy");
}
SECTION("Normal construction with multiple ampersands")
{
sfz::Opcode opcode { "&sample&_ampersand&", "dummy" };
REQUIRE(opcode.opcode == "&sample&_ampersand&");
REQUIRE(opcode.lettersOnlyHash == hash("sample_ampersand"));
REQUIRE(opcode.parameters.empty());
REQUIRE(opcode.value == "dummy");
} }
SECTION("Parameterized opcode") SECTION("Parameterized opcode")
{ {
sfz::Opcode opcode { "sample123", "dummy" }; sfz::Opcode opcode { "sample123", "dummy" };
REQUIRE(opcode.opcode == "sample123"); REQUIRE(opcode.opcode == "sample123");
REQUIRE(opcode.lettersOnlyHash == hash("sample")); REQUIRE(opcode.lettersOnlyHash == hash("sample&"));
REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.value == "dummy");
REQUIRE(opcode.parameters.size() == 1); REQUIRE(opcode.parameters.size() == 1);
REQUIRE(opcode.parameters == std::vector<uint8_t>({ 123 })); REQUIRE(opcode.parameters == std::vector<uint16_t>({ 123 }));
REQUIRE(opcode.parameterPositions == std::vector<int>({ 6 })); }
REQUIRE(opcode.backParameter());
REQUIRE(*opcode.backParameter() == 123); SECTION("Parameterized opcode with ampersand")
REQUIRE(!opcode.firstParameter()); {
REQUIRE(!opcode.middleParameter()); sfz::Opcode opcode { "sample&123", "dummy" };
REQUIRE(opcode.opcode == "sample&123");
REQUIRE(opcode.lettersOnlyHash == hash("sample&"));
REQUIRE(opcode.value == "dummy");
REQUIRE(opcode.parameters.size() == 1);
REQUIRE(opcode.parameters == std::vector<uint16_t>({ 123 }));
} }
SECTION("Parameterized opcode with underscore") SECTION("Parameterized opcode with underscore")
{ {
sfz::Opcode opcode { "sample_underscore123", "dummy" }; sfz::Opcode opcode { "sample_underscore123", "dummy" };
REQUIRE(opcode.opcode == "sample_underscore123"); REQUIRE(opcode.opcode == "sample_underscore123");
REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore&"));
REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.value == "dummy");
REQUIRE(opcode.parameters == std::vector<uint8_t>({ 123 })); REQUIRE(opcode.parameters == std::vector<uint16_t>({ 123 }));
REQUIRE(opcode.parameterPositions == std::vector<int>({ 17 }));
REQUIRE(opcode.backParameter());
REQUIRE(*opcode.backParameter() == 123);
} }
SECTION("Parameterized opcode within the opcode") SECTION("Parameterized opcode within the opcode")
{ {
sfz::Opcode opcode { "sample1_underscore", "dummy" }; sfz::Opcode opcode { "sample1_underscore", "dummy" };
REQUIRE(opcode.opcode == "sample1_underscore"); REQUIRE(opcode.opcode == "sample1_underscore");
REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); REQUIRE(opcode.lettersOnlyHash == hash("sample&_underscore"));
REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.value == "dummy");
REQUIRE(opcode.parameters == std::vector<uint8_t>({ 1 })); REQUIRE(opcode.parameters == std::vector<uint16_t>({ 1 }));
REQUIRE(!opcode.backParameter());
REQUIRE(opcode.firstParameter());
REQUIRE(*opcode.firstParameter() == 1);
REQUIRE(!opcode.middleParameter());
} }
SECTION("Parameterized opcode within the opcode") SECTION("Parameterized opcode within the opcode")
{ {
sfz::Opcode opcode { "sample123_underscore", "dummy" }; sfz::Opcode opcode { "sample123_underscore", "dummy" };
REQUIRE(opcode.opcode == "sample123_underscore"); REQUIRE(opcode.opcode == "sample123_underscore");
REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); REQUIRE(opcode.lettersOnlyHash == hash("sample&_underscore"));
REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.value == "dummy");
REQUIRE(opcode.parameters.size() == 1); REQUIRE(opcode.parameters.size() == 1);
REQUIRE(opcode.parameters[0] == 123); REQUIRE(opcode.parameters[0] == 123);
@ -88,35 +98,22 @@ TEST_CASE("[Opcode] Construction")
{ {
sfz::Opcode opcode { "sample123_double44_underscore", "dummy" }; sfz::Opcode opcode { "sample123_double44_underscore", "dummy" };
REQUIRE(opcode.opcode == "sample123_double44_underscore"); REQUIRE(opcode.opcode == "sample123_double44_underscore");
REQUIRE(opcode.lettersOnlyHash == hash("sample_double_underscore")); REQUIRE(opcode.lettersOnlyHash == hash("sample&_double&_underscore"));
REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.value == "dummy");
REQUIRE(opcode.parameters.size() == 2); REQUIRE(opcode.parameters.size() == 2);
REQUIRE(opcode.parameters[0] == 123); REQUIRE(opcode.parameters[0] == 123);
REQUIRE(opcode.parameters[1] == 44); REQUIRE(opcode.parameters[1] == 44);
REQUIRE(opcode.parameters == std::vector<uint8_t>({ 123, 44 })); REQUIRE(opcode.parameters == std::vector<uint16_t>({ 123, 44 }));
REQUIRE(opcode.parameterPositions == std::vector<int>({ 6, 13 }));
REQUIRE(!opcode.backParameter());
REQUIRE(opcode.firstParameter());
REQUIRE(*opcode.firstParameter() == 123);
REQUIRE(opcode.middleParameter());
REQUIRE(*opcode.middleParameter() == 44);
} }
SECTION("Parameterized opcode within the opcode twice, with a back parameter") SECTION("Parameterized opcode within the opcode twice, with a back parameter")
{ {
sfz::Opcode opcode { "sample123_double44_underscore23", "dummy" }; sfz::Opcode opcode { "sample123_double44_underscore23", "dummy" };
REQUIRE(opcode.opcode == "sample123_double44_underscore23"); REQUIRE(opcode.opcode == "sample123_double44_underscore23");
REQUIRE(opcode.lettersOnlyHash == hash("sample_double_underscore")); REQUIRE(opcode.lettersOnlyHash == hash("sample&_double&_underscore&"));
REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.value == "dummy");
REQUIRE(opcode.parameters.size() == 3); REQUIRE(opcode.parameters.size() == 3);
REQUIRE(opcode.parameters == std::vector<uint8_t>({ 123, 44, 23 })); REQUIRE(opcode.parameters == std::vector<uint16_t>({ 123, 44, 23 }));
REQUIRE(opcode.parameterPositions == std::vector<int>({ 6, 13, 24 }));
REQUIRE(opcode.backParameter());
REQUIRE(*opcode.backParameter() == 23);
REQUIRE(opcode.firstParameter());
REQUIRE(*opcode.firstParameter() == 123);
REQUIRE(opcode.middleParameter());
REQUIRE(*opcode.middleParameter() == 44);
} }
} }