commit
dc422d07d9
19 changed files with 285 additions and 40 deletions
|
|
@ -34,6 +34,8 @@ struct FlexEGDescription {
|
||||||
int dynamic { Default::flexEGDynamic }; // whether parameters can be modulated while EG runs
|
int dynamic { Default::flexEGDynamic }; // whether parameters can be modulated while EG runs
|
||||||
int sustain { Default::flexEGSustain }; // index of the sustain point (default to 0 in ARIA)
|
int sustain { Default::flexEGSustain }; // index of the sustain point (default to 0 in ARIA)
|
||||||
std::vector<FlexEGPoint> points;
|
std::vector<FlexEGPoint> points;
|
||||||
|
// ARIA
|
||||||
|
bool ampeg = false; // replaces the SFZv1 AmpEG (lowest with this bit wins)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,25 @@ void FlexEnvelope::release(unsigned releaseDelay)
|
||||||
impl.currentFramesUntilRelease_ = releaseDelay;
|
impl.currentFramesUntilRelease_ = releaseDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned FlexEnvelope::getRemainingDelay() const noexcept
|
||||||
|
{
|
||||||
|
const Impl& impl = *impl_;
|
||||||
|
return static_cast<unsigned>(impl.delayFramesLeft_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FlexEnvelope::isReleased() const noexcept
|
||||||
|
{
|
||||||
|
const Impl& impl = *impl_;
|
||||||
|
return impl.isReleased_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FlexEnvelope::isFinished() const noexcept
|
||||||
|
{
|
||||||
|
const Impl& impl = *impl_;
|
||||||
|
const FlexEGDescription& desc = *impl.desc_;
|
||||||
|
return impl.currentStageNumber_ >= desc.points.size();
|
||||||
|
}
|
||||||
|
|
||||||
void FlexEnvelope::process(absl::Span<float> out)
|
void FlexEnvelope::process(absl::Span<float> out)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,21 @@ public:
|
||||||
*/
|
*/
|
||||||
void release(unsigned releaseDelay);
|
void release(unsigned releaseDelay);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the remaining delay samples
|
||||||
|
*/
|
||||||
|
unsigned getRemainingDelay() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Is the envelope released?
|
||||||
|
*/
|
||||||
|
bool isReleased() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Is the envelope finished?
|
||||||
|
*/
|
||||||
|
bool isFinished() const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Process a cycle of the generator.
|
Process a cycle of the generator.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -210,14 +210,18 @@ absl::optional<ValueType> readOpcode(absl::string_view value, const Range<ValueT
|
||||||
|
|
||||||
absl::optional<bool> readBooleanFromOpcode(const Opcode& opcode)
|
absl::optional<bool> readBooleanFromOpcode(const Opcode& opcode)
|
||||||
{
|
{
|
||||||
switch (hash(opcode.value)) {
|
// Cakewalk-style booleans, case-insensitive
|
||||||
case hash("off"):
|
if (absl::EqualsIgnoreCase(opcode.value, "off"))
|
||||||
return false;
|
return false;
|
||||||
case hash("on"):
|
if (absl::EqualsIgnoreCase(opcode.value, "on"))
|
||||||
return true;
|
return true;
|
||||||
default:
|
|
||||||
return {};
|
// ARIA-style booleans? (seen in egN_dynamic=1 for example)
|
||||||
}
|
// TODO check this
|
||||||
|
if (auto value = readOpcode(opcode.value, Range<int64_t>::wholeRange()))
|
||||||
|
return *value != 0;
|
||||||
|
|
||||||
|
return absl::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class ValueType>
|
template <class ValueType>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include "MathHelpers.h"
|
#include "MathHelpers.h"
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
|
|
@ -116,6 +117,17 @@ public:
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a range which covers the whole numeric domain
|
||||||
|
*/
|
||||||
|
static constexpr Range<Type> wholeRange() noexcept
|
||||||
|
{
|
||||||
|
return Range<Type> {
|
||||||
|
std::numeric_limits<Type>::min(),
|
||||||
|
std::numeric_limits<Type>::max(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Type _start { static_cast<Type>(0.0) };
|
Type _start { static_cast<Type>(0.0) };
|
||||||
Type _end { static_cast<Type>(0.0) };
|
Type _end { static_cast<Type>(0.0) };
|
||||||
|
|
|
||||||
|
|
@ -1162,6 +1162,27 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
||||||
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqBandwidth, Default::eqBandwidthModRange);
|
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqBandwidth, Default::eqBandwidthModRange);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case hash("eg&_ampeg"):
|
||||||
|
{
|
||||||
|
const auto egNumber = opcode.parameters.front();
|
||||||
|
if (egNumber == 0)
|
||||||
|
return false;
|
||||||
|
if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs))
|
||||||
|
return false;
|
||||||
|
if (auto ampeg = readBooleanFromOpcode(opcode)) {
|
||||||
|
FlexEGDescription& desc = flexEGs[egNumber - 1];
|
||||||
|
if (desc.ampeg != *ampeg) {
|
||||||
|
desc.ampeg = *ampeg;
|
||||||
|
flexAmpEG = absl::nullopt;
|
||||||
|
for (size_t i = 0, n = flexEGs.size(); i < n && !flexAmpEG; ++i) {
|
||||||
|
if (flexEGs[i].ampeg)
|
||||||
|
flexAmpEG = static_cast<uint8_t>(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Amplitude Envelope
|
// Amplitude Envelope
|
||||||
case hash("ampeg_attack"):
|
case hash("ampeg_attack"):
|
||||||
case hash("ampeg_decay"):
|
case hash("ampeg_decay"):
|
||||||
|
|
@ -1907,7 +1928,7 @@ float sfz::Region::getBendInCents(float bend) const noexcept
|
||||||
return bend > 0.0f ? bend * static_cast<float>(bendUp) : -bend * static_cast<float>(bendDown);
|
return bend > 0.0f ? bend * static_cast<float>(bendUp) : -bend * static_cast<float>(bendDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
sfz::Region::Connection& sfz::Region::getOrCreateConnection(const ModKey& source, const ModKey& target)
|
sfz::Region::Connection* sfz::Region::getConnection(const ModKey& source, const ModKey& target)
|
||||||
{
|
{
|
||||||
auto pred = [&source, &target](const Connection& c)
|
auto pred = [&source, &target](const Connection& c)
|
||||||
{
|
{
|
||||||
|
|
@ -1915,8 +1936,13 @@ sfz::Region::Connection& sfz::Region::getOrCreateConnection(const ModKey& source
|
||||||
};
|
};
|
||||||
|
|
||||||
auto it = std::find_if(connections.begin(), connections.end(), pred);
|
auto it = std::find_if(connections.begin(), connections.end(), pred);
|
||||||
if (it != connections.end())
|
return (it == connections.end()) ? nullptr : &*it;
|
||||||
return *it;
|
}
|
||||||
|
|
||||||
|
sfz::Region::Connection& sfz::Region::getOrCreateConnection(const ModKey& source, const ModKey& target)
|
||||||
|
{
|
||||||
|
if (Connection* c = getConnection(source, target))
|
||||||
|
return *c;
|
||||||
|
|
||||||
sfz::Region::Connection c;
|
sfz::Region::Connection c;
|
||||||
c.source = source;
|
c.source = source;
|
||||||
|
|
|
||||||
|
|
@ -425,6 +425,7 @@ struct Region {
|
||||||
|
|
||||||
// Envelopes
|
// Envelopes
|
||||||
std::vector<FlexEGDescription> flexEGs;
|
std::vector<FlexEGDescription> flexEGs;
|
||||||
|
absl::optional<uint8_t> flexAmpEG; // egN_ampeg
|
||||||
|
|
||||||
// LFOs
|
// LFOs
|
||||||
std::vector<LFODescription> lfos;
|
std::vector<LFODescription> lfos;
|
||||||
|
|
@ -445,6 +446,7 @@ struct Region {
|
||||||
float velToDepth = 0.0f;
|
float velToDepth = 0.0f;
|
||||||
};
|
};
|
||||||
std::vector<Connection> connections;
|
std::vector<Connection> connections;
|
||||||
|
Connection* getConnection(const ModKey& source, const ModKey& target);
|
||||||
Connection& getOrCreateConnection(const ModKey& source, const ModKey& target);
|
Connection& getOrCreateConnection(const ModKey& source, const ModKey& target);
|
||||||
|
|
||||||
// Parent
|
// Parent
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,16 @@ void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
|
||||||
parseOpcodes(groupOpcodes);
|
parseOpcodes(groupOpcodes);
|
||||||
parseOpcodes(regionOpcodes);
|
parseOpcodes(regionOpcodes);
|
||||||
|
|
||||||
|
// Create the amplitude envelope
|
||||||
|
if (!lastRegion->flexAmpEG)
|
||||||
|
lastRegion->getOrCreateConnection(
|
||||||
|
ModKey::createNXYZ(ModId::AmpEG, lastRegion->id),
|
||||||
|
ModKey::createNXYZ(ModId::MasterAmplitude, lastRegion->id)).sourceDepth = 1.0f;
|
||||||
|
else
|
||||||
|
lastRegion->getOrCreateConnection(
|
||||||
|
ModKey::createNXYZ(ModId::Envelope, lastRegion->id, *lastRegion->flexAmpEG),
|
||||||
|
ModKey::createNXYZ(ModId::MasterAmplitude, lastRegion->id)).sourceDepth = 1.0f;
|
||||||
|
|
||||||
if (octaveOffset != 0 || noteOffset != 0)
|
if (octaveOffset != 0 || noteOffset != 0)
|
||||||
lastRegion->offsetAllKeys(octaveOffset * 12 + noteOffset);
|
lastRegion->offsetAllKeys(octaveOffset * 12 + noteOffset);
|
||||||
|
|
||||||
|
|
@ -1522,6 +1532,7 @@ void sfz::Synth::setupModMatrix()
|
||||||
case ModId::Envelope:
|
case ModId::Envelope:
|
||||||
gen = genFlexEnvelope.get();
|
gen = genFlexEnvelope.get();
|
||||||
break;
|
break;
|
||||||
|
case ModId::AmpEG:
|
||||||
case ModId::PitchEG:
|
case ModId::PitchEG:
|
||||||
case ModId::FilEG:
|
case ModId::FilEG:
|
||||||
gen = genADSREnvelope.get();
|
gen = genADSREnvelope.get();
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,6 @@ void sfz::Voice::startVoice(Region* region, int delay, const TriggerEvent& event
|
||||||
bendStepFactor = centsFactor(region->bendStep);
|
bendStepFactor = centsFactor(region->bendStep);
|
||||||
bendSmoother.setSmoothing(region->bendSmooth, sampleRate);
|
bendSmoother.setSmoothing(region->bendSmooth, sampleRate);
|
||||||
bendSmoother.reset(centsFactor(region->getBendInCents(resources.midiState.getPitchBend())));
|
bendSmoother.reset(centsFactor(region->getBendInCents(resources.midiState.getPitchBend())));
|
||||||
egAmplitude.reset(region->amplitudeEG, *region, resources.midiState, delay, triggerEvent.value, sampleRate);
|
|
||||||
|
|
||||||
resources.modMatrix.initVoice(id, region->getId(), delay);
|
resources.modMatrix.initVoice(id, region->getId(), delay);
|
||||||
saveModulationTargets(region);
|
saveModulationTargets(region);
|
||||||
|
|
@ -152,10 +151,13 @@ void sfz::Voice::release(int delay) noexcept
|
||||||
if (state != State::playing)
|
if (state != State::playing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (egAmplitude.getRemainingDelay() > delay) {
|
if (!region->flexAmpEG) {
|
||||||
switchState(State::cleanMeUp);
|
if (egAmplitude.getRemainingDelay() > delay)
|
||||||
} else {
|
switchState(State::cleanMeUp);
|
||||||
egAmplitude.startRelease(delay);
|
}
|
||||||
|
else {
|
||||||
|
if (flexEGs[*region->flexAmpEG]->getRemainingDelay() > static_cast<unsigned>(delay))
|
||||||
|
switchState(State::cleanMeUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
resources.modMatrix.releaseVoice(id, region->getId(), delay);
|
resources.modMatrix.releaseVoice(id, region->getId(), delay);
|
||||||
|
|
@ -163,10 +165,15 @@ void sfz::Voice::release(int delay) noexcept
|
||||||
|
|
||||||
void sfz::Voice::off(int delay) noexcept
|
void sfz::Voice::off(int delay) noexcept
|
||||||
{
|
{
|
||||||
if (region->offMode == SfzOffMode::fast) {
|
if (!region->flexAmpEG) {
|
||||||
egAmplitude.setReleaseTime( Default::offTime );
|
if (region->offMode == SfzOffMode::fast) {
|
||||||
} else if (region->offMode == SfzOffMode::time) {
|
egAmplitude.setReleaseTime( Default::offTime );
|
||||||
egAmplitude.setReleaseTime(region->offTime);
|
} else if (region->offMode == SfzOffMode::time) {
|
||||||
|
egAmplitude.setReleaseTime(region->offTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO(jpc): Flex AmpEG
|
||||||
}
|
}
|
||||||
|
|
||||||
release(delay);
|
release(delay);
|
||||||
|
|
@ -286,8 +293,14 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
panStageMono(buffer);
|
panStageMono(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!egAmplitude.isSmoothing())
|
if (!region->flexAmpEG) {
|
||||||
switchState(State::cleanMeUp);
|
if (!egAmplitude.isSmoothing())
|
||||||
|
switchState(State::cleanMeUp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (flexEGs[*region->flexAmpEG]->isFinished())
|
||||||
|
switchState(State::cleanMeUp);
|
||||||
|
}
|
||||||
|
|
||||||
powerFollower.process(buffer);
|
powerFollower.process(buffer);
|
||||||
|
|
||||||
|
|
@ -367,8 +380,10 @@ void sfz::Voice::amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept
|
||||||
|
|
||||||
ModMatrix& mm = resources.modMatrix;
|
ModMatrix& mm = resources.modMatrix;
|
||||||
|
|
||||||
// AmpEG envelope
|
// Amplitude EG
|
||||||
egAmplitude.getBlock(modulationSpan);
|
absl::Span<const float> ampegOut(mm.getModulation(masterAmplitudeTarget), numSamples);
|
||||||
|
ASSERT(ampegOut.data());
|
||||||
|
copy(ampegOut, modulationSpan);
|
||||||
|
|
||||||
// Amplitude envelope
|
// Amplitude envelope
|
||||||
applyGain1<float>(baseGain, modulationSpan);
|
applyGain1<float>(baseGain, modulationSpan);
|
||||||
|
|
@ -572,8 +587,14 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
|
||||||
<< " for sample " << region->sampleId);
|
<< " for sample " << region->sampleId);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
egAmplitude.setReleaseTime(0.0f);
|
if (!region->flexAmpEG) {
|
||||||
egAmplitude.startRelease(i);
|
egAmplitude.setReleaseTime(0.0f);
|
||||||
|
egAmplitude.startRelease(i);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO(jpc): Flex AmpEG
|
||||||
|
flexEGs[*region->flexAmpEG]->release(i);
|
||||||
|
}
|
||||||
fill<int>(indices->subspan(i), sampleEnd);
|
fill<int>(indices->subspan(i), sampleEnd);
|
||||||
fill<float>(coeffs->subspan(i), 1.0f);
|
fill<float>(coeffs->subspan(i), 1.0f);
|
||||||
break;
|
break;
|
||||||
|
|
@ -853,7 +874,12 @@ float sfz::Voice::getAveragePower() const noexcept
|
||||||
|
|
||||||
bool sfz::Voice::releasedOrFree() const noexcept
|
bool sfz::Voice::releasedOrFree() const noexcept
|
||||||
{
|
{
|
||||||
return state != State::playing || egAmplitude.isReleased();
|
if (state != State::playing)
|
||||||
|
return true;
|
||||||
|
if (!region->flexAmpEG)
|
||||||
|
return egAmplitude.isReleased();
|
||||||
|
else
|
||||||
|
return flexEGs[*region->flexAmpEG]->isReleased();
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::setMaxFiltersPerVoice(size_t numFilters)
|
void sfz::Voice::setMaxFiltersPerVoice(size_t numFilters)
|
||||||
|
|
@ -1021,6 +1047,7 @@ void sfz::Voice::resetSmoothers() noexcept
|
||||||
void sfz::Voice::saveModulationTargets(const Region* region) noexcept
|
void sfz::Voice::saveModulationTargets(const Region* region) noexcept
|
||||||
{
|
{
|
||||||
ModMatrix& mm = resources.modMatrix;
|
ModMatrix& mm = resources.modMatrix;
|
||||||
|
masterAmplitudeTarget = mm.findTarget(ModKey::createNXYZ(ModId::MasterAmplitude, region->getId()));
|
||||||
amplitudeTarget = mm.findTarget(ModKey::createNXYZ(ModId::Amplitude, region->getId()));
|
amplitudeTarget = mm.findTarget(ModKey::createNXYZ(ModId::Amplitude, region->getId()));
|
||||||
volumeTarget = mm.findTarget(ModKey::createNXYZ(ModId::Volume, region->getId()));
|
volumeTarget = mm.findTarget(ModKey::createNXYZ(ModId::Volume, region->getId()));
|
||||||
panTarget = mm.findTarget(ModKey::createNXYZ(ModId::Pan, region->getId()));
|
panTarget = mm.findTarget(ModKey::createNXYZ(ModId::Pan, region->getId()));
|
||||||
|
|
|
||||||
|
|
@ -330,6 +330,10 @@ public:
|
||||||
Duration getLastFilterDuration() const noexcept { return filterDuration; }
|
Duration getLastFilterDuration() const noexcept { return filterDuration; }
|
||||||
Duration getLastPanningDuration() const noexcept { return panningDuration; }
|
Duration getLastPanningDuration() const noexcept { return panningDuration; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the SFZv1 amplitude EG, if existing
|
||||||
|
*/
|
||||||
|
ADSREnvelope<float>* getAmplitudeEG() { return &egAmplitude; }
|
||||||
/**
|
/**
|
||||||
* @brief Get the SFZv1 pitch EG, if existing
|
* @brief Get the SFZv1 pitch EG, if existing
|
||||||
*/
|
*/
|
||||||
|
|
@ -510,6 +514,7 @@ private:
|
||||||
Smoother xfadeSmoother;
|
Smoother xfadeSmoother;
|
||||||
void resetSmoothers() noexcept;
|
void resetSmoothers() noexcept;
|
||||||
|
|
||||||
|
ModMatrix::TargetId masterAmplitudeTarget;
|
||||||
ModMatrix::TargetId amplitudeTarget;
|
ModMatrix::TargetId amplitudeTarget;
|
||||||
ModMatrix::TargetId volumeTarget;
|
ModMatrix::TargetId volumeTarget;
|
||||||
ModMatrix::TargetId panTarget;
|
ModMatrix::TargetId panTarget;
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,16 @@ int ModIds::flags(ModId id) noexcept
|
||||||
return kModIsPerVoice;
|
return kModIsPerVoice;
|
||||||
case ModId::LFO:
|
case ModId::LFO:
|
||||||
return kModIsPerVoice;
|
return kModIsPerVoice;
|
||||||
|
case ModId::AmpEG:
|
||||||
|
return kModIsPerVoice;
|
||||||
case ModId::PitchEG:
|
case ModId::PitchEG:
|
||||||
return kModIsPerVoice;
|
return kModIsPerVoice;
|
||||||
case ModId::FilEG:
|
case ModId::FilEG:
|
||||||
return kModIsPerVoice;
|
return kModIsPerVoice;
|
||||||
|
|
||||||
// targets
|
// targets
|
||||||
|
case ModId::MasterAmplitude:
|
||||||
|
return kModIsPerVoice|kModIsPercentMultiplicative;
|
||||||
case ModId::Amplitude:
|
case ModId::Amplitude:
|
||||||
return kModIsPerVoice|kModIsPercentMultiplicative;
|
return kModIsPerVoice|kModIsPercentMultiplicative;
|
||||||
case ModId::Pan:
|
case ModId::Pan:
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ enum class ModId : int {
|
||||||
Controller = _SourcesStart,
|
Controller = _SourcesStart,
|
||||||
Envelope,
|
Envelope,
|
||||||
LFO,
|
LFO,
|
||||||
|
AmpEG,
|
||||||
PitchEG,
|
PitchEG,
|
||||||
FilEG,
|
FilEG,
|
||||||
|
|
||||||
|
|
@ -33,7 +34,8 @@ enum class ModId : int {
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
_TargetsStart = _SourcesEnd,
|
_TargetsStart = _SourcesEnd,
|
||||||
|
|
||||||
Amplitude = _TargetsStart,
|
MasterAmplitude = _TargetsStart,
|
||||||
|
Amplitude,
|
||||||
Pan,
|
Pan,
|
||||||
Width,
|
Width,
|
||||||
Position,
|
Position,
|
||||||
|
|
|
||||||
|
|
@ -74,11 +74,15 @@ 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::AmpEG:
|
||||||
|
return absl::StrCat("AmplitudeEG {", region_.number(), "}");
|
||||||
case ModId::PitchEG:
|
case ModId::PitchEG:
|
||||||
return absl::StrCat("PitchEG {", region_.number(), "}");
|
return absl::StrCat("PitchEG {", region_.number(), "}");
|
||||||
case ModId::FilEG:
|
case ModId::FilEG:
|
||||||
return absl::StrCat("FilterEG {", region_.number(), "}");
|
return absl::StrCat("FilterEG {", region_.number(), "}");
|
||||||
|
|
||||||
|
case ModId::MasterAmplitude:
|
||||||
|
return absl::StrCat("MasterAmplitude {", region_.number(), "}");
|
||||||
case ModId::Amplitude:
|
case ModId::Amplitude:
|
||||||
return absl::StrCat("Amplitude {", region_.number(), "}");
|
return absl::StrCat("Amplitude {", region_.number(), "}");
|
||||||
case ModId::Pan:
|
case ModId::Pan:
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,11 @@ void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId,
|
||||||
const EGDescription* desc = nullptr;
|
const EGDescription* desc = nullptr;
|
||||||
|
|
||||||
switch (sourceKey.id()) {
|
switch (sourceKey.id()) {
|
||||||
|
case ModId::AmpEG:
|
||||||
|
eg = voice->getAmplitudeEG();
|
||||||
|
ASSERT(eg);
|
||||||
|
desc = ®ion->amplitudeEG;
|
||||||
|
break;
|
||||||
case ModId::PitchEG:
|
case ModId::PitchEG:
|
||||||
eg = voice->getPitchEG();
|
eg = voice->getPitchEG();
|
||||||
ASSERT(eg);
|
ASSERT(eg);
|
||||||
|
|
@ -69,6 +74,10 @@ void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voice
|
||||||
ADSREnvelope<float>* eg = nullptr;
|
ADSREnvelope<float>* eg = nullptr;
|
||||||
|
|
||||||
switch (sourceKey.id()) {
|
switch (sourceKey.id()) {
|
||||||
|
case ModId::AmpEG:
|
||||||
|
eg = voice->getAmplitudeEG();
|
||||||
|
ASSERT(eg);
|
||||||
|
break;
|
||||||
case ModId::PitchEG:
|
case ModId::PitchEG:
|
||||||
eg = voice->getPitchEG();
|
eg = voice->getPitchEG();
|
||||||
ASSERT(eg);
|
ASSERT(eg);
|
||||||
|
|
@ -98,6 +107,10 @@ void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voic
|
||||||
ADSREnvelope<float>* eg = nullptr;
|
ADSREnvelope<float>* eg = nullptr;
|
||||||
|
|
||||||
switch (sourceKey.id()) {
|
switch (sourceKey.id()) {
|
||||||
|
case ModId::AmpEG:
|
||||||
|
eg = voice->getAmplitudeEG();
|
||||||
|
ASSERT(eg);
|
||||||
|
break;
|
||||||
case ModId::PitchEG:
|
case ModId::PitchEG:
|
||||||
eg = voice->getPitchEG();
|
eg = voice->getPitchEG();
|
||||||
ASSERT(eg);
|
ASSERT(eg);
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ TEST_CASE("[FlexEG] Values")
|
||||||
REQUIRE( egDescription.points[4].time == .4_a );
|
REQUIRE( egDescription.points[4].time == .4_a );
|
||||||
REQUIRE( egDescription.points[4].level == 1.0_a );
|
REQUIRE( egDescription.points[4].level == 1.0_a );
|
||||||
REQUIRE( egDescription.sustain == 3 );
|
REQUIRE( egDescription.sustain == 3 );
|
||||||
REQUIRE(synth.getResources().modMatrix.toDotGraph() == createReferenceGraph({
|
REQUIRE(synth.getResources().modMatrix.toDotGraph() == createDefaultGraph({
|
||||||
R"("EG 1 {0}" -> "Amplitude {0}")",
|
R"("EG 1 {0}" -> "Amplitude {0}")",
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +67,7 @@ TEST_CASE("[FlexEG] Default values")
|
||||||
REQUIRE( egDescription.points[1].level == 0.0_a );
|
REQUIRE( egDescription.points[1].level == 0.0_a );
|
||||||
REQUIRE( egDescription.points[2].time == .1_a );
|
REQUIRE( egDescription.points[2].time == .1_a );
|
||||||
REQUIRE( egDescription.points[2].level == .25_a );
|
REQUIRE( egDescription.points[2].level == .25_a );
|
||||||
REQUIRE( synth.getResources().modMatrix.toDotGraph() == createReferenceGraph({}) );
|
REQUIRE( synth.getResources().modMatrix.toDotGraph() == createDefaultGraph({}) );
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[FlexEG] Connections")
|
TEST_CASE("[FlexEG] Connections")
|
||||||
|
|
@ -85,7 +85,7 @@ TEST_CASE("[FlexEG] Connections")
|
||||||
REQUIRE(synth.getNumRegions() == 6);
|
REQUIRE(synth.getNumRegions() == 6);
|
||||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||||
REQUIRE( synth.getRegionView(0)->flexEGs[0].points.size() == 2 );
|
REQUIRE( synth.getRegionView(0)->flexEGs[0].points.size() == 2 );
|
||||||
REQUIRE( synth.getResources().modMatrix.toDotGraph() == createReferenceGraph({
|
REQUIRE( synth.getResources().modMatrix.toDotGraph() == createDefaultGraph({
|
||||||
R"("EG 1 {0}" -> "Amplitude {0}")",
|
R"("EG 1 {0}" -> "Amplitude {0}")",
|
||||||
R"("EG 1 {1}" -> "Pan {1}")",
|
R"("EG 1 {1}" -> "Pan {1}")",
|
||||||
R"("EG 1 {2}" -> "Width {2}")",
|
R"("EG 1 {2}" -> "Width {2}")",
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ width_oncc425=29
|
||||||
)");
|
)");
|
||||||
|
|
||||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||||
REQUIRE(graph == createReferenceGraph({
|
REQUIRE(graph == createDefaultGraph({
|
||||||
R"("Controller 20 {curve=3, smooth=0, value=59, step=0}" -> "Amplitude {0}")",
|
R"("Controller 20 {curve=3, smooth=0, value=59, step=0}" -> "Amplitude {0}")",
|
||||||
R"("Controller 42 {curve=0, smooth=32, value=71, step=0}" -> "Pitch {0}")",
|
R"("Controller 42 {curve=0, smooth=32, value=71, step=0}" -> "Pitch {0}")",
|
||||||
R"("Controller 36 {curve=0, smooth=0, value=14.5, step=1.5}" -> "Pan {0}")",
|
R"("Controller 36 {curve=0, smooth=0, value=14.5, step=1.5}" -> "Pan {0}")",
|
||||||
|
|
@ -111,7 +111,7 @@ TEST_CASE("[Modulations] Filter CC connections")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||||
REQUIRE(graph == createReferenceGraph({
|
REQUIRE(graph == createDefaultGraph({
|
||||||
R"("Controller 1 {curve=0, smooth=10, value=2, step=0}" -> "FilterResonance {0, N=3}")",
|
R"("Controller 1 {curve=0, smooth=10, value=2, step=0}" -> "FilterResonance {0, N=3}")",
|
||||||
R"("Controller 2 {curve=2, smooth=0, value=100, step=0}" -> "FilterCutoff {0, N=2}")",
|
R"("Controller 2 {curve=2, smooth=0, value=100, step=0}" -> "FilterCutoff {0, N=2}")",
|
||||||
R"("Controller 3 {curve=0, smooth=0, value=5, step=0.5}" -> "FilterGain {0, N=1}")",
|
R"("Controller 3 {curve=0, smooth=0, value=5, step=0.5}" -> "FilterGain {0, N=1}")",
|
||||||
|
|
@ -129,7 +129,7 @@ TEST_CASE("[Modulations] EQ CC connections")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||||
REQUIRE(graph == createReferenceGraph({
|
REQUIRE(graph == createDefaultGraph({
|
||||||
R"("Controller 1 {curve=0, smooth=10, value=2, step=0}" -> "EqBandwidth {0, N=3}")",
|
R"("Controller 1 {curve=0, smooth=10, value=2, step=0}" -> "EqBandwidth {0, N=3}")",
|
||||||
R"("Controller 2 {curve=0, smooth=0, value=5, step=0.5}" -> "EqGain {0, N=1}")",
|
R"("Controller 2 {curve=0, smooth=0, value=5, step=0.5}" -> "EqGain {0, N=1}")",
|
||||||
R"("Controller 3 {curve=3, smooth=0, value=300, step=0}" -> "EqFrequency {0, N=2}")",
|
R"("Controller 3 {curve=3, smooth=0, value=300, step=0}" -> "EqFrequency {0, N=2}")",
|
||||||
|
|
@ -150,7 +150,7 @@ TEST_CASE("[Modulations] LFO Filter connections")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||||
REQUIRE(graph == createReferenceGraph({
|
REQUIRE(graph == createDefaultGraph({
|
||||||
R"("LFO 1 {0}" -> "FilterCutoff {0, N=1}")",
|
R"("LFO 1 {0}" -> "FilterCutoff {0, N=1}")",
|
||||||
R"("LFO 2 {0}" -> "FilterCutoff {0, N=1}")",
|
R"("LFO 2 {0}" -> "FilterCutoff {0, N=1}")",
|
||||||
R"("LFO 3 {0}" -> "FilterResonance {0, N=1}")",
|
R"("LFO 3 {0}" -> "FilterResonance {0, N=1}")",
|
||||||
|
|
@ -174,7 +174,7 @@ TEST_CASE("[Modulations] EG Filter connections")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||||
REQUIRE(graph == createReferenceGraph({
|
REQUIRE(graph == createDefaultGraph({
|
||||||
R"("EG 1 {0}" -> "FilterCutoff {0, N=1}")",
|
R"("EG 1 {0}" -> "FilterCutoff {0, N=1}")",
|
||||||
R"("EG 2 {0}" -> "FilterCutoff {0, N=1}")",
|
R"("EG 2 {0}" -> "FilterCutoff {0, N=1}")",
|
||||||
R"("EG 3 {0}" -> "FilterResonance {0, N=1}")",
|
R"("EG 3 {0}" -> "FilterResonance {0, N=1}")",
|
||||||
|
|
@ -198,7 +198,7 @@ TEST_CASE("[Modulations] LFO EQ connections")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||||
REQUIRE(graph == createReferenceGraph({
|
REQUIRE(graph == createDefaultGraph({
|
||||||
R"("LFO 1 {0}" -> "EqBandwidth {0, N=1}")",
|
R"("LFO 1 {0}" -> "EqBandwidth {0, N=1}")",
|
||||||
R"("LFO 2 {0}" -> "EqFrequency {0, N=2}")",
|
R"("LFO 2 {0}" -> "EqFrequency {0, N=2}")",
|
||||||
R"("LFO 3 {0}" -> "EqGain {0, N=3}")",
|
R"("LFO 3 {0}" -> "EqGain {0, N=3}")",
|
||||||
|
|
@ -222,7 +222,7 @@ TEST_CASE("[Modulations] EG EQ connections")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||||
REQUIRE(graph == createReferenceGraph({
|
REQUIRE(graph == createDefaultGraph({
|
||||||
R"("EG 1 {0}" -> "EqBandwidth {0, N=1}")",
|
R"("EG 1 {0}" -> "EqBandwidth {0, N=1}")",
|
||||||
R"("EG 2 {0}" -> "EqFrequency {0, N=2}")",
|
R"("EG 2 {0}" -> "EqFrequency {0, N=2}")",
|
||||||
R"("EG 3 {0}" -> "EqGain {0, N=3}")",
|
R"("EG 3 {0}" -> "EqGain {0, N=3}")",
|
||||||
|
|
@ -231,3 +231,76 @@ TEST_CASE("[Modulations] EG EQ connections")
|
||||||
R"("EG 6 {0}" -> "EqFrequency {0, N=1}")",
|
R"("EG 6 {0}" -> "EqFrequency {0, N=1}")",
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("[Modulations] FlexEG Ampeg target")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
|
||||||
|
synth.loadSfzString(fs::current_path(), R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
eg1_time1=0 eg1_level1=1
|
||||||
|
eg1_time2=1 eg1_level2=0
|
||||||
|
eg1_time3=1 eg1_level3=.5 eg1_sustain=3
|
||||||
|
eg1_time4=1 eg1_level4=1
|
||||||
|
eg1_ampeg=1
|
||||||
|
)");
|
||||||
|
|
||||||
|
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||||
|
REQUIRE(graph == createModulationDotGraph({
|
||||||
|
R"("Controller 10 {curve=1, smooth=10, value=100, step=0}" -> "Pan {0}")",
|
||||||
|
R"("Controller 7 {curve=4, smooth=10, value=100, step=0}" -> "Amplitude {0}")",
|
||||||
|
R"("EG 1 {0}" -> "MasterAmplitude {0}")",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Modulations] FlexEG Ampeg target with 2 FlexEGs")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
|
||||||
|
synth.loadSfzString(fs::current_path(), R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
eg1_time1=0 eg1_level1=1
|
||||||
|
eg1_time2=1 eg1_level2=0
|
||||||
|
eg1_time3=1 eg1_level3=.5 eg1_sustain=3
|
||||||
|
eg1_time4=1 eg1_level4=1
|
||||||
|
eg2_time1=0 eg2_level1=1
|
||||||
|
eg2_time2=1 eg2_level2=0
|
||||||
|
eg2_time3=1 eg2_level3=.5 eg1_sustain=3
|
||||||
|
eg2_ampeg=1
|
||||||
|
)");
|
||||||
|
|
||||||
|
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||||
|
REQUIRE(graph == createModulationDotGraph({
|
||||||
|
R"("Controller 10 {curve=1, smooth=10, value=100, step=0}" -> "Pan {0}")",
|
||||||
|
R"("Controller 7 {curve=4, smooth=10, value=100, step=0}" -> "Amplitude {0}")",
|
||||||
|
R"("EG 2 {0}" -> "MasterAmplitude {0}")",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("[Modulations] FlexEG Ampeg target with multiple EGs targeting ampeg")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
|
||||||
|
synth.loadSfzString(fs::current_path(), R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
eg1_time1=0 eg1_level1=1
|
||||||
|
eg1_time2=1 eg1_level2=0
|
||||||
|
eg1_time3=1 eg1_level3=.5 eg1_sustain=3
|
||||||
|
eg1_time4=1 eg1_level4=1
|
||||||
|
eg1_ampeg=1
|
||||||
|
eg2_time1=0 eg2_level1=1
|
||||||
|
eg2_time2=1 eg2_level2=0
|
||||||
|
eg2_time3=1 eg2_level3=.5 eg1_sustain=3
|
||||||
|
eg2_ampeg=1
|
||||||
|
)");
|
||||||
|
|
||||||
|
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||||
|
REQUIRE(graph == createModulationDotGraph({
|
||||||
|
R"("Controller 10 {curve=1, smooth=10, value=100, step=0}" -> "Pan {0}")",
|
||||||
|
R"("Controller 7 {curve=4, smooth=10, value=100, step=0}" -> "Amplitude {0}")",
|
||||||
|
R"("EG 1 {0}" -> "MasterAmplitude {0}")",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -285,3 +285,14 @@ TEST_CASE("[Opcode] readOpcode")
|
||||||
REQUIRE( !sfz::readOpcode("garbage50.25", sfz::Range<int>(-20, 100)) );
|
REQUIRE( !sfz::readOpcode("garbage50.25", sfz::Range<int>(-20, 100)) );
|
||||||
REQUIRE( !sfz::readOpcode("garbage", sfz::Range<int>(-20, 100)) );
|
REQUIRE( !sfz::readOpcode("garbage", sfz::Range<int>(-20, 100)) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Opcode] readBooleanFromOpcode")
|
||||||
|
{
|
||||||
|
REQUIRE(sfz::readBooleanFromOpcode({"", "1"}) == true);
|
||||||
|
REQUIRE(sfz::readBooleanFromOpcode({"", "0"}) == false);
|
||||||
|
REQUIRE(sfz::readBooleanFromOpcode({"", "777"}) == true);
|
||||||
|
REQUIRE(sfz::readBooleanFromOpcode({"", "on"}) == true);
|
||||||
|
REQUIRE(sfz::readBooleanFromOpcode({"", "off"}) == false);
|
||||||
|
REQUIRE(sfz::readBooleanFromOpcode({"", "On"}) == true);
|
||||||
|
REQUIRE(sfz::readBooleanFromOpcode({"", "oFf"}) == false);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,12 @@ unsigned numPlayingVoices(const sfz::Synth& synth)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string createReferenceGraph(std::vector<std::string> lines, int numRegions)
|
std::string createDefaultGraph(std::vector<std::string> lines, int numRegions)
|
||||||
{
|
{
|
||||||
for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) {
|
for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) {
|
||||||
|
lines.push_back(absl::StrCat(
|
||||||
|
R"("AmplitudeEG {)", regionIdx, R"(}" -> "MasterAmplitude {)", regionIdx, R"(}")"
|
||||||
|
));
|
||||||
lines.push_back(absl::StrCat(
|
lines.push_back(absl::StrCat(
|
||||||
R"("Controller 7 {curve=4, smooth=10, value=100, step=0}" -> "Amplitude {)",
|
R"("Controller 7 {curve=4, smooth=10, value=100, step=0}" -> "Amplitude {)",
|
||||||
regionIdx,
|
regionIdx,
|
||||||
|
|
@ -84,6 +87,11 @@ std::string createReferenceGraph(std::vector<std::string> lines, int numRegions)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return createModulationDotGraph(lines);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string createModulationDotGraph(std::vector<std::string> lines)
|
||||||
|
{
|
||||||
std::sort(lines.begin(), lines.end());
|
std::sort(lines.begin(), lines.end());
|
||||||
|
|
||||||
std::string graph;
|
std::string graph;
|
||||||
|
|
@ -98,4 +106,4 @@ std::string createReferenceGraph(std::vector<std::string> lines, int numRegions)
|
||||||
graph += "}\n";
|
graph += "}\n";
|
||||||
|
|
||||||
return graph;
|
return graph;
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,10 +67,17 @@ const std::vector<const sfz::Voice*> getPlayingVoices(const sfz::Synth& synth);
|
||||||
unsigned numPlayingVoices(const sfz::Synth& synth);
|
unsigned numPlayingVoices(const sfz::Synth& synth);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create the dot graph representation from a list of strings
|
* @brief Create the default dot graph representation for standard regions
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
std::string createReferenceGraph(std::vector<std::string> lines, int numRegions = 1);
|
std::string createDefaultGraph(std::vector<std::string> lines, int numRegions = 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a dot graph with the specified lines.
|
||||||
|
* The lines are sorted.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
std::string createModulationDotGraph(std::vector<std::string> lines);
|
||||||
|
|
||||||
template <class Type>
|
template <class Type>
|
||||||
inline bool approxEqual(absl::Span<const Type> lhs, absl::Span<const Type> rhs, Type eps = 1e-3)
|
inline bool approxEqual(absl::Span<const Type> lhs, absl::Span<const Type> rhs, Type eps = 1e-3)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue