Merge pull request #753 from paulfd/lfo-cc
Add lfoN_fade_oncc, lfoN_delay_oncc, lfoN_phase_oncc
This commit is contained in:
commit
40d18f0e23
13 changed files with 111 additions and 52 deletions
|
|
@ -110,14 +110,14 @@ int main(int argc, char* argv[])
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr size_t bufferSize = 1024;
|
constexpr size_t bufferSize = 1024;
|
||||||
sfz::BufferPool bufferPool;
|
sfz::Resources resources;
|
||||||
bufferPool.setBufferSize(bufferSize);
|
resources.setSamplesPerBlock(bufferSize);
|
||||||
|
|
||||||
size_t numLfos = desc.size();
|
size_t numLfos = desc.size();
|
||||||
std::vector<std::unique_ptr<sfz::LFO>> lfos(numLfos);
|
std::vector<std::unique_ptr<sfz::LFO>> lfos(numLfos);
|
||||||
|
|
||||||
for (size_t l = 0; l < numLfos; ++l) {
|
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);
|
lfos[l].reset(lfo);
|
||||||
lfo->setSampleRate(sampleRate);
|
lfo->setSampleRate(sampleRate);
|
||||||
lfo->configure(&desc[l]);
|
lfo->configure(&desc[l]);
|
||||||
|
|
|
||||||
|
|
@ -121,8 +121,11 @@ FloatSpec lfoFreqMod { 0.0f, {-100.0f, 100.0f}, kPermissiveBounds };
|
||||||
FloatSpec lfoBeats { 0.0f, {0.0f, 1000.0f}, kPermissiveBounds };
|
FloatSpec lfoBeats { 0.0f, {0.0f, 1000.0f}, kPermissiveBounds };
|
||||||
FloatSpec lfoBeatsMod { 0.0f, {-1000.0f, 1000.0f}, kPermissiveBounds };
|
FloatSpec lfoBeatsMod { 0.0f, {-1000.0f, 1000.0f}, kPermissiveBounds };
|
||||||
FloatSpec lfoPhase { 0.0f, {0.0f, 1.0f}, kWrapPhase|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 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 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 lfoCount { 0, {0, 1000}, kEnforceLowerBound|kPermissiveUpperBound };
|
||||||
UInt32Spec lfoSteps { 0, {0, static_cast<unsigned>(config::maxLFOSteps)}, kEnforceBounds };
|
UInt32Spec lfoSteps { 0, {0, static_cast<unsigned>(config::maxLFOSteps)}, kEnforceBounds };
|
||||||
FloatSpec lfoStepX { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };
|
FloatSpec lfoStepX { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,10 @@ struct OpcodeSpec
|
||||||
Range<T> bounds;
|
Range<T> bounds;
|
||||||
int flags;
|
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
|
* @brief Normalizes an input as needed for the spec
|
||||||
*
|
*
|
||||||
|
|
@ -76,7 +80,7 @@ struct OpcodeSpec
|
||||||
* @return U
|
* @return U
|
||||||
*/
|
*/
|
||||||
template<class U=T>
|
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 {
|
constexpr int needsOperation {
|
||||||
kNormalizePercent |
|
kNormalizePercent |
|
||||||
|
|
@ -107,7 +111,7 @@ struct OpcodeSpec
|
||||||
* @return U
|
* @return U
|
||||||
*/
|
*/
|
||||||
template<class U=T>
|
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;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
@ -221,8 +225,11 @@ namespace Default
|
||||||
extern const OpcodeSpec<float> lfoBeats;
|
extern const OpcodeSpec<float> lfoBeats;
|
||||||
extern const OpcodeSpec<float> lfoBeatsMod;
|
extern const OpcodeSpec<float> lfoBeatsMod;
|
||||||
extern const OpcodeSpec<float> lfoPhase;
|
extern const OpcodeSpec<float> lfoPhase;
|
||||||
|
extern const OpcodeSpec<float> lfoPhaseMod;
|
||||||
extern const OpcodeSpec<float> lfoDelay;
|
extern const OpcodeSpec<float> lfoDelay;
|
||||||
|
extern const OpcodeSpec<float> lfoDelayMod;
|
||||||
extern const OpcodeSpec<float> lfoFade;
|
extern const OpcodeSpec<float> lfoFade;
|
||||||
|
extern const OpcodeSpec<float> lfoFadeMod;
|
||||||
extern const OpcodeSpec<uint32_t> lfoCount;
|
extern const OpcodeSpec<uint32_t> lfoCount;
|
||||||
extern const OpcodeSpec<uint32_t> lfoSteps;
|
extern const OpcodeSpec<uint32_t> lfoSteps;
|
||||||
extern const OpcodeSpec<float> lfoStepX;
|
extern const OpcodeSpec<float> lfoStepX;
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,10 @@
|
||||||
|
|
||||||
#include "LFO.h"
|
#include "LFO.h"
|
||||||
#include "LFODescription.h"
|
#include "LFODescription.h"
|
||||||
#include "BeatClock.h"
|
|
||||||
#include "BufferPool.h"
|
|
||||||
#include "MathHelpers.h"
|
#include "MathHelpers.h"
|
||||||
#include "SIMDHelpers.h"
|
#include "SIMDHelpers.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "modulations/ModMatrix.h"
|
#include "Resources.h"
|
||||||
#include "modulations/ModKey.h"
|
#include "modulations/ModKey.h"
|
||||||
#include "modulations/ModId.h"
|
#include "modulations/ModId.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
@ -21,18 +19,14 @@
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
|
||||||
struct LFO::Impl {
|
struct LFO::Impl {
|
||||||
explicit Impl(BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
|
explicit Impl(Resources& resources)
|
||||||
: bufferPool_(bufferPool),
|
: resources_(resources),
|
||||||
beatClock_(beatClock),
|
|
||||||
modMatrix_(modMatrix),
|
|
||||||
sampleRate_(config::defaultSampleRate),
|
sampleRate_(config::defaultSampleRate),
|
||||||
desc_(&LFODescription::getDefault())
|
desc_(&LFODescription::getDefault())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferPool& bufferPool_;
|
Resources& resources_;
|
||||||
BeatClock* beatClock_ = nullptr;
|
|
||||||
ModMatrix* modMatrix_ = nullptr;
|
|
||||||
float sampleRate_ = 0;
|
float sampleRate_ = 0;
|
||||||
|
|
||||||
// control
|
// control
|
||||||
|
|
@ -40,14 +34,15 @@ struct LFO::Impl {
|
||||||
|
|
||||||
// state
|
// state
|
||||||
size_t delayFramesLeft_ = 0;
|
size_t delayFramesLeft_ = 0;
|
||||||
|
float fadeTime_ = 0;
|
||||||
float fadePosition_ = 0;
|
float fadePosition_ = 0;
|
||||||
std::array<float, config::maxLFOSubs> subPhases_ {{}};
|
std::array<float, config::maxLFOSubs> subPhases_ {{}};
|
||||||
std::array<float, config::maxLFOSubs> sampleHoldMem_ {{}};
|
std::array<float, config::maxLFOSubs> sampleHoldMem_ {{}};
|
||||||
std::array<int, config::maxLFOSubs> sampleHoldState_ {{}};
|
std::array<int, config::maxLFOSubs> sampleHoldState_ {{}};
|
||||||
};
|
};
|
||||||
|
|
||||||
LFO::LFO(BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
|
LFO::LFO(Resources& resources)
|
||||||
: impl_(new Impl(bufferPool, beatClock, modMatrix))
|
: impl_(new Impl(resources))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,16 +65,25 @@ void LFO::start(unsigned triggerDelay)
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
const LFODescription& desc = *impl.desc_;
|
const LFODescription& desc = *impl.desc_;
|
||||||
const float sampleRate = impl.sampleRate_;
|
const float sampleRate = impl.sampleRate_;
|
||||||
|
const MidiState& state = impl.resources_.midiState;
|
||||||
|
|
||||||
impl.subPhases_.fill(0.0f);
|
impl.subPhases_.fill(0.0f);
|
||||||
impl.sampleHoldMem_.fill(0.0f);
|
impl.sampleHoldMem_.fill(0.0f);
|
||||||
impl.sampleHoldState_.fill(0);
|
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;
|
size_t delayFrames = (delay > 0) ? static_cast<size_t>(std::ceil(sampleRate * delay)) : 0u;
|
||||||
impl.delayFramesLeft_ = triggerDelay + delayFrames;
|
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 <>
|
template <>
|
||||||
|
|
@ -218,6 +222,7 @@ void LFO::process(absl::Span<float> out)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
const LFODescription& desc = *impl.desc_;
|
const LFODescription& desc = *impl.desc_;
|
||||||
|
BufferPool& pool = impl.resources_.bufferPool;
|
||||||
size_t numFrames = out.size();
|
size_t numFrames = out.size();
|
||||||
|
|
||||||
fill(out, 0.0f);
|
fill(out, 0.0f);
|
||||||
|
|
@ -235,7 +240,7 @@ void LFO::process(absl::Span<float> out)
|
||||||
if (countSubs < 1)
|
if (countSubs < 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto phasesTemp = impl.bufferPool_.getBuffer(numFrames);
|
auto phasesTemp = pool.getBuffer(numFrames);
|
||||||
if (!phasesTemp) {
|
if (!phasesTemp) {
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
fill(out, 0.0f);
|
fill(out, 0.0f);
|
||||||
|
|
@ -289,7 +294,6 @@ void LFO::process(absl::Span<float> out)
|
||||||
void LFO::processFadeIn(absl::Span<float> out)
|
void LFO::processFadeIn(absl::Span<float> out)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
const LFODescription& desc = *impl.desc_;
|
|
||||||
const float samplePeriod = 1.0f / impl.sampleRate_;
|
const float samplePeriod = 1.0f / impl.sampleRate_;
|
||||||
size_t numFrames = out.size();
|
size_t numFrames = out.size();
|
||||||
|
|
||||||
|
|
@ -297,7 +301,7 @@ void LFO::processFadeIn(absl::Span<float> out)
|
||||||
if (fadePosition >= 1.0f)
|
if (fadePosition >= 1.0f)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const float fadeTime = desc.fade;
|
const float fadeTime = impl.fadeTime_;
|
||||||
const float fadeStep = samplePeriod / fadeTime;
|
const float fadeStep = samplePeriod / fadeTime;
|
||||||
|
|
||||||
for (size_t i = 0; i < numFrames && fadePosition < 1; ++i) {
|
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)
|
void LFO::generatePhase(unsigned nth, absl::Span<float> phases)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
BufferPool& bufferPool = impl.bufferPool_;
|
BufferPool& bufferPool = impl.resources_.bufferPool;
|
||||||
BeatClock* beatClock = impl.beatClock_;
|
BeatClock& beatClock = impl.resources_.beatClock;
|
||||||
ModMatrix* modMatrix = impl.modMatrix_;
|
ModMatrix& modMatrix = impl.resources_.modMatrix;
|
||||||
const LFODescription& desc = *impl.desc_;
|
const LFODescription& desc = *impl.desc_;
|
||||||
const LFODescription::Sub& sub = desc.sub[nth];
|
const LFODescription::Sub& sub = desc.sub[nth];
|
||||||
const float samplePeriod = 1.0f / impl.sampleRate_;
|
const float samplePeriod = 1.0f / impl.sampleRate_;
|
||||||
|
|
@ -329,39 +333,39 @@ void LFO::generatePhase(unsigned nth, absl::Span<float> phases)
|
||||||
// modulations
|
// modulations
|
||||||
const float* beatsMod = nullptr;
|
const float* beatsMod = nullptr;
|
||||||
const float* freqMod = nullptr;
|
const float* freqMod = nullptr;
|
||||||
if (modMatrix) {
|
const float* phaseMod = nullptr;
|
||||||
// Note(jpc) we might switch between beats and frequency, if host
|
// Note(jpc) we might switch between beats and frequency, if host
|
||||||
// switches play state on and off; continually generate both.
|
// switches play state on and off; continually generate both.
|
||||||
beatsMod = modMatrix->getModulationByKey(desc.beatsKey);
|
beatsMod = modMatrix.getModulationByKey(desc.beatsKey);
|
||||||
freqMod = modMatrix->getModulationByKey(desc.freqKey);
|
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
|
// generate using the beat clock
|
||||||
float beatRatio = (ratio > 0) ? (1.0f / ratio) : 0.0f;
|
float beatRatio = (ratio > 0) ? (1.0f / ratio) : 0.0f;
|
||||||
|
|
||||||
if (!beatsMod)
|
if (!beatsMod)
|
||||||
beatClock->calculatePhase(beats * beatRatio, phases.data());
|
beatClock.calculatePhase(beats * beatRatio, phases.data());
|
||||||
else {
|
else {
|
||||||
auto temp = bufferPool.getBuffer(numFrames);
|
auto temp = bufferPool.getBuffer(numFrames);
|
||||||
if (!temp) {
|
if (!temp) {
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
beatClock->calculatePhase(beats * beatRatio, phases.data());
|
beatClock.calculatePhase(beats * beatRatio, phases.data());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
fill(*temp, beats);
|
fill(*temp, beats);
|
||||||
add(absl::MakeConstSpan(beatsMod, numFrames), *temp);
|
add(absl::MakeConstSpan(beatsMod, numFrames), *temp);
|
||||||
applyGain1(beatRatio, *temp);
|
applyGain1(beatRatio, *temp);
|
||||||
beatClock->calculatePhaseModulated(temp->data(), phases.data());
|
beatClock.calculatePhaseModulated(temp->data(), phases.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// generate using the frequency
|
// generate using the frequency
|
||||||
if (!freqMod) {
|
if (!freqMod) {
|
||||||
|
float incr = ratio * samplePeriod * baseFreq;
|
||||||
for (size_t i = 0; i < numFrames; ++i) {
|
for (size_t i = 0; i < numFrames; ++i) {
|
||||||
phases[i] = phase;
|
phases[i] = phase;
|
||||||
float incr = ratio * samplePeriod * baseFreq;
|
|
||||||
phase = wrapPhase(phase + incr);
|
phase = wrapPhase(phase + incr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -372,13 +376,16 @@ void LFO::generatePhase(unsigned nth, absl::Span<float> phases)
|
||||||
phase = wrapPhase(phase + incr);
|
phase = wrapPhase(phase + incr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply phase offsets
|
// apply phase offsets
|
||||||
for (size_t i = 0; i < numFrames; ++i) {
|
if (!phaseMod) {
|
||||||
float withOffset = phases[i] + phaseOffset;
|
for (size_t i = 0; i < numFrames; ++i)
|
||||||
withOffset -= (int)withOffset;
|
phases[i] = wrapPhase(phases[i] + phaseOffset);
|
||||||
phases[i] = withOffset;
|
} else {
|
||||||
|
for (size_t i = 0; i < numFrames; ++i)
|
||||||
|
phases[i] = wrapPhase(phases[i] + phaseOffset + phaseMod[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl.subPhases_[nth] = phase;
|
impl.subPhases_[nth] = phase;
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
class BufferPool;
|
struct Resources;
|
||||||
class BeatClock;
|
|
||||||
class ModMatrix;
|
|
||||||
struct Region;
|
struct Region;
|
||||||
|
|
||||||
enum class LFOWave : int;
|
enum class LFOWave : int;
|
||||||
|
|
@ -54,10 +52,7 @@ struct LFODescription;
|
||||||
|
|
||||||
class LFO {
|
class LFO {
|
||||||
public:
|
public:
|
||||||
explicit LFO(
|
explicit LFO(Resources& resources);
|
||||||
BufferPool& bufferPool,
|
|
||||||
BeatClock* beatClock = nullptr,
|
|
||||||
ModMatrix* modMatrix = nullptr);
|
|
||||||
~LFO();
|
~LFO();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Defaults.h"
|
#include "Defaults.h"
|
||||||
|
#include "CCMap.h"
|
||||||
#include "modulations/ModKey.h"
|
#include "modulations/ModKey.h"
|
||||||
#include <absl/types/optional.h>
|
#include <absl/types/optional.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
@ -19,8 +20,11 @@ struct LFODescription {
|
||||||
float freq { Default::lfoFreq }; // lfoN_freq
|
float freq { Default::lfoFreq }; // lfoN_freq
|
||||||
float beats { Default::lfoBeats }; // lfoN_beats
|
float beats { Default::lfoBeats }; // lfoN_beats
|
||||||
float phase0 { Default::lfoPhase }; // lfoN_phase
|
float phase0 { Default::lfoPhase }; // lfoN_phase
|
||||||
|
CCMap<float> phaseCC { Default::lfoPhaseMod }; // lfoN_phase_cc
|
||||||
float delay { Default::lfoDelay }; // lfoN_delay
|
float delay { Default::lfoDelay }; // lfoN_delay
|
||||||
|
CCMap<float> delayCC { Default::lfoDelayMod }; // lfoN_phase_cc
|
||||||
float fade { Default::lfoFade }; // lfoN_fade
|
float fade { Default::lfoFade }; // lfoN_fade
|
||||||
|
CCMap<float> fadeCC { Default::lfoFadeMod }; // lfoN_phase_cc
|
||||||
unsigned count { Default::lfoCount }; // lfoN_count
|
unsigned count { Default::lfoCount }; // lfoN_count
|
||||||
struct Sub {
|
struct Sub {
|
||||||
LFOWave wave { Default::lfoWave }; // lfoN_wave[X]
|
LFOWave wave { Default::lfoWave }; // lfoN_wave[X]
|
||||||
|
|
@ -37,6 +41,7 @@ struct LFODescription {
|
||||||
// modulations
|
// modulations
|
||||||
ModKey beatsKey;
|
ModKey beatsKey;
|
||||||
ModKey freqKey;
|
ModKey freqKey;
|
||||||
|
ModKey phaseKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -1140,6 +1140,7 @@ bool sfz::Region::parseLFOOpcodeV2(const Opcode& opcode)
|
||||||
//
|
//
|
||||||
lfo.beatsKey = ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber);
|
lfo.beatsKey = ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber);
|
||||||
lfo.freqKey = ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber);
|
lfo.freqKey = ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber);
|
||||||
|
lfo.phaseKey = ModKey::createNXYZ(ModId::LFOPhase, id, lfoNumber);
|
||||||
|
|
||||||
//
|
//
|
||||||
auto getOrCreateLFOStep = [&opcode, &lfo]() -> float* {
|
auto getOrCreateLFOStep = [&opcode, &lfo]() -> float* {
|
||||||
|
|
@ -1189,12 +1190,27 @@ bool sfz::Region::parseLFOOpcodeV2(const Opcode& opcode)
|
||||||
case hash("lfo&_phase"):
|
case hash("lfo&_phase"):
|
||||||
lfo.phase0 = opcode.read(Default::lfoPhase);
|
lfo.phase0 = opcode.read(Default::lfoPhase);
|
||||||
break;
|
break;
|
||||||
|
case_any_ccN("lfo&_phase"):
|
||||||
|
processGenericCc(opcode, Default::lfoPhaseMod, ModKey::createNXYZ(ModId::LFOPhase, id, lfoNumber));
|
||||||
|
break;
|
||||||
case hash("lfo&_delay"):
|
case hash("lfo&_delay"):
|
||||||
lfo.delay = opcode.read(Default::lfoDelay);
|
lfo.delay = opcode.read(Default::lfoDelay);
|
||||||
break;
|
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"):
|
case hash("lfo&_fade"):
|
||||||
lfo.fade = opcode.read(Default::lfoFade);
|
lfo.fade = opcode.read(Default::lfoFade);
|
||||||
break;
|
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"):
|
case hash("lfo&_count"):
|
||||||
lfo.count = opcode.read(Default::lfoCount);
|
lfo.count = opcode.read(Default::lfoCount);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1677,7 +1677,7 @@ void Voice::setMaxLFOsPerVoice(size_t numLFOs)
|
||||||
impl.lfos_.resize(numLFOs);
|
impl.lfos_.resize(numLFOs);
|
||||||
|
|
||||||
for (size_t i = 0; i < numLFOs; ++i) {
|
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_);
|
lfo->setSampleRate(impl.sampleRate_);
|
||||||
impl.lfos_[i] = std::move(lfo);
|
impl.lfos_[i] = std::move(lfo);
|
||||||
}
|
}
|
||||||
|
|
@ -1718,7 +1718,7 @@ void Voice::setAmplitudeLFOEnabledPerVoice(bool haveAmplitudeLFO)
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
Resources& res = impl.resources_;
|
Resources& res = impl.resources_;
|
||||||
if (haveAmplitudeLFO) {
|
if (haveAmplitudeLFO) {
|
||||||
LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix);
|
LFO* lfo = new LFO(res);
|
||||||
impl.lfoAmplitude_.reset(lfo);
|
impl.lfoAmplitude_.reset(lfo);
|
||||||
lfo->setSampleRate(impl.sampleRate_);
|
lfo->setSampleRate(impl.sampleRate_);
|
||||||
}
|
}
|
||||||
|
|
@ -1731,7 +1731,7 @@ void Voice::setPitchLFOEnabledPerVoice(bool havePitchLFO)
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
Resources& res = impl.resources_;
|
Resources& res = impl.resources_;
|
||||||
if (havePitchLFO) {
|
if (havePitchLFO) {
|
||||||
LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix);
|
LFO* lfo = new LFO(res);
|
||||||
impl.lfoPitch_.reset(lfo);
|
impl.lfoPitch_.reset(lfo);
|
||||||
lfo->setSampleRate(impl.sampleRate_);
|
lfo->setSampleRate(impl.sampleRate_);
|
||||||
}
|
}
|
||||||
|
|
@ -1744,7 +1744,7 @@ void Voice::setFilterLFOEnabledPerVoice(bool haveFilterLFO)
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
Resources& res = impl.resources_;
|
Resources& res = impl.resources_;
|
||||||
if (haveFilterLFO) {
|
if (haveFilterLFO) {
|
||||||
LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix);
|
LFO* lfo = new LFO(res);
|
||||||
impl.lfoFilter_.reset(lfo);
|
impl.lfoFilter_.reset(lfo);
|
||||||
lfo->setSampleRate(impl.sampleRate_);
|
lfo->setSampleRate(impl.sampleRate_);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,8 @@ int ModIds::flags(ModId id) noexcept
|
||||||
return kModIsPerVoice|kModIsAdditive;
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
case ModId::LFOBeats:
|
case ModId::LFOBeats:
|
||||||
return kModIsPerVoice|kModIsAdditive;
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
case ModId::LFOPhase:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
|
||||||
// unknown
|
// unknown
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ enum class ModId : int {
|
||||||
FilLFOFrequency,
|
FilLFOFrequency,
|
||||||
LFOFrequency,
|
LFOFrequency,
|
||||||
LFOBeats,
|
LFOBeats,
|
||||||
|
LFOPhase,
|
||||||
|
|
||||||
_TargetsEnd,
|
_TargetsEnd,
|
||||||
// [/targets] --------------------------------------------------------------
|
// [/targets] --------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,8 @@ std::string ModKey::toString() const
|
||||||
return absl::StrCat("LFOFrequency {", region_.number(), ", N=", 1 + params_.N, "}");
|
return absl::StrCat("LFOFrequency {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||||
case ModId::LFOBeats:
|
case ModId::LFOBeats:
|
||||||
return absl::StrCat("LFOBeats {", region_.number(), ", N=", 1 + params_.N, "}");
|
return absl::StrCat("LFOBeats {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||||
|
case ModId::LFOPhase:
|
||||||
|
return absl::StrCat("LFOPhase {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ static bool computeLFO(DataPoints& dp, const fs::path& sfzPath, double sampleRat
|
||||||
std::vector<std::unique_ptr<sfz::LFO>> lfos(numLfos);
|
std::vector<std::unique_ptr<sfz::LFO>> lfos(numLfos);
|
||||||
|
|
||||||
for (size_t l = 0; l < numLfos; ++l) {
|
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);
|
lfos[l].reset(lfo);
|
||||||
lfo->setSampleRate(sampleRate);
|
lfo->setSampleRate(sampleRate);
|
||||||
lfo->configure(&desc[l]);
|
lfo->configure(&desc[l]);
|
||||||
|
|
|
||||||
|
|
@ -408,3 +408,24 @@ TEST_CASE("[Modulations] EG v1 CC connections")
|
||||||
R"("FilterEG {1}" -> "FilterCutoff {1, N=1}")",
|
R"("FilterEG {1}" -> "FilterCutoff {1, N=1}")",
|
||||||
}, 2));
|
}, 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));
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue