diff --git a/src/sfizz/Defaults.cpp b/src/sfizz/Defaults.cpp index a1ca1d14a..7d9a5459c 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 83d8658c4..adc14cd6a 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 feb7cb4a3..c866a96ad 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 5330ee844..fd983db3b 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 f292875d8..c99bcaf67 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 bb69fdc7b..af694fa04 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 5bc66bc84..5d01ae7b9 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 648ed8c7f..a7374083a 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 4b61552c3..55d9cee52 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 a485833de..aabd5f70a 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); }