From b865e23578040206c34f915a752fc4f85089049c Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sun, 21 Mar 2021 15:51:58 +0100 Subject: [PATCH 1/2] Implement SFZv1 depthcc modulations for EG and LFO --- src/sfizz/Region.cpp | 24 +++++++++++++++++++- src/sfizz/Region.h | 1 + src/sfizz/Synth.cpp | 2 +- src/sfizz/modulations/ModId.cpp | 10 +++++++++ src/sfizz/modulations/ModId.h | 5 +++++ src/sfizz/modulations/ModKey.cpp | 10 +++++++++ src/sfizz/modulations/ModMatrix.cpp | 34 ++++++++++++++++++++++++----- src/sfizz/modulations/ModMatrix.h | 3 ++- 8 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 061988de..e066e430 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -849,26 +849,34 @@ bool sfz::Region::parseLFOOpcode(const Opcode& opcode, LFODescription& lfo) // ModKey sourceKey; + ModKey sourceDepthKey; ModKey targetKey; OpcodeSpec depthSpec; + OpcodeSpec depthModSpec; if (absl::StartsWith(opcode.name, "amplfo_")) { sourceKey = ModKey::createNXYZ(ModId::AmpLFO, id); + sourceDepthKey = ModKey::createNXYZ(ModId::AmpLFODepth, id); targetKey = ModKey::createNXYZ(ModId::Volume, id); lfo.freqKey = ModKey::createNXYZ(ModId::AmpLFOFrequency, id); depthSpec = Default::ampLFODepth; + depthModSpec = Default::volumeMod; } else if (absl::StartsWith(opcode.name, "pitchlfo_")) { sourceKey = ModKey::createNXYZ(ModId::PitchLFO, id); + sourceDepthKey = ModKey::createNXYZ(ModId::PitchLFODepth, id); targetKey = ModKey::createNXYZ(ModId::Pitch, id); lfo.freqKey = ModKey::createNXYZ(ModId::PitchLFOFrequency, id); depthSpec = Default::pitchLFODepth; + depthModSpec = Default::pitchMod; } else if (absl::StartsWith(opcode.name, "fillfo_")) { sourceKey = ModKey::createNXYZ(ModId::FilLFO, id); + sourceDepthKey = ModKey::createNXYZ(ModId::FilLFODepth, id); targetKey = ModKey::createNXYZ(ModId::FilCutoff, id); lfo.freqKey = ModKey::createNXYZ(ModId::FilLFOFrequency, id); depthSpec = Default::filLFODepth; + depthModSpec = Default::filterCutoffMod; } else { ASSERTFALSE; @@ -885,7 +893,8 @@ bool sfz::Region::parseLFOOpcode(const Opcode& opcode, LFODescription& lfo) getOrCreateConnection(sourceKey, targetKey).sourceDepth = opcode.read(depthSpec); break; case_any_lfo_any_ccN("depth"): // also depthcc& - // TODO(jpc) LFO v1 + getOrCreateConnection(sourceKey, targetKey).sourceDepthMod = sourceDepthKey; + processGenericCc(opcode, depthModSpec, sourceDepthKey); break; case_any_lfo("depthchanaft"): // TODO(jpc) LFO v1 @@ -1057,6 +1066,19 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) ModKey::createNXYZ(ModId::FilCutoff, id)).velToDepth = opcode.read(Default::egVel2Depth); break; + case_any_ccN("pitcheg_depth"): + getOrCreateConnection( + ModKey::createNXYZ(ModId::PitchEG, id), + ModKey::createNXYZ(ModId::Pitch, id)).sourceDepthMod = ModKey::createNXYZ(ModId::PitchEGDepth, id); + processGenericCc(opcode, Default::pitchMod, ModKey::createNXYZ(ModId::PitchEGDepth, id)); + break; + case_any_ccN("fileg_depth"): + getOrCreateConnection( + ModKey::createNXYZ(ModId::FilEG, id), + ModKey::createNXYZ(ModId::FilCutoff, id)).sourceDepthMod = ModKey::createNXYZ(ModId::FilEGDepth, id); + processGenericCc(opcode, Default::filterCutoffMod, ModKey::createNXYZ(ModId::FilEGDepth, id)); + break; + default: return false; } diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index dec0c9c8..5cf21180 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -494,6 +494,7 @@ struct Region { ModKey source; ModKey target; float sourceDepth = 0.0f; + ModKey sourceDepthMod; float velToDepth = 0.0f; }; std::vector connections; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 47bcd3e9..d94aba30 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1683,7 +1683,7 @@ void Synth::Impl::setupModMatrix() continue; } - if (!mm.connect(source, target, conn.sourceDepth, conn.velToDepth)) { + if (!mm.connect(source, target, conn.sourceDepth, conn.sourceDepthMod, conn.velToDepth)) { DBG("[sfizz] Failed to connect modulation source and target"); ASSERTFALSE; } diff --git a/src/sfizz/modulations/ModId.cpp b/src/sfizz/modulations/ModId.cpp index 5d01ae7b..c207b59f 100644 --- a/src/sfizz/modulations/ModId.cpp +++ b/src/sfizz/modulations/ModId.cpp @@ -76,10 +76,20 @@ int ModIds::flags(ModId id) noexcept return kModIsPerVoice|kModIsAdditive; case ModId::OscillatorModDepth: return kModIsPerVoice|kModIsPercentMultiplicative; + case ModId::PitchEGDepth: + return kModIsPerVoice|kModIsAdditive; + case ModId::FilEGDepth: + return kModIsPerVoice|kModIsAdditive; + case ModId::AmpLFODepth: + return kModIsPerVoice|kModIsAdditive; case ModId::AmpLFOFrequency: return kModIsPerVoice|kModIsAdditive; + case ModId::PitchLFODepth: + return kModIsPerVoice|kModIsAdditive; case ModId::PitchLFOFrequency: return kModIsPerVoice|kModIsAdditive; + case ModId::FilLFODepth: + return kModIsPerVoice|kModIsAdditive; case ModId::FilLFOFrequency: return kModIsPerVoice|kModIsAdditive; case ModId::LFOFrequency: diff --git a/src/sfizz/modulations/ModId.h b/src/sfizz/modulations/ModId.h index a7374083..cef95c50 100644 --- a/src/sfizz/modulations/ModId.h +++ b/src/sfizz/modulations/ModId.h @@ -53,8 +53,13 @@ enum class ModId : int { EqBandwidth, OscillatorDetune, OscillatorModDepth, + PitchEGDepth, + FilEGDepth, + AmpLFODepth, AmpLFOFrequency, + PitchLFODepth, PitchLFOFrequency, + FilLFODepth, FilLFOFrequency, LFOFrequency, LFOBeats, diff --git a/src/sfizz/modulations/ModKey.cpp b/src/sfizz/modulations/ModKey.cpp index 5dbdc057..6abc40c5 100644 --- a/src/sfizz/modulations/ModKey.cpp +++ b/src/sfizz/modulations/ModKey.cpp @@ -144,10 +144,20 @@ 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::PitchEGDepth: + return absl::StrCat("PitchEGDepth {", region_.number(), "}"); + case ModId::FilEGDepth: + return absl::StrCat("FilterEGDepth {", region_.number(), "}"); + case ModId::AmpLFODepth: + return absl::StrCat("AmplitudeLFODepth {", region_.number(), "}"); case ModId::AmpLFOFrequency: return absl::StrCat("AmplitudeLFOFrequency {", region_.number(), "}"); + case ModId::PitchLFODepth: + return absl::StrCat("PitchLFODepth {", region_.number(), "}"); case ModId::PitchLFOFrequency: return absl::StrCat("PitchLFOFrequency {", region_.number(), "}"); + case ModId::FilLFODepth: + return absl::StrCat("FilterLFODepth {", region_.number(), "}"); case ModId::FilLFOFrequency: return absl::StrCat("FilterLFOFrequency {", region_.number(), "}"); case ModId::LFOFrequency: diff --git a/src/sfizz/modulations/ModMatrix.cpp b/src/sfizz/modulations/ModMatrix.cpp index ffba3f6a..5899c4be 100644 --- a/src/sfizz/modulations/ModMatrix.cpp +++ b/src/sfizz/modulations/ModMatrix.cpp @@ -38,6 +38,7 @@ struct ModMatrix::Impl { struct ConnectionData { float sourceDepth_ {}; + ModKey sourceDepthMod_ {}; float velToDepth_ {}; }; @@ -193,7 +194,7 @@ ModMatrix::TargetId ModMatrix::findTarget(const ModKey& key) const return TargetId(it->second); } -bool ModMatrix::connect(SourceId sourceId, TargetId targetId, float sourceDepth, float velToDepth) +bool ModMatrix::connect(SourceId sourceId, TargetId targetId, float sourceDepth, const ModKey& sourceDepthMod, float velToDepth) { Impl& impl = *impl_; unsigned sourceIndex = sourceId.number(); @@ -205,6 +206,7 @@ bool ModMatrix::connect(SourceId sourceId, TargetId targetId, float sourceDepth, Impl::Target& target = impl.targets_[targetIndex]; Impl::ConnectionData& conn = target.connectedSources[sourceIndex]; conn.sourceDepth_ = sourceDepth; + conn.sourceDepthMod_ = sourceDepthMod; conn.velToDepth_ = velToDepth; return true; @@ -412,26 +414,46 @@ float* ModMatrix::getModulation(TargetId targetId) sourceDepth += triggerValue * velToDepth; } + const float* sourceDepthMod = getModulationByKey(sourcesPos->second.sourceDepthMod_); + if (isFirstSource) { - if (sourceDepth != 1) { + if (sourceDepth == 1 && !sourceDepthMod) + copy(absl::Span(sourceBuffer), buffer); + else if (!sourceDepthMod) { for (uint32_t i = 0; i < numFrames; ++i) buffer[i] = sourceDepth * sourceBuffer[i]; } else { - copy(absl::Span(sourceBuffer), buffer); + for (uint32_t i = 0; i < numFrames; ++i) + buffer[i] = (sourceDepth + sourceDepthMod[i]) * sourceBuffer[i]; } isFirstSource = false; } else { if (targetFlags & kModIsMultiplicative) { - multiplyMul1(sourceDepth, sourceBuffer, buffer); + if (!sourceDepthMod) + multiplyMul1(sourceDepth, sourceBuffer, buffer); + else { + for (uint32_t i = 0; i < numFrames; ++i) + buffer[i] *= (sourceDepth + sourceDepthMod[i]) * sourceBuffer[i]; + } } else if (targetFlags & kModIsPercentMultiplicative) { - multiplyMul1(0.01f * sourceDepth, sourceBuffer, buffer); + if (!sourceDepthMod) + multiplyMul1(0.01f * sourceDepth, sourceBuffer, buffer); + else { + for (uint32_t i = 0; i < numFrames; ++i) + buffer[i] *= 0.01f * (sourceDepth + sourceDepthMod[i]) * sourceBuffer[i]; + } } else { ASSERT(targetFlags & kModIsAdditive); - multiplyAdd1(sourceDepth, sourceBuffer, buffer); + if (!sourceDepthMod) + multiplyAdd1(sourceDepth, sourceBuffer, buffer); + else { + for (uint32_t i = 0; i < numFrames; ++i) + buffer[i] += (sourceDepth + sourceDepthMod[i]) * sourceBuffer[i]; + } } } } diff --git a/src/sfizz/modulations/ModMatrix.h b/src/sfizz/modulations/ModMatrix.h index 0a01dd5b..2cef2c0e 100644 --- a/src/sfizz/modulations/ModMatrix.h +++ b/src/sfizz/modulations/ModMatrix.h @@ -92,10 +92,11 @@ public: * @param sourceId source of the connection * @param targetId target of the connection * @param sourceDepth amount which multiplies the source output + * @param sourceDepthMod optional modulation which adds to the source depth * @param velToDepth amount which full velocity adds to the source depth * @return true if the connection was successfully made, otherwise false */ - bool connect(SourceId sourceId, TargetId targetId, float sourceDepth, float velToDepth = 0.0f); + bool connect(SourceId sourceId, TargetId targetId, float sourceDepth, const ModKey& sourceDepthMod, float velToDepth); /** * @brief Reinitialize modulation sources overall. From 107bfbb255055bf75bf62a63979da6940b98b238 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sun, 21 Mar 2021 16:59:08 +0100 Subject: [PATCH 2/2] Add unit tests --- tests/ModulationsT.cpp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/ModulationsT.cpp b/tests/ModulationsT.cpp index 516a34ae..98fbec57 100644 --- a/tests/ModulationsT.cpp +++ b/tests/ModulationsT.cpp @@ -371,3 +371,40 @@ TEST_CASE("[Modulations] LFO v1 connections") R"("FilterLFO {2}" -> "FilterCutoff {2, N=1}")", }, 3)); } + +TEST_CASE("[Modulations] LFO v1 CC connections") +{ + sfz::Synth synth; + synth.loadSfzString("/modulation.sfz", R"( + sample=*sine amplfo_depth_oncc1=10 + sample=*sine pitchlfo_depth_oncc2=1200 + sample=*sine fillfo_depth_oncc3=-3600 + )"); + + const std::string graph = synth.getResources().modMatrix.toDotGraph(); + REQUIRE(graph == createDefaultGraph({ + R"("Controller 1 {curve=0, smooth=0, step=0}" -> "AmplitudeLFODepth {0}")", + R"("Controller 2 {curve=0, smooth=0, step=0}" -> "PitchLFODepth {1}")", + R"("Controller 3 {curve=0, smooth=0, step=-0}" -> "FilterLFODepth {2}")", + R"("AmplitudeLFO {0}" -> "Volume {0}")", + R"("PitchLFO {1}" -> "Pitch {1}")", + R"("FilterLFO {2}" -> "FilterCutoff {2, N=1}")", + }, 3)); +} + +TEST_CASE("[Modulations] EG v1 CC connections") +{ + sfz::Synth synth; + synth.loadSfzString("/modulation.sfz", R"( + sample=*sine pitcheg_depth_oncc2=1200 + sample=*sine fileg_depth_oncc3=-3600 + )"); + + const std::string graph = synth.getResources().modMatrix.toDotGraph(); + REQUIRE(graph == createDefaultGraph({ + R"("Controller 2 {curve=0, smooth=0, step=0}" -> "PitchEGDepth {0}")", + R"("Controller 3 {curve=0, smooth=0, step=-0}" -> "FilterEGDepth {1}")", + R"("PitchEG {0}" -> "Pitch {0}")", + R"("FilterEG {1}" -> "FilterCutoff {1, N=1}")", + }, 2)); +}