Merge pull request #930 from jpcima/flex-eg-mods
Add modulations for Flex EG time and level
This commit is contained in:
commit
ba7cb52b9c
10 changed files with 121 additions and 13 deletions
|
|
@ -152,7 +152,9 @@ BoolSpec flexEGAmpeg { false, {0, 1}, kEnforceBounds };
|
|||
BoolSpec flexEGDynamic { 0, {0, 1}, kEnforceBounds };
|
||||
Int32Spec flexEGSustain { 0, {0, 100}, kEnforceLowerBound|kPermissiveUpperBound };
|
||||
FloatSpec flexEGPointTime { 0.0f, {0.0f, 100.0f}, kPermissiveBounds };
|
||||
FloatSpec flexEGPointTimeMod { 0.0f, {-100.0f, 100.0f}, kPermissiveBounds };
|
||||
FloatSpec flexEGPointLevel { 0.0f, {-1.0f, 1.0f}, kPermissiveBounds };
|
||||
FloatSpec flexEGPointLevelMod { 0.0f, {-1.0f, 1.0f}, kPermissiveBounds };
|
||||
FloatSpec flexEGPointShape { 0.0f, {-100.0f, 100.0f}, kPermissiveBounds };
|
||||
Int32Spec sampleQuality { 2, {0, 10}, 0 };
|
||||
Int32Spec octaveOffset { 0, {-10, 10}, kPermissiveBounds };
|
||||
|
|
|
|||
|
|
@ -264,7 +264,9 @@ namespace Default
|
|||
extern const OpcodeSpec<bool> flexEGDynamic;
|
||||
extern const OpcodeSpec<int32_t> flexEGSustain;
|
||||
extern const OpcodeSpec<float> flexEGPointTime;
|
||||
extern const OpcodeSpec<float> flexEGPointTimeMod;
|
||||
extern const OpcodeSpec<float> flexEGPointLevel;
|
||||
extern const OpcodeSpec<float> flexEGPointLevelMod;
|
||||
extern const OpcodeSpec<float> flexEGPointShape;
|
||||
extern const OpcodeSpec<int32_t> sampleQuality;
|
||||
extern const OpcodeSpec<int32_t> octaveOffset;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "FlexEGDescription.h"
|
||||
#include "Curve.h"
|
||||
#include "MidiState.h"
|
||||
#include <absl/container/flat_hash_map.h>
|
||||
#include <cmath>
|
||||
|
||||
|
|
@ -84,4 +85,21 @@ void FlexEGs::clearUnusedCurves()
|
|||
}
|
||||
}
|
||||
|
||||
///
|
||||
float FlexEGPoint::getTime(const MidiState& state) const noexcept
|
||||
{
|
||||
float returnedValue { time };
|
||||
for (const CCData<float>& mod : ccTime)
|
||||
returnedValue += state.getCCValue(mod.cc) * mod.data;
|
||||
return returnedValue;
|
||||
}
|
||||
|
||||
float FlexEGPoint::getLevel(const MidiState& state) const noexcept
|
||||
{
|
||||
float returnedValue { level };
|
||||
for (const CCData<float>& mod : ccLevel)
|
||||
returnedValue += state.getCCValue(mod.cc) * mod.data;
|
||||
return returnedValue;
|
||||
}
|
||||
|
||||
} // namespace sfz
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@
|
|||
|
||||
#pragma once
|
||||
#include "Defaults.h"
|
||||
#include "CCMap.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace sfz {
|
||||
class Curve;
|
||||
class MidiState;
|
||||
|
||||
namespace FlexEGs {
|
||||
std::shared_ptr<Curve> getShapeCurve(float shape);
|
||||
|
|
@ -21,6 +23,12 @@ struct FlexEGPoint {
|
|||
float time { Default::flexEGPointTime }; // duration until next step (s)
|
||||
float level { Default::flexEGPointLevel }; // normalized amplitude
|
||||
|
||||
CCMap<float> ccTime;
|
||||
CCMap<float> ccLevel;
|
||||
|
||||
float getTime(const MidiState& state) const noexcept;
|
||||
float getLevel(const MidiState& state) const noexcept;
|
||||
|
||||
void setShape(float shape);
|
||||
float shape() const noexcept { return shape_; }
|
||||
const Curve& curve() const;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@
|
|||
|
||||
- [ ] egN_points (purpose unknown)
|
||||
- [x] egN_timeX
|
||||
- [x] egN_timeX_onccY
|
||||
- [x] egN_levelX
|
||||
- [x] egN_levelX_onccY
|
||||
- [x] egN_shapeX
|
||||
- [ ] egN_shapeX_onccY
|
||||
- [x] egN_sustain
|
||||
- [ ] egN_dynamic
|
||||
- [ ] egN_loop
|
||||
|
|
@ -21,6 +24,8 @@
|
|||
#include "FlexEnvelope.h"
|
||||
#include "FlexEGDescription.h"
|
||||
#include "Curve.h"
|
||||
#include "MidiState.h"
|
||||
#include "Resources.h"
|
||||
#include "Config.h"
|
||||
#include "SIMDHelpers.h"
|
||||
#include <absl/types/optional.h>
|
||||
|
|
@ -28,6 +33,7 @@
|
|||
namespace sfz {
|
||||
|
||||
struct FlexEnvelope::Impl {
|
||||
const Resources* resources_ { nullptr };
|
||||
const FlexEGDescription* desc_ { nullptr };
|
||||
float samplePeriod_ { 1.0 / config::defaultSampleRate };
|
||||
size_t delayFramesLeft_ { 0 };
|
||||
|
|
@ -53,9 +59,11 @@ struct FlexEnvelope::Impl {
|
|||
bool advanceToNextStage();
|
||||
};
|
||||
|
||||
FlexEnvelope::FlexEnvelope()
|
||||
FlexEnvelope::FlexEnvelope(Resources &resources)
|
||||
: impl_(new Impl)
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
impl.resources_ = &resources;
|
||||
}
|
||||
|
||||
FlexEnvelope::~FlexEnvelope()
|
||||
|
|
@ -249,6 +257,7 @@ void FlexEnvelope::Impl::process(absl::Span<float> out)
|
|||
bool FlexEnvelope::Impl::advanceToStage(unsigned stageNumber)
|
||||
{
|
||||
const FlexEGDescription& desc = *desc_;
|
||||
const MidiState& midiState = resources_->getMidiState();
|
||||
|
||||
currentStageNumber_ = stageNumber;
|
||||
|
||||
|
|
@ -257,8 +266,8 @@ bool FlexEnvelope::Impl::advanceToStage(unsigned stageNumber)
|
|||
|
||||
const FlexEGPoint& point = desc.points[stageNumber];
|
||||
stageSourceLevel_ = currentLevel_;
|
||||
stageTargetLevel_ = point.level;
|
||||
stageTime_ = point.time;
|
||||
stageTargetLevel_ = point.getLevel(midiState);
|
||||
stageTime_ = point.getTime(midiState);
|
||||
stageSustained_ = int(stageNumber) == desc.sustain;
|
||||
stageCurve_ = &point.curve();
|
||||
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@
|
|||
|
||||
namespace sfz {
|
||||
struct FlexEGDescription;
|
||||
class Resources;
|
||||
|
||||
/**
|
||||
Flex envelope generator (according to ARIA)
|
||||
*/
|
||||
class FlexEnvelope {
|
||||
public:
|
||||
FlexEnvelope();
|
||||
explicit FlexEnvelope(Resources &resources);
|
||||
~FlexEnvelope();
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1452,12 +1452,32 @@ bool sfz::Region::parseEGOpcodeV2(const Opcode& opcode)
|
|||
else
|
||||
return false;
|
||||
break;
|
||||
case hash("eg&_time&_oncc&"):
|
||||
if (FlexEGPoint* point = getOrCreateEGPoint()) {
|
||||
auto ccNumber = opcode.parameters.back();
|
||||
if (ccNumber >= config::numCCs)
|
||||
return false;
|
||||
point->ccTime[ccNumber] = opcode.read(Default::flexEGPointTimeMod);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
case hash("eg&_level&"):
|
||||
if (FlexEGPoint* point = getOrCreateEGPoint())
|
||||
point->level = opcode.read(Default::flexEGPointLevel);
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
case hash("eg&_level&_oncc&"):
|
||||
if (FlexEGPoint* point = getOrCreateEGPoint()) {
|
||||
auto ccNumber = opcode.parameters.back();
|
||||
if (ccNumber >= config::numCCs)
|
||||
return false;
|
||||
point->ccLevel[ccNumber] = opcode.read(Default::flexEGPointLevelMod);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
case hash("eg&_shape&"):
|
||||
if (FlexEGPoint* point = getOrCreateEGPoint())
|
||||
point->setShape(opcode.read(Default::flexEGPointShape));
|
||||
|
|
|
|||
|
|
@ -2068,6 +2068,12 @@ void Synth::Impl::collectUsedCCsFromRegion(BitArray<config::numCCs>& usedCCs, co
|
|||
collectUsedCCsFromCCMap(usedCCs, lfo.delayCC);
|
||||
collectUsedCCsFromCCMap(usedCCs, lfo.fadeCC);
|
||||
}
|
||||
for (const FlexEGDescription& flexEG : region.flexEGs) {
|
||||
for (const FlexEGPoint& point : flexEG.points) {
|
||||
collectUsedCCsFromCCMap(usedCCs, point.ccTime);
|
||||
collectUsedCCsFromCCMap(usedCCs, point.ccLevel);
|
||||
}
|
||||
}
|
||||
collectUsedCCsFromCCMap(usedCCs, region.ccConditions);
|
||||
collectUsedCCsFromCCMap(usedCCs, region.ccTriggers);
|
||||
collectUsedCCsFromCCMap(usedCCs, region.crossfadeCCInRange);
|
||||
|
|
|
|||
|
|
@ -1817,10 +1817,12 @@ void Voice::setMaxLFOsPerVoice(size_t numLFOs)
|
|||
void Voice::setMaxFlexEGsPerVoice(size_t numFlexEGs)
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
Resources& resources = impl.resources_;
|
||||
|
||||
impl.flexEGs_.resize(numFlexEGs);
|
||||
|
||||
for (size_t i = 0; i < numFlexEGs; ++i) {
|
||||
auto eg = absl::make_unique<FlexEnvelope>();
|
||||
auto eg = absl::make_unique<FlexEnvelope>(resources);
|
||||
eg->setSampleRate(impl.sampleRate_);
|
||||
impl.flexEGs_[i] = std::move(eg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ TEST_CASE("[FlexEG] Coarse numerical envelope test (No release)")
|
|||
eg1_time2=0.5 eg1_level2=1
|
||||
eg1_sustain=2
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
sfz::FlexEnvelope envelope(synth.getResources());
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
|
|
@ -132,7 +132,7 @@ TEST_CASE("[FlexEG] Detailed numerical envelope test")
|
|||
eg1_time2=0.5 eg1_level2=1
|
||||
eg1_sustain=2
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
sfz::FlexEnvelope envelope(synth.getResources());
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
|
|
@ -155,7 +155,7 @@ TEST_CASE("[FlexEG] Coarse numerical envelope test (with release)")
|
|||
eg1_time2=0.5 eg1_level2=1
|
||||
eg1_sustain=2
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
sfz::FlexEnvelope envelope(synth.getResources());
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
|
|
@ -184,7 +184,7 @@ TEST_CASE("[FlexEG] Detailed numerical envelope test (with release and release r
|
|||
eg1_time3=0.5 eg1_level3=0
|
||||
eg1_sustain=2
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
sfz::FlexEnvelope envelope(synth.getResources());
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
|
|
@ -216,7 +216,7 @@ TEST_CASE("[FlexEG] Coarse numerical envelope test (with shapes)")
|
|||
eg1_sustain=2
|
||||
eg1_time3=0.5 eg1_level3=0 eg1_shape3=4
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
sfz::FlexEnvelope envelope(synth.getResources());
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
|
|
@ -244,7 +244,7 @@ TEST_CASE("[FlexEG] Detailed numerical envelope test (with shapes)")
|
|||
eg1_time3=0.5 eg1_level3=0 eg1_shape3=4
|
||||
eg1_sustain=2
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
sfz::FlexEnvelope envelope(synth.getResources());
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
|
|
@ -276,7 +276,7 @@ TEST_CASE("[FlexEG] Zero delay transitions")
|
|||
eg1_time3=1 eg1_level3=.5 eg1_sustain=3
|
||||
eg1_time4=1 eg1_level4=1
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
sfz::FlexEnvelope envelope(synth.getResources());
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE(synth.getRegionView(0)->flexEGs.size() == 1);
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
|
|
@ -303,7 +303,7 @@ TEST_CASE("[FlexEG] Early release")
|
|||
eg1_time2=1.0 eg1_level2=1.0 eg1_sustain=2
|
||||
eg1_time3=1.0 eg1_level3=0.0
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
sfz::FlexEnvelope envelope(synth.getResources());
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE(synth.getRegionView(0)->flexEGs.size() == 1);
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
|
|
@ -417,3 +417,43 @@ TEST_CASE("[FlexEG] Free-running flex AmpEG (no sustain)")
|
|||
synth.renderBlock(buffer);
|
||||
REQUIRE( synth.getNumActiveVoices() == 0 );
|
||||
}
|
||||
|
||||
TEST_CASE("[FlexEG] Modulation of time and level")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> sample=*noise
|
||||
eg1_time1=0 eg1_level1=1
|
||||
eg1_time2=0.7 eg1_level2=0.5
|
||||
eg1_time2_oncc1=-0.7 eg1_level2_oncc2=0.5
|
||||
eg1_time3=0.3 eg1_level3=0.0
|
||||
)");
|
||||
|
||||
REQUIRE( synth.getNumRegions() == 1 );
|
||||
const sfz::Region* region = synth.getRegionView(0);
|
||||
REQUIRE( region->flexEGs.size() == 1 );
|
||||
const sfz::FlexEGDescription& desc = synth.getRegionView(0)->flexEGs[0];
|
||||
REQUIRE( desc.points.size() == 4 );
|
||||
|
||||
REQUIRE( desc.points[2].time == Approx(0.7f) );
|
||||
REQUIRE( desc.points[2].level == Approx(0.5f) );
|
||||
|
||||
sfz::MidiState state;
|
||||
|
||||
REQUIRE( desc.points[2].getTime(state) == Approx(0.7f) );
|
||||
state.ccEvent(0, 1, 0.0f);
|
||||
REQUIRE( desc.points[2].getTime(state) == Approx(0.7f) );
|
||||
state.ccEvent(0, 1, 0.5f);
|
||||
REQUIRE( desc.points[2].getTime(state) == Approx(0.35f) );
|
||||
state.ccEvent(0, 1, 1.0f);
|
||||
REQUIRE( desc.points[2].getTime(state) == Approx(0.0f) );
|
||||
|
||||
REQUIRE( desc.points[2].getLevel(state) == Approx(0.5f) );
|
||||
state.ccEvent(0, 2, 0.0f);
|
||||
REQUIRE( desc.points[2].getLevel(state) == Approx(0.5f) );
|
||||
state.ccEvent(0, 2, 0.5f);
|
||||
REQUIRE( desc.points[2].getLevel(state) == Approx(0.75f) );
|
||||
state.ccEvent(0, 2, 1.0f);
|
||||
REQUIRE( desc.points[2].getLevel(state) == Approx(1.0f) );
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue