Merge pull request #753 from paulfd/lfo-cc

Add lfoN_fade_oncc, lfoN_delay_oncc, lfoN_phase_oncc
This commit is contained in:
JP Cimalando 2021-03-31 02:11:37 +02:00 committed by GitHub
commit 40d18f0e23
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 111 additions and 52 deletions

View file

@ -110,14 +110,14 @@ int main(int argc, char* argv[])
}
constexpr size_t bufferSize = 1024;
sfz::BufferPool bufferPool;
bufferPool.setBufferSize(bufferSize);
sfz::Resources resources;
resources.setSamplesPerBlock(bufferSize);
size_t numLfos = desc.size();
std::vector<std::unique_ptr<sfz::LFO>> lfos(numLfos);
for (size_t l = 0; l < numLfos; ++l) {
sfz::LFO* lfo = new sfz::LFO(bufferPool);
sfz::LFO* lfo = new sfz::LFO(resources);
lfos[l].reset(lfo);
lfo->setSampleRate(sampleRate);
lfo->configure(&desc[l]);

View file

@ -121,8 +121,11 @@ FloatSpec lfoFreqMod { 0.0f, {-100.0f, 100.0f}, kPermissiveBounds };
FloatSpec lfoBeats { 0.0f, {0.0f, 1000.0f}, kPermissiveBounds };
FloatSpec lfoBeatsMod { 0.0f, {-1000.0f, 1000.0f}, kPermissiveBounds };
FloatSpec lfoPhase { 0.0f, {0.0f, 1.0f}, kWrapPhase|kPermissiveBounds };
FloatSpec lfoPhaseMod { 0.0f, {0.0f, 1.0f}, kPermissiveBounds };
FloatSpec lfoDelay { 0.0f, {0.0f, 30.0f}, kPermissiveBounds };
FloatSpec lfoDelayMod { 0.0f, {0.0f, 30.0f}, kPermissiveBounds };
FloatSpec lfoFade { 0.0f, {0.0f, 30.0f}, kPermissiveBounds };
FloatSpec lfoFadeMod { 0.0f, {0.0f, 30.0f}, kPermissiveBounds };
UInt32Spec lfoCount { 0, {0, 1000}, kEnforceLowerBound|kPermissiveUpperBound };
UInt32Spec lfoSteps { 0, {0, static_cast<unsigned>(config::maxLFOSteps)}, kEnforceBounds };
FloatSpec lfoStepX { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };

View file

@ -68,6 +68,10 @@ struct OpcodeSpec
Range<T> bounds;
int flags;
template <class U>
using IsNormalizable = std::integral_constant<
bool, std::is_arithmetic<U>::value && !std::is_same<U, bool>::value>;
/**
* @brief Normalizes an input as needed for the spec
*
@ -76,7 +80,7 @@ struct OpcodeSpec
* @return U
*/
template<class U=T>
typename std::enable_if<std::is_arithmetic<U>::value, U>::type normalizeInput(U input) const
typename std::enable_if<IsNormalizable<U>::value, U>::type normalizeInput(U input) const
{
constexpr int needsOperation {
kNormalizePercent |
@ -107,7 +111,7 @@ struct OpcodeSpec
* @return U
*/
template<class U=T>
typename std::enable_if<!std::is_arithmetic<U>::value, U>::type normalizeInput(U input) const
typename std::enable_if<!IsNormalizable<U>::value, U>::type normalizeInput(U input) const
{
return input;
}
@ -221,8 +225,11 @@ namespace Default
extern const OpcodeSpec<float> lfoBeats;
extern const OpcodeSpec<float> lfoBeatsMod;
extern const OpcodeSpec<float> lfoPhase;
extern const OpcodeSpec<float> lfoPhaseMod;
extern const OpcodeSpec<float> lfoDelay;
extern const OpcodeSpec<float> lfoDelayMod;
extern const OpcodeSpec<float> lfoFade;
extern const OpcodeSpec<float> lfoFadeMod;
extern const OpcodeSpec<uint32_t> lfoCount;
extern const OpcodeSpec<uint32_t> lfoSteps;
extern const OpcodeSpec<float> lfoStepX;

View file

@ -6,12 +6,10 @@
#include "LFO.h"
#include "LFODescription.h"
#include "BeatClock.h"
#include "BufferPool.h"
#include "MathHelpers.h"
#include "SIMDHelpers.h"
#include "Config.h"
#include "modulations/ModMatrix.h"
#include "Resources.h"
#include "modulations/ModKey.h"
#include "modulations/ModId.h"
#include <array>
@ -21,18 +19,14 @@
namespace sfz {
struct LFO::Impl {
explicit Impl(BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
: bufferPool_(bufferPool),
beatClock_(beatClock),
modMatrix_(modMatrix),
explicit Impl(Resources& resources)
: resources_(resources),
sampleRate_(config::defaultSampleRate),
desc_(&LFODescription::getDefault())
{
}
BufferPool& bufferPool_;
BeatClock* beatClock_ = nullptr;
ModMatrix* modMatrix_ = nullptr;
Resources& resources_;
float sampleRate_ = 0;
// control
@ -40,14 +34,15 @@ struct LFO::Impl {
// state
size_t delayFramesLeft_ = 0;
float fadeTime_ = 0;
float fadePosition_ = 0;
std::array<float, config::maxLFOSubs> subPhases_ {{}};
std::array<float, config::maxLFOSubs> sampleHoldMem_ {{}};
std::array<int, config::maxLFOSubs> sampleHoldState_ {{}};
};
LFO::LFO(BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
: impl_(new Impl(bufferPool, beatClock, modMatrix))
LFO::LFO(Resources& resources)
: impl_(new Impl(resources))
{
}
@ -70,16 +65,25 @@ void LFO::start(unsigned triggerDelay)
Impl& impl = *impl_;
const LFODescription& desc = *impl.desc_;
const float sampleRate = impl.sampleRate_;
const MidiState& state = impl.resources_.midiState;
impl.subPhases_.fill(0.0f);
impl.sampleHoldMem_.fill(0.0f);
impl.sampleHoldState_.fill(0);
const float delay = desc.delay;
float delay = desc.delay;
for (const auto& mod: desc.delayCC)
delay += mod.data * state.getCCValue(mod.cc);
size_t delayFrames = (delay > 0) ? static_cast<size_t>(std::ceil(sampleRate * delay)) : 0u;
impl.delayFramesLeft_ = triggerDelay + delayFrames;
impl.fadePosition_ = (desc.fade > 0) ? 0.0f : 1.0f;
float fade = desc.fade;
for (const auto& mod: desc.fadeCC)
fade += mod.data * state.getCCValue(mod.cc);
impl.fadeTime_ = fade;
impl.fadePosition_ = (fade > 0) ? 0.0f : 1.0f;
}
template <>
@ -218,6 +222,7 @@ void LFO::process(absl::Span<float> out)
{
Impl& impl = *impl_;
const LFODescription& desc = *impl.desc_;
BufferPool& pool = impl.resources_.bufferPool;
size_t numFrames = out.size();
fill(out, 0.0f);
@ -235,7 +240,7 @@ void LFO::process(absl::Span<float> out)
if (countSubs < 1)
return;
auto phasesTemp = impl.bufferPool_.getBuffer(numFrames);
auto phasesTemp = pool.getBuffer(numFrames);
if (!phasesTemp) {
ASSERTFALSE;
fill(out, 0.0f);
@ -289,7 +294,6 @@ void LFO::process(absl::Span<float> out)
void LFO::processFadeIn(absl::Span<float> out)
{
Impl& impl = *impl_;
const LFODescription& desc = *impl.desc_;
const float samplePeriod = 1.0f / impl.sampleRate_;
size_t numFrames = out.size();
@ -297,7 +301,7 @@ void LFO::processFadeIn(absl::Span<float> out)
if (fadePosition >= 1.0f)
return;
const float fadeTime = desc.fade;
const float fadeTime = impl.fadeTime_;
const float fadeStep = samplePeriod / fadeTime;
for (size_t i = 0; i < numFrames && fadePosition < 1; ++i) {
@ -311,9 +315,9 @@ void LFO::processFadeIn(absl::Span<float> out)
void LFO::generatePhase(unsigned nth, absl::Span<float> phases)
{
Impl& impl = *impl_;
BufferPool& bufferPool = impl.bufferPool_;
BeatClock* beatClock = impl.beatClock_;
ModMatrix* modMatrix = impl.modMatrix_;
BufferPool& bufferPool = impl.resources_.bufferPool;
BeatClock& beatClock = impl.resources_.beatClock;
ModMatrix& modMatrix = impl.resources_.modMatrix;
const LFODescription& desc = *impl.desc_;
const LFODescription::Sub& sub = desc.sub[nth];
const float samplePeriod = 1.0f / impl.sampleRate_;
@ -329,39 +333,39 @@ void LFO::generatePhase(unsigned nth, absl::Span<float> phases)
// modulations
const float* beatsMod = nullptr;
const float* freqMod = nullptr;
if (modMatrix) {
// Note(jpc) we might switch between beats and frequency, if host
// switches play state on and off; continually generate both.
beatsMod = modMatrix->getModulationByKey(desc.beatsKey);
freqMod = modMatrix->getModulationByKey(desc.freqKey);
}
const float* phaseMod = nullptr;
// Note(jpc) we might switch between beats and frequency, if host
// switches play state on and off; continually generate both.
beatsMod = modMatrix.getModulationByKey(desc.beatsKey);
freqMod = modMatrix.getModulationByKey(desc.freqKey);
phaseMod = modMatrix.getModulationByKey(desc.phaseKey);
if (beatClock && beatClock->isPlaying() && beats > 0) {
if (beatClock.isPlaying() && beats > 0) {
// generate using the beat clock
float beatRatio = (ratio > 0) ? (1.0f / ratio) : 0.0f;
if (!beatsMod)
beatClock->calculatePhase(beats * beatRatio, phases.data());
beatClock.calculatePhase(beats * beatRatio, phases.data());
else {
auto temp = bufferPool.getBuffer(numFrames);
if (!temp) {
ASSERTFALSE;
beatClock->calculatePhase(beats * beatRatio, phases.data());
beatClock.calculatePhase(beats * beatRatio, phases.data());
}
else {
fill(*temp, beats);
add(absl::MakeConstSpan(beatsMod, numFrames), *temp);
applyGain1(beatRatio, *temp);
beatClock->calculatePhaseModulated(temp->data(), phases.data());
beatClock.calculatePhaseModulated(temp->data(), phases.data());
}
}
}
else {
// generate using the frequency
if (!freqMod) {
float incr = ratio * samplePeriod * baseFreq;
for (size_t i = 0; i < numFrames; ++i) {
phases[i] = phase;
float incr = ratio * samplePeriod * baseFreq;
phase = wrapPhase(phase + incr);
}
}
@ -372,13 +376,16 @@ void LFO::generatePhase(unsigned nth, absl::Span<float> phases)
phase = wrapPhase(phase + incr);
}
}
}
// apply phase offsets
for (size_t i = 0; i < numFrames; ++i) {
float withOffset = phases[i] + phaseOffset;
withOffset -= (int)withOffset;
phases[i] = withOffset;
if (!phaseMod) {
for (size_t i = 0; i < numFrames; ++i)
phases[i] = wrapPhase(phases[i] + phaseOffset);
} else {
for (size_t i = 0; i < numFrames; ++i)
phases[i] = wrapPhase(phases[i] + phaseOffset + phaseMod[i]);
}
impl.subPhases_[nth] = phase;

View file

@ -10,9 +10,7 @@
#include <memory>
namespace sfz {
class BufferPool;
class BeatClock;
class ModMatrix;
struct Resources;
struct Region;
enum class LFOWave : int;
@ -54,10 +52,7 @@ struct LFODescription;
class LFO {
public:
explicit LFO(
BufferPool& bufferPool,
BeatClock* beatClock = nullptr,
ModMatrix* modMatrix = nullptr);
explicit LFO(Resources& resources);
~LFO();
/**

View file

@ -6,6 +6,7 @@
#pragma once
#include "Defaults.h"
#include "CCMap.h"
#include "modulations/ModKey.h"
#include <absl/types/optional.h>
#include <vector>
@ -19,8 +20,11 @@ struct LFODescription {
float freq { Default::lfoFreq }; // lfoN_freq
float beats { Default::lfoBeats }; // lfoN_beats
float phase0 { Default::lfoPhase }; // lfoN_phase
CCMap<float> phaseCC { Default::lfoPhaseMod }; // lfoN_phase_cc
float delay { Default::lfoDelay }; // lfoN_delay
CCMap<float> delayCC { Default::lfoDelayMod }; // lfoN_phase_cc
float fade { Default::lfoFade }; // lfoN_fade
CCMap<float> fadeCC { Default::lfoFadeMod }; // lfoN_phase_cc
unsigned count { Default::lfoCount }; // lfoN_count
struct Sub {
LFOWave wave { Default::lfoWave }; // lfoN_wave[X]
@ -37,6 +41,7 @@ struct LFODescription {
// modulations
ModKey beatsKey;
ModKey freqKey;
ModKey phaseKey;
};
} // namespace sfz

View file

@ -1140,6 +1140,7 @@ bool sfz::Region::parseLFOOpcodeV2(const Opcode& opcode)
//
lfo.beatsKey = ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber);
lfo.freqKey = ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber);
lfo.phaseKey = ModKey::createNXYZ(ModId::LFOPhase, id, lfoNumber);
//
auto getOrCreateLFOStep = [&opcode, &lfo]() -> float* {
@ -1189,12 +1190,27 @@ bool sfz::Region::parseLFOOpcodeV2(const Opcode& opcode)
case hash("lfo&_phase"):
lfo.phase0 = opcode.read(Default::lfoPhase);
break;
case_any_ccN("lfo&_phase"):
processGenericCc(opcode, Default::lfoPhaseMod, ModKey::createNXYZ(ModId::LFOPhase, id, lfoNumber));
break;
case hash("lfo&_delay"):
lfo.delay = opcode.read(Default::lfoDelay);
break;
case hash("lfo&_delay_oncc&"):
if (opcode.parameters.back() > config::numCCs)
return false;
lfo.delayCC[opcode.parameters.back()] = opcode.read(Default::lfoDelayMod);
break;
case hash("lfo&_fade"):
lfo.fade = opcode.read(Default::lfoFade);
break;
case hash("lfo&_fade_oncc&"):
if (opcode.parameters.back() > config::numCCs)
return false;
lfo.fadeCC[opcode.parameters.back()] = opcode.read(Default::lfoFadeMod);
break;
case hash("lfo&_count"):
lfo.count = opcode.read(Default::lfoCount);
break;

View file

@ -1677,7 +1677,7 @@ void Voice::setMaxLFOsPerVoice(size_t numLFOs)
impl.lfos_.resize(numLFOs);
for (size_t i = 0; i < numLFOs; ++i) {
auto lfo = absl::make_unique<LFO>(resources.bufferPool, &resources.beatClock, &resources.modMatrix);
auto lfo = absl::make_unique<LFO>(resources);
lfo->setSampleRate(impl.sampleRate_);
impl.lfos_[i] = std::move(lfo);
}
@ -1718,7 +1718,7 @@ void Voice::setAmplitudeLFOEnabledPerVoice(bool haveAmplitudeLFO)
Impl& impl = *impl_;
Resources& res = impl.resources_;
if (haveAmplitudeLFO) {
LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix);
LFO* lfo = new LFO(res);
impl.lfoAmplitude_.reset(lfo);
lfo->setSampleRate(impl.sampleRate_);
}
@ -1731,7 +1731,7 @@ void Voice::setPitchLFOEnabledPerVoice(bool havePitchLFO)
Impl& impl = *impl_;
Resources& res = impl.resources_;
if (havePitchLFO) {
LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix);
LFO* lfo = new LFO(res);
impl.lfoPitch_.reset(lfo);
lfo->setSampleRate(impl.sampleRate_);
}
@ -1744,7 +1744,7 @@ void Voice::setFilterLFOEnabledPerVoice(bool haveFilterLFO)
Impl& impl = *impl_;
Resources& res = impl.resources_;
if (haveFilterLFO) {
LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix);
LFO* lfo = new LFO(res);
impl.lfoFilter_.reset(lfo);
lfo->setSampleRate(impl.sampleRate_);
}

View file

@ -96,6 +96,8 @@ int ModIds::flags(ModId id) noexcept
return kModIsPerVoice|kModIsAdditive;
case ModId::LFOBeats:
return kModIsPerVoice|kModIsAdditive;
case ModId::LFOPhase:
return kModIsPerVoice|kModIsAdditive;
// unknown
default:

View file

@ -63,6 +63,7 @@ enum class ModId : int {
FilLFOFrequency,
LFOFrequency,
LFOBeats,
LFOPhase,
_TargetsEnd,
// [/targets] --------------------------------------------------------------

View file

@ -164,6 +164,8 @@ std::string ModKey::toString() const
return absl::StrCat("LFOFrequency {", region_.number(), ", N=", 1 + params_.N, "}");
case ModId::LFOBeats:
return absl::StrCat("LFOBeats {", region_.number(), ", N=", 1 + params_.N, "}");
case ModId::LFOPhase:
return absl::StrCat("LFOPhase {", region_.number(), ", N=", 1 + params_.N, "}");
default:
return {};

View file

@ -28,7 +28,7 @@ static bool computeLFO(DataPoints& dp, const fs::path& sfzPath, double sampleRat
std::vector<std::unique_ptr<sfz::LFO>> lfos(numLfos);
for (size_t l = 0; l < numLfos; ++l) {
sfz::LFO* lfo = new sfz::LFO(resources.bufferPool);
sfz::LFO* lfo = new sfz::LFO(resources);
lfos[l].reset(lfo);
lfo->setSampleRate(sampleRate);
lfo->configure(&desc[l]);

View file

@ -408,3 +408,24 @@ TEST_CASE("[Modulations] EG v1 CC connections")
R"("FilterEG {1}" -> "FilterCutoff {1, N=1}")",
}, 2));
}
TEST_CASE("[Modulations] LFO CC connections")
{
sfz::Synth synth;
synth.loadSfzString("/modulation.sfz", R"(
<region> sample=*sine
lfo1_freq=2 lfo1_freq_cc1=0.1 lfo1_volume=0.5
lfo2_freq=0.1 lfo2_freq_cc1=2 lfo2_freq_smoothcc1=10 lfo2_freq_stepcc1=0.2 lfo2_freq_curvecc1=1 lfo2_pitch=1200
lfo3_freq=0.1 lfo3_phase_cc1=2 lfo3_phase_smoothcc1=10 lfo3_phase_stepcc1=0.2 lfo3_phase_curvecc1=1 lfo3_amplitude=50
)");
const std::string graph = synth.getResources().modMatrix.toDotGraph();
REQUIRE(graph == createDefaultGraph({
R"("LFO 1 {0}" -> "Volume {0}")",
R"("LFO 2 {0}" -> "Pitch {0}")",
R"("LFO 3 {0}" -> "Amplitude {0}")",
R"("Controller 1 {curve=0, smooth=0, step=0}" -> "LFOFrequency {0, N=1}")",
R"("Controller 1 {curve=1, smooth=10, step=0.1}" -> "LFOFrequency {0, N=2}")",
R"("Controller 1 {curve=1, smooth=10, step=0.1}" -> "LFOPhase {0, N=3}")",
}, 1));
}