From adefb7d60e7d183a24317d33736828dfcd91a734 Mon Sep 17 00:00:00 2001 From: Python Blue Date: Thu, 8 Jun 2023 22:19:09 -0400 Subject: [PATCH] Add support for `curvecc` opcodes on v1 EGs --- external/st_audiofile/thirdparty/wavpack | 2 +- src/sfizz/ADSREnvelope.cpp | 14 +- src/sfizz/ADSREnvelope.h | 6 +- src/sfizz/EGDescription.h | 60 ++++-- src/sfizz/Region.cpp | 63 +++++- src/sfizz/SynthMessaging.cpp | 252 +++++++++++++++++++++-- src/sfizz/Voice.cpp | 6 +- tests/EGDescriptionT.cpp | 183 ++++++++-------- 8 files changed, 442 insertions(+), 144 deletions(-) diff --git a/external/st_audiofile/thirdparty/wavpack b/external/st_audiofile/thirdparty/wavpack index 78e75a85..36b08dbc 160000 --- a/external/st_audiofile/thirdparty/wavpack +++ b/external/st_audiofile/thirdparty/wavpack @@ -1 +1 @@ -Subproject commit 78e75a8527fffe2f87cdd56d5989ff01c0723ef7 +Subproject commit 36b08dbcb1de136e9ae477e9f1e2b57c958fff18 diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index 21a19ed2..47907560 100644 --- a/src/sfizz/ADSREnvelope.cpp +++ b/src/sfizz/ADSREnvelope.cpp @@ -58,13 +58,13 @@ void ADSREnvelope::reset(const EGDescription& desc, const Region& region, int de 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); + this->delay = delay + secondsToSamples(desc_->getDelay(midiState_, curveSet_, triggerVelocity_, delay)); + this->attackStep = secondsToLinRate(desc_->getAttack(midiState_, curveSet_, triggerVelocity_, delay)); + this->decayRate = secondsToExpRate(desc_->getDecay(midiState_, curveSet_, triggerVelocity_, delay)); + this->releaseRate = secondsToExpRate(desc_->getRelease(midiState_, curveSet_, triggerVelocity_, delay)); + this->hold = secondsToSamples(desc_->getHold(midiState_, curveSet_, triggerVelocity_, delay)); + this->sustain = clamp(desc_->getSustain(midiState_, curveSet_, triggerVelocity_, delay), 0.0f, 1.0f); + this->start = clamp(desc_->getStart(midiState_, curveSet_, triggerVelocity_, delay), 0.0f, 1.0f); sustainThreshold = this->sustain + config::virtuallyZero; } diff --git a/src/sfizz/ADSREnvelope.h b/src/sfizz/ADSREnvelope.h index 17007d95..33a4d1fa 100644 --- a/src/sfizz/ADSREnvelope.h +++ b/src/sfizz/ADSREnvelope.h @@ -14,12 +14,13 @@ namespace sfz { * @brief Describe an attack/delay/sustain/release envelope that can * produce its coefficient in a blockwise manner for SIMD-type operations. */ + class ADSREnvelope { public: using Float = float; - ADSREnvelope(const MidiState& state) - : midiState_(state) {} + ADSREnvelope(const MidiState& state, CurveSet& curveSet) + : midiState_(state), curveSet_(curveSet) {} /** * @brief Resets the ADSR envelope given a Region, the current midi state, and a delay and * trigger velocity @@ -104,6 +105,7 @@ private: Float currentValue { 0.0 }; const EGDescription* desc_ { nullptr }; const MidiState& midiState_; + CurveSet& curveSet_; float triggerVelocity_ { 0.0f }; bool dynamic_ { false }; int delay { 0 }; diff --git a/src/sfizz/EGDescription.h b/src/sfizz/EGDescription.h index fce5fc75..0337a305 100644 --- a/src/sfizz/EGDescription.h +++ b/src/sfizz/EGDescription.h @@ -32,6 +32,8 @@ #include "utility/LeakDetector.h" #include +#include "Curve.h" + namespace sfz { /** * @brief A description for an SFZ envelope generator, with its envelope parameters @@ -66,13 +68,20 @@ struct EGDescription { float vel2sustain { Default::egPercentMod }; float vel2depth { Default::egVel2Depth }; - CCMap ccAttack; - CCMap ccDecay; - CCMap ccDelay; - CCMap ccHold; - CCMap ccRelease; - CCMap ccStart; - CCMap ccSustain; + CCMap> ccAttack { ModifierCurvePair{ Default::egTime, Default::curveCC } }; + CCMap> ccDecay { ModifierCurvePair{ Default::egTime, Default::curveCC } }; + CCMap> ccDelay { ModifierCurvePair{ Default::egTime, Default::curveCC } }; + CCMap> ccHold { ModifierCurvePair{ Default::egTime, Default::curveCC } }; + CCMap> ccRelease { ModifierCurvePair{ Default::egTime, Default::curveCC } }; + CCMap> ccStart { ModifierCurvePair{ Default::egPercentMod, Default::curveCC } }; + CCMap> ccSustain { ModifierCurvePair{ Default:: egPercentMod, Default::curveCC } }; + //CCMap ccAttack; + //CCMap ccDecay; + //CCMap ccDelay; + //CCMap ccHold; + //CCMap ccRelease; + //CCMap ccStart; + //CCMap ccSustain; bool dynamic { false }; /** @@ -82,12 +91,13 @@ struct EGDescription { * @param velocity * @return float */ - float getAttack(const MidiState& state, float velocity, int delay = 0) const noexcept + float getAttack(const MidiState& state, CurveSet& curveSet, 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.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } @@ -98,12 +108,13 @@ struct EGDescription { * @param velocity * @return float */ - float getDecay(const MidiState& state, float velocity, int delay = 0) const noexcept + float getDecay(const MidiState& state, CurveSet& curveSet, 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.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } @@ -114,12 +125,13 @@ struct EGDescription { * @param velocity * @return float */ - float getDelay(const MidiState& state, float velocity, int delay = 0) const noexcept + float getDelay(const MidiState& state, CurveSet& curveSet, float velocity, int delay = 0) const noexcept { ASSERT(velocity >= 0.0f && velocity <= 1.0f); float returnedValue { this->delay + velocity * vel2delay }; for (auto& mod: ccDelay) { - returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } @@ -130,12 +142,13 @@ struct EGDescription { * @param velocity * @return float */ - float getHold(const MidiState& state, float velocity, int delay = 0) const noexcept + float getHold(const MidiState& state, CurveSet& curveSet, 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.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } @@ -146,12 +159,13 @@ struct EGDescription { * @param velocity * @return float */ - float getRelease(const MidiState& state, float velocity, int delay = 0) const noexcept + float getRelease(const MidiState& state, CurveSet& curveSet, 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.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } @@ -162,12 +176,13 @@ struct EGDescription { * @param velocity * @return float */ - float getStart(const MidiState& state, float velocity, int delay = 0) const noexcept + float getStart(const MidiState& state, CurveSet& curveSet, float velocity, int delay = 0) const noexcept { - UNUSED(velocity); + ASSERT(velocity >= 0.0f && velocity <= 1.0f); float returnedValue { start }; for (auto& mod: ccStart) { - returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } @@ -178,12 +193,13 @@ struct EGDescription { * @param velocity * @return float */ - float getSustain(const MidiState& state, float velocity, int delay = 0) const noexcept + float getSustain(const MidiState& state, CurveSet& curveSet, 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.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 8c260e22..8fe46f57 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1110,49 +1110,98 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccAttack[opcode.parameters.back()] = opcode.read(Default::egTimeMod); + eg.ccAttack[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); + + break; + case_any_eg("attack_curvecc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccAttack[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; case_any_eg("decay_oncc&"): // also decaycc& if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccDecay[opcode.parameters.back()] = opcode.read(Default::egTimeMod); + eg.ccDecay[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); + + break; + case_any_eg("decay_curvecc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccDecay[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; case_any_eg("delay_oncc&"): // also delaycc& if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccDelay[opcode.parameters.back()] = opcode.read(Default::egTimeMod); + eg.ccDelay[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); + + break; + case_any_eg("delay_curvecc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccDelay[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; case_any_eg("hold_oncc&"): // also holdcc& if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccHold[opcode.parameters.back()] = opcode.read(Default::egTimeMod); + eg.ccHold[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); + + break; + case_any_eg("hold_curvecc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccHold[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; case_any_eg("release_oncc&"): // also releasecc& if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccRelease[opcode.parameters.back()] = opcode.read(Default::egTimeMod); + eg.ccRelease[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); + + break; + case_any_eg("release_curvecc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccRelease[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; case_any_eg("start_oncc&"): // also startcc& if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccStart[opcode.parameters.back()] = opcode.read(Default::egPercentMod); + eg.ccStart[opcode.parameters.back()].modifier = opcode.read(Default::egPercentMod); + + break; + case_any_eg("start_curvecc&"): // also startcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccStart[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; case_any_eg("sustain_oncc&"): // also sustaincc& if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccSustain[opcode.parameters.back()] = opcode.read(Default::egPercentMod); + eg.ccSustain[opcode.parameters.back()].modifier = opcode.read(Default::egPercentMod); + + break; + case_any_eg("sustain_curvecc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccSustain[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index f065a4a7..889fba52 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -1222,6 +1222,90 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } } break; + MATCH("/region&/pitcheg_attack_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccAttack.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/pitcheg_decay_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccDecay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/pitcheg_delay_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccDelay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/pitcheg_hold_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccHold.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/pitcheg_release_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccRelease.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/pitcheg_start_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccStart.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); + } break; + + MATCH("/region&/pitcheg_sustain_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccSustain.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); + } break; + + MATCH("/region&/pitcheg_attack_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccAttack.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/pitcheg_decay_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccDecay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/pitcheg_delay_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccDelay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/pitcheg_hold_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccHold.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/pitcheg_release_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccRelease.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/pitcheg_start_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccStart.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/pitcheg_sustain_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccSustain.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + MATCH("/region&/note_polyphony", "") { GET_REGION_OR_BREAK(indices[0]) if (region.notePolyphony) { @@ -1338,44 +1422,86 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co MATCH("/region&/ampeg_attack_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value); + const auto& cc = region.amplitudeEG.ccAttack.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/ampeg_decay_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value); + const auto& cc = region.amplitudeEG.ccDecay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/ampeg_delay_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value); + const auto& cc = region.amplitudeEG.ccDelay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/ampeg_hold_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value); + const auto& cc = region.amplitudeEG.ccHold.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/ampeg_release_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value); + const auto& cc = region.amplitudeEG.ccRelease.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/ampeg_start_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value * 100.0f); + const auto& cc = region.amplitudeEG.ccStart.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); } break; MATCH("/region&/ampeg_sustain_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value * 100.0f); + const auto& cc = region.amplitudeEG.ccSustain.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); + } break; + + MATCH("/region&/ampeg_attack_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccAttack.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/ampeg_decay_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccDecay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/ampeg_delay_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccDelay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/ampeg_hold_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccHold.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/ampeg_release_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccRelease.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/ampeg_start_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccStart.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/ampeg_sustain_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccSustain.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); } break; MATCH("/region&/filter&/cutoff", "") { @@ -1495,6 +1621,104 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } } break; + MATCH("/region&/filter&/fileg_attack_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccAttack.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/filter&/fileg_decay_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccDecay.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/filter&/fileg_delay_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccDelay.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/filter&/fileg_hold_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccHold.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/filter&/fileg_release_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccRelease.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/filter&/fileg_start_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccStart.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); + } break; + + MATCH("/region&/filter&/fileg_sustain_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccSustain.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); + } break; + + MATCH("/region&/filter&/fileg_attack_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccAttack.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/filter&/fileg_decay_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccDecay.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/filter&/fileg_delay_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccDelay.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/filter&/fileg_hold_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccHold.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/filter&/fileg_release_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccRelease.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/filter&/fileg_start_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccStart.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/filter&/fileg_sustain_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccSustain.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + MATCH("/region&/eq&/gain", "") { GET_REGION_OR_BREAK(indices[0]) GET_EQ_OR_BREAK(indices[1]) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 3315fb69..63db7cdb 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -275,7 +275,7 @@ struct Voice::Impl std::unique_ptr lfoPitch_; std::unique_ptr lfoFilter_; - ADSREnvelope egAmplitude_ { resources_.getMidiState() }; + ADSREnvelope egAmplitude_ { resources_.getMidiState(), resources_.getCurves() }; std::unique_ptr egPitch_; std::unique_ptr egFilter_; @@ -1853,7 +1853,7 @@ void Voice::setPitchEGEnabledPerVoice(bool havePitchEG) { Impl& impl = *impl_; if (havePitchEG) - impl.egPitch_.reset(new ADSREnvelope(impl.resources_.getMidiState())); + impl.egPitch_.reset(new ADSREnvelope(impl.resources_.getMidiState(), impl.resources_.getCurves())); else impl.egPitch_.reset(); } @@ -1862,7 +1862,7 @@ void Voice::setFilterEGEnabledPerVoice(bool haveFilterEG) { Impl& impl = *impl_; if (haveFilterEG) - impl.egFilter_.reset(new ADSREnvelope(impl.resources_.getMidiState())); + impl.egFilter_.reset(new ADSREnvelope(impl.resources_.getMidiState(), impl.resources_.getCurves())); else impl.egFilter_.reset(); } diff --git a/tests/EGDescriptionT.cpp b/tests/EGDescriptionT.cpp index 5813ce97..b28abd70 100644 --- a/tests/EGDescriptionT.cpp +++ b/tests/EGDescriptionT.cpp @@ -15,158 +15,165 @@ TEST_CASE("[EGDescription] Attack range") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.attack = 1; eg.vel2attack = -1.27f; - eg.ccAttack[63] = 1.27f; - REQUIRE(eg.getAttack(state, 0_norm) == 1.0f); - //REQUIRE(eg.getAttack(state, 127_norm) == 0.0f); + eg.ccAttack[63].modifier = 1.27f; + REQUIRE(eg.getAttack(state, curveSet, 0_norm) == 1.0f); + //REQUIRE(eg.getAttack(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE(eg.getAttack(state, 127_norm) == 1.0f); - REQUIRE(eg.getAttack(state, 0_norm) == 2.27f); - //eg.ccAttack[63] = 127.0f; - //REQUIRE(eg.getAttack(state, 0_norm) == 100.0f); - eg.ccAttack[63] = 1.27f; - eg.ccAttack[65] = 1.0f; - REQUIRE(eg.getAttack(state, 0_norm) == 2.27f); - REQUIRE(eg.getAttack(state, 127_norm) == 1.0f); + REQUIRE(eg.getAttack(state, curveSet, 127_norm) == 1.0f); + REQUIRE(eg.getAttack(state, curveSet, 0_norm) == 2.27f); + //eg.ccAttack[63].modifier = 127.0f; + //REQUIRE(eg.getAttack(state, curveSet, 0_norm) == 100.0f); + eg.ccAttack[63].modifier = 1.27f; + eg.ccAttack[65].modifier = 1.0f; + REQUIRE(eg.getAttack(state, curveSet, 0_norm) == 2.27f); + REQUIRE(eg.getAttack(state, curveSet, 127_norm) == 1.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getAttack(state, 0_norm) == 3.27f); - REQUIRE(eg.getAttack(state, 127_norm) == 2.0f); + REQUIRE(eg.getAttack(state, curveSet, 0_norm) == 3.27f); + REQUIRE(eg.getAttack(state, curveSet, 127_norm) == 2.0f); } TEST_CASE("[EGDescription] Delay range") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.delay = 1; eg.vel2delay = -1.27f; - eg.ccDelay[63] = 1.27f; - REQUIRE(eg.getDelay(state, 0_norm) == 1.0f); - //REQUIRE(eg.getDelay(state, 127_norm) == 0.0f); + eg.ccDelay[63].modifier = 1.27f; + REQUIRE(eg.getDelay(state, curveSet, 0_norm) == 1.0f); + //REQUIRE(eg.getDelay(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE(eg.getDelay(state, 127_norm) == 1.0f); - REQUIRE(eg.getDelay(state, 0_norm, 1) == 2.27f); - //eg.ccDelay[63] = 127.0f; - //REQUIRE(eg.getDelay(state, 0_norm) == 100.0f); - eg.ccDelay[63] = 1.27f; - eg.ccDelay[65] = 1.0f; - REQUIRE(eg.getDelay(state, 0_norm) == 2.27f); - REQUIRE(eg.getDelay(state, 127_norm) == 1.0f); + REQUIRE(eg.getDelay(state, curveSet, 127_norm) == 1.0f); + REQUIRE(eg.getDelay(state, curveSet, 0_norm, 1) == 2.27f); + //eg.ccDelay[63].modifier = 127.0f; + //REQUIRE(eg.getDelay(state, curveSet, 0_norm) == 100.0f); + eg.ccDelay[63].modifier = 1.27f; + eg.ccDelay[65].modifier = 1.0f; + REQUIRE(eg.getDelay(state, curveSet, 0_norm) == 2.27f); + REQUIRE(eg.getDelay(state, curveSet, 127_norm) == 1.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getDelay(state, 0_norm) == 3.27f); - REQUIRE(eg.getDelay(state, 127_norm) == 2.0f); + REQUIRE(eg.getDelay(state, curveSet, 0_norm) == 3.27f); + REQUIRE(eg.getDelay(state, curveSet, 127_norm) == 2.0f); } TEST_CASE("[EGDescription] Decay range") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.decay = 1.0f; eg.vel2decay = -1.27f; - eg.ccDecay[63] = 1.27f; - REQUIRE(eg.getDecay(state, 0_norm) == 1.0f); - //REQUIRE(eg.getDecay(state, 127_norm) == 0.0f); + eg.ccDecay[63].modifier = 1.27f; + REQUIRE(eg.getDecay(state, curveSet, 0_norm) == 1.0f); + //REQUIRE(eg.getDecay(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE(eg.getDecay(state, 127_norm) == 1.0f); - REQUIRE(eg.getDecay(state, 0_norm) == 2.27f); - //eg.ccDecay[63] = 127.0f; - //REQUIRE(eg.getDecay(state, 0_norm) == 100.0f); - eg.ccDecay[63] = 1.27f; - eg.ccDecay[65] = 1.0f; - REQUIRE(eg.getDecay(state, 0_norm) == 2.27f); - REQUIRE(eg.getDecay(state, 127_norm) == 1.0f); + REQUIRE(eg.getDecay(state, curveSet, 127_norm) == 1.0f); + REQUIRE(eg.getDecay(state, curveSet, 0_norm) == 2.27f); + //eg.ccDecay[63].modifier = 127.0f; + //REQUIRE(eg.getDecay(state, curveSet, 0_norm) == 100.0f); + eg.ccDecay[63].modifier = 1.27f; + eg.ccDecay[65].modifier = 1.0f; + REQUIRE(eg.getDecay(state, curveSet, 0_norm) == 2.27f); + REQUIRE(eg.getDecay(state, curveSet, 127_norm) == 1.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getDecay(state, 0_norm) == 3.27f); - REQUIRE(eg.getDecay(state, 127_norm) == 2.0f); + REQUIRE(eg.getDecay(state, curveSet, 0_norm) == 3.27f); + REQUIRE(eg.getDecay(state, curveSet, 127_norm) == 2.0f); } TEST_CASE("[EGDescription] Release range") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.release = 1; eg.vel2release = -1.27f; - eg.ccRelease[63] = 1.27f; - REQUIRE(eg.getRelease(state, 0_norm) == 1.0f); - //REQUIRE(eg.getRelease(state, 127_norm) == 0.0f); + eg.ccRelease[63].modifier = 1.27f; + REQUIRE(eg.getRelease(state, curveSet, 0_norm) == 1.0f); + //REQUIRE(eg.getRelease(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE(eg.getRelease(state, 127_norm) == 1.0f); - REQUIRE(eg.getRelease(state, 0_norm) == 2.27f); - //eg.ccRelease[63] = 127.0f; - //REQUIRE(eg.getRelease(state, 0_norm) == 100.0f); - eg.ccRelease[63] = 1.27f; - eg.ccRelease[65] = 1.0f; - REQUIRE(eg.getRelease(state, 0_norm) == 2.27f); - REQUIRE(eg.getRelease(state, 127_norm) == 1.0f); + REQUIRE(eg.getRelease(state, curveSet, 127_norm) == 1.0f); + REQUIRE(eg.getRelease(state, curveSet, 0_norm) == 2.27f); + //eg.ccRelease[63].modifier = 127.0f; + //REQUIRE(eg.getRelease(state, curveSet, 0_norm) == 100.0f); + eg.ccRelease[63].modifier = 1.27f; + eg.ccRelease[65].modifier = 1.0f; + REQUIRE(eg.getRelease(state, curveSet, 0_norm) == 2.27f); + REQUIRE(eg.getRelease(state, curveSet, 127_norm) == 1.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getRelease(state, 0_norm) == 3.27f); - REQUIRE(eg.getRelease(state, 127_norm) == 2.0f); + REQUIRE(eg.getRelease(state, curveSet, 0_norm) == 3.27f); + REQUIRE(eg.getRelease(state, curveSet, 127_norm) == 2.0f); } TEST_CASE("[EGDescription] Hold range") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.hold = 1; eg.vel2hold = -1.27f; - eg.ccHold[63] = 1.27f; - REQUIRE(eg.getHold(state, 0_norm) == 1.0f); - //REQUIRE(eg.getHold(state, 127_norm) == 0.0f); + eg.ccHold[63].modifier = 1.27f; + REQUIRE(eg.getHold(state, curveSet, 0_norm) == 1.0f); + //REQUIRE(eg.getHold(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE(eg.getHold(state, 127_norm) == 1.0f); - REQUIRE(eg.getHold(state, 0_norm) == 2.27f); - //eg.ccHold[63] = 127.0f; - //REQUIRE(eg.getHold(state, 0_norm) == 100.0f); - eg.ccHold[63] = 1.27f; - eg.ccHold[65] = 1.0f; - REQUIRE(eg.getHold(state, 0_norm) == 2.27f); - REQUIRE(eg.getHold(state, 127_norm) == 1.0f); + REQUIRE(eg.getHold(state, curveSet, 127_norm) == 1.0f); + REQUIRE(eg.getHold(state, curveSet, 0_norm) == 2.27f); + //eg.ccHold[63].modifier = 127.0f; + //REQUIRE(eg.getHold(state, curveSet, 0_norm) == 100.0f); + eg.ccHold[63].modifier = 1.27f; + eg.ccHold[65].modifier = 1.0f; + REQUIRE(eg.getHold(state, curveSet, 0_norm) == 2.27f); + REQUIRE(eg.getHold(state, curveSet, 127_norm) == 1.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getHold(state, 0_norm) == 3.27f); - REQUIRE(eg.getHold(state, 127_norm) == 2.0f); + REQUIRE(eg.getHold(state, curveSet, 0_norm) == 3.27f); + REQUIRE(eg.getHold(state, curveSet, 127_norm) == 2.0f); } TEST_CASE("[EGDescription] Sustain level") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.sustain = 50; eg.vel2sustain = -100; - eg.ccSustain[63] = 100.0f; - REQUIRE(eg.getSustain(state, 0_norm) == 50.0f); - //REQUIRE(eg.getSustain(state, 127_norm) == 0.0f); + eg.ccSustain[63].modifier = 100.0f; + REQUIRE(eg.getSustain(state, curveSet, 0_norm) == 50.0f); + //REQUIRE(eg.getSustain(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE(eg.getSustain(state, 127_norm) == 50.0f); - //eg.ccSustain[63] = 200.0f; - //REQUIRE(eg.getSustain(state, 0_norm) == 100.0f); + REQUIRE(eg.getSustain(state, curveSet, 127_norm) == 50.0f); + //eg.ccSustain[63].modifier = 200.0f; + //REQUIRE(eg.getSustain(state, curveSet, 0_norm) == 100.0f); eg.sustain = 0; - eg.ccSustain[63] = 50.0f; - eg.ccSustain[65] = 50.0f; - REQUIRE(eg.getSustain(state, 0_norm) == 50.0f); - //REQUIRE(eg.getSustain(state, 127_norm) == 0.0f); + eg.ccSustain[63].modifier = 50.0f; + eg.ccSustain[65].modifier = 50.0f; + REQUIRE(eg.getSustain(state, curveSet, 0_norm) == 50.0f); + //REQUIRE(eg.getSustain(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getSustain(state, 0_norm) == 100.0f); - REQUIRE(eg.getSustain(state, 127_norm) == 0.0f); + REQUIRE(eg.getSustain(state, curveSet, 0_norm) == 100.0f); + REQUIRE(eg.getSustain(state, curveSet, 127_norm) == 0.0f); } TEST_CASE("[EGDescription] Start level") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.start = 0; - eg.ccStart[63] = 127.0f; - REQUIRE(eg.getStart(state, 0_norm) == 0.0f); - REQUIRE(eg.getStart(state, 127_norm) == 0.0f); + eg.ccStart[63].modifier = 127.0f; + REQUIRE(eg.getStart(state, curveSet, 0_norm) == 0.0f); + REQUIRE(eg.getStart(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - //REQUIRE(eg.getStart(state, 0_norm) == 100.0f); + //REQUIRE(eg.getStart(state, curveSet, 0_norm) == 100.0f); //eg.ccStart[63] = -127.0f; - //REQUIRE(eg.getStart(state, 0_norm) == 0.0f); + //REQUIRE(eg.getStart(state, curveSet, 0_norm) == 0.0f); eg.start = 0; - eg.ccStart[63] = 50.0f; - eg.ccStart[65] = 50.0f; - REQUIRE(eg.getStart(state, 0_norm) == 50.0f); - REQUIRE(eg.getStart(state, 127_norm) == 50.0f); + eg.ccStart[63].modifier = 50.0f; + eg.ccStart[65].modifier = 50.0f; + REQUIRE(eg.getStart(state, curveSet, 0_norm) == 50.0f); + REQUIRE(eg.getStart(state, curveSet, 127_norm) == 50.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getStart(state, 0_norm) == 100.0f); - REQUIRE(eg.getStart(state, 127_norm) == 100.0f); + REQUIRE(eg.getStart(state, curveSet, 0_norm) == 100.0f); + REQUIRE(eg.getStart(state, curveSet, 127_norm) == 100.0f); }