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 sourceKey;
|
||||||
|
ModKey sourceDepthKey;
|
||||||
ModKey targetKey;
|
ModKey targetKey;
|
||||||
OpcodeSpec<float> depthSpec;
|
OpcodeSpec<float> depthSpec;
|
||||||
|
OpcodeSpec<float> depthModSpec;
|
||||||
|
|
||||||
if (absl::StartsWith(opcode.name, "amplfo_")) {
|
if (absl::StartsWith(opcode.name, "amplfo_")) {
|
||||||
sourceKey = ModKey::createNXYZ(ModId::AmpLFO, id);
|
sourceKey = ModKey::createNXYZ(ModId::AmpLFO, id);
|
||||||
|
sourceDepthKey = ModKey::createNXYZ(ModId::AmpLFODepth, id);
|
||||||
targetKey = ModKey::createNXYZ(ModId::Volume, id);
|
targetKey = ModKey::createNXYZ(ModId::Volume, id);
|
||||||
lfo.freqKey = ModKey::createNXYZ(ModId::AmpLFOFrequency, id);
|
lfo.freqKey = ModKey::createNXYZ(ModId::AmpLFOFrequency, id);
|
||||||
depthSpec = Default::ampLFODepth;
|
depthSpec = Default::ampLFODepth;
|
||||||
|
depthModSpec = Default::volumeMod;
|
||||||
}
|
}
|
||||||
else if (absl::StartsWith(opcode.name, "pitchlfo_")) {
|
else if (absl::StartsWith(opcode.name, "pitchlfo_")) {
|
||||||
sourceKey = ModKey::createNXYZ(ModId::PitchLFO, id);
|
sourceKey = ModKey::createNXYZ(ModId::PitchLFO, id);
|
||||||
|
sourceDepthKey = ModKey::createNXYZ(ModId::PitchLFODepth, id);
|
||||||
targetKey = ModKey::createNXYZ(ModId::Pitch, id);
|
targetKey = ModKey::createNXYZ(ModId::Pitch, id);
|
||||||
lfo.freqKey = ModKey::createNXYZ(ModId::PitchLFOFrequency, id);
|
lfo.freqKey = ModKey::createNXYZ(ModId::PitchLFOFrequency, id);
|
||||||
depthSpec = Default::pitchLFODepth;
|
depthSpec = Default::pitchLFODepth;
|
||||||
|
depthModSpec = Default::pitchMod;
|
||||||
}
|
}
|
||||||
else if (absl::StartsWith(opcode.name, "fillfo_")) {
|
else if (absl::StartsWith(opcode.name, "fillfo_")) {
|
||||||
sourceKey = ModKey::createNXYZ(ModId::FilLFO, id);
|
sourceKey = ModKey::createNXYZ(ModId::FilLFO, id);
|
||||||
|
sourceDepthKey = ModKey::createNXYZ(ModId::FilLFODepth, id);
|
||||||
targetKey = ModKey::createNXYZ(ModId::FilCutoff, id);
|
targetKey = ModKey::createNXYZ(ModId::FilCutoff, id);
|
||||||
lfo.freqKey = ModKey::createNXYZ(ModId::FilLFOFrequency, id);
|
lfo.freqKey = ModKey::createNXYZ(ModId::FilLFOFrequency, id);
|
||||||
depthSpec = Default::filLFODepth;
|
depthSpec = Default::filLFODepth;
|
||||||
|
depthModSpec = Default::filterCutoffMod;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
|
|
@ -885,7 +893,8 @@ bool sfz::Region::parseLFOOpcode(const Opcode& opcode, LFODescription& lfo)
|
||||||
getOrCreateConnection(sourceKey, targetKey).sourceDepth = opcode.read(depthSpec);
|
getOrCreateConnection(sourceKey, targetKey).sourceDepth = opcode.read(depthSpec);
|
||||||
break;
|
break;
|
||||||
case_any_lfo_any_ccN("depth"): // also depthcc&
|
case_any_lfo_any_ccN("depth"): // also depthcc&
|
||||||
// TODO(jpc) LFO v1
|
getOrCreateConnection(sourceKey, targetKey).sourceDepthMod = sourceDepthKey;
|
||||||
|
processGenericCc(opcode, depthModSpec, sourceDepthKey);
|
||||||
break;
|
break;
|
||||||
case_any_lfo("depthchanaft"):
|
case_any_lfo("depthchanaft"):
|
||||||
// TODO(jpc) LFO v1
|
// 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);
|
ModKey::createNXYZ(ModId::FilCutoff, id)).velToDepth = opcode.read(Default::egVel2Depth);
|
||||||
break;
|
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:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -494,6 +494,7 @@ struct Region {
|
||||||
ModKey source;
|
ModKey source;
|
||||||
ModKey target;
|
ModKey target;
|
||||||
float sourceDepth = 0.0f;
|
float sourceDepth = 0.0f;
|
||||||
|
ModKey sourceDepthMod;
|
||||||
float velToDepth = 0.0f;
|
float velToDepth = 0.0f;
|
||||||
};
|
};
|
||||||
std::vector<Connection> connections;
|
std::vector<Connection> connections;
|
||||||
|
|
|
||||||
|
|
@ -1683,7 +1683,7 @@ void Synth::Impl::setupModMatrix()
|
||||||
continue;
|
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");
|
DBG("[sfizz] Failed to connect modulation source and target");
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,10 +76,20 @@ int ModIds::flags(ModId id) noexcept
|
||||||
return kModIsPerVoice|kModIsAdditive;
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
case ModId::OscillatorModDepth:
|
case ModId::OscillatorModDepth:
|
||||||
return kModIsPerVoice|kModIsPercentMultiplicative;
|
return kModIsPerVoice|kModIsPercentMultiplicative;
|
||||||
|
case ModId::PitchEGDepth:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
case ModId::FilEGDepth:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
case ModId::AmpLFODepth:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
case ModId::AmpLFOFrequency:
|
case ModId::AmpLFOFrequency:
|
||||||
return kModIsPerVoice|kModIsAdditive;
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
case ModId::PitchLFODepth:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
case ModId::PitchLFOFrequency:
|
case ModId::PitchLFOFrequency:
|
||||||
return kModIsPerVoice|kModIsAdditive;
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
case ModId::FilLFODepth:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
case ModId::FilLFOFrequency:
|
case ModId::FilLFOFrequency:
|
||||||
return kModIsPerVoice|kModIsAdditive;
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
case ModId::LFOFrequency:
|
case ModId::LFOFrequency:
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,13 @@ enum class ModId : int {
|
||||||
EqBandwidth,
|
EqBandwidth,
|
||||||
OscillatorDetune,
|
OscillatorDetune,
|
||||||
OscillatorModDepth,
|
OscillatorModDepth,
|
||||||
|
PitchEGDepth,
|
||||||
|
FilEGDepth,
|
||||||
|
AmpLFODepth,
|
||||||
AmpLFOFrequency,
|
AmpLFOFrequency,
|
||||||
|
PitchLFODepth,
|
||||||
PitchLFOFrequency,
|
PitchLFOFrequency,
|
||||||
|
FilLFODepth,
|
||||||
FilLFOFrequency,
|
FilLFOFrequency,
|
||||||
LFOFrequency,
|
LFOFrequency,
|
||||||
LFOBeats,
|
LFOBeats,
|
||||||
|
|
|
||||||
|
|
@ -144,10 +144,20 @@ std::string ModKey::toString() const
|
||||||
return absl::StrCat("OscillatorDetune {", region_.number(), ", N=", 1 + params_.N, "}");
|
return absl::StrCat("OscillatorDetune {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||||
case ModId::OscillatorModDepth:
|
case ModId::OscillatorModDepth:
|
||||||
return absl::StrCat("OscillatorModDepth {", region_.number(), ", N=", 1 + params_.N, "}");
|
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:
|
case ModId::AmpLFOFrequency:
|
||||||
return absl::StrCat("AmplitudeLFOFrequency {", region_.number(), "}");
|
return absl::StrCat("AmplitudeLFOFrequency {", region_.number(), "}");
|
||||||
|
case ModId::PitchLFODepth:
|
||||||
|
return absl::StrCat("PitchLFODepth {", region_.number(), "}");
|
||||||
case ModId::PitchLFOFrequency:
|
case ModId::PitchLFOFrequency:
|
||||||
return absl::StrCat("PitchLFOFrequency {", region_.number(), "}");
|
return absl::StrCat("PitchLFOFrequency {", region_.number(), "}");
|
||||||
|
case ModId::FilLFODepth:
|
||||||
|
return absl::StrCat("FilterLFODepth {", region_.number(), "}");
|
||||||
case ModId::FilLFOFrequency:
|
case ModId::FilLFOFrequency:
|
||||||
return absl::StrCat("FilterLFOFrequency {", region_.number(), "}");
|
return absl::StrCat("FilterLFOFrequency {", region_.number(), "}");
|
||||||
case ModId::LFOFrequency:
|
case ModId::LFOFrequency:
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ struct ModMatrix::Impl {
|
||||||
|
|
||||||
struct ConnectionData {
|
struct ConnectionData {
|
||||||
float sourceDepth_ {};
|
float sourceDepth_ {};
|
||||||
|
ModKey sourceDepthMod_ {};
|
||||||
float velToDepth_ {};
|
float velToDepth_ {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -193,7 +194,7 @@ ModMatrix::TargetId ModMatrix::findTarget(const ModKey& key) const
|
||||||
return TargetId(it->second);
|
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_;
|
Impl& impl = *impl_;
|
||||||
unsigned sourceIndex = sourceId.number();
|
unsigned sourceIndex = sourceId.number();
|
||||||
|
|
@ -205,6 +206,7 @@ bool ModMatrix::connect(SourceId sourceId, TargetId targetId, float sourceDepth,
|
||||||
Impl::Target& target = impl.targets_[targetIndex];
|
Impl::Target& target = impl.targets_[targetIndex];
|
||||||
Impl::ConnectionData& conn = target.connectedSources[sourceIndex];
|
Impl::ConnectionData& conn = target.connectedSources[sourceIndex];
|
||||||
conn.sourceDepth_ = sourceDepth;
|
conn.sourceDepth_ = sourceDepth;
|
||||||
|
conn.sourceDepthMod_ = sourceDepthMod;
|
||||||
conn.velToDepth_ = velToDepth;
|
conn.velToDepth_ = velToDepth;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -412,26 +414,46 @@ float* ModMatrix::getModulation(TargetId targetId)
|
||||||
sourceDepth += triggerValue * velToDepth;
|
sourceDepth += triggerValue * velToDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const float* sourceDepthMod = getModulationByKey(sourcesPos->second.sourceDepthMod_);
|
||||||
|
|
||||||
if (isFirstSource) {
|
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)
|
for (uint32_t i = 0; i < numFrames; ++i)
|
||||||
buffer[i] = sourceDepth * sourceBuffer[i];
|
buffer[i] = sourceDepth * sourceBuffer[i];
|
||||||
}
|
}
|
||||||
else {
|
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;
|
isFirstSource = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (targetFlags & kModIsMultiplicative) {
|
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) {
|
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 {
|
else {
|
||||||
ASSERT(targetFlags & kModIsAdditive);
|
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 sourceId source of the connection
|
||||||
* @param targetId target of the connection
|
* @param targetId target of the connection
|
||||||
* @param sourceDepth amount which multiplies the source output
|
* @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
|
* @param velToDepth amount which full velocity adds to the source depth
|
||||||
* @return true if the connection was successfully made, otherwise false
|
* @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.
|
* @brief Reinitialize modulation sources overall.
|
||||||
|
|
|
||||||
|
|
@ -371,3 +371,40 @@ TEST_CASE("[Modulations] LFO v1 connections")
|
||||||
R"("FilterLFO {2}" -> "FilterCutoff {2, N=1}")",
|
R"("FilterLFO {2}" -> "FilterCutoff {2, N=1}")",
|
||||||
}, 3));
|
}, 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