Merge pull request #730 from jpcima/depth-v1-oncc
Implement SFZv1 depthcc modulations for EG and LFO
This commit is contained in:
commit
ce0a4df29f
9 changed files with 117 additions and 9 deletions
|
|
@ -849,26 +849,34 @@ bool sfz::Region::parseLFOOpcode(const Opcode& opcode, LFODescription& lfo)
|
|||
|
||||
//
|
||||
ModKey sourceKey;
|
||||
ModKey sourceDepthKey;
|
||||
ModKey targetKey;
|
||||
OpcodeSpec<float> depthSpec;
|
||||
OpcodeSpec<float> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -494,6 +494,7 @@ struct Region {
|
|||
ModKey source;
|
||||
ModKey target;
|
||||
float sourceDepth = 0.0f;
|
||||
ModKey sourceDepthMod;
|
||||
float velToDepth = 0.0f;
|
||||
};
|
||||
std::vector<Connection> connections;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -53,8 +53,13 @@ enum class ModId : int {
|
|||
EqBandwidth,
|
||||
OscillatorDetune,
|
||||
OscillatorModDepth,
|
||||
PitchEGDepth,
|
||||
FilEGDepth,
|
||||
AmpLFODepth,
|
||||
AmpLFOFrequency,
|
||||
PitchLFODepth,
|
||||
PitchLFOFrequency,
|
||||
FilLFODepth,
|
||||
FilLFOFrequency,
|
||||
LFOFrequency,
|
||||
LFOBeats,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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<const float>(sourceBuffer), buffer);
|
||||
else if (!sourceDepthMod) {
|
||||
for (uint32_t i = 0; i < numFrames; ++i)
|
||||
buffer[i] = sourceDepth * sourceBuffer[i];
|
||||
}
|
||||
else {
|
||||
copy(absl::Span<const float>(sourceBuffer), buffer);
|
||||
for (uint32_t i = 0; i < numFrames; ++i)
|
||||
buffer[i] = (sourceDepth + sourceDepthMod[i]) * sourceBuffer[i];
|
||||
}
|
||||
isFirstSource = false;
|
||||
}
|
||||
else {
|
||||
if (targetFlags & kModIsMultiplicative) {
|
||||
multiplyMul1<float>(sourceDepth, sourceBuffer, buffer);
|
||||
if (!sourceDepthMod)
|
||||
multiplyMul1<float>(sourceDepth, sourceBuffer, buffer);
|
||||
else {
|
||||
for (uint32_t i = 0; i < numFrames; ++i)
|
||||
buffer[i] *= (sourceDepth + sourceDepthMod[i]) * sourceBuffer[i];
|
||||
}
|
||||
}
|
||||
else if (targetFlags & kModIsPercentMultiplicative) {
|
||||
multiplyMul1<float>(0.01f * sourceDepth, sourceBuffer, buffer);
|
||||
if (!sourceDepthMod)
|
||||
multiplyMul1<float>(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<float>(sourceDepth, sourceBuffer, buffer);
|
||||
if (!sourceDepthMod)
|
||||
multiplyAdd1<float>(sourceDepth, sourceBuffer, buffer);
|
||||
else {
|
||||
for (uint32_t i = 0; i < numFrames; ++i)
|
||||
buffer[i] += (sourceDepth + sourceDepthMod[i]) * sourceBuffer[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"(
|
||||
<region> sample=*sine amplfo_depth_oncc1=10
|
||||
<region> sample=*sine pitchlfo_depth_oncc2=1200
|
||||
<region> 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"(
|
||||
<region> sample=*sine pitcheg_depth_oncc2=1200
|
||||
<region> 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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue