Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic EGs #933

Merged
merged 3 commits into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 36 additions & 12 deletions src/sfizz/ADSREnvelope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,54 @@ Float ADSREnvelope::secondsToExpRate(Float timeInSeconds) const noexcept
return std::exp(Float(-9.0) / (timeInSeconds * sampleRate));
};

void ADSREnvelope::reset(const EGDescription& desc, const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept
void ADSREnvelope::reset(const EGDescription& desc, const Region& region, int delay, float velocity, float sampleRate) noexcept
{
this->sampleRate = sampleRate;

this->delay = delay + secondsToSamples(desc.getDelay(state, velocity));
this->attackStep = secondsToLinRate(desc.getAttack(state, velocity));
this->decayRate = secondsToExpRate(desc.getDecay(state, velocity));
this->releaseRate = secondsToExpRate(desc.getRelease(state, velocity));
this->hold = secondsToSamples(desc.getHold(state, velocity));
this->sustain = clamp(desc.getSustain(state, velocity), 0.0f, 1.0f);
this->start = clamp(desc.getStart(state, velocity), 0.0f, 1.0f);

desc_ = &desc;
triggerVelocity_ = velocity;
currentState = State::Delay; // Has to be before the update
updateValues(delay);
releaseDelay = 0;
sustainThreshold = this->sustain + config::virtuallyZero;
shouldRelease = false;
freeRunning = (
(this->sustain <= Float(config::sustainFreeRunningThreshold))
|| (region.loopMode == LoopMode::one_shot && region.isOscillator())
);
currentValue = this->start;
currentState = State::Delay;
}

void ADSREnvelope::updateValues(int delay) noexcept
{
if (currentState == State::Delay)
this->delay = delay + secondsToSamples(desc_->getDelay(midiState_, triggerVelocity_, delay));

this->attackStep = secondsToLinRate(desc_->getAttack(midiState_, triggerVelocity_, delay));
this->decayRate = secondsToExpRate(desc_->getDecay(midiState_, triggerVelocity_, delay));
this->releaseRate = secondsToExpRate(desc_->getRelease(midiState_, triggerVelocity_, delay));
this->hold = secondsToSamples(desc_->getHold(midiState_, triggerVelocity_, delay));
this->sustain = clamp(desc_->getSustain(midiState_, triggerVelocity_, delay), 0.0f, 1.0f);
this->start = clamp(desc_->getStart(midiState_, triggerVelocity_, delay), 0.0f, 1.0f);
sustainThreshold = this->sustain + config::virtuallyZero;
}

void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
{
if (desc_ && desc_->dynamic) {
int processed = 0;
int remaining = static_cast<int>(output.size());
while(remaining > 0) {
updateValues(processed);
int chunkSize = min(config::processChunkSize, remaining);
getBlockInternal(output.subspan(processed, chunkSize));
processed += chunkSize;
remaining -= chunkSize;
}
} else {
getBlockInternal(output);
}
}

void ADSREnvelope::getBlockInternal(absl::Span<Float> output) noexcept
{
State currentState = this->currentState;
Float currentValue = this->currentValue;
Expand Down
13 changes: 9 additions & 4 deletions src/sfizz/ADSREnvelope.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ class ADSREnvelope {
public:
using Float = float;

ADSREnvelope() = default;
ADSREnvelope(const MidiState& state)
: midiState_(state) {}
/**
* @brief Resets the ADSR envelope given a Region, the current midi state, and a delay and
* trigger velocity
Expand All @@ -29,10 +30,9 @@ class ADSREnvelope {
* @param delay
* @param velocity
*/
void reset(const EGDescription& desc, const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept;
void reset(const EGDescription& desc, const Region& region, int delay, float velocity, float sampleRate) noexcept;
/**
* @brief Get a block of values for the envelope. This method tries hard to be efficient
* and hopefully it is.
* @brief Get the next block of values for the envelope.
*
* @param output
*/
Expand Down Expand Up @@ -81,6 +81,8 @@ class ADSREnvelope {
int secondsToSamples(Float timeInSeconds) const noexcept;
Float secondsToLinRate(Float timeInSeconds) const noexcept;
Float secondsToExpRate(Float timeInSeconds) const noexcept;
void updateValues(int delay = 0) noexcept;
void getBlockInternal(absl::Span<Float> output) noexcept;

enum class State {
Delay,
Expand All @@ -94,6 +96,9 @@ class ADSREnvelope {
};
State currentState { State::Done };
Float currentValue { 0.0 };
const EGDescription* desc_ { nullptr };
const MidiState& midiState_;
float triggerVelocity_ { 0.0f };
int delay { 0 };
Float attackStep { 0 };
Float decayRate { 0 };
Expand Down
3 changes: 2 additions & 1 deletion src/sfizz/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ namespace config {
constexpr float powerFollowerReleaseTime { 200e-3f };
constexpr uint16_t numCCs { 512 };
constexpr int maxCurves { 256 };
constexpr int chunkSize { 1024 };
constexpr int fileChunkSize { 1024 };
constexpr int processChunkSize { 16 };
constexpr unsigned int defaultAlignment { 16 };
constexpr int filtersInPool { maxVoices * 2 };
constexpr int excessFileFrames { 64 };
Expand Down
1 change: 1 addition & 0 deletions src/sfizz/Defaults.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ FloatSpec egPercent { 0.0f, {0.0f, 100.0f}, kNormalizePercent|kPermissiveBounds
FloatSpec egPercentMod { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };
FloatSpec egDepth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
FloatSpec egVel2Depth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
BoolSpec egDynamic { 0, {0, 1}, kEnforceBounds };
BoolSpec flexEGAmpeg { false, {0, 1}, kEnforceBounds };
BoolSpec flexEGDynamic { 0, {0, 1}, kEnforceBounds };
Int32Spec flexEGSustain { 0, {0, 100}, kEnforceLowerBound|kPermissiveUpperBound };
Expand Down
1 change: 1 addition & 0 deletions src/sfizz/Defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ namespace Default
extern const OpcodeSpec<float> egPercentMod;
extern const OpcodeSpec<float> egDepth;
extern const OpcodeSpec<float> egVel2Depth;
extern const OpcodeSpec<bool> egDynamic;
extern const OpcodeSpec<bool> flexEGAmpeg;
extern const OpcodeSpec<bool> flexEGDynamic;
extern const OpcodeSpec<int32_t> flexEGSustain;
Expand Down
47 changes: 16 additions & 31 deletions src/sfizz/EGDescription.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,6 @@ namespace sfz {
*
*/

/**
* @brief If a cc switch exists for the value, returns the value with the CC modifier, otherwise returns the value alone.
*
* @param ccValues
* @param ccSwitch
* @param value
* @return float
*/
inline float ccSwitchedValue(const MidiState& state, const absl::optional<CCData<float>>& ccSwitch, float value) noexcept
{
if (ccSwitch)
return value + ccSwitch->data * state.getCCValue(ccSwitch->cc);
else
return value;
}

struct EGDescription {
EGDescription() = default;
EGDescription(const EGDescription&) = default;
Expand Down Expand Up @@ -89,6 +73,7 @@ struct EGDescription {
CCMap<float> ccRelease;
CCMap<float> ccStart;
CCMap<float> ccSustain;
bool dynamic { false };

/**
* @brief Get the attack with possibly a CC modifier and a velocity modifier
Expand All @@ -97,12 +82,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getAttack(const MidiState& state, float velocity) const noexcept
float getAttack(const MidiState& state, float velocity, int delay = 0) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { attack + velocity * vel2attack };
for (auto& mod: ccAttack) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
Expand All @@ -113,12 +98,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getDecay(const MidiState& state, float velocity) const noexcept
float getDecay(const MidiState& state, float velocity, int delay = 0) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { decay + velocity * vel2decay };
for (auto& mod: ccDecay) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
Expand All @@ -129,12 +114,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getDelay(const MidiState& state, float velocity) const noexcept
float getDelay(const MidiState& state, float velocity, int delay = 0) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { delay + velocity * vel2delay };
float returnedValue { this->delay + velocity * vel2delay };
for (auto& mod: ccDelay) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
Expand All @@ -145,12 +130,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getHold(const MidiState& state, float velocity) const noexcept
float getHold(const MidiState& state, float velocity, int delay = 0) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { hold + velocity * vel2hold };
for (auto& mod: ccHold) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
Expand All @@ -161,12 +146,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getRelease(const MidiState& state, float velocity) const noexcept
float getRelease(const MidiState& state, float velocity, int delay = 0) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { release + velocity * vel2release };
for (auto& mod: ccRelease) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
Expand All @@ -177,12 +162,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getStart(const MidiState& state, float velocity) const noexcept
float getStart(const MidiState& state, float velocity, int delay = 0) const noexcept
{
UNUSED(velocity);
float returnedValue { start };
for (auto& mod: ccStart) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
Expand All @@ -193,12 +178,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getSustain(const MidiState& state, float velocity) const noexcept
float getSustain(const MidiState& state, float velocity, int delay = 0) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { sustain + velocity * vel2sustain };
for (auto& mod: ccSustain) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
Expand Down
2 changes: 1 addition & 1 deletion src/sfizz/FilePool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ void streamFromFile(sfz::AudioReader& reader, sfz::FileAudioBuffer& output, std:
{
const auto numFrames = static_cast<size_t>(reader.frames());
const auto numChannels = reader.channels();
const auto chunkSize = static_cast<size_t>(sfz::config::chunkSize);
const auto chunkSize = static_cast<size_t>(sfz::config::fileChunkSize);

output.reset();
output.addChannels(reader.channels());
Expand Down
8 changes: 4 additions & 4 deletions src/sfizz/FlexEGDescription.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,19 @@ void FlexEGs::clearUnusedCurves()
}

///
float FlexEGPoint::getTime(const MidiState& state) const noexcept
float FlexEGPoint::getTime(const MidiState& state, int delay) const noexcept
{
float returnedValue { time };
for (const CCData<float>& mod : ccTime)
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
return returnedValue;
}

float FlexEGPoint::getLevel(const MidiState& state) const noexcept
float FlexEGPoint::getLevel(const MidiState& state, int delay) const noexcept
{
float returnedValue { level };
for (const CCData<float>& mod : ccLevel)
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
return returnedValue;
}

Expand Down
4 changes: 2 additions & 2 deletions src/sfizz/FlexEGDescription.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ struct FlexEGPoint {
CCMap<float> ccTime;
CCMap<float> ccLevel;

float getTime(const MidiState& state) const noexcept;
float getLevel(const MidiState& state) const noexcept;
float getTime(const MidiState& state, int delay = 0) const noexcept;
float getLevel(const MidiState& state, int delay = 0) const noexcept;

void setShape(float shape);
float shape() const noexcept { return shape_; }
Expand Down
22 changes: 17 additions & 5 deletions src/sfizz/FlexEnvelope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ struct FlexEnvelope::Impl {
void process(absl::Span<float> out);
bool advanceToStage(unsigned stageNumber);
bool advanceToNextStage();
void updateCurrentTimeAndLevel();
void updateCurrentTimeAndLevel(int delay = 0);
};

FlexEnvelope::FlexEnvelope(Resources &resources)
Expand Down Expand Up @@ -155,7 +155,19 @@ bool FlexEnvelope::isFinished() const noexcept
void FlexEnvelope::process(absl::Span<float> out)
{
Impl& impl = *impl_;
impl.process(out);
if (impl.desc_->dynamic) {
int processed = 0;
int remaining = static_cast<int>(out.size());
while(remaining > 0) {
impl.updateCurrentTimeAndLevel(processed);
int chunkSize = min(config::processChunkSize, remaining);
impl.process(out.subspan(processed, chunkSize));
processed += chunkSize;
remaining -= chunkSize;
}
} else {
impl.process(out);
}
}

void FlexEnvelope::Impl::process(absl::Span<float> out)
Expand Down Expand Up @@ -271,16 +283,16 @@ bool FlexEnvelope::Impl::advanceToNextStage()
return advanceToStage(currentStageNumber_ + 1);
}

void FlexEnvelope::Impl::updateCurrentTimeAndLevel()
void FlexEnvelope::Impl::updateCurrentTimeAndLevel(int delay)
{
const FlexEGDescription& desc = *desc_;
if (currentStageNumber_ >= desc.points.size())
return;

const FlexEGPoint& point = desc.points[currentStageNumber_];
const MidiState& midiState = resources_->getMidiState();
stageTargetLevel_ = point.getLevel(midiState);
stageTime_ = point.getTime(midiState);
stageTargetLevel_ = point.getLevel(midiState, delay);
stageTime_ = point.getTime(midiState, delay);
}

} // namespace sfz
11 changes: 11 additions & 0 deletions src/sfizz/MidiState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,17 @@ float sfz::MidiState::getCCValue(int ccNumber) const noexcept
return ccEvents[ccNumber].back().value;
}

float sfz::MidiState::getCCValueAt(int ccNumber, int delay) const noexcept
{
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
const auto ccEvent = absl::c_lower_bound(
ccEvents[ccNumber], delay, MidiEventDelayComparator {});
if (ccEvent != ccEvents[ccNumber].end())
return ccEvent->value;
else
return ccEvents[ccNumber].back().value;
}

void sfz::MidiState::reset() noexcept
{
for (auto& velocity: lastNoteVelocities)
Expand Down
Loading