From 97c9411f8d5fc41905bc3f3c2641894838f4fe60 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Tue, 9 Mar 2021 18:43:59 +0100 Subject: [PATCH 1/6] 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. * From dfd777fd3e039eb318e71e2670e5aa7c63b9cd55 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 10 Mar 2021 14:18:09 +0100 Subject: [PATCH 2/6] Move LFO modulation keys into the description --- demos/PlotLFO.cpp | 3 +-- src/sfizz/LFO.cpp | 32 +++++++++------------------ src/sfizz/LFO.h | 7 ++---- src/sfizz/LFODescription.h | 5 +++++ src/sfizz/Region.cpp | 4 ++++ src/sfizz/Voice.cpp | 3 +-- src/sfizz/modulations/sources/LFO.cpp | 2 +- tests/LFOT.cpp | 3 +-- 8 files changed, 26 insertions(+), 33 deletions(-) diff --git a/demos/PlotLFO.cpp b/demos/PlotLFO.cpp index 680ee721..57e94f51 100644 --- a/demos/PlotLFO.cpp +++ b/demos/PlotLFO.cpp @@ -116,8 +116,7 @@ int main(int argc, char* argv[]) std::vector> lfos(numLfos); for (size_t l = 0; l < numLfos; ++l) { - const NumericId id { static_cast(l) }; - sfz::LFO* lfo = new sfz::LFO(id, bufferPool); + sfz::LFO* lfo = new sfz::LFO(bufferPool); lfos[l].reset(lfo); lfo->setSampleRate(sampleRate); lfo->configure(&desc[l]); diff --git a/src/sfizz/LFO.cpp b/src/sfizz/LFO.cpp index 053ef1b1..24aa8d6e 100644 --- a/src/sfizz/LFO.cpp +++ b/src/sfizz/LFO.cpp @@ -21,9 +21,8 @@ namespace sfz { struct LFO::Impl { - explicit Impl(NumericId id, BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix) - : id_(id), - bufferPool_(bufferPool), + explicit Impl(BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix) + : bufferPool_(bufferPool), beatClock_(beatClock), modMatrix_(modMatrix), sampleRate_(config::defaultSampleRate), @@ -31,7 +30,6 @@ struct LFO::Impl { { } - NumericId id_; BufferPool& bufferPool_; BeatClock* beatClock_ = nullptr; ModMatrix* modMatrix_ = nullptr; @@ -48,8 +46,8 @@ struct LFO::Impl { std::array sampleHoldState_ {{}}; }; -LFO::LFO(NumericId id, BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix) - : impl_(new Impl(id, bufferPool, beatClock, modMatrix)) +LFO::LFO(BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix) + : impl_(new Impl(bufferPool, beatClock, modMatrix)) { } @@ -57,11 +55,6 @@ LFO::~LFO() { } -NumericId LFO::getId() const noexcept -{ - return impl_->id_; -} - void LFO::setSampleRate(double sampleRate) { impl_->sampleRate_ = sampleRate; @@ -221,7 +214,7 @@ void LFO::processSteps(absl::Span out, const float* phaseIn) } } -void LFO::process(absl::Span out, NumericId regionId) +void LFO::process(absl::Span out) { Impl& impl = *impl_; const LFODescription& desc = *impl.desc_; @@ -252,13 +245,13 @@ void LFO::process(absl::Span out, NumericId regionId) absl::Span phases = *phasesTemp; if (desc.seq) { - generatePhase(0, phases, regionId); + generatePhase(0, phases); processSteps(out, phases.data()); ++subno; } for (; subno < countSubs; ++subno) { - generatePhase(subno, phases, regionId); + generatePhase(subno, phases); switch (desc.sub[subno].wave) { case LFOWave::Triangle: processWave(subno, out, phases.data()); @@ -315,13 +308,12 @@ void LFO::processFadeIn(absl::Span out) impl.fadePosition_ = fadePosition; } -void LFO::generatePhase(unsigned nth, absl::Span phases, NumericId regionId) +void LFO::generatePhase(unsigned nth, absl::Span phases) { Impl& impl = *impl_; BufferPool& bufferPool = impl.bufferPool_; BeatClock* beatClock = impl.beatClock_; ModMatrix* modMatrix = impl.modMatrix_; - const NumericId id { impl.id_ }; const LFODescription& desc = *impl.desc_; const LFODescription::Sub& sub = desc.sub[nth]; const float samplePeriod = 1.0f / impl.sampleRate_; @@ -337,13 +329,11 @@ void LFO::generatePhase(unsigned nth, absl::Span phases, NumericIdgetModulationByKey(beatsKey); - freqMod = modMatrix->getModulationByKey(freqKey); + beatsMod = modMatrix->getModulationByKey(desc.beatsKey); + freqMod = modMatrix->getModulationByKey(desc.freqKey); } if (beatClock && beatClock->isPlaying() && beats > 0) { diff --git a/src/sfizz/LFO.h b/src/sfizz/LFO.h index caac1c27..ab2f415b 100644 --- a/src/sfizz/LFO.h +++ b/src/sfizz/LFO.h @@ -55,14 +55,11 @@ struct LFODescription; class LFO { public: explicit LFO( - NumericId id, BufferPool& bufferPool, BeatClock* beatClock = nullptr, ModMatrix* modMatrix = nullptr); ~LFO(); - NumericId getId() const noexcept; - /** Sets the sample rate. */ @@ -85,7 +82,7 @@ public: TODO(jpc) frequency modulations */ - void process(absl::Span out, NumericId regionId = {}); + void process(absl::Span out); private: /** @@ -123,7 +120,7 @@ private: /** Generate the phase of the N-th generator */ - void generatePhase(unsigned nth, absl::Span phases, NumericId regionId); + void generatePhase(unsigned nth, absl::Span phases); private: struct Impl; diff --git a/src/sfizz/LFODescription.h b/src/sfizz/LFODescription.h index 82859779..a4abfa62 100644 --- a/src/sfizz/LFODescription.h +++ b/src/sfizz/LFODescription.h @@ -6,6 +6,7 @@ #pragma once #include "Defaults.h" +#include "modulations/ModKey.h" #include #include @@ -32,6 +33,10 @@ struct LFODescription { }; absl::optional seq; std::vector sub; + + // modulations + ModKey beatsKey; + ModKey freqKey; }; } // namespace sfz diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 453e432f..feb7cb4a 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -955,6 +955,10 @@ bool sfz::Region::parseLFOOpcodeV2(const Opcode& opcode) const unsigned lfoNumber = lfoNumber1Based - 1; LFODescription& lfo = lfos[lfoNumber]; + // + lfo.beatsKey = ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber); + lfo.freqKey = ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber); + // auto getOrCreateLFOStep = [&opcode, &lfo]() -> float* { const unsigned stepNumber1Based = opcode.parameters[1]; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index e6a67170..dbffda04 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -1604,8 +1604,7 @@ void Voice::setMaxLFOsPerVoice(size_t numLFOs) impl.lfos_.resize(numLFOs); for (size_t i = 0; i < numLFOs; ++i) { - const NumericId id { static_cast(i) }; - auto lfo = absl::make_unique(id, resources.bufferPool, &resources.beatClock, &resources.modMatrix); + auto lfo = absl::make_unique(resources.bufferPool, &resources.beatClock, &resources.modMatrix); lfo->setSampleRate(impl.sampleRate_); impl.lfos_[i] = std::move(lfo); } diff --git a/src/sfizz/modulations/sources/LFO.cpp b/src/sfizz/modulations/sources/LFO.cpp index 206f0ca1..a485833d 100644 --- a/src/sfizz/modulations/sources/LFO.cpp +++ b/src/sfizz/modulations/sources/LFO.cpp @@ -59,7 +59,7 @@ void LFOSource::generate(const ModKey& sourceKey, NumericId voiceId, absl } LFO* lfo = voice->getLFO(lfoIndex); - lfo->process(buffer, region->getId()); + lfo->process(buffer); } } // namespace sfz diff --git a/tests/LFOT.cpp b/tests/LFOT.cpp index 26bd0fb1..486a8cc6 100644 --- a/tests/LFOT.cpp +++ b/tests/LFOT.cpp @@ -28,8 +28,7 @@ static bool computeLFO(DataPoints& dp, const fs::path& sfzPath, double sampleRat std::vector> lfos(numLfos); for (size_t l = 0; l < numLfos; ++l) { - const NumericId id { static_cast(l) }; - sfz::LFO* lfo = new sfz::LFO(id, resources.bufferPool); + sfz::LFO* lfo = new sfz::LFO(resources.bufferPool); lfos[l].reset(lfo); lfo->setSampleRate(sampleRate); lfo->configure(&desc[l]); From 3957668d0fc1040edfce2f15509e8401d0652f45 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 10 Mar 2021 14:44:50 +0100 Subject: [PATCH 3/6] Add modulation key for v1 LFOs --- src/sfizz/modulations/ModId.cpp | 6 ++++++ src/sfizz/modulations/ModId.h | 3 +++ src/sfizz/modulations/ModKey.cpp | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/src/sfizz/modulations/ModId.cpp b/src/sfizz/modulations/ModId.cpp index 374b0887..5bc66bc8 100644 --- a/src/sfizz/modulations/ModId.cpp +++ b/src/sfizz/modulations/ModId.cpp @@ -30,6 +30,12 @@ int ModIds::flags(ModId id) noexcept return kModIsPerVoice; case ModId::LFO: return kModIsPerVoice; + case ModId::AmpLFO: + return kModIsPerVoice; + case ModId::PitchLFO: + return kModIsPerVoice; + case ModId::FilLFO: + return kModIsPerVoice; case ModId::AmpEG: return kModIsPerVoice; case ModId::PitchEG: diff --git a/src/sfizz/modulations/ModId.h b/src/sfizz/modulations/ModId.h index 055dacdc..648ed8c7 100644 --- a/src/sfizz/modulations/ModId.h +++ b/src/sfizz/modulations/ModId.h @@ -23,6 +23,9 @@ enum class ModId : int { Controller = _SourcesStart, Envelope, LFO, + AmpLFO, + PitchLFO, + FilLFO, AmpEG, PitchEG, FilEG, diff --git a/src/sfizz/modulations/ModKey.cpp b/src/sfizz/modulations/ModKey.cpp index 1cb95991..4b61552c 100644 --- a/src/sfizz/modulations/ModKey.cpp +++ b/src/sfizz/modulations/ModKey.cpp @@ -99,6 +99,12 @@ std::string ModKey::toString() const return absl::StrCat("EG ", 1 + params_.N, " {", region_.number(), "}"); case ModId::LFO: return absl::StrCat("LFO ", 1 + params_.N, " {", region_.number(), "}"); + case ModId::AmpLFO: + return absl::StrCat("AmplitudeLFO {", region_.number(), "}"); + case ModId::PitchLFO: + return absl::StrCat("PitchLFO {", region_.number(), "}"); + case ModId::FilLFO: + return absl::StrCat("FilterLFO {", region_.number(), "}"); case ModId::AmpEG: return absl::StrCat("AmplitudeEG {", region_.number(), "}"); case ModId::PitchEG: From ef1a548d9883187abafdfbf684b5823107b92c9b Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 10 Mar 2021 14:49:29 +0100 Subject: [PATCH 4/6] Add v1 LFOs in voices --- src/sfizz/Voice.cpp | 67 +++++++++++++++++++++++++++++++++++++++++++++ src/sfizz/Voice.h | 31 +++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index dbffda04..34b98c8d 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -241,6 +241,10 @@ struct Voice::Impl std::vector> lfos_; std::vector> flexEGs_; + std::unique_ptr lfoAmplitude_; + std::unique_ptr lfoPitch_; + std::unique_ptr lfoFilter_; + ADSREnvelope egAmplitude_; std::unique_ptr egPitch_; std::unique_ptr egFilter_; @@ -595,6 +599,12 @@ void Voice::setSampleRate(float sampleRate) noexcept for (auto& lfo : impl.lfos_) lfo->setSampleRate(sampleRate); + if (auto* lfo = impl.lfoAmplitude_.get()) + lfo->setSampleRate(sampleRate); + if (auto* lfo = impl.lfoPitch_.get()) + lfo->setSampleRate(sampleRate); + if (auto* lfo = impl.lfoFilter_.get()) + lfo->setSampleRate(sampleRate); for (auto& filter : impl.filters_) filter.setSampleRate(sampleRate); @@ -1640,6 +1650,45 @@ void Voice::setFilterEGEnabledPerVoice(bool haveFilterEG) impl.egFilter_.reset(); } +void Voice::setAmplitudeLFOEnabledPerVoice(bool haveAmplitudeLFO) +{ + Impl& impl = *impl_; + Resources& res = impl.resources_; + if (haveAmplitudeLFO) { + LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix); + impl.lfoAmplitude_.reset(lfo); + lfo->setSampleRate(impl.sampleRate_); + } + else + impl.lfoAmplitude_.reset(); +} + +void Voice::setPitchLFOEnabledPerVoice(bool havePitchLFO) +{ + Impl& impl = *impl_; + Resources& res = impl.resources_; + if (havePitchLFO) { + LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix); + impl.lfoPitch_.reset(lfo); + lfo->setSampleRate(impl.sampleRate_); + } + else + impl.lfoPitch_.reset(); +} + +void Voice::setFilterLFOEnabledPerVoice(bool haveFilterLFO) +{ + Impl& impl = *impl_; + Resources& res = impl.resources_; + if (haveFilterLFO) { + LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix); + impl.lfoFilter_.reset(lfo); + lfo->setSampleRate(impl.sampleRate_); + } + else + impl.lfoFilter_.reset(); +} + void Voice::Impl::setupOscillatorUnison() { const int m = region_->oscillatorMulti; @@ -1855,6 +1904,24 @@ Duration Voice::getLastPanningDuration() const noexcept return impl.panningDuration_; } +LFO* Voice::getAmplitudeLFO() +{ + Impl& impl = *impl_; + return impl.lfoAmplitude_.get(); +} + +LFO* Voice::getPitchLFO() +{ + Impl& impl = *impl_; + return impl.lfoPitch_.get(); +} + +LFO* Voice::getFilterLFO() +{ + Impl& impl = *impl_; + return impl.lfoFilter_.get(); +} + ADSREnvelope* Voice::getAmplitudeEG() { Impl& impl = *impl_; diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 34885cbb..595bf1c1 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -305,6 +305,24 @@ public: * @param haveFilterEG */ void setFilterEGEnabledPerVoice(bool haveFilterEG); + /** + * @brief Set whether SFZv1 amplitude LFO is enabled on this voice + * + * @param haveAmplitudeLFO + */ + void setAmplitudeLFOEnabledPerVoice(bool haveAmplitudeLFO); + /** + * @brief Set whether SFZv1 pitch LFO is enabled on this voice + * + * @param havePitchLFO + */ + void setPitchLFOEnabledPerVoice(bool havePitchLFO); + /** + * @brief Set whether SFZv1 filter LFO is enabled on this voice + * + * @param haveFilterLFO + */ + void setFilterLFOEnabledPerVoice(bool haveFilterLFO); /** * @brief Release the voice after a given delay * @@ -333,6 +351,19 @@ public: Duration getLastFilterDuration() const noexcept; Duration getLastPanningDuration() const noexcept; + /** + * @brief Get the SFZv1 amplitude LFO, if existing + */ + LFO* getAmplitudeLFO(); + /** + * @brief Get the SFZv1 pitch LFO, if existing + */ + LFO* getPitchLFO(); + /** + * @brief Get the SFZv1 filter LFO, if existing + */ + LFO* getFilterLFO(); + /** * @brief Get the SFZv1 amplitude EG, if existing */ From 7f2daa24e79d5ecbd7dbb1e0adc56400846bd08d Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 10 Mar 2021 15:07:14 +0100 Subject: [PATCH 5/6] Implement the LFOs v1 --- src/sfizz/Defaults.cpp | 3 + src/sfizz/Defaults.h | 3 + src/sfizz/Region.cpp | 131 ++++++++++++++++++++++++++ src/sfizz/Region.h | 23 +++++ src/sfizz/Synth.cpp | 15 +++ src/sfizz/SynthPrivate.h | 3 + src/sfizz/modulations/ModId.cpp | 6 ++ src/sfizz/modulations/ModId.h | 3 + src/sfizz/modulations/ModKey.cpp | 6 ++ src/sfizz/modulations/sources/LFO.cpp | 60 ++++++++++-- 10 files changed, 245 insertions(+), 8 deletions(-) diff --git a/src/sfizz/Defaults.cpp b/src/sfizz/Defaults.cpp index a1ca1d14..7d9a5459 100644 --- a/src/sfizz/Defaults.cpp +++ b/src/sfizz/Defaults.cpp @@ -101,6 +101,9 @@ extern const OpcodeSpec pitchMod { 0.0f, Range(-2400.0f, 2400.0f), extern const OpcodeSpec bendUp { 200.0f, Range(-12000.0f, 12000.0f), 0 }; extern const OpcodeSpec bendDown { -200.0f, Range(-12000.0f, 12000.0f), 0 }; extern const OpcodeSpec bendStep { 1.0f, Range(1.0f, 1200.0f), 0 }; +extern const OpcodeSpec ampLFODepth { 0.0f, Range(-10.0f, 10.0f), 0 }; +extern const OpcodeSpec pitchLFODepth { 0.0f, Range(-1200.0f, 1200.0f), 0 }; +extern const OpcodeSpec filLFODepth { 0.0f, Range(-1200.0f, 1200.0f), 0 }; extern const OpcodeSpec lfoFreq { 0.0f, Range(0.0f, 100.0f), 0 }; extern const OpcodeSpec lfoFreqMod { 0.0f, Range(-100.0f, 100.0f), 0 }; extern const OpcodeSpec lfoBeats { 0.0f, Range(0.0f, 1000.0f), 0 }; diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 83d8658c..adc14cd6 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -209,6 +209,9 @@ namespace Default extern const OpcodeSpec bendUp; extern const OpcodeSpec bendDown; extern const OpcodeSpec bendStep; + extern const OpcodeSpec ampLFODepth; + extern const OpcodeSpec pitchLFODepth; + extern const OpcodeSpec filLFODepth; extern const OpcodeSpec lfoFreq; extern const OpcodeSpec lfoFreqMod; extern const OpcodeSpec lfoBeats; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index feb7cb4a..c866a96a 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -780,6 +780,34 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) } } + // Amplitude LFO + if (absl::StartsWith(opcode.name, "amplfo_")) { + if (parseLFOOpcode(opcode, amplitudeLFO)) { + getOrCreateConnection( + ModKey::createNXYZ(ModId::AmpLFO, id), + ModKey::createNXYZ(ModId::Volume, id)); + return true; + } + } + // Pitch LFO + if (absl::StartsWith(opcode.name, "pitchlfo_")) { + if (parseLFOOpcode(opcode, pitchLFO)) { + getOrCreateConnection( + ModKey::createNXYZ(ModId::PitchLFO, id), + ModKey::createNXYZ(ModId::Pitch, id)); + return true; + } + } + // Filter LFO + if (absl::StartsWith(opcode.name, "fillfo_")) { + if (parseLFOOpcode(opcode, filterLFO)) { + getOrCreateConnection( + ModKey::createNXYZ(ModId::FilLFO, id), + ModKey::createNXYZ(ModId::FilCutoff, id)); + return true; + } + } + // const std::string letterOnlyName = opcode.getLetterOnlyName(); @@ -802,6 +830,109 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return true; } +bool sfz::Region::parseLFOOpcode(const Opcode& opcode, LFODescription& lfo) +{ + #define case_any_lfo(param) \ + case hash("amplfo_" param): \ + case hash("pitchlfo_" param): \ + case hash("fillfo_" param) \ + + #define case_any_lfo_any_ccN(param) \ + case_any_ccN("amplfo_" param): \ + case_any_ccN("pitchlfo_" param): \ + case_any_ccN("fillfo_" param) \ + + // + ModKey sourceKey; + ModKey targetKey; + OpcodeSpec depthSpec; + + if (absl::StartsWith(opcode.name, "amplfo_")) { + sourceKey = ModKey::createNXYZ(ModId::AmpLFO, id); + targetKey = ModKey::createNXYZ(ModId::Volume, id); + lfo.freqKey = ModKey::createNXYZ(ModId::AmpLFOFrequency, id); + depthSpec = Default::ampLFODepth; + } + else if (absl::StartsWith(opcode.name, "pitchlfo_")) { + sourceKey = ModKey::createNXYZ(ModId::PitchLFO, id); + targetKey = ModKey::createNXYZ(ModId::Pitch, id); + lfo.freqKey = ModKey::createNXYZ(ModId::PitchLFOFrequency, id); + depthSpec = Default::pitchLFODepth; + } + else if (absl::StartsWith(opcode.name, "fillfo_")) { + sourceKey = ModKey::createNXYZ(ModId::FilLFO, id); + targetKey = ModKey::createNXYZ(ModId::FilCutoff, id); + lfo.freqKey = ModKey::createNXYZ(ModId::FilLFOFrequency, id); + depthSpec = Default::filLFODepth; + } + else { + ASSERTFALSE; + return false; + } + + // + switch (opcode.lettersOnlyHash) { + + case_any_lfo("delay"): + lfo.delay = opcode.read(Default::lfoDelay); + break; + case_any_lfo("depth"): + getOrCreateConnection(sourceKey, targetKey).sourceDepth = opcode.read(depthSpec); + break; + case_any_lfo_any_ccN("depth"): // also depthcc& + // TODO(jpc) LFO v1 + break; + case_any_lfo("depthchanaft"): + // TODO(jpc) LFO v1 + break; + case_any_lfo("depthpolyaft"): + // TODO(jpc) LFO v1 + break; + case_any_lfo("fade"): + lfo.fade = opcode.read(Default::lfoFade); + break; + case_any_lfo("freq"): + lfo.freq = opcode.read(Default::lfoFreq); + break; + case_any_lfo_any_ccN("freq"): // also freqcc& + processGenericCc(opcode, Default::lfoFreqMod, lfo.freqKey); + break; + case_any_lfo("freqchanaft"): + // TODO(jpc) LFO v1 + break; + case_any_lfo("freqpolyaft"): + // TODO(jpc) LFO v1 + break; + + // sfizz extension + case_any_lfo("wave"): + lfo.sub[0].wave = opcode.read(Default::lfoWave); + break; + + default: + return false; + } + + #undef case_any_lfo + + return true; +} + +bool sfz::Region::parseLFOOpcode(const Opcode& opcode, absl::optional& lfo) +{ + bool create = lfo == absl::nullopt; + if (create) { + lfo = LFODescription(); + lfo->sub[0].wave = LFOWave::Sine; // the LFO v1 default + } + + bool parsed = parseLFOOpcode(opcode, *lfo); + if (!parsed && create) + lfo = absl::nullopt; + + return parsed; +} + bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) { #define case_any_eg(param) \ diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 5330ee84..fd983db3 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -246,6 +246,26 @@ struct Region { * @return false */ bool parseOpcode(const Opcode& opcode); + /** + * @brief Parse a opcode which is specific to a particular SFZv1 LFO: + * amplfo, pitchlfo, fillfo. + * + * @param opcode + * @param lfo + * @return true if the opcode was properly read and stored. + * @return false + */ + bool parseLFOOpcode(const Opcode& opcode, LFODescription& lfo); + /** + * @brief Parse a opcode which is specific to a particular SFZv1 LFO: + * amplfo, pitchlfo, fillfo. + * + * @param opcode + * @param lfo + * @return true if the opcode was properly read and stored. + * @return false + */ + bool parseLFOOpcode(const Opcode& opcode, absl::optional& lfo); /** * @brief Parse a opcode which is specific to a particular SFZv1 EG: * ampeg, pitcheg, fileg. @@ -457,6 +477,9 @@ struct Region { // LFOs std::vector lfos; + absl::optional amplitudeLFO; + absl::optional pitchLFO; + absl::optional filterLFO; bool hasStereoSample { false }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index f292875d..c99bcaf6 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -551,6 +551,9 @@ void Synth::Impl::finalizeSfzLoad() size_t maxFlexEGs { 0 }; bool havePitchEG { false }; bool haveFilterEG { false }; + bool haveAmplitudeLFO { false }; + bool havePitchLFO { false }; + bool haveFilterLFO { false }; FlexEGs::clearUnusedCurves(); @@ -683,6 +686,9 @@ void Synth::Impl::finalizeSfzLoad() maxFlexEGs = max(maxFlexEGs, region->flexEGs.size()); havePitchEG = havePitchEG || region->pitchEG != absl::nullopt; haveFilterEG = haveFilterEG || region->filterEG != absl::nullopt; + haveAmplitudeLFO = haveAmplitudeLFO || region->amplitudeLFO != absl::nullopt; + havePitchLFO = havePitchLFO || region->pitchLFO != absl::nullopt; + haveFilterLFO = haveFilterLFO || region->filterLFO != absl::nullopt; ++currentRegionIndex; } @@ -731,6 +737,9 @@ void Synth::Impl::finalizeSfzLoad() settingsPerVoice_.maxFlexEGs = maxFlexEGs; settingsPerVoice_.havePitchEG = havePitchEG; settingsPerVoice_.haveFilterEG = haveFilterEG; + settingsPerVoice_.haveAmplitudeLFO = haveAmplitudeLFO; + settingsPerVoice_.havePitchLFO = havePitchLFO; + settingsPerVoice_.haveFilterLFO = haveFilterLFO; applySettingsPerVoice(); @@ -1579,6 +1588,9 @@ void Synth::Impl::applySettingsPerVoice() voice.setMaxFlexEGsPerVoice(settingsPerVoice_.maxFlexEGs); voice.setPitchEGEnabledPerVoice(settingsPerVoice_.havePitchEG); voice.setFilterEGEnabledPerVoice(settingsPerVoice_.haveFilterEG); + voice.setAmplitudeLFOEnabledPerVoice(settingsPerVoice_.haveAmplitudeLFO); + voice.setPitchLFOEnabledPerVoice(settingsPerVoice_.havePitchLFO); + voice.setFilterLFOEnabledPerVoice(settingsPerVoice_.haveFilterLFO); } } @@ -1605,6 +1617,9 @@ void Synth::Impl::setupModMatrix() case ModId::Controller: gen = genController_.get(); break; + case ModId::AmpLFO: + case ModId::PitchLFO: + case ModId::FilLFO: case ModId::LFO: gen = genLFO_.get(); break; diff --git a/src/sfizz/SynthPrivate.h b/src/sfizz/SynthPrivate.h index bb69fdc7..af694fa0 100644 --- a/src/sfizz/SynthPrivate.h +++ b/src/sfizz/SynthPrivate.h @@ -283,6 +283,9 @@ struct Synth::Impl final: public Parser::Listener { size_t maxFlexEGs { 0 }; bool havePitchEG { false }; bool haveFilterEG { false }; + bool haveAmplitudeLFO { false }; + bool havePitchLFO { false }; + bool haveFilterLFO { false }; } settingsPerVoice_; Duration dispatchDuration_ { 0 }; diff --git a/src/sfizz/modulations/ModId.cpp b/src/sfizz/modulations/ModId.cpp index 5bc66bc8..5d01ae7b 100644 --- a/src/sfizz/modulations/ModId.cpp +++ b/src/sfizz/modulations/ModId.cpp @@ -76,6 +76,12 @@ int ModIds::flags(ModId id) noexcept return kModIsPerVoice|kModIsAdditive; case ModId::OscillatorModDepth: return kModIsPerVoice|kModIsPercentMultiplicative; + case ModId::AmpLFOFrequency: + return kModIsPerVoice|kModIsAdditive; + case ModId::PitchLFOFrequency: + return kModIsPerVoice|kModIsAdditive; + case ModId::FilLFOFrequency: + return kModIsPerVoice|kModIsAdditive; case ModId::LFOFrequency: return kModIsPerVoice|kModIsAdditive; case ModId::LFOBeats: diff --git a/src/sfizz/modulations/ModId.h b/src/sfizz/modulations/ModId.h index 648ed8c7..a7374083 100644 --- a/src/sfizz/modulations/ModId.h +++ b/src/sfizz/modulations/ModId.h @@ -53,6 +53,9 @@ enum class ModId : int { EqBandwidth, OscillatorDetune, OscillatorModDepth, + AmpLFOFrequency, + PitchLFOFrequency, + FilLFOFrequency, LFOFrequency, LFOBeats, diff --git a/src/sfizz/modulations/ModKey.cpp b/src/sfizz/modulations/ModKey.cpp index 4b61552c..55d9cee5 100644 --- a/src/sfizz/modulations/ModKey.cpp +++ b/src/sfizz/modulations/ModKey.cpp @@ -144,6 +144,12 @@ std::string ModKey::toString() const return absl::StrCat("OscillatorDetune {", region_.number(), ", N=", 1 + params_.N, "}"); case ModId::OscillatorModDepth: return absl::StrCat("OscillatorModDepth {", region_.number(), ", N=", 1 + params_.N, "}"); + case ModId::AmpLFOFrequency: + return absl::StrCat("AmplitudeLFOFrequency {", region_.number(), "}"); + case ModId::PitchLFOFrequency: + return absl::StrCat("PitchLFOFrequency {", region_.number(), "}"); + case ModId::FilLFOFrequency: + return absl::StrCat("FilterLFOFrequency {", region_.number(), "}"); case ModId::LFOFrequency: return absl::StrCat("LFOFrequency {", region_.number(), ", N=", 1 + params_.N, "}"); case ModId::LFOBeats: diff --git a/src/sfizz/modulations/sources/LFO.cpp b/src/sfizz/modulations/sources/LFO.cpp index a485833d..aabd5f70 100644 --- a/src/sfizz/modulations/sources/LFO.cpp +++ b/src/sfizz/modulations/sources/LFO.cpp @@ -21,8 +21,6 @@ LFOSource::LFOSource(VoiceManager& manager) void LFOSource::init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) { - unsigned lfoIndex = sourceKey.parameters().N; - Voice* voice = voiceManager_.getVoiceById(voiceId); if (!voice) { ASSERTFALSE; @@ -30,13 +28,39 @@ void LFOSource::init(const ModKey& sourceKey, NumericId voiceId, unsigned } const Region* region = voice->getRegion(); - if (lfoIndex >= region->lfos.size()) { + LFO* lfo = nullptr; + const LFODescription* desc = nullptr; + + switch (sourceKey.id()) { + case ModId::AmpLFO: + lfo = voice->getAmplitudeLFO(); + desc = &*region->amplitudeLFO; + break; + case ModId::PitchLFO: + lfo = voice->getPitchLFO(); + desc = &*region->pitchLFO; + break; + case ModId::FilLFO: + lfo = voice->getFilterLFO(); + desc = &*region->filterLFO; + break; + case ModId::LFO: + { + unsigned lfoIndex = sourceKey.parameters().N; + if (lfoIndex >= region->lfos.size()) { + ASSERTFALSE; + return; + } + lfo = voice->getLFO(lfoIndex); + desc = ®ion->lfos[lfoIndex]; + } + break; + default: ASSERTFALSE; return; } - LFO* lfo = voice->getLFO(lfoIndex); - lfo->configure(®ion->lfos[lfoIndex]); + lfo->configure(desc); lfo->start(delay); } @@ -52,13 +76,33 @@ void LFOSource::generate(const ModKey& sourceKey, NumericId voiceId, absl } const Region* region = voice->getRegion(); - if (lfoIndex >= region->lfos.size()) { + LFO* lfo = nullptr; + + switch (sourceKey.id()) { + case ModId::AmpLFO: + lfo = voice->getAmplitudeLFO(); + break; + case ModId::PitchLFO: + lfo = voice->getPitchLFO(); + break; + case ModId::FilLFO: + lfo = voice->getFilterLFO(); + break; + case ModId::LFO: + { + if (lfoIndex >= region->lfos.size()) { + ASSERTFALSE; + fill(buffer, 0.0f); + return; + } + lfo = voice->getLFO(lfoIndex); + } + break; + default: ASSERTFALSE; - fill(buffer, 0.0f); return; } - LFO* lfo = voice->getLFO(lfoIndex); lfo->process(buffer); } From de7c125c54c499c2a5f3bf31bb72bbd57f6fe5f3 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Mar 2021 08:11:07 +0100 Subject: [PATCH 6/6] Add connections unit test --- tests/ModulationsT.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/ModulationsT.cpp b/tests/ModulationsT.cpp index 72c38cb6..516a34ae 100644 --- a/tests/ModulationsT.cpp +++ b/tests/ModulationsT.cpp @@ -354,3 +354,20 @@ TEST_CASE("[Modulations] Aftertouch connections") R"("ChannelAftertouch" -> "FilterCutoff {1, N=2}")", }, 2)); } + +TEST_CASE("[Modulations] LFO v1 connections") +{ + sfz::Synth synth; + synth.loadSfzString("/modulation.sfz", R"( + sample=*sine amplfo_freq=1.0 + sample=*sine pitchlfo_freq=1.0 + sample=*sine fillfo_freq=1.0 + )"); + + const std::string graph = synth.getResources().modMatrix.toDotGraph(); + REQUIRE(graph == createDefaultGraph({ + R"("AmplitudeLFO {0}" -> "Volume {0}")", + R"("PitchLFO {1}" -> "Pitch {1}")", + R"("FilterLFO {2}" -> "FilterCutoff {2, N=1}")", + }, 3)); +}