From 97c9411f8d5fc41905bc3f3c2641894838f4fe60 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Tue, 9 Mar 2021 18:43:59 +0100 Subject: [PATCH] Refactor the opcode handling for LFO and EG --- src/sfizz/Opcode.cpp | 22 + src/sfizz/Opcode.h | 6 + src/sfizz/Region.cpp | 942 ++++++++++++++++++------------------------- src/sfizz/Region.h | 16 + 4 files changed, 444 insertions(+), 542 deletions(-) diff --git a/src/sfizz/Opcode.cpp b/src/sfizz/Opcode.cpp index 47fbb98f..79ac2eac 100644 --- a/src/sfizz/Opcode.cpp +++ b/src/sfizz/Opcode.cpp @@ -53,6 +53,28 @@ static absl::string_view extractBackInteger(absl::string_view opcodeName) return opcodeName.substr(i); } +std::string Opcode::getLetterOnlyName() const +{ + absl::string_view name { this->name }; + + std::string letterOnlyName; + letterOnlyName.reserve(name.size()); + + bool charWasDigit = false; + for (unsigned char c : name) { + bool charIsDigit = absl::ascii_isdigit(c); + + if (!charIsDigit) + letterOnlyName.push_back(c); + else if (!charWasDigit) + letterOnlyName.push_back('&'); + + charWasDigit = charIsDigit; + } + + return letterOnlyName; +} + std::string Opcode::getDerivedName(OpcodeCategory newCategory, unsigned number) const { std::string derivedName(name); diff --git a/src/sfizz/Opcode.h b/src/sfizz/Opcode.h index ad4d4acc..0982a678 100644 --- a/src/sfizz/Opcode.h +++ b/src/sfizz/Opcode.h @@ -82,6 +82,12 @@ struct Opcode { */ Opcode cleanUp(OpcodeScope scope) const; + /** + * @brief Calculate a letter-only name, replacing any digit sequence with + * in the opcode name with a single ampersand character. + */ + std::string getLetterOnlyName() const; + /* * @brief Get the derived opcode name to convert it to another category. * diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 6283cbf4..453e432f 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -14,6 +14,7 @@ #include "modulations/ModId.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_cat.h" +#include "absl/strings/match.h" #include "absl/algorithm/container.h" #include #include @@ -45,32 +46,18 @@ sfz::Region::Region(int regionNumber, const MidiState& midiState, absl::string_v amplitudeEG.release = Default::egRelease; } +// Helper for ccN processing +#define case_any_ccN(x) \ + case hash(x "_oncc&"): \ + case hash(x "_curvecc&"): \ + case hash(x "_stepcc&"): \ + case hash(x "_smoothcc&") + bool sfz::Region::parseOpcode(const Opcode& rawOpcode) { const Opcode opcode = rawOpcode.cleanUp(kOpcodeScopeRegion); switch (opcode.lettersOnlyHash) { - // Helper for ccN processing - #define case_any_ccN(x) \ - case hash(x "_oncc&"): \ - case hash(x "_curvecc&"): \ - case hash(x "_stepcc&"): \ - case hash(x "_smoothcc&") - - #define LFO_EG_filter_EQ_target(sourceKey, targetKey, spec) \ - { \ - const auto number = opcode.parameters.front(); \ - if (number == 0) \ - return false; \ - \ - const auto index = opcode.parameters.size() == 2 ? opcode.parameters.back() - 1 : 0; \ - if (!extendIfNecessary(filters, index + 1, Default::numFilters)) \ - return false; \ - \ - const ModKey source = ModKey::createNXYZ(sourceKey, id, number - 1); \ - const ModKey target = ModKey::createNXYZ(targetKey, id, index); \ - getOrCreateConnection(source, target).sourceDepth = opcode.read(spec); \ - } // Sound source: sample playback case hash("sample"): @@ -747,523 +734,6 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) bendSmooth = opcode.read(Default::smoothCC); break; - // Modulation: LFO - case hash("lfo&_freq"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - lfos[lfoNumber - 1].freq = opcode.read(Default::lfoFreq); - } - break; - case_any_ccN("lfo&_freq"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - processGenericCc(opcode, Default::lfoFreqMod, ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber - 1)); - } - break; - case hash("lfo&_beats"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - lfos[lfoNumber - 1].beats = opcode.read(Default::lfoBeats); - } - break; - case_any_ccN("lfo&_beats"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - processGenericCc(opcode, Default::lfoBeatsMod, ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber - 1)); - } - break; - case hash("lfo&_phase"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - lfos[lfoNumber - 1].phase0 = opcode.read(Default::lfoPhase); - } - break; - case hash("lfo&_delay"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - lfos[lfoNumber - 1].delay = opcode.read(Default::lfoDelay); - } - break; - case hash("lfo&_fade"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - lfos[lfoNumber - 1].fade = opcode.read(Default::lfoFade); - } - break; - case hash("lfo&_count"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - lfos[lfoNumber - 1].count = opcode.read(Default::lfoCount); - } - break; - case hash("lfo&_steps"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - if (!lfos[lfoNumber - 1].seq) - lfos[lfoNumber - 1].seq = LFODescription::StepSequence(); - lfos[lfoNumber - 1].seq->steps.resize(opcode.read(Default::lfoSteps)); - } - break; - case hash("lfo&_step&"): - { - const auto lfoNumber = opcode.parameters.front(); - const auto stepNumber = opcode.parameters[1]; - if (lfoNumber == 0 || stepNumber == 0 || stepNumber > config::maxLFOSteps) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - if (!lfos[lfoNumber - 1].seq) - lfos[lfoNumber - 1].seq = LFODescription::StepSequence(); - if (!extendIfNecessary(lfos[lfoNumber - 1].seq->steps, stepNumber, Default::numLFOSteps)) - return false; - lfos[lfoNumber - 1].seq->steps[stepNumber - 1] = opcode.read(Default::lfoStepX); - } - break; - case hash("lfo&_wave&"): // also lfo&_wave - { - const auto lfoNumber = opcode.parameters.front(); - const auto subNumber = opcode.parameters[1]; - if (lfoNumber == 0 || subNumber == 0 || subNumber > config::maxLFOSubs) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs)) - return false; - lfos[lfoNumber - 1].sub[subNumber - 1].wave = opcode.read(Default::lfoWave); - } - break; - case hash("lfo&_offset&"): // also lfo&_offset - { - const auto lfoNumber = opcode.parameters.front(); - const auto subNumber = opcode.parameters[1]; - if (lfoNumber == 0 || subNumber == 0 || subNumber > config::maxLFOSubs) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs)) - return false; - lfos[lfoNumber - 1].sub[subNumber - 1].offset = opcode.read(Default::lfoOffset); - } - break; - case hash("lfo&_ratio&"): // also lfo&_ratio - { - const auto lfoNumber = opcode.parameters.front(); - const auto subNumber = opcode.parameters[1]; - if (lfoNumber == 0 || subNumber == 0 || subNumber > config::maxLFOSubs) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs)) - return false; - lfos[lfoNumber - 1].sub[subNumber - 1].ratio = opcode.read(Default::lfoRatio); - } - break; - case hash("lfo&_scale&"): // also lfo&_scale - { - const auto lfoNumber = opcode.parameters.front(); - const auto subNumber = opcode.parameters[1]; - if (lfoNumber == 0 || subNumber == 0 || subNumber > config::maxLFOSubs) - return false; - if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) - return false; - if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs)) - return false; - lfos[lfoNumber - 1].sub[subNumber - 1].scale = opcode.read(Default::lfoScale); - } - break; - - // Modulation: LFO (targets) - case hash("lfo&_amplitude"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1); - const ModKey target = ModKey::createNXYZ(ModId::Amplitude, id); - getOrCreateConnection(source, target).sourceDepth = - opcode.read(Default::amplitudeMod); - } - break; - case hash("lfo&_pan"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1); - const ModKey target = ModKey::createNXYZ(ModId::Pan, id); - getOrCreateConnection(source, target).sourceDepth = - opcode.read(Default::panMod); - } - break; - case hash("lfo&_width"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1); - const ModKey target = ModKey::createNXYZ(ModId::Width, id); - getOrCreateConnection(source, target).sourceDepth = - opcode.read(Default::widthMod); - } - break; - case hash("lfo&_position"): // sfizz extension - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1); - const ModKey target = ModKey::createNXYZ(ModId::Position, id); - getOrCreateConnection(source, target).sourceDepth = - opcode.read(Default::positionMod); - } - break; - case hash("lfo&_pitch"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1); - const ModKey target = ModKey::createNXYZ(ModId::Pitch, id); - getOrCreateConnection(source, target).sourceDepth = - opcode.read(Default::pitchMod); - } - break; - case hash("lfo&_volume"): - { - const auto lfoNumber = opcode.parameters.front(); - if (lfoNumber == 0) - return false; - const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1); - const ModKey target = ModKey::createNXYZ(ModId::Volume, id); - getOrCreateConnection(source, target).sourceDepth = - opcode.read(Default::volumeMod); - } - break; - case hash("lfo&_cutoff&"): - LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilCutoff, Default::filterCutoffMod); - break; - case hash("lfo&_resonance&"): - LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilResonance, Default::filterResonanceMod); - break; - case hash("lfo&_fil&gain"): - LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilGain, Default::filterGainMod); - break; - case hash("lfo&_eq&gain"): - LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqGain, Default::eqGainMod); - break; - case hash("lfo&_eq&freq"): - LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqFrequency, Default::eqFrequencyMod); - break; - case hash("lfo&_eq&bw"): - LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqBandwidth, Default::eqBandwidthMod); - break; - - // Modulation: Flex EG (targets) - case hash("eg&_amplitude"): - { - const auto egNumber = opcode.parameters.front(); - if (egNumber == 0) - return false; - const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); - const ModKey target = ModKey::createNXYZ(ModId::Amplitude, id); - getOrCreateConnection(source, target).sourceDepth = - opcode.read(Default::amplitudeMod); - } - break; - case hash("eg&_pan"): - { - const auto egNumber = opcode.parameters.front(); - if (egNumber == 0) - return false; - const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); - const ModKey target = ModKey::createNXYZ(ModId::Pan, id); - getOrCreateConnection(source, target).sourceDepth = - opcode.read(Default::panMod); - } - break; - case hash("eg&_width"): - { - const auto egNumber = opcode.parameters.front(); - if (egNumber == 0) - return false; - const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); - const ModKey target = ModKey::createNXYZ(ModId::Width, id); - getOrCreateConnection(source, target).sourceDepth = - opcode.read(Default::widthMod); - } - break; - case hash("eg&_position"): // sfizz extension - { - const auto egNumber = opcode.parameters.front(); - if (egNumber == 0) - return false; - const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); - const ModKey target = ModKey::createNXYZ(ModId::Position, id); - getOrCreateConnection(source, target).sourceDepth = - opcode.read(Default::positionMod); - } - break; - case hash("eg&_pitch"): - { - const auto egNumber = opcode.parameters.front(); - if (egNumber == 0) - return false; - const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); - const ModKey target = ModKey::createNXYZ(ModId::Pitch, id); - getOrCreateConnection(source, target).sourceDepth = - opcode.read(Default::pitchMod); - } - break; - case hash("eg&_volume"): - { - const auto egNumber = opcode.parameters.front(); - if (egNumber == 0) - return false; - const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); - const ModKey target = ModKey::createNXYZ(ModId::Volume, id); - getOrCreateConnection(source, target).sourceDepth = - opcode.read(Default::volumeMod); - } - break; - case hash("eg&_cutoff&"): - LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilCutoff, Default::filterCutoffMod); - break; - case hash("eg&_resonance&"): - LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilResonance, Default::filterResonanceMod); - break; - case hash("eg&_fil&gain"): - LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilGain, Default::filterGainMod); - break; - case hash("eg&_eq&gain"): - LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqGain, Default::eqGainMod); - break; - case hash("eg&_eq&freq"): - LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqFrequency, Default::eqFrequencyMod); - break; - case hash("eg&_eq&bw"): - LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqBandwidth, Default::eqBandwidthMod); - break; - - case hash("eg&_ampeg"): - { - const auto egNumber = opcode.parameters.front(); - if (egNumber == 0) - return false; - if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) - return false; - auto ampeg = opcode.read(Default::flexEGAmpeg); - FlexEGDescription& desc = flexEGs[egNumber - 1]; - if (desc.ampeg != ampeg) { - desc.ampeg = ampeg; - flexAmpEG = absl::nullopt; - for (size_t i = 0, n = flexEGs.size(); i < n && !flexAmpEG; ++i) { - if (flexEGs[i].ampeg) - flexAmpEG = static_cast(i); - } - } - break; - } - - // Amplitude Envelope - case hash("ampeg_attack"): - case hash("ampeg_decay"): - case hash("ampeg_delay"): - case hash("ampeg_hold"): - case hash("ampeg_release"): - case hash("ampeg_start"): - case hash("ampeg_sustain"): - case hash("ampeg_veltoattack"): // also ampeg_vel2attack - case hash("ampeg_veltodecay"): // also ampeg_vel2decay - case hash("ampeg_veltodelay"): // also ampeg_vel2delay - case hash("ampeg_veltohold"): // also ampeg_vel2hold - case hash("ampeg_veltorelease"): // also ampeg_vel2release - case hash("ampeg_veltosustain"): // also ampeg_vel2sustain - case hash("ampeg_attack_oncc&"): // also ampeg_attackcc& - case hash("ampeg_decay_oncc&"): // also ampeg_decaycc& - case hash("ampeg_delay_oncc&"): // also ampeg_delaycc& - case hash("ampeg_hold_oncc&"): // also ampeg_holdcc& - case hash("ampeg_release_oncc&"): // also ampeg_releasecc& - case hash("ampeg_start_oncc&"): // also ampeg_startcc& - case hash("ampeg_sustain_oncc&"): // also ampeg_sustaincc& - parseEGOpcode(opcode, amplitudeEG); - break; - - case hash("pitcheg_attack"): - case hash("pitcheg_decay"): - case hash("pitcheg_delay"): - case hash("pitcheg_hold"): - case hash("pitcheg_release"): - case hash("pitcheg_start"): - case hash("pitcheg_sustain"): - case hash("pitcheg_veltoattack"): // also pitcheg_vel2attack - case hash("pitcheg_veltodecay"): // also pitcheg_vel2decay - case hash("pitcheg_veltodelay"): // also pitcheg_vel2delay - case hash("pitcheg_veltohold"): // also pitcheg_vel2hold - case hash("pitcheg_veltorelease"): // also pitcheg_vel2release - case hash("pitcheg_veltosustain"): // also pitcheg_vel2sustain - case hash("pitcheg_attack_oncc&"): // also pitcheg_attackcc& - case hash("pitcheg_decay_oncc&"): // also pitcheg_decaycc& - case hash("pitcheg_delay_oncc&"): // also pitcheg_delaycc& - case hash("pitcheg_hold_oncc&"): // also pitcheg_holdcc& - case hash("pitcheg_release_oncc&"): // also pitcheg_releasecc& - case hash("pitcheg_start_oncc&"): // also pitcheg_startcc& - case hash("pitcheg_sustain_oncc&"): // also pitcheg_sustaincc& - if (parseEGOpcode(opcode, pitchEG)) - getOrCreateConnection( - ModKey::createNXYZ(ModId::PitchEG, id), - ModKey::createNXYZ(ModId::Pitch, id)); - break; - - case hash("fileg_attack"): - case hash("fileg_decay"): - case hash("fileg_delay"): - case hash("fileg_hold"): - case hash("fileg_release"): - case hash("fileg_start"): - case hash("fileg_sustain"): - case hash("fileg_veltoattack"): // also fileg_vel2attack - case hash("fileg_veltodecay"): // also fileg_vel2decay - case hash("fileg_veltodelay"): // also fileg_vel2delay - case hash("fileg_veltohold"): // also fileg_vel2hold - case hash("fileg_veltorelease"): // also fileg_vel2release - case hash("fileg_veltosustain"): // also fileg_vel2sustain - case hash("fileg_attack_oncc&"): // also fileg_attackcc& - case hash("fileg_decay_oncc&"): // also fileg_decaycc& - case hash("fileg_delay_oncc&"): // also fileg_delaycc& - case hash("fileg_hold_oncc&"): // also fileg_holdcc& - case hash("fileg_release_oncc&"): // also fileg_releasecc& - case hash("fileg_start_oncc&"): // also fileg_startcc& - case hash("fileg_sustain_oncc&"): // also fileg_sustaincc& - if (parseEGOpcode(opcode, filterEG)) - getOrCreateConnection( - ModKey::createNXYZ(ModId::FilEG, id), - ModKey::createNXYZ(ModId::FilCutoff, id)); - break; - - case hash("pitcheg_depth"): - getOrCreateConnection( - ModKey::createNXYZ(ModId::PitchEG, id), - ModKey::createNXYZ(ModId::Pitch, id)).sourceDepth = opcode.read(Default::egDepth); - break; - case hash("fileg_depth"): - getOrCreateConnection( - ModKey::createNXYZ(ModId::FilEG, id), - ModKey::createNXYZ(ModId::FilCutoff, id)).sourceDepth = opcode.read(Default::egDepth); - break; - - case hash("pitcheg_veltodepth"): // also pitcheg_vel2depth - getOrCreateConnection( - ModKey::createNXYZ(ModId::PitchEG, id), - ModKey::createNXYZ(ModId::Pitch, id)).velToDepth = opcode.read(Default::egVel2Depth); - break; - case hash("fileg_veltodepth"): // also fileg_vel2depth - getOrCreateConnection( - ModKey::createNXYZ(ModId::FilEG, id), - ModKey::createNXYZ(ModId::FilCutoff, id)).velToDepth = opcode.read(Default::egVel2Depth); - break; - - // Flex envelopes - case hash("eg&_dynamic"): - { - const auto egNumber = opcode.parameters.front(); - if (egNumber == 0) - return false; - if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) - return false; - auto& eg = flexEGs[egNumber - 1]; - eg.dynamic = opcode.read(Default::flexEGDynamic); - } - break; - case hash("eg&_sustain"): - { - const auto egNumber = opcode.parameters.front(); - if (egNumber == 0) - return false; - if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) - return false; - auto& eg = flexEGs[egNumber - 1]; - eg.sustain = opcode.read(Default::flexEGSustain); - } - break; - case hash("eg&_time&"): - { - const auto egNumber = opcode.parameters.front(); - if (egNumber == 0) - return false; - if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) - return false; - auto& eg = flexEGs[egNumber - 1]; - const auto pointNumber = opcode.parameters[1]; - if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints)) - return false; - eg.points[pointNumber].time = opcode.read(Default::flexEGPointTime); - } - break; - case hash("eg&_level&"): - { - const auto egNumber = opcode.parameters.front(); - if (egNumber == 0) - return false; - if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) - return false; - auto& eg = flexEGs[egNumber - 1]; - const auto pointNumber = opcode.parameters[1]; - if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints)) - return false; - eg.points[pointNumber].level = opcode.read(Default::flexEGPointLevel); - } - break; - case hash("eg&_shape&"): - { - const auto egNumber = opcode.parameters.front(); - if (egNumber == 0) - return false; - if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) - return false; - auto& eg = flexEGs[egNumber - 1]; - const auto pointNumber = opcode.parameters[1]; - if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints)) - return false; - eg.points[pointNumber].setShape(opcode.read(Default::flexEGPointShape)); - } - break; - case hash("effect&"): { const auto effectNumber = opcode.parameters.back(); @@ -1284,11 +754,49 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("ampeg_depth"): case hash("ampeg_veltodepth"): // also ampeg_vel2depth break; - default: - return false; - #undef case_any_ccN - #undef LFO_EG_filter_EQ_target + default: { + // Amplitude Envelope + if (absl::StartsWith(opcode.name, "ampeg_")) { + if (parseEGOpcode(opcode, amplitudeEG)) + return true; + } + // Pitch Envelope + if (absl::StartsWith(opcode.name, "pitcheg_")) { + if (parseEGOpcode(opcode, pitchEG)) { + getOrCreateConnection( + ModKey::createNXYZ(ModId::PitchEG, id), + ModKey::createNXYZ(ModId::Pitch, id)); + return true; + } + } + // Filter Envelope + if (absl::StartsWith(opcode.name, "fileg_")) { + if (parseEGOpcode(opcode, filterEG)) { + getOrCreateConnection( + ModKey::createNXYZ(ModId::FilEG, id), + ModKey::createNXYZ(ModId::FilCutoff, id)); + return true; + } + } + + // + const std::string letterOnlyName = opcode.getLetterOnlyName(); + + // Modulation: LFO + if (absl::StartsWith(letterOnlyName, "lfo&_")) { + if (parseLFOOpcodeV2(opcode)) + return true; + } + // Modulation: Flex EG + if (absl::StartsWith(letterOnlyName, "eg&_")) { + if (parseEGOpcodeV2(opcode)) + return true; + } + + return false; + } + } return true; @@ -1390,6 +898,29 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) eg.ccSustain[opcode.parameters.back()] = opcode.read(Default::egPercentMod); break; + + case hash("pitcheg_depth"): + getOrCreateConnection( + ModKey::createNXYZ(ModId::PitchEG, id), + ModKey::createNXYZ(ModId::Pitch, id)).sourceDepth = opcode.read(Default::egDepth); + break; + case hash("fileg_depth"): + getOrCreateConnection( + ModKey::createNXYZ(ModId::FilEG, id), + ModKey::createNXYZ(ModId::FilCutoff, id)).sourceDepth = opcode.read(Default::egDepth); + break; + + case hash("pitcheg_veltodepth"): // also pitcheg_vel2depth + getOrCreateConnection( + ModKey::createNXYZ(ModId::PitchEG, id), + ModKey::createNXYZ(ModId::Pitch, id)).velToDepth = opcode.read(Default::egVel2Depth); + break; + case hash("fileg_veltodepth"): // also fileg_vel2depth + getOrCreateConnection( + ModKey::createNXYZ(ModId::FilEG, id), + ModKey::createNXYZ(ModId::FilCutoff, id)).velToDepth = opcode.read(Default::egVel2Depth); + break; + default: return false; } @@ -1412,6 +943,333 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, absl::optional float* { + const unsigned stepNumber1Based = opcode.parameters[1]; + if (stepNumber1Based <= 0 || stepNumber1Based > config::maxLFOSteps) + return nullptr; + if (!lfo.seq) + lfo.seq = LFODescription::StepSequence(); + if (!extendIfNecessary(lfo.seq->steps, stepNumber1Based, Default::numLFOSteps)) + return nullptr; + return &lfo.seq->steps[stepNumber1Based - 1]; + }; + auto getOrCreateLFOSub = [&opcode, &lfo]() -> LFODescription::Sub* { + const unsigned subNumber1Based = opcode.parameters[1]; + if (subNumber1Based <= 0 || subNumber1Based > config::maxLFOSubs) + return nullptr; + if (!extendIfNecessary(lfo.sub, subNumber1Based, Default::numLFOSubs)) + return nullptr; + return &lfo.sub[subNumber1Based - 1]; + }; + auto LFO_EG_filter_EQ_target = [this, &opcode, lfoNumber](ModId sourceId, ModId targetId, const OpcodeSpec& spec) -> bool { + const unsigned index = opcode.parameters.size() == 2 ? opcode.parameters.back() - 1 : 0; + if (!extendIfNecessary(filters, index + 1, Default::numFilters)) + return false; + const ModKey source = ModKey::createNXYZ(sourceId, id, lfoNumber); + const ModKey target = ModKey::createNXYZ(targetId, id, index); + getOrCreateConnection(source, target).sourceDepth = opcode.read(spec); + return true; + }; + + // + switch (opcode.lettersOnlyHash) { + + // Modulation: LFO + case hash("lfo&_freq"): + lfo.freq = opcode.read(Default::lfoFreq); + break; + case_any_ccN("lfo&_freq"): + processGenericCc(opcode, Default::lfoFreqMod, ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber)); + break; + case hash("lfo&_beats"): + lfo.beats = opcode.read(Default::lfoBeats); + break; + case_any_ccN("lfo&_beats"): + processGenericCc(opcode, Default::lfoBeatsMod, ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber)); + break; + case hash("lfo&_phase"): + lfo.phase0 = opcode.read(Default::lfoPhase); + break; + case hash("lfo&_delay"): + lfo.delay = opcode.read(Default::lfoDelay); + break; + case hash("lfo&_fade"): + lfo.fade = opcode.read(Default::lfoFade); + break; + case hash("lfo&_count"): + lfo.count = opcode.read(Default::lfoCount); + break; + case hash("lfo&_steps"): + if (!lfo.seq) + lfo.seq = LFODescription::StepSequence(); + lfo.seq->steps.resize(opcode.read(Default::lfoSteps)); + break; + case hash("lfo&_step&"): + if (float* step = getOrCreateLFOStep()) + *step = opcode.read(Default::lfoStepX); + else + return false; + break; + case hash("lfo&_wave&"): // also lfo&_wave + if (LFODescription::Sub* sub = getOrCreateLFOSub()) + sub->wave = opcode.read(Default::lfoWave); + else + return false; + break; + case hash("lfo&_offset&"): // also lfo&_offset + if (LFODescription::Sub* sub = getOrCreateLFOSub()) + sub->offset = opcode.read(Default::lfoOffset); + else + return false; + break; + case hash("lfo&_ratio&"): // also lfo&_ratio + if (LFODescription::Sub* sub = getOrCreateLFOSub()) + sub->ratio = opcode.read(Default::lfoRatio); + else + return false; + break; + case hash("lfo&_scale&"): // also lfo&_scale + if (LFODescription::Sub* sub = getOrCreateLFOSub()) + sub->scale = opcode.read(Default::lfoScale); + else + return false; + break; + + // Modulation: LFO (targets) + case hash("lfo&_amplitude"): + { + const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber); + const ModKey target = ModKey::createNXYZ(ModId::Amplitude, id); + getOrCreateConnection(source, target).sourceDepth = + opcode.read(Default::amplitudeMod); + } + break; + case hash("lfo&_pan"): + { + const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber); + const ModKey target = ModKey::createNXYZ(ModId::Pan, id); + getOrCreateConnection(source, target).sourceDepth = + opcode.read(Default::panMod); + } + break; + case hash("lfo&_width"): + { + const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber); + const ModKey target = ModKey::createNXYZ(ModId::Width, id); + getOrCreateConnection(source, target).sourceDepth = + opcode.read(Default::widthMod); + } + break; + case hash("lfo&_position"): // sfizz extension + { + const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber); + const ModKey target = ModKey::createNXYZ(ModId::Position, id); + getOrCreateConnection(source, target).sourceDepth = + opcode.read(Default::positionMod); + } + break; + case hash("lfo&_pitch"): + { + const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber); + const ModKey target = ModKey::createNXYZ(ModId::Pitch, id); + getOrCreateConnection(source, target).sourceDepth = + opcode.read(Default::pitchMod); + } + break; + case hash("lfo&_volume"): + { + const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber); + const ModKey target = ModKey::createNXYZ(ModId::Volume, id); + getOrCreateConnection(source, target).sourceDepth = + opcode.read(Default::volumeMod); + } + break; + + case hash("lfo&_cutoff&"): + LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilCutoff, Default::filterCutoffMod); + break; + case hash("lfo&_resonance&"): + LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilResonance, Default::filterResonanceMod); + break; + case hash("lfo&_fil&gain"): + LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilGain, Default::filterGainMod); + break; + case hash("lfo&_eq&gain"): + LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqGain, Default::eqGainMod); + break; + case hash("lfo&_eq&freq"): + LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqFrequency, Default::eqFrequencyMod); + break; + case hash("lfo&_eq&bw"): + LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqBandwidth, Default::eqBandwidthMod); + break; + + default: + return false; + } + + return true; +} + +bool sfz::Region::parseEGOpcodeV2(const Opcode& opcode) +{ + const unsigned egNumber1Based = opcode.parameters.front(); + if (egNumber1Based <= 0) + return false; + if (!extendIfNecessary(flexEGs, egNumber1Based, Default::numFlexEGs)) + return false; + + const unsigned egNumber = egNumber1Based - 1; + FlexEGDescription& eg = flexEGs[egNumber]; + + // + auto getOrCreateEGPoint = [&opcode, &eg]() -> FlexEGPoint* { + const auto pointNumber = opcode.parameters[1]; + if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints)) + return nullptr; + return &eg.points[pointNumber]; + }; + auto LFO_EG_filter_EQ_target = [this, &opcode, egNumber](ModId sourceId, ModId targetId, const OpcodeSpec& spec) -> bool { + const unsigned index = opcode.parameters.size() == 2 ? opcode.parameters.back() - 1 : 0; + if (!extendIfNecessary(filters, index + 1, Default::numFilters)) + return false; + const ModKey source = ModKey::createNXYZ(sourceId, id, egNumber); + const ModKey target = ModKey::createNXYZ(targetId, id, index); + getOrCreateConnection(source, target).sourceDepth = opcode.read(spec); + return true; + }; + + // + switch (opcode.lettersOnlyHash) { + + // Flex envelopes + case hash("eg&_dynamic"): + eg.dynamic = opcode.read(Default::flexEGDynamic); + break; + case hash("eg&_sustain"): + eg.sustain = opcode.read(Default::flexEGSustain); + break; + case hash("eg&_time&"): + if (FlexEGPoint* point = getOrCreateEGPoint()) + point->time = opcode.read(Default::flexEGPointTime); + else + return false; + break; + case hash("eg&_level&"): + if (FlexEGPoint* point = getOrCreateEGPoint()) + point->level = opcode.read(Default::flexEGPointLevel); + else + return false; + break; + case hash("eg&_shape&"): + if (FlexEGPoint* point = getOrCreateEGPoint()) + point->setShape(opcode.read(Default::flexEGPointShape)); + else + return false; + break; + + // Modulation: Flex EG (targets) + case hash("eg&_amplitude"): + { + const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber); + const ModKey target = ModKey::createNXYZ(ModId::Amplitude, id); + getOrCreateConnection(source, target).sourceDepth = + opcode.read(Default::amplitudeMod); + } + break; + case hash("eg&_pan"): + { + const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber); + const ModKey target = ModKey::createNXYZ(ModId::Pan, id); + getOrCreateConnection(source, target).sourceDepth = + opcode.read(Default::panMod); + } + break; + case hash("eg&_width"): + { + const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber); + const ModKey target = ModKey::createNXYZ(ModId::Width, id); + getOrCreateConnection(source, target).sourceDepth = + opcode.read(Default::widthMod); + } + break; + case hash("eg&_position"): // sfizz extension + { + const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber); + const ModKey target = ModKey::createNXYZ(ModId::Position, id); + getOrCreateConnection(source, target).sourceDepth = + opcode.read(Default::positionMod); + } + break; + case hash("eg&_pitch"): + { + const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber); + const ModKey target = ModKey::createNXYZ(ModId::Pitch, id); + getOrCreateConnection(source, target).sourceDepth = + opcode.read(Default::pitchMod); + } + break; + case hash("eg&_volume"): + { + const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber); + const ModKey target = ModKey::createNXYZ(ModId::Volume, id); + getOrCreateConnection(source, target).sourceDepth = + opcode.read(Default::volumeMod); + } + break; + case hash("eg&_cutoff&"): + LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilCutoff, Default::filterCutoffMod); + break; + case hash("eg&_resonance&"): + LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilResonance, Default::filterResonanceMod); + break; + case hash("eg&_fil&gain"): + LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilGain, Default::filterGainMod); + break; + case hash("eg&_eq&gain"): + LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqGain, Default::eqGainMod); + break; + case hash("eg&_eq&freq"): + LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqFrequency, Default::eqFrequencyMod); + break; + case hash("eg&_eq&bw"): + LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqBandwidth, Default::eqBandwidthMod); + break; + + case hash("eg&_ampeg"): + { + auto ampeg = opcode.read(Default::flexEGAmpeg); + if (eg.ampeg != ampeg) { + eg.ampeg = ampeg; + flexAmpEG = absl::nullopt; + for (size_t i = 0, n = flexEGs.size(); i < n && !flexAmpEG; ++i) { + if (flexEGs[i].ampeg) + flexAmpEG = static_cast(i); + } + } + break; + } + + default: + return false; + } + + return true; +} + bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec spec, const ModKey& target) { if (!opcode.isAnyCcN()) diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index bb72cfd4..5330ee84 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -266,6 +266,22 @@ struct Region { * @return false */ bool parseEGOpcode(const Opcode& opcode, absl::optional& eg); + /** + * @brief Parse a opcode which is specific to a particular SFZv2 LFO: lfoN. + * + * @param opcode + * @return true if the opcode was properly read and stored. + * @return false + */ + bool parseLFOOpcodeV2(const Opcode& opcode); + /** + * @brief Parse a opcode which is specific to a particular SFZv2 EG: egN. + * + * @param opcode + * @return true if the opcode was properly read and stored. + * @return false + */ + bool parseEGOpcodeV2(const Opcode& opcode); /** * @brief Process a generic CC opcode, and fill the modulation parameters. *