commit
3fcb0a234e
20 changed files with 845 additions and 583 deletions
|
|
@ -116,8 +116,7 @@ int main(int argc, char* argv[])
|
||||||
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) {
|
||||||
const NumericId<sfz::LFO> id { static_cast<int>(l) };
|
sfz::LFO* lfo = new sfz::LFO(bufferPool);
|
||||||
sfz::LFO* lfo = new sfz::LFO(id, bufferPool);
|
|
||||||
lfos[l].reset(lfo);
|
lfos[l].reset(lfo);
|
||||||
lfo->setSampleRate(sampleRate);
|
lfo->setSampleRate(sampleRate);
|
||||||
lfo->configure(&desc[l]);
|
lfo->configure(&desc[l]);
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,9 @@ extern const OpcodeSpec<float> pitchMod { 0.0f, Range<float>(-2400.0f, 2400.0f),
|
||||||
extern const OpcodeSpec<float> bendUp { 200.0f, Range<float>(-12000.0f, 12000.0f), 0 };
|
extern const OpcodeSpec<float> bendUp { 200.0f, Range<float>(-12000.0f, 12000.0f), 0 };
|
||||||
extern const OpcodeSpec<float> bendDown { -200.0f, Range<float>(-12000.0f, 12000.0f), 0 };
|
extern const OpcodeSpec<float> bendDown { -200.0f, Range<float>(-12000.0f, 12000.0f), 0 };
|
||||||
extern const OpcodeSpec<float> bendStep { 1.0f, Range<float>(1.0f, 1200.0f), 0 };
|
extern const OpcodeSpec<float> bendStep { 1.0f, Range<float>(1.0f, 1200.0f), 0 };
|
||||||
|
extern const OpcodeSpec<float> ampLFODepth { 0.0f, Range<float>(-10.0f, 10.0f), 0 };
|
||||||
|
extern const OpcodeSpec<float> pitchLFODepth { 0.0f, Range<float>(-1200.0f, 1200.0f), 0 };
|
||||||
|
extern const OpcodeSpec<float> filLFODepth { 0.0f, Range<float>(-1200.0f, 1200.0f), 0 };
|
||||||
extern const OpcodeSpec<float> lfoFreq { 0.0f, Range<float>(0.0f, 100.0f), 0 };
|
extern const OpcodeSpec<float> lfoFreq { 0.0f, Range<float>(0.0f, 100.0f), 0 };
|
||||||
extern const OpcodeSpec<float> lfoFreqMod { 0.0f, Range<float>(-100.0f, 100.0f), 0 };
|
extern const OpcodeSpec<float> lfoFreqMod { 0.0f, Range<float>(-100.0f, 100.0f), 0 };
|
||||||
extern const OpcodeSpec<float> lfoBeats { 0.0f, Range<float>(0.0f, 1000.0f), 0 };
|
extern const OpcodeSpec<float> lfoBeats { 0.0f, Range<float>(0.0f, 1000.0f), 0 };
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,9 @@ namespace Default
|
||||||
extern const OpcodeSpec<float> bendUp;
|
extern const OpcodeSpec<float> bendUp;
|
||||||
extern const OpcodeSpec<float> bendDown;
|
extern const OpcodeSpec<float> bendDown;
|
||||||
extern const OpcodeSpec<float> bendStep;
|
extern const OpcodeSpec<float> bendStep;
|
||||||
|
extern const OpcodeSpec<float> ampLFODepth;
|
||||||
|
extern const OpcodeSpec<float> pitchLFODepth;
|
||||||
|
extern const OpcodeSpec<float> filLFODepth;
|
||||||
extern const OpcodeSpec<float> lfoFreq;
|
extern const OpcodeSpec<float> lfoFreq;
|
||||||
extern const OpcodeSpec<float> lfoFreqMod;
|
extern const OpcodeSpec<float> lfoFreqMod;
|
||||||
extern const OpcodeSpec<float> lfoBeats;
|
extern const OpcodeSpec<float> lfoBeats;
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,8 @@
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
|
||||||
struct LFO::Impl {
|
struct LFO::Impl {
|
||||||
explicit Impl(NumericId<LFO> id, BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
|
explicit Impl(BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
|
||||||
: id_(id),
|
: bufferPool_(bufferPool),
|
||||||
bufferPool_(bufferPool),
|
|
||||||
beatClock_(beatClock),
|
beatClock_(beatClock),
|
||||||
modMatrix_(modMatrix),
|
modMatrix_(modMatrix),
|
||||||
sampleRate_(config::defaultSampleRate),
|
sampleRate_(config::defaultSampleRate),
|
||||||
|
|
@ -31,7 +30,6 @@ struct LFO::Impl {
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
NumericId<LFO> id_;
|
|
||||||
BufferPool& bufferPool_;
|
BufferPool& bufferPool_;
|
||||||
BeatClock* beatClock_ = nullptr;
|
BeatClock* beatClock_ = nullptr;
|
||||||
ModMatrix* modMatrix_ = nullptr;
|
ModMatrix* modMatrix_ = nullptr;
|
||||||
|
|
@ -48,8 +46,8 @@ struct LFO::Impl {
|
||||||
std::array<int, config::maxLFOSubs> sampleHoldState_ {{}};
|
std::array<int, config::maxLFOSubs> sampleHoldState_ {{}};
|
||||||
};
|
};
|
||||||
|
|
||||||
LFO::LFO(NumericId<LFO> id, BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
|
LFO::LFO(BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
|
||||||
: impl_(new Impl(id, bufferPool, beatClock, modMatrix))
|
: impl_(new Impl(bufferPool, beatClock, modMatrix))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,11 +55,6 @@ LFO::~LFO()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
NumericId<LFO> LFO::getId() const noexcept
|
|
||||||
{
|
|
||||||
return impl_->id_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LFO::setSampleRate(double sampleRate)
|
void LFO::setSampleRate(double sampleRate)
|
||||||
{
|
{
|
||||||
impl_->sampleRate_ = sampleRate;
|
impl_->sampleRate_ = sampleRate;
|
||||||
|
|
@ -221,7 +214,7 @@ void LFO::processSteps(absl::Span<float> out, const float* phaseIn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LFO::process(absl::Span<float> out, NumericId<Region> regionId)
|
void LFO::process(absl::Span<float> out)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
const LFODescription& desc = *impl.desc_;
|
const LFODescription& desc = *impl.desc_;
|
||||||
|
|
@ -252,13 +245,13 @@ void LFO::process(absl::Span<float> out, NumericId<Region> regionId)
|
||||||
absl::Span<float> phases = *phasesTemp;
|
absl::Span<float> phases = *phasesTemp;
|
||||||
|
|
||||||
if (desc.seq) {
|
if (desc.seq) {
|
||||||
generatePhase(0, phases, regionId);
|
generatePhase(0, phases);
|
||||||
processSteps(out, phases.data());
|
processSteps(out, phases.data());
|
||||||
++subno;
|
++subno;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; subno < countSubs; ++subno) {
|
for (; subno < countSubs; ++subno) {
|
||||||
generatePhase(subno, phases, regionId);
|
generatePhase(subno, phases);
|
||||||
switch (desc.sub[subno].wave) {
|
switch (desc.sub[subno].wave) {
|
||||||
case LFOWave::Triangle:
|
case LFOWave::Triangle:
|
||||||
processWave<LFOWave::Triangle>(subno, out, phases.data());
|
processWave<LFOWave::Triangle>(subno, out, phases.data());
|
||||||
|
|
@ -315,13 +308,12 @@ void LFO::processFadeIn(absl::Span<float> out)
|
||||||
impl.fadePosition_ = fadePosition;
|
impl.fadePosition_ = fadePosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LFO::generatePhase(unsigned nth, absl::Span<float> phases, NumericId<Region> regionId)
|
void LFO::generatePhase(unsigned nth, absl::Span<float> phases)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
BufferPool& bufferPool = impl.bufferPool_;
|
BufferPool& bufferPool = impl.bufferPool_;
|
||||||
BeatClock* beatClock = impl.beatClock_;
|
BeatClock* beatClock = impl.beatClock_;
|
||||||
ModMatrix* modMatrix = impl.modMatrix_;
|
ModMatrix* modMatrix = impl.modMatrix_;
|
||||||
const NumericId<LFO> id { impl.id_ };
|
|
||||||
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_;
|
||||||
|
|
@ -337,13 +329,11 @@ void LFO::generatePhase(unsigned nth, absl::Span<float> phases, NumericId<Region
|
||||||
// modulations
|
// modulations
|
||||||
const float* beatsMod = nullptr;
|
const float* beatsMod = nullptr;
|
||||||
const float* freqMod = nullptr;
|
const float* freqMod = nullptr;
|
||||||
if (modMatrix && id && regionId) {
|
if (modMatrix) {
|
||||||
// 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.
|
||||||
ModKey beatsKey = ModKey::createNXYZ(ModId::LFOBeats, regionId, id.number());
|
beatsMod = modMatrix->getModulationByKey(desc.beatsKey);
|
||||||
ModKey freqKey = ModKey::createNXYZ(ModId::LFOFrequency, regionId, id.number());
|
freqMod = modMatrix->getModulationByKey(desc.freqKey);
|
||||||
beatsMod = modMatrix->getModulationByKey(beatsKey);
|
|
||||||
freqMod = modMatrix->getModulationByKey(freqKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (beatClock && beatClock->isPlaying() && beats > 0) {
|
if (beatClock && beatClock->isPlaying() && beats > 0) {
|
||||||
|
|
|
||||||
|
|
@ -55,14 +55,11 @@ struct LFODescription;
|
||||||
class LFO {
|
class LFO {
|
||||||
public:
|
public:
|
||||||
explicit LFO(
|
explicit LFO(
|
||||||
NumericId<LFO> id,
|
|
||||||
BufferPool& bufferPool,
|
BufferPool& bufferPool,
|
||||||
BeatClock* beatClock = nullptr,
|
BeatClock* beatClock = nullptr,
|
||||||
ModMatrix* modMatrix = nullptr);
|
ModMatrix* modMatrix = nullptr);
|
||||||
~LFO();
|
~LFO();
|
||||||
|
|
||||||
NumericId<LFO> getId() const noexcept;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Sets the sample rate.
|
Sets the sample rate.
|
||||||
*/
|
*/
|
||||||
|
|
@ -85,7 +82,7 @@ public:
|
||||||
|
|
||||||
TODO(jpc) frequency modulations
|
TODO(jpc) frequency modulations
|
||||||
*/
|
*/
|
||||||
void process(absl::Span<float> out, NumericId<Region> regionId = {});
|
void process(absl::Span<float> out);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
|
|
@ -123,7 +120,7 @@ private:
|
||||||
/**
|
/**
|
||||||
Generate the phase of the N-th generator
|
Generate the phase of the N-th generator
|
||||||
*/
|
*/
|
||||||
void generatePhase(unsigned nth, absl::Span<float> phases, NumericId<Region> regionId);
|
void generatePhase(unsigned nth, absl::Span<float> phases);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Impl;
|
struct Impl;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Defaults.h"
|
#include "Defaults.h"
|
||||||
|
#include "modulations/ModKey.h"
|
||||||
#include <absl/types/optional.h>
|
#include <absl/types/optional.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
@ -32,6 +33,10 @@ struct LFODescription {
|
||||||
};
|
};
|
||||||
absl::optional<StepSequence> seq;
|
absl::optional<StepSequence> seq;
|
||||||
std::vector<Sub> sub;
|
std::vector<Sub> sub;
|
||||||
|
|
||||||
|
// modulations
|
||||||
|
ModKey beatsKey;
|
||||||
|
ModKey freqKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,28 @@ static absl::string_view extractBackInteger(absl::string_view opcodeName)
|
||||||
return opcodeName.substr(i);
|
return opcodeName.substr(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Opcode::getLetterOnlyName() const
|
||||||
|
{
|
||||||
|
absl::string_view name { this->name };
|
||||||
|
|
||||||
|
std::string letterOnlyName;
|
||||||
|
letterOnlyName.reserve(name.size());
|
||||||
|
|
||||||
|
bool charWasDigit = false;
|
||||||
|
for (unsigned char c : name) {
|
||||||
|
bool charIsDigit = absl::ascii_isdigit(c);
|
||||||
|
|
||||||
|
if (!charIsDigit)
|
||||||
|
letterOnlyName.push_back(c);
|
||||||
|
else if (!charWasDigit)
|
||||||
|
letterOnlyName.push_back('&');
|
||||||
|
|
||||||
|
charWasDigit = charIsDigit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return letterOnlyName;
|
||||||
|
}
|
||||||
|
|
||||||
std::string Opcode::getDerivedName(OpcodeCategory newCategory, unsigned number) const
|
std::string Opcode::getDerivedName(OpcodeCategory newCategory, unsigned number) const
|
||||||
{
|
{
|
||||||
std::string derivedName(name);
|
std::string derivedName(name);
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,12 @@ struct Opcode {
|
||||||
*/
|
*/
|
||||||
Opcode cleanUp(OpcodeScope scope) const;
|
Opcode cleanUp(OpcodeScope scope) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate a letter-only name, replacing any digit sequence with
|
||||||
|
* in the opcode name with a single ampersand character.
|
||||||
|
*/
|
||||||
|
std::string getLetterOnlyName() const;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @brief Get the derived opcode name to convert it to another category.
|
* @brief Get the derived opcode name to convert it to another category.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
1077
src/sfizz/Region.cpp
1077
src/sfizz/Region.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -246,6 +246,26 @@ struct Region {
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
bool parseOpcode(const Opcode& opcode);
|
bool parseOpcode(const Opcode& opcode);
|
||||||
|
/**
|
||||||
|
* @brief Parse a opcode which is specific to a particular SFZv1 LFO:
|
||||||
|
* amplfo, pitchlfo, fillfo.
|
||||||
|
*
|
||||||
|
* @param opcode
|
||||||
|
* @param lfo
|
||||||
|
* @return true if the opcode was properly read and stored.
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
|
bool parseLFOOpcode(const Opcode& opcode, LFODescription& lfo);
|
||||||
|
/**
|
||||||
|
* @brief Parse a opcode which is specific to a particular SFZv1 LFO:
|
||||||
|
* amplfo, pitchlfo, fillfo.
|
||||||
|
*
|
||||||
|
* @param opcode
|
||||||
|
* @param lfo
|
||||||
|
* @return true if the opcode was properly read and stored.
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
|
bool parseLFOOpcode(const Opcode& opcode, absl::optional<LFODescription>& lfo);
|
||||||
/**
|
/**
|
||||||
* @brief Parse a opcode which is specific to a particular SFZv1 EG:
|
* @brief Parse a opcode which is specific to a particular SFZv1 EG:
|
||||||
* ampeg, pitcheg, fileg.
|
* ampeg, pitcheg, fileg.
|
||||||
|
|
@ -266,6 +286,22 @@ struct Region {
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
bool parseEGOpcode(const Opcode& opcode, absl::optional<EGDescription>& eg);
|
bool parseEGOpcode(const Opcode& opcode, absl::optional<EGDescription>& eg);
|
||||||
|
/**
|
||||||
|
* @brief Parse a opcode which is specific to a particular SFZv2 LFO: lfoN.
|
||||||
|
*
|
||||||
|
* @param opcode
|
||||||
|
* @return true if the opcode was properly read and stored.
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
|
bool parseLFOOpcodeV2(const Opcode& opcode);
|
||||||
|
/**
|
||||||
|
* @brief Parse a opcode which is specific to a particular SFZv2 EG: egN.
|
||||||
|
*
|
||||||
|
* @param opcode
|
||||||
|
* @return true if the opcode was properly read and stored.
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
|
bool parseEGOpcodeV2(const Opcode& opcode);
|
||||||
/**
|
/**
|
||||||
* @brief Process a generic CC opcode, and fill the modulation parameters.
|
* @brief Process a generic CC opcode, and fill the modulation parameters.
|
||||||
*
|
*
|
||||||
|
|
@ -441,6 +477,9 @@ struct Region {
|
||||||
|
|
||||||
// LFOs
|
// LFOs
|
||||||
std::vector<LFODescription> lfos;
|
std::vector<LFODescription> lfos;
|
||||||
|
absl::optional<LFODescription> amplitudeLFO;
|
||||||
|
absl::optional<LFODescription> pitchLFO;
|
||||||
|
absl::optional<LFODescription> filterLFO;
|
||||||
|
|
||||||
bool hasStereoSample { false };
|
bool hasStereoSample { false };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -551,6 +551,9 @@ void Synth::Impl::finalizeSfzLoad()
|
||||||
size_t maxFlexEGs { 0 };
|
size_t maxFlexEGs { 0 };
|
||||||
bool havePitchEG { false };
|
bool havePitchEG { false };
|
||||||
bool haveFilterEG { false };
|
bool haveFilterEG { false };
|
||||||
|
bool haveAmplitudeLFO { false };
|
||||||
|
bool havePitchLFO { false };
|
||||||
|
bool haveFilterLFO { false };
|
||||||
|
|
||||||
FlexEGs::clearUnusedCurves();
|
FlexEGs::clearUnusedCurves();
|
||||||
|
|
||||||
|
|
@ -683,6 +686,9 @@ void Synth::Impl::finalizeSfzLoad()
|
||||||
maxFlexEGs = max(maxFlexEGs, region->flexEGs.size());
|
maxFlexEGs = max(maxFlexEGs, region->flexEGs.size());
|
||||||
havePitchEG = havePitchEG || region->pitchEG != absl::nullopt;
|
havePitchEG = havePitchEG || region->pitchEG != absl::nullopt;
|
||||||
haveFilterEG = haveFilterEG || region->filterEG != absl::nullopt;
|
haveFilterEG = haveFilterEG || region->filterEG != absl::nullopt;
|
||||||
|
haveAmplitudeLFO = haveAmplitudeLFO || region->amplitudeLFO != absl::nullopt;
|
||||||
|
havePitchLFO = havePitchLFO || region->pitchLFO != absl::nullopt;
|
||||||
|
haveFilterLFO = haveFilterLFO || region->filterLFO != absl::nullopt;
|
||||||
|
|
||||||
++currentRegionIndex;
|
++currentRegionIndex;
|
||||||
}
|
}
|
||||||
|
|
@ -731,6 +737,9 @@ void Synth::Impl::finalizeSfzLoad()
|
||||||
settingsPerVoice_.maxFlexEGs = maxFlexEGs;
|
settingsPerVoice_.maxFlexEGs = maxFlexEGs;
|
||||||
settingsPerVoice_.havePitchEG = havePitchEG;
|
settingsPerVoice_.havePitchEG = havePitchEG;
|
||||||
settingsPerVoice_.haveFilterEG = haveFilterEG;
|
settingsPerVoice_.haveFilterEG = haveFilterEG;
|
||||||
|
settingsPerVoice_.haveAmplitudeLFO = haveAmplitudeLFO;
|
||||||
|
settingsPerVoice_.havePitchLFO = havePitchLFO;
|
||||||
|
settingsPerVoice_.haveFilterLFO = haveFilterLFO;
|
||||||
|
|
||||||
applySettingsPerVoice();
|
applySettingsPerVoice();
|
||||||
|
|
||||||
|
|
@ -1579,6 +1588,9 @@ void Synth::Impl::applySettingsPerVoice()
|
||||||
voice.setMaxFlexEGsPerVoice(settingsPerVoice_.maxFlexEGs);
|
voice.setMaxFlexEGsPerVoice(settingsPerVoice_.maxFlexEGs);
|
||||||
voice.setPitchEGEnabledPerVoice(settingsPerVoice_.havePitchEG);
|
voice.setPitchEGEnabledPerVoice(settingsPerVoice_.havePitchEG);
|
||||||
voice.setFilterEGEnabledPerVoice(settingsPerVoice_.haveFilterEG);
|
voice.setFilterEGEnabledPerVoice(settingsPerVoice_.haveFilterEG);
|
||||||
|
voice.setAmplitudeLFOEnabledPerVoice(settingsPerVoice_.haveAmplitudeLFO);
|
||||||
|
voice.setPitchLFOEnabledPerVoice(settingsPerVoice_.havePitchLFO);
|
||||||
|
voice.setFilterLFOEnabledPerVoice(settingsPerVoice_.haveFilterLFO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1605,6 +1617,9 @@ void Synth::Impl::setupModMatrix()
|
||||||
case ModId::Controller:
|
case ModId::Controller:
|
||||||
gen = genController_.get();
|
gen = genController_.get();
|
||||||
break;
|
break;
|
||||||
|
case ModId::AmpLFO:
|
||||||
|
case ModId::PitchLFO:
|
||||||
|
case ModId::FilLFO:
|
||||||
case ModId::LFO:
|
case ModId::LFO:
|
||||||
gen = genLFO_.get();
|
gen = genLFO_.get();
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -283,6 +283,9 @@ struct Synth::Impl final: public Parser::Listener {
|
||||||
size_t maxFlexEGs { 0 };
|
size_t maxFlexEGs { 0 };
|
||||||
bool havePitchEG { false };
|
bool havePitchEG { false };
|
||||||
bool haveFilterEG { false };
|
bool haveFilterEG { false };
|
||||||
|
bool haveAmplitudeLFO { false };
|
||||||
|
bool havePitchLFO { false };
|
||||||
|
bool haveFilterLFO { false };
|
||||||
} settingsPerVoice_;
|
} settingsPerVoice_;
|
||||||
|
|
||||||
Duration dispatchDuration_ { 0 };
|
Duration dispatchDuration_ { 0 };
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,10 @@ struct Voice::Impl
|
||||||
std::vector<std::unique_ptr<LFO>> lfos_;
|
std::vector<std::unique_ptr<LFO>> lfos_;
|
||||||
std::vector<std::unique_ptr<FlexEnvelope>> flexEGs_;
|
std::vector<std::unique_ptr<FlexEnvelope>> flexEGs_;
|
||||||
|
|
||||||
|
std::unique_ptr<LFO> lfoAmplitude_;
|
||||||
|
std::unique_ptr<LFO> lfoPitch_;
|
||||||
|
std::unique_ptr<LFO> lfoFilter_;
|
||||||
|
|
||||||
ADSREnvelope egAmplitude_;
|
ADSREnvelope egAmplitude_;
|
||||||
std::unique_ptr<ADSREnvelope> egPitch_;
|
std::unique_ptr<ADSREnvelope> egPitch_;
|
||||||
std::unique_ptr<ADSREnvelope> egFilter_;
|
std::unique_ptr<ADSREnvelope> egFilter_;
|
||||||
|
|
@ -595,6 +599,12 @@ void Voice::setSampleRate(float sampleRate) noexcept
|
||||||
|
|
||||||
for (auto& lfo : impl.lfos_)
|
for (auto& lfo : impl.lfos_)
|
||||||
lfo->setSampleRate(sampleRate);
|
lfo->setSampleRate(sampleRate);
|
||||||
|
if (auto* lfo = impl.lfoAmplitude_.get())
|
||||||
|
lfo->setSampleRate(sampleRate);
|
||||||
|
if (auto* lfo = impl.lfoPitch_.get())
|
||||||
|
lfo->setSampleRate(sampleRate);
|
||||||
|
if (auto* lfo = impl.lfoFilter_.get())
|
||||||
|
lfo->setSampleRate(sampleRate);
|
||||||
|
|
||||||
for (auto& filter : impl.filters_)
|
for (auto& filter : impl.filters_)
|
||||||
filter.setSampleRate(sampleRate);
|
filter.setSampleRate(sampleRate);
|
||||||
|
|
@ -1604,8 +1614,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) {
|
||||||
const NumericId<LFO> id { static_cast<int>(i) };
|
auto lfo = absl::make_unique<LFO>(resources.bufferPool, &resources.beatClock, &resources.modMatrix);
|
||||||
auto lfo = absl::make_unique<LFO>(id, resources.bufferPool, &resources.beatClock, &resources.modMatrix);
|
|
||||||
lfo->setSampleRate(impl.sampleRate_);
|
lfo->setSampleRate(impl.sampleRate_);
|
||||||
impl.lfos_[i] = std::move(lfo);
|
impl.lfos_[i] = std::move(lfo);
|
||||||
}
|
}
|
||||||
|
|
@ -1641,6 +1650,45 @@ void Voice::setFilterEGEnabledPerVoice(bool haveFilterEG)
|
||||||
impl.egFilter_.reset();
|
impl.egFilter_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Voice::setAmplitudeLFOEnabledPerVoice(bool haveAmplitudeLFO)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
Resources& res = impl.resources_;
|
||||||
|
if (haveAmplitudeLFO) {
|
||||||
|
LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix);
|
||||||
|
impl.lfoAmplitude_.reset(lfo);
|
||||||
|
lfo->setSampleRate(impl.sampleRate_);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
impl.lfoAmplitude_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Voice::setPitchLFOEnabledPerVoice(bool havePitchLFO)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
Resources& res = impl.resources_;
|
||||||
|
if (havePitchLFO) {
|
||||||
|
LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix);
|
||||||
|
impl.lfoPitch_.reset(lfo);
|
||||||
|
lfo->setSampleRate(impl.sampleRate_);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
impl.lfoPitch_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Voice::setFilterLFOEnabledPerVoice(bool haveFilterLFO)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
Resources& res = impl.resources_;
|
||||||
|
if (haveFilterLFO) {
|
||||||
|
LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix);
|
||||||
|
impl.lfoFilter_.reset(lfo);
|
||||||
|
lfo->setSampleRate(impl.sampleRate_);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
impl.lfoFilter_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
void Voice::Impl::setupOscillatorUnison()
|
void Voice::Impl::setupOscillatorUnison()
|
||||||
{
|
{
|
||||||
const int m = region_->oscillatorMulti;
|
const int m = region_->oscillatorMulti;
|
||||||
|
|
@ -1856,6 +1904,24 @@ Duration Voice::getLastPanningDuration() const noexcept
|
||||||
return impl.panningDuration_;
|
return impl.panningDuration_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LFO* Voice::getAmplitudeLFO()
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
return impl.lfoAmplitude_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
LFO* Voice::getPitchLFO()
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
return impl.lfoPitch_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
LFO* Voice::getFilterLFO()
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
return impl.lfoFilter_.get();
|
||||||
|
}
|
||||||
|
|
||||||
ADSREnvelope* Voice::getAmplitudeEG()
|
ADSREnvelope* Voice::getAmplitudeEG()
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,24 @@ public:
|
||||||
* @param haveFilterEG
|
* @param haveFilterEG
|
||||||
*/
|
*/
|
||||||
void setFilterEGEnabledPerVoice(bool haveFilterEG);
|
void setFilterEGEnabledPerVoice(bool haveFilterEG);
|
||||||
|
/**
|
||||||
|
* @brief Set whether SFZv1 amplitude LFO is enabled on this voice
|
||||||
|
*
|
||||||
|
* @param haveAmplitudeLFO
|
||||||
|
*/
|
||||||
|
void setAmplitudeLFOEnabledPerVoice(bool haveAmplitudeLFO);
|
||||||
|
/**
|
||||||
|
* @brief Set whether SFZv1 pitch LFO is enabled on this voice
|
||||||
|
*
|
||||||
|
* @param havePitchLFO
|
||||||
|
*/
|
||||||
|
void setPitchLFOEnabledPerVoice(bool havePitchLFO);
|
||||||
|
/**
|
||||||
|
* @brief Set whether SFZv1 filter LFO is enabled on this voice
|
||||||
|
*
|
||||||
|
* @param haveFilterLFO
|
||||||
|
*/
|
||||||
|
void setFilterLFOEnabledPerVoice(bool haveFilterLFO);
|
||||||
/**
|
/**
|
||||||
* @brief Release the voice after a given delay
|
* @brief Release the voice after a given delay
|
||||||
*
|
*
|
||||||
|
|
@ -333,6 +351,19 @@ public:
|
||||||
Duration getLastFilterDuration() const noexcept;
|
Duration getLastFilterDuration() const noexcept;
|
||||||
Duration getLastPanningDuration() const noexcept;
|
Duration getLastPanningDuration() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the SFZv1 amplitude LFO, if existing
|
||||||
|
*/
|
||||||
|
LFO* getAmplitudeLFO();
|
||||||
|
/**
|
||||||
|
* @brief Get the SFZv1 pitch LFO, if existing
|
||||||
|
*/
|
||||||
|
LFO* getPitchLFO();
|
||||||
|
/**
|
||||||
|
* @brief Get the SFZv1 filter LFO, if existing
|
||||||
|
*/
|
||||||
|
LFO* getFilterLFO();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the SFZv1 amplitude EG, if existing
|
* @brief Get the SFZv1 amplitude EG, if existing
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,12 @@ int ModIds::flags(ModId id) noexcept
|
||||||
return kModIsPerVoice;
|
return kModIsPerVoice;
|
||||||
case ModId::LFO:
|
case ModId::LFO:
|
||||||
return kModIsPerVoice;
|
return kModIsPerVoice;
|
||||||
|
case ModId::AmpLFO:
|
||||||
|
return kModIsPerVoice;
|
||||||
|
case ModId::PitchLFO:
|
||||||
|
return kModIsPerVoice;
|
||||||
|
case ModId::FilLFO:
|
||||||
|
return kModIsPerVoice;
|
||||||
case ModId::AmpEG:
|
case ModId::AmpEG:
|
||||||
return kModIsPerVoice;
|
return kModIsPerVoice;
|
||||||
case ModId::PitchEG:
|
case ModId::PitchEG:
|
||||||
|
|
@ -70,6 +76,12 @@ 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::AmpLFOFrequency:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
case ModId::PitchLFOFrequency:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
case ModId::FilLFOFrequency:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
case ModId::LFOFrequency:
|
case ModId::LFOFrequency:
|
||||||
return kModIsPerVoice|kModIsAdditive;
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
case ModId::LFOBeats:
|
case ModId::LFOBeats:
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,9 @@ enum class ModId : int {
|
||||||
Controller = _SourcesStart,
|
Controller = _SourcesStart,
|
||||||
Envelope,
|
Envelope,
|
||||||
LFO,
|
LFO,
|
||||||
|
AmpLFO,
|
||||||
|
PitchLFO,
|
||||||
|
FilLFO,
|
||||||
AmpEG,
|
AmpEG,
|
||||||
PitchEG,
|
PitchEG,
|
||||||
FilEG,
|
FilEG,
|
||||||
|
|
@ -50,6 +53,9 @@ enum class ModId : int {
|
||||||
EqBandwidth,
|
EqBandwidth,
|
||||||
OscillatorDetune,
|
OscillatorDetune,
|
||||||
OscillatorModDepth,
|
OscillatorModDepth,
|
||||||
|
AmpLFOFrequency,
|
||||||
|
PitchLFOFrequency,
|
||||||
|
FilLFOFrequency,
|
||||||
LFOFrequency,
|
LFOFrequency,
|
||||||
LFOBeats,
|
LFOBeats,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,12 @@ std::string ModKey::toString() const
|
||||||
return absl::StrCat("EG ", 1 + params_.N, " {", region_.number(), "}");
|
return absl::StrCat("EG ", 1 + params_.N, " {", region_.number(), "}");
|
||||||
case ModId::LFO:
|
case ModId::LFO:
|
||||||
return absl::StrCat("LFO ", 1 + params_.N, " {", region_.number(), "}");
|
return absl::StrCat("LFO ", 1 + params_.N, " {", region_.number(), "}");
|
||||||
|
case ModId::AmpLFO:
|
||||||
|
return absl::StrCat("AmplitudeLFO {", region_.number(), "}");
|
||||||
|
case ModId::PitchLFO:
|
||||||
|
return absl::StrCat("PitchLFO {", region_.number(), "}");
|
||||||
|
case ModId::FilLFO:
|
||||||
|
return absl::StrCat("FilterLFO {", region_.number(), "}");
|
||||||
case ModId::AmpEG:
|
case ModId::AmpEG:
|
||||||
return absl::StrCat("AmplitudeEG {", region_.number(), "}");
|
return absl::StrCat("AmplitudeEG {", region_.number(), "}");
|
||||||
case ModId::PitchEG:
|
case ModId::PitchEG:
|
||||||
|
|
@ -138,6 +144,12 @@ 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::AmpLFOFrequency:
|
||||||
|
return absl::StrCat("AmplitudeLFOFrequency {", region_.number(), "}");
|
||||||
|
case ModId::PitchLFOFrequency:
|
||||||
|
return absl::StrCat("PitchLFOFrequency {", region_.number(), "}");
|
||||||
|
case ModId::FilLFOFrequency:
|
||||||
|
return absl::StrCat("FilterLFOFrequency {", region_.number(), "}");
|
||||||
case ModId::LFOFrequency:
|
case ModId::LFOFrequency:
|
||||||
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:
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@ LFOSource::LFOSource(VoiceManager& manager)
|
||||||
|
|
||||||
void LFOSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
void LFOSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||||
{
|
{
|
||||||
unsigned lfoIndex = sourceKey.parameters().N;
|
|
||||||
|
|
||||||
Voice* voice = voiceManager_.getVoiceById(voiceId);
|
Voice* voice = voiceManager_.getVoiceById(voiceId);
|
||||||
if (!voice) {
|
if (!voice) {
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
|
|
@ -30,13 +28,39 @@ void LFOSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned
|
||||||
}
|
}
|
||||||
|
|
||||||
const Region* region = voice->getRegion();
|
const Region* region = voice->getRegion();
|
||||||
if (lfoIndex >= region->lfos.size()) {
|
LFO* lfo = nullptr;
|
||||||
|
const LFODescription* desc = nullptr;
|
||||||
|
|
||||||
|
switch (sourceKey.id()) {
|
||||||
|
case ModId::AmpLFO:
|
||||||
|
lfo = voice->getAmplitudeLFO();
|
||||||
|
desc = &*region->amplitudeLFO;
|
||||||
|
break;
|
||||||
|
case ModId::PitchLFO:
|
||||||
|
lfo = voice->getPitchLFO();
|
||||||
|
desc = &*region->pitchLFO;
|
||||||
|
break;
|
||||||
|
case ModId::FilLFO:
|
||||||
|
lfo = voice->getFilterLFO();
|
||||||
|
desc = &*region->filterLFO;
|
||||||
|
break;
|
||||||
|
case ModId::LFO:
|
||||||
|
{
|
||||||
|
unsigned lfoIndex = sourceKey.parameters().N;
|
||||||
|
if (lfoIndex >= region->lfos.size()) {
|
||||||
|
ASSERTFALSE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lfo = voice->getLFO(lfoIndex);
|
||||||
|
desc = ®ion->lfos[lfoIndex];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LFO* lfo = voice->getLFO(lfoIndex);
|
lfo->configure(desc);
|
||||||
lfo->configure(®ion->lfos[lfoIndex]);
|
|
||||||
lfo->start(delay);
|
lfo->start(delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,14 +76,34 @@ void LFOSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl
|
||||||
}
|
}
|
||||||
|
|
||||||
const Region* region = voice->getRegion();
|
const Region* region = voice->getRegion();
|
||||||
if (lfoIndex >= region->lfos.size()) {
|
LFO* lfo = nullptr;
|
||||||
|
|
||||||
|
switch (sourceKey.id()) {
|
||||||
|
case ModId::AmpLFO:
|
||||||
|
lfo = voice->getAmplitudeLFO();
|
||||||
|
break;
|
||||||
|
case ModId::PitchLFO:
|
||||||
|
lfo = voice->getPitchLFO();
|
||||||
|
break;
|
||||||
|
case ModId::FilLFO:
|
||||||
|
lfo = voice->getFilterLFO();
|
||||||
|
break;
|
||||||
|
case ModId::LFO:
|
||||||
|
{
|
||||||
|
if (lfoIndex >= region->lfos.size()) {
|
||||||
|
ASSERTFALSE;
|
||||||
|
fill(buffer, 0.0f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lfo = voice->getLFO(lfoIndex);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
fill(buffer, 0.0f);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LFO* lfo = voice->getLFO(lfoIndex);
|
lfo->process(buffer);
|
||||||
lfo->process(buffer, region->getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -28,8 +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) {
|
||||||
const NumericId<sfz::LFO> id { static_cast<int>(l) };
|
sfz::LFO* lfo = new sfz::LFO(resources.bufferPool);
|
||||||
sfz::LFO* lfo = new sfz::LFO(id, resources.bufferPool);
|
|
||||||
lfos[l].reset(lfo);
|
lfos[l].reset(lfo);
|
||||||
lfo->setSampleRate(sampleRate);
|
lfo->setSampleRate(sampleRate);
|
||||||
lfo->configure(&desc[l]);
|
lfo->configure(&desc[l]);
|
||||||
|
|
|
||||||
|
|
@ -354,3 +354,20 @@ TEST_CASE("[Modulations] Aftertouch connections")
|
||||||
R"("ChannelAftertouch" -> "FilterCutoff {1, N=2}")",
|
R"("ChannelAftertouch" -> "FilterCutoff {1, N=2}")",
|
||||||
}, 2));
|
}, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Modulations] LFO v1 connections")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzString("/modulation.sfz", R"(
|
||||||
|
<region> sample=*sine amplfo_freq=1.0
|
||||||
|
<region> sample=*sine pitchlfo_freq=1.0
|
||||||
|
<region> sample=*sine fillfo_freq=1.0
|
||||||
|
)");
|
||||||
|
|
||||||
|
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||||
|
REQUIRE(graph == createDefaultGraph({
|
||||||
|
R"("AmplitudeLFO {0}" -> "Volume {0}")",
|
||||||
|
R"("PitchLFO {1}" -> "Pitch {1}")",
|
||||||
|
R"("FilterLFO {2}" -> "FilterCutoff {2, N=1}")",
|
||||||
|
}, 3));
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue