From 3412ead0ac25e47ac2e48b1cc7592b678f976f32 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 26 Sep 2020 14:51:43 +0200 Subject: [PATCH 1/8] Ampeg in modulation matrix --- src/sfizz/Synth.cpp | 6 ++++++ src/sfizz/Voice.cpp | 11 ++++------- src/sfizz/Voice.h | 4 ++++ src/sfizz/modulations/ModId.cpp | 2 ++ src/sfizz/modulations/ModId.h | 1 + src/sfizz/modulations/ModKey.cpp | 2 ++ src/sfizz/modulations/sources/ADSREnvelope.cpp | 13 +++++++++++++ 7 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index b191d841..52b02c96 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -159,6 +159,11 @@ void sfz::Synth::buildRegion(const std::vector& regionOpcodes) ModKey::createCC(10, 1, defaultSmoothness, 100, 0), ModKey::createNXYZ(ModId::Pan, lastRegion->id)).sourceDepth = 1.0f; + // Create the amplitude envelope + lastRegion->getOrCreateConnection( + ModKey::createNXYZ(ModId::AmpEG, lastRegion->id), + ModKey::createNXYZ(ModId::Amplitude, lastRegion->id)).sourceDepth = 100.0f; + // auto parseOpcodes = [&](const std::vector& opcodes) { for (auto& opcode : opcodes) { @@ -1530,6 +1535,7 @@ void sfz::Synth::setupModMatrix() case ModId::Envelope: gen = genFlexEnvelope.get(); break; + case ModId::AmpEG: case ModId::PitchEG: case ModId::FilEG: gen = genADSREnvelope.get(); diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index a60b1af7..98ecf495 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -130,7 +130,6 @@ void sfz::Voice::startVoice(Region* region, int delay, const TriggerEvent& event bendStepFactor = centsFactor(region->bendStep); bendSmoother.setSmoothing(region->bendSmooth, sampleRate); 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); saveModulationTargets(region); @@ -154,8 +153,6 @@ void sfz::Voice::release(int delay) noexcept if (egAmplitude.getRemainingDelay() > delay) { switchState(State::cleanMeUp); - } else { - egAmplitude.startRelease(delay); } resources.modMatrix.releaseVoice(id, region->getId(), delay); @@ -367,14 +364,14 @@ void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept ModMatrix& mm = resources.modMatrix; - // AmpEG envelope - egAmplitude.getBlock(modulationSpan); - // Amplitude envelope applyGain1(baseGain, modulationSpan); if (float* mod = mm.getModulation(amplitudeTarget)) { for (size_t i = 0; i < numSamples; ++i) - modulationSpan[i] *= normalizePercents(mod[i]); + modulationSpan[i] = normalizePercents(mod[i]); + } + else { + ASSERTFALSE; } // Volume envelope diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 419c6584..86b1c5ab 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -336,6 +336,10 @@ public: Duration getLastFilterDuration() const noexcept { return filterDuration; } Duration getLastPanningDuration() const noexcept { return panningDuration; } + /** + * @brief Get the SFZv1 amplitude EG, if existing + */ + ADSREnvelope* getAmplitudeEG() { return &egAmplitude; } /** * @brief Get the SFZv1 pitch EG, if existing */ diff --git a/src/sfizz/modulations/ModId.cpp b/src/sfizz/modulations/ModId.cpp index 427d7511..d21ba9e0 100644 --- a/src/sfizz/modulations/ModId.cpp +++ b/src/sfizz/modulations/ModId.cpp @@ -30,6 +30,8 @@ int ModIds::flags(ModId id) noexcept return kModIsPerVoice; case ModId::LFO: return kModIsPerVoice; + case ModId::AmpEG: + return kModIsPerVoice; case ModId::PitchEG: return kModIsPerVoice; case ModId::FilEG: diff --git a/src/sfizz/modulations/ModId.h b/src/sfizz/modulations/ModId.h index ccd25b98..3eb7022d 100644 --- a/src/sfizz/modulations/ModId.h +++ b/src/sfizz/modulations/ModId.h @@ -23,6 +23,7 @@ enum class ModId : int { Controller = _SourcesStart, Envelope, LFO, + AmpEG, PitchEG, FilEG, diff --git a/src/sfizz/modulations/ModKey.cpp b/src/sfizz/modulations/ModKey.cpp index 80c3accb..2b9767d3 100644 --- a/src/sfizz/modulations/ModKey.cpp +++ b/src/sfizz/modulations/ModKey.cpp @@ -74,6 +74,8 @@ std::string ModKey::toString() const return absl::StrCat("EG ", 1 + params_.N, " {", region_.number(), "}"); case ModId::LFO: return absl::StrCat("LFO ", 1 + params_.N, " {", region_.number(), "}"); + case ModId::AmpEG: + return absl::StrCat("AmplitudeEG {", region_.number(), "}"); case ModId::PitchEG: return absl::StrCat("PitchEG {", region_.number(), "}"); case ModId::FilEG: diff --git a/src/sfizz/modulations/sources/ADSREnvelope.cpp b/src/sfizz/modulations/sources/ADSREnvelope.cpp index 7128b216..57f8ba7d 100644 --- a/src/sfizz/modulations/sources/ADSREnvelope.cpp +++ b/src/sfizz/modulations/sources/ADSREnvelope.cpp @@ -35,6 +35,11 @@ void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, const EGDescription* desc = nullptr; switch (sourceKey.id()) { + case ModId::AmpEG: + eg = voice->getAmplitudeEG(); + ASSERT(eg); + desc = ®ion->amplitudeEG; + break; case ModId::PitchEG: eg = voice->getPitchEG(); ASSERT(eg); @@ -69,6 +74,10 @@ void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId voice ADSREnvelope* eg = nullptr; switch (sourceKey.id()) { + case ModId::AmpEG: + eg = voice->getAmplitudeEG(); + ASSERT(eg); + break; case ModId::PitchEG: eg = voice->getPitchEG(); ASSERT(eg); @@ -98,6 +107,10 @@ void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId voic ADSREnvelope* eg = nullptr; switch (sourceKey.id()) { + case ModId::AmpEG: + eg = voice->getAmplitudeEG(); + ASSERT(eg); + break; case ModId::PitchEG: eg = voice->getPitchEG(); ASSERT(eg); From c30cad25340e58f499d4a268351ee4f9c2f26090 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 26 Sep 2020 15:54:41 +0200 Subject: [PATCH 2/8] Implement egN_ampeg --- src/sfizz/FlexEGDescription.h | 2 ++ src/sfizz/FlexEnvelope.cpp | 19 ++++++++++++++ src/sfizz/FlexEnvelope.h | 15 +++++++++++ src/sfizz/Region.cpp | 33 ++++++++++++++++++++--- src/sfizz/Region.h | 2 ++ src/sfizz/Synth.cpp | 18 +++++++++---- src/sfizz/Voice.cpp | 49 +++++++++++++++++++++++++++-------- 7 files changed, 119 insertions(+), 19 deletions(-) diff --git a/src/sfizz/FlexEGDescription.h b/src/sfizz/FlexEGDescription.h index fd8e9428..b8a0f59b 100644 --- a/src/sfizz/FlexEGDescription.h +++ b/src/sfizz/FlexEGDescription.h @@ -34,6 +34,8 @@ struct FlexEGDescription { 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) std::vector points; + // ARIA + bool ampeg = false; // replaces the SFZv1 AmpEG (lowest with this bit wins) }; } // namespace sfz diff --git a/src/sfizz/FlexEnvelope.cpp b/src/sfizz/FlexEnvelope.cpp index c58d3ae5..c9302c0b 100644 --- a/src/sfizz/FlexEnvelope.cpp +++ b/src/sfizz/FlexEnvelope.cpp @@ -104,6 +104,25 @@ void FlexEnvelope::release(unsigned releaseDelay) impl.currentFramesUntilRelease_ = releaseDelay; } +unsigned FlexEnvelope::getRemainingDelay() const noexcept +{ + const Impl& impl = *impl_; + return static_cast(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 out) { Impl& impl = *impl_; diff --git a/src/sfizz/FlexEnvelope.h b/src/sfizz/FlexEnvelope.h index 3316e715..d1d01a4a 100644 --- a/src/sfizz/FlexEnvelope.h +++ b/src/sfizz/FlexEnvelope.h @@ -40,6 +40,21 @@ public: */ 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. */ diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index c599bc66..b48ed7f3 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1162,6 +1162,28 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqBandwidth, Default::eqBandwidthModRange); 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 value = readOpcode(opcode.value, Range { 0, 1 })) { + FlexEGDescription& desc = flexEGs[egNumber - 1]; + bool ampeg = *value != 0; + 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(i); + } + } + } + break; + } + // Amplitude Envelope case hash("ampeg_attack"): case hash("ampeg_decay"): @@ -1907,7 +1929,7 @@ float sfz::Region::getBendInCents(float bend) const noexcept return bend > 0.0f ? bend * static_cast(bendUp) : -bend * static_cast(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) { @@ -1915,8 +1937,13 @@ sfz::Region::Connection& sfz::Region::getOrCreateConnection(const ModKey& source }; auto it = std::find_if(connections.begin(), connections.end(), pred); - if (it != connections.end()) - return *it; + return (it == connections.end()) ? nullptr : &*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; c.source = source; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 76840c53..62692437 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -425,6 +425,7 @@ struct Region { // Envelopes std::vector flexEGs; + absl::optional flexAmpEG; // egN_ampeg // LFOs std::vector lfos; @@ -445,6 +446,7 @@ struct Region { float velToDepth = 0.0f; }; std::vector connections; + Connection* getConnection(const ModKey& source, const ModKey& target); Connection& getOrCreateConnection(const ModKey& source, const ModKey& target); // Parent diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 52b02c96..4e7a9f7d 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -159,11 +159,6 @@ void sfz::Synth::buildRegion(const std::vector& regionOpcodes) ModKey::createCC(10, 1, defaultSmoothness, 100, 0), ModKey::createNXYZ(ModId::Pan, lastRegion->id)).sourceDepth = 1.0f; - // Create the amplitude envelope - lastRegion->getOrCreateConnection( - ModKey::createNXYZ(ModId::AmpEG, lastRegion->id), - ModKey::createNXYZ(ModId::Amplitude, lastRegion->id)).sourceDepth = 100.0f; - // auto parseOpcodes = [&](const std::vector& opcodes) { for (auto& opcode : opcodes) { @@ -182,6 +177,19 @@ void sfz::Synth::buildRegion(const std::vector& regionOpcodes) parseOpcodes(groupOpcodes); parseOpcodes(regionOpcodes); + // Create the amplitude envelope + if (!lastRegion->flexAmpEG) { + lastRegion->getOrCreateConnection( + ModKey::createNXYZ(ModId::AmpEG, lastRegion->id), + ModKey::createNXYZ(ModId::Amplitude, lastRegion->id)).sourceDepth = 100.0f; + } + else { + ModKey source = ModKey::createNXYZ(ModId::Envelope, lastRegion->id, *lastRegion->flexAmpEG); + ModKey target = ModKey::createNXYZ(ModId::Amplitude, lastRegion->id); + if (!lastRegion->getConnection(source, target)) + lastRegion->getOrCreateConnection(source, target).sourceDepth = 100.0f; + } + if (octaveOffset != 0 || noteOffset != 0) lastRegion->offsetAllKeys(octaveOffset * 12 + noteOffset); diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 98ecf495..ebed105b 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -151,8 +151,13 @@ void sfz::Voice::release(int delay) noexcept if (state != State::playing) return; - if (egAmplitude.getRemainingDelay() > delay) { - switchState(State::cleanMeUp); + if (!region->flexAmpEG) { + if (egAmplitude.getRemainingDelay() > delay) + switchState(State::cleanMeUp); + } + else { + if (flexEGs[*region->flexAmpEG]->getRemainingDelay() > static_cast(delay)) + switchState(State::cleanMeUp); } resources.modMatrix.releaseVoice(id, region->getId(), delay); @@ -160,10 +165,15 @@ void sfz::Voice::release(int delay) noexcept void sfz::Voice::off(int delay) noexcept { - if (region->offMode == SfzOffMode::fast) { - egAmplitude.setReleaseTime( Default::offTime ); - } else if (region->offMode == SfzOffMode::time) { - egAmplitude.setReleaseTime(region->offTime); + if (!region->flexAmpEG) { + if (region->offMode == SfzOffMode::fast) { + egAmplitude.setReleaseTime( Default::offTime ); + } else if (region->offMode == SfzOffMode::time) { + egAmplitude.setReleaseTime(region->offTime); + } + } + else { + // TODO(jpc): Flex AmpEG } release(delay); @@ -283,8 +293,14 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept panStageMono(buffer); } - if (!egAmplitude.isSmoothing()) - switchState(State::cleanMeUp); + if (!region->flexAmpEG) { + if (!egAmplitude.isSmoothing()) + switchState(State::cleanMeUp); + } + else { + if (flexEGs[*region->flexAmpEG]->isFinished()) + switchState(State::cleanMeUp); + } powerFollower.process(buffer); @@ -569,8 +585,14 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept << " for sample " << region->sampleId); } #endif - egAmplitude.setReleaseTime(0.0f); - egAmplitude.startRelease(i); + if (!region->flexAmpEG) { + egAmplitude.setReleaseTime(0.0f); + egAmplitude.startRelease(i); + } + else { + // TODO(jpc): Flex AmpEG + flexEGs[*region->flexAmpEG]->release(i); + } fill(indices->subspan(i), sampleEnd); fill(coeffs->subspan(i), 1.0f); break; @@ -850,7 +872,12 @@ float sfz::Voice::getAveragePower() 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(); } uint32_t sfz::Voice::getSourcePosition() const noexcept From 2d3f82331d5cb2fb2e8884d2fb9988f17fb3c451 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 26 Sep 2020 17:10:02 +0200 Subject: [PATCH 3/8] Allow egN_ampeg and egN_amplitude both together --- src/sfizz/Synth.cpp | 15 ++++++--------- src/sfizz/Voice.cpp | 11 +++++++---- src/sfizz/Voice.h | 1 + src/sfizz/modulations/ModId.cpp | 2 ++ src/sfizz/modulations/ModId.h | 3 ++- src/sfizz/modulations/ModKey.cpp | 2 ++ 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 4e7a9f7d..53f5c944 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -178,17 +178,14 @@ void sfz::Synth::buildRegion(const std::vector& regionOpcodes) parseOpcodes(regionOpcodes); // Create the amplitude envelope - if (!lastRegion->flexAmpEG) { + if (!lastRegion->flexAmpEG) lastRegion->getOrCreateConnection( ModKey::createNXYZ(ModId::AmpEG, lastRegion->id), - ModKey::createNXYZ(ModId::Amplitude, lastRegion->id)).sourceDepth = 100.0f; - } - else { - ModKey source = ModKey::createNXYZ(ModId::Envelope, lastRegion->id, *lastRegion->flexAmpEG); - ModKey target = ModKey::createNXYZ(ModId::Amplitude, lastRegion->id); - if (!lastRegion->getConnection(source, target)) - lastRegion->getOrCreateConnection(source, target).sourceDepth = 100.0f; - } + 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) lastRegion->offsetAllKeys(octaveOffset * 12 + noteOffset); diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index ebed105b..ffb15f75 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -380,14 +380,16 @@ void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept ModMatrix& mm = resources.modMatrix; + // Amplitude EG + absl::Span ampegOut(mm.getModulation(masterAmplitudeTarget), numSamples); + ASSERT(ampegOut.data()); + copy(ampegOut, modulationSpan); + // Amplitude envelope applyGain1(baseGain, modulationSpan); if (float* mod = mm.getModulation(amplitudeTarget)) { for (size_t i = 0; i < numSamples; ++i) - modulationSpan[i] = normalizePercents(mod[i]); - } - else { - ASSERTFALSE; + modulationSpan[i] *= normalizePercents(mod[i]); } // Volume envelope @@ -1050,6 +1052,7 @@ void sfz::Voice::resetSmoothers() noexcept void sfz::Voice::saveModulationTargets(const Region* region) noexcept { ModMatrix& mm = resources.modMatrix; + masterAmplitudeTarget = mm.findTarget(ModKey::createNXYZ(ModId::MasterAmplitude, region->getId())); amplitudeTarget = mm.findTarget(ModKey::createNXYZ(ModId::Amplitude, region->getId())); volumeTarget = mm.findTarget(ModKey::createNXYZ(ModId::Volume, region->getId())); panTarget = mm.findTarget(ModKey::createNXYZ(ModId::Pan, region->getId())); diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 86b1c5ab..aa49297c 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -520,6 +520,7 @@ private: Smoother xfadeSmoother; void resetSmoothers() noexcept; + ModMatrix::TargetId masterAmplitudeTarget; ModMatrix::TargetId amplitudeTarget; ModMatrix::TargetId volumeTarget; ModMatrix::TargetId panTarget; diff --git a/src/sfizz/modulations/ModId.cpp b/src/sfizz/modulations/ModId.cpp index d21ba9e0..3d837ec5 100644 --- a/src/sfizz/modulations/ModId.cpp +++ b/src/sfizz/modulations/ModId.cpp @@ -38,6 +38,8 @@ int ModIds::flags(ModId id) noexcept return kModIsPerVoice; // targets + case ModId::MasterAmplitude: + return kModIsPerVoice|kModIsPercentMultiplicative; case ModId::Amplitude: return kModIsPerVoice|kModIsPercentMultiplicative; case ModId::Pan: diff --git a/src/sfizz/modulations/ModId.h b/src/sfizz/modulations/ModId.h index 3eb7022d..48d9b2d9 100644 --- a/src/sfizz/modulations/ModId.h +++ b/src/sfizz/modulations/ModId.h @@ -34,7 +34,8 @@ enum class ModId : int { //-------------------------------------------------------------------------- _TargetsStart = _SourcesEnd, - Amplitude = _TargetsStart, + MasterAmplitude = _TargetsStart, + Amplitude, Pan, Width, Position, diff --git a/src/sfizz/modulations/ModKey.cpp b/src/sfizz/modulations/ModKey.cpp index 2b9767d3..5fa48181 100644 --- a/src/sfizz/modulations/ModKey.cpp +++ b/src/sfizz/modulations/ModKey.cpp @@ -81,6 +81,8 @@ std::string ModKey::toString() const case ModId::FilEG: return absl::StrCat("FilterEG {", region_.number(), "}"); + case ModId::MasterAmplitude: + return absl::StrCat("MasterAmplitude {", region_.number(), "}"); case ModId::Amplitude: return absl::StrCat("Amplitude {", region_.number(), "}"); case ModId::Pan: From 1afc987794f7d0adb58494578300db373fdf39b0 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 26 Sep 2020 17:29:40 +0200 Subject: [PATCH 4/8] Update tests --- tests/TestHelpers.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/TestHelpers.cpp b/tests/TestHelpers.cpp index b3521c4a..6e1fee3f 100644 --- a/tests/TestHelpers.cpp +++ b/tests/TestHelpers.cpp @@ -72,6 +72,9 @@ unsigned numPlayingVoices(const sfz::Synth& synth) std::string createReferenceGraph(std::vector lines, int numRegions) { for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) { + lines.push_back(absl::StrCat( + R"("AmplitudeEG {)", regionIdx, R"(}" -> "MasterAmplitude {)", regionIdx, R"(}")" + )); lines.push_back(absl::StrCat( R"("Controller 7 {curve=4, smooth=10, value=100, step=0}" -> "Amplitude {)", regionIdx, From 9d0ee6969c457bb256dbb5e0d6274097e497459c Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 28 Sep 2020 21:49:20 +0200 Subject: [PATCH 5/8] flexEG: allow release to bypass the pre-sustain stages It makes the EG equivalent to ARIA in case of early release. --- src/sfizz/FlexEnvelope.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/sfizz/FlexEnvelope.cpp b/src/sfizz/FlexEnvelope.cpp index c9302c0b..3a7402dd 100644 --- a/src/sfizz/FlexEnvelope.cpp +++ b/src/sfizz/FlexEnvelope.cpp @@ -160,12 +160,24 @@ void FlexEnvelope::Impl::process(absl::Span out) } // Perform stage transitions - const bool isReleased = isReleased_; - while ((!stageSustained_ && currentTime_ >= stageTime_) || - (stageSustained_ && isReleased)) { - // If stage is of zero duration, immediate transition to level - if (!stageSustained_ && stageTime_ == 0) + if (isReleased_) { + // on release, fast forward past the sustain stage + const unsigned sustainStage = desc.sustain; + while (currentStageNumber_ <= sustainStage) { + if (!advanceToNextStage()) { + out.remove_prefix(frameIndex); + fill(out, 0.0f); + return; + } + } + } + while (!stageSustained_ && currentTime_ >= stageTime_) { + // advance through completed timed stages + ASSERT(isReleased_ || !stageSustained_); + if (stageTime_ == 0) { + // if stage is of zero duration, immediate transition to level currentLevel_ = stageTargetLevel_; + } if (!advanceToNextStage()) { out.remove_prefix(frameIndex); fill(out, 0.0f); From ab3715f7a91769c1db43dc7bd19fc17b3c288e2c Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 4 Oct 2020 23:00:21 +0200 Subject: [PATCH 6/8] Read 0 or 1 values as valid booleans --- src/sfizz/Opcode.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Opcode.cpp b/src/sfizz/Opcode.cpp index 1ffafdbf..673501d1 100644 --- a/src/sfizz/Opcode.cpp +++ b/src/sfizz/Opcode.cpp @@ -211,9 +211,11 @@ absl::optional readOpcode(absl::string_view value, const Range readBooleanFromOpcode(const Opcode& opcode) { switch (hash(opcode.value)) { - case hash("off"): + case hash("off"): // fallthrough + case hash("0"): return false; - case hash("on"): + case hash("on"): // fallthrough + case hash("1"): return true; default: return {}; From 5b4e673fe7437aa8ebe72264792ca8bb78023e3c Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 4 Oct 2020 23:00:52 +0200 Subject: [PATCH 7/8] Use the new boolean helper and test the ampeg connection --- src/sfizz/Region.cpp | 7 ++-- tests/FlexEGT.cpp | 6 +-- tests/ModulationsT.cpp | 87 ++++++++++++++++++++++++++++++++++++++---- tests/TestHelpers.cpp | 9 ++++- tests/TestHelpers.h | 11 +++++- 5 files changed, 102 insertions(+), 18 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index b48ed7f3..f7ff8798 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1169,11 +1169,10 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) return false; - if (auto value = readOpcode(opcode.value, Range { 0, 1 })) { + if (auto ampeg = readBooleanFromOpcode(opcode)) { FlexEGDescription& desc = flexEGs[egNumber - 1]; - bool ampeg = *value != 0; - if (desc.ampeg != ampeg) { - desc.ampeg = ampeg; + 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) diff --git a/tests/FlexEGT.cpp b/tests/FlexEGT.cpp index 229bee47..9d380127 100644 --- a/tests/FlexEGT.cpp +++ b/tests/FlexEGT.cpp @@ -41,7 +41,7 @@ TEST_CASE("[FlexEG] Values") REQUIRE( egDescription.points[4].time == .4_a ); REQUIRE( egDescription.points[4].level == 1.0_a ); REQUIRE( egDescription.sustain == 3 ); - REQUIRE(synth.getResources().modMatrix.toDotGraph() == createReferenceGraph({ + REQUIRE(synth.getResources().modMatrix.toDotGraph() == createDefaultGraph({ 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[2].time == .1_a ); REQUIRE( egDescription.points[2].level == .25_a ); - REQUIRE( synth.getResources().modMatrix.toDotGraph() == createReferenceGraph({}) ); + REQUIRE( synth.getResources().modMatrix.toDotGraph() == createDefaultGraph({}) ); } TEST_CASE("[FlexEG] Connections") @@ -85,7 +85,7 @@ TEST_CASE("[FlexEG] Connections") REQUIRE(synth.getNumRegions() == 6); REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 ); 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 {1}" -> "Pan {1}")", R"("EG 1 {2}" -> "Width {2}")", diff --git a/tests/ModulationsT.cpp b/tests/ModulationsT.cpp index 05136501..634a92a2 100644 --- a/tests/ModulationsT.cpp +++ b/tests/ModulationsT.cpp @@ -92,7 +92,7 @@ width_oncc425=29 )"); 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 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}")", @@ -111,7 +111,7 @@ TEST_CASE("[Modulations] Filter CC connections") )"); 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 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}")", @@ -129,7 +129,7 @@ TEST_CASE("[Modulations] EQ CC connections") )"); 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 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}")", @@ -150,7 +150,7 @@ TEST_CASE("[Modulations] LFO Filter connections") )"); const std::string graph = synth.getResources().modMatrix.toDotGraph(); - REQUIRE(graph == createReferenceGraph({ + REQUIRE(graph == createDefaultGraph({ R"("LFO 1 {0}" -> "FilterCutoff {0, N=1}")", R"("LFO 2 {0}" -> "FilterCutoff {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(); - REQUIRE(graph == createReferenceGraph({ + REQUIRE(graph == createDefaultGraph({ R"("EG 1 {0}" -> "FilterCutoff {0, N=1}")", R"("EG 2 {0}" -> "FilterCutoff {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(); - REQUIRE(graph == createReferenceGraph({ + REQUIRE(graph == createDefaultGraph({ R"("LFO 1 {0}" -> "EqBandwidth {0, N=1}")", R"("LFO 2 {0}" -> "EqFrequency {0, N=2}")", 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(); - REQUIRE(graph == createReferenceGraph({ + REQUIRE(graph == createDefaultGraph({ R"("EG 1 {0}" -> "EqBandwidth {0, N=1}")", R"("EG 2 {0}" -> "EqFrequency {0, N=2}")", 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}")", })); } + + +TEST_CASE("[Modulations] FlexEG Ampeg target") +{ + sfz::Synth synth; + + synth.loadSfzString(fs::current_path(), R"( + 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"( + 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"( + 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}")", + })); +} + diff --git a/tests/TestHelpers.cpp b/tests/TestHelpers.cpp index 6e1fee3f..300e1cef 100644 --- a/tests/TestHelpers.cpp +++ b/tests/TestHelpers.cpp @@ -69,7 +69,7 @@ unsigned numPlayingVoices(const sfz::Synth& synth) }); } -std::string createReferenceGraph(std::vector lines, int numRegions) +std::string createDefaultGraph(std::vector lines, int numRegions) { for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) { lines.push_back(absl::StrCat( @@ -87,6 +87,11 @@ std::string createReferenceGraph(std::vector lines, int numRegions) )); } + return createModulationDotGraph(lines); +}; + +std::string createModulationDotGraph(std::vector lines) +{ std::sort(lines.begin(), lines.end()); std::string graph; @@ -101,4 +106,4 @@ std::string createReferenceGraph(std::vector lines, int numRegions) graph += "}\n"; return graph; -}; +} diff --git a/tests/TestHelpers.h b/tests/TestHelpers.h index 272fb612..efc5d6d3 100644 --- a/tests/TestHelpers.h +++ b/tests/TestHelpers.h @@ -67,10 +67,17 @@ const std::vector getPlayingVoices(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 lines, int numRegions = 1); +std::string createDefaultGraph(std::vector lines, int numRegions = 1); + +/** + * @brief Create a dot graph with the specified lines. + * The lines are sorted. + * + */ +std::string createModulationDotGraph(std::vector lines); template inline bool approxEqual(absl::Span lhs, absl::Span rhs, Type eps = 1e-3) From 86d0cf3a49eb9a997c36989bf1d1202d77b374cb Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 5 Oct 2020 00:00:15 +0200 Subject: [PATCH 8/8] Update the boolean reader --- src/sfizz/Opcode.cpp | 18 ++++++++++-------- src/sfizz/Range.h | 12 ++++++++++++ tests/OpcodeT.cpp | 11 +++++++++++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/sfizz/Opcode.cpp b/src/sfizz/Opcode.cpp index 673501d1..ff6cbc6d 100644 --- a/src/sfizz/Opcode.cpp +++ b/src/sfizz/Opcode.cpp @@ -210,16 +210,18 @@ absl::optional readOpcode(absl::string_view value, const Range readBooleanFromOpcode(const Opcode& opcode) { - switch (hash(opcode.value)) { - case hash("off"): // fallthrough - case hash("0"): + // Cakewalk-style booleans, case-insensitive + if (absl::EqualsIgnoreCase(opcode.value, "off")) return false; - case hash("on"): // fallthrough - case hash("1"): + if (absl::EqualsIgnoreCase(opcode.value, "on")) return true; - default: - return {}; - } + + // ARIA-style booleans? (seen in egN_dynamic=1 for example) + // TODO check this + if (auto value = readOpcode(opcode.value, Range::wholeRange())) + return *value != 0; + + return absl::nullopt; } template diff --git a/src/sfizz/Range.h b/src/sfizz/Range.h index 3b548535..7bbe369f 100644 --- a/src/sfizz/Range.h +++ b/src/sfizz/Range.h @@ -8,6 +8,7 @@ #include "MathHelpers.h" #include #include +#include namespace sfz { @@ -116,6 +117,17 @@ public: }; } + /** + * @brief Construct a range which covers the whole numeric domain + */ + static constexpr Range wholeRange() noexcept + { + return Range { + std::numeric_limits::min(), + std::numeric_limits::max(), + }; + } + private: Type _start { static_cast(0.0) }; Type _end { static_cast(0.0) }; diff --git a/tests/OpcodeT.cpp b/tests/OpcodeT.cpp index 771db749..a2ade6a7 100644 --- a/tests/OpcodeT.cpp +++ b/tests/OpcodeT.cpp @@ -280,3 +280,14 @@ TEST_CASE("[Opcode] readOpcode") REQUIRE( !sfz::readOpcode("garbage50.25", sfz::Range(-20, 100)) ); REQUIRE( !sfz::readOpcode("garbage", sfz::Range(-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); +}