diff --git a/benchmarks/BM_ADSR.cpp b/benchmarks/BM_ADSR.cpp index 0977c149..762c4804 100644 --- a/benchmarks/BM_ADSR.cpp +++ b/benchmarks/BM_ADSR.cpp @@ -35,7 +35,7 @@ public: } sfz::MidiState midiState; - sfz::Region region{0, midiState}; + sfz::Region region{0}; sfz::ADSREnvelope envelope; std::vector output; }; diff --git a/common.mk b/common.mk index abe2b26c..21ca24b3 100644 --- a/common.mk +++ b/common.mk @@ -89,6 +89,7 @@ SFIZZ_SOURCES = \ src/sfizz/FlexEGDescription.cpp \ src/sfizz/FlexEnvelope.cpp \ src/sfizz/Interpolators.cpp \ + src/sfizz/Layer.cpp \ src/sfizz/Logger.cpp \ src/sfizz/LFO.cpp \ src/sfizz/LFODescription.cpp \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 64d616aa..1538b4cc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -75,6 +75,7 @@ set(SFIZZ_HEADERS sfizz/HistoricalBuffer.h sfizz/Interpolators.h sfizz/Interpolators.hpp + sfizz/Layer.h sfizz/Logger.h sfizz/LFO.h sfizz/LFOCommon.h @@ -157,6 +158,7 @@ set(SFIZZ_SOURCES sfizz/SynthMessaging.cpp sfizz/WindowedSinc.cpp sfizz/Interpolators.cpp + sfizz/Layer.cpp sfizz/modulations/ModId.cpp sfizz/modulations/ModKey.cpp sfizz/modulations/ModKeyHash.cpp diff --git a/src/sfizz/Layer.cpp b/src/sfizz/Layer.cpp new file mode 100644 index 00000000..3f3b823f --- /dev/null +++ b/src/sfizz/Layer.cpp @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "Layer.h" +#include "Region.h" +#include "utility/Debug.h" +#include "utility/SwapAndPop.h" +#include + +namespace sfz { + +Layer::Layer(std::unique_ptr region, const MidiState& midiState) + : Layer(region.get(), midiState) +{ + regionOwned_ = true; + region.release(); +} + +Layer::Layer(Region* region, const MidiState& midiState) + : region_(region), regionOwned_(false), midiState_(midiState) +{ + initializeActivations(); +} + +Layer::~Layer() +{ + if (regionOwned_) + delete region_; +} + +void Layer::initializeActivations() +{ + const Region& region = *region_; + + keySwitched_ = !region.usesKeySwitches; + previousKeySwitched_ = !region.usesPreviousKeySwitches; + sequenceSwitched_ = !region.usesSequenceSwitches; + pitchSwitched_ = true; + bpmSwitched_ = true; + aftertouchSwitched_ = true; + ccSwitched_.set(); +} + +bool Layer::isSwitchedOn() const noexcept +{ + return keySwitched_ && previousKeySwitched_ && sequenceSwitched_ && pitchSwitched_ && bpmSwitched_ && aftertouchSwitched_ && ccSwitched_.all(); +} + +bool Layer::registerNoteOn(int noteNumber, float velocity, float randValue) noexcept +{ + ASSERT(velocity >= 0.0f && velocity <= 1.0f); + + const Region& region = *region_; + + const bool keyOk = region.keyRange.containsWithEnd(noteNumber); + if (keyOk) { + // Sequence activation + sequenceSwitched_ = + ((sequenceCounter_++ % region.sequenceLength) == region.sequencePosition - 1); + } + + if (!isSwitchedOn()) + return false; + + if (!region.triggerOnNote) + return false; + + if (region.velocityOverride == VelocityOverride::previous) + velocity = midiState_.getLastVelocity(); + + const bool velOk = region.velocityRange.containsWithEnd(velocity); + const bool randOk = region.randRange.contains(randValue) || (randValue >= 1.0f && region.randRange.isValid() && region.randRange.getEnd() >= 1.0f); + const bool firstLegatoNote = (region.trigger == Trigger::first && midiState_.getActiveNotes() == 1); + const bool attackTrigger = (region.trigger == Trigger::attack); + const bool notFirstLegatoNote = (region.trigger == Trigger::legato && midiState_.getActiveNotes() > 1); + + return keyOk && velOk && randOk && (attackTrigger || firstLegatoNote || notFirstLegatoNote); +} + +bool Layer::registerNoteOff(int noteNumber, float velocity, float randValue) noexcept +{ + ASSERT(velocity >= 0.0f && velocity <= 1.0f); + + const Region& region = *region_; + + if (!isSwitchedOn()) + return false; + + if (!region.triggerOnNote) + return false; + + // Prerequisites + + const bool keyOk = region.keyRange.containsWithEnd(noteNumber); + const bool velOk = region.velocityRange.containsWithEnd(velocity); + const bool randOk = region.randRange.contains(randValue) || (randValue >= 1.0f && region.randRange.isValid() && region.randRange.getEnd() >= 1.0f); + + if (!(velOk && keyOk && randOk)) + return false; + + // Release logic + + if (region.trigger == Trigger::release_key) + return true; + + if (region.trigger == Trigger::release) { + const bool sostenutoed = isNoteSostenutoed(noteNumber); + + if (sostenutoed && !sostenutoPressed_) { + removeFromSostenutoReleases(noteNumber); + if (sustainPressed_) + delaySustainRelease(noteNumber, midiState_.getNoteVelocity(noteNumber)); + } + + if (!sostenutoPressed_ || !sostenutoed) { + if (sustainPressed_) + delaySustainRelease(noteNumber, midiState_.getNoteVelocity(noteNumber)); + else + return true; + } + } + + return false; +} + +bool Layer::registerCC(int ccNumber, float ccValue) noexcept +{ + ASSERT(ccValue >= 0.0f && ccValue <= 1.0f); + + const Region& region = *region_; + + if (ccNumber == region.sustainCC) + sustainPressed_ = region.checkSustain && ccValue >= region.sustainThreshold; + + if (ccNumber == region.sostenutoCC) { + const bool newState = region.checkSostenuto && ccValue >= region.sostenutoThreshold; + if (!sostenutoPressed_ && newState) + storeSostenutoNotes(); + + if (!newState && sostenutoPressed_) + delayedSostenutoReleases_.clear(); + + sostenutoPressed_ = newState; + } + + if (region.ccConditions.getWithDefault(ccNumber).containsWithEnd(ccValue)) + ccSwitched_.set(ccNumber, true); + else + ccSwitched_.set(ccNumber, false); + + if (!isSwitchedOn()) + return false; + + if (!region.triggerOnCC) + return false; + + if (auto triggerRange = region.ccTriggers.get(ccNumber)) { + if (triggerRange->containsWithEnd(ccValue)) + return true; + } + + return false; +} + +void Layer::registerPitchWheel(float pitch) noexcept +{ + const Region& region = *region_; + if (region.bendRange.containsWithEnd(pitch)) + pitchSwitched_ = true; + else + pitchSwitched_ = false; +} + +void Layer::registerAftertouch(float aftertouch) noexcept +{ + const Region& region = *region_; + if (region.aftertouchRange.containsWithEnd(aftertouch)) + aftertouchSwitched_ = true; + else + aftertouchSwitched_ = false; +} + +void Layer::registerTempo(float secondsPerQuarter) noexcept +{ + const Region& region = *region_; + const float bpm = 60.0f / secondsPerQuarter; + if (region.bpmRange.containsWithEnd(bpm)) + bpmSwitched_ = true; + else + bpmSwitched_ = false; +} + +void Layer::delaySustainRelease(int noteNumber, float velocity) noexcept +{ + if (delayedSustainReleases_.size() == delayedSustainReleases_.capacity()) + return; + + delayedSustainReleases_.emplace_back(noteNumber, velocity); +} + +void Layer::delaySostenutoRelease(int noteNumber, float velocity) noexcept +{ + if (delayedSostenutoReleases_.size() == delayedSostenutoReleases_.capacity()) + return; + + delayedSostenutoReleases_.emplace_back(noteNumber, velocity); +} + +void Layer::removeFromSostenutoReleases(int noteNumber) noexcept +{ + swapAndPopFirst(delayedSostenutoReleases_, [=](const std::pair& p) { + return p.first == noteNumber; + }); +} + +void Layer::storeSostenutoNotes() noexcept +{ + ASSERT(delayedSostenutoReleases_.empty()); + const Region& region = *region_; + for (int note = region.keyRange.getStart(); note <= region.keyRange.getEnd(); ++note) { + if (midiState_.isNotePressed(note)) + delaySostenutoRelease(note, midiState_.getNoteVelocity(note)); + } +} + + +bool Layer::isNoteSustained(int noteNumber) const noexcept +{ + return absl::c_find_if(delayedSustainReleases_, [=](const std::pair& p) { + return p.first == noteNumber; + }) != delayedSustainReleases_.end(); +} + +bool Layer::isNoteSostenutoed(int noteNumber) const noexcept +{ + return absl::c_find_if(delayedSostenutoReleases_, [=](const std::pair& p) { + return p.first == noteNumber; + }) != delayedSostenutoReleases_.end(); +} + +} // namespace sfz diff --git a/src/sfizz/Layer.h b/src/sfizz/Layer.h new file mode 100644 index 00000000..619d7d5a --- /dev/null +++ b/src/sfizz/Layer.h @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "Config.h" +#include "utility/NumericId.h" +#include "utility/LeakDetector.h" +#include +#include +#include +#include +#include + +namespace sfz { +struct Region; +class MidiState; + +struct Layer { +public: + Layer(std::unique_ptr region, const MidiState& midiState); + Layer(Region* region, const MidiState& midiState); + ~Layer(); + + Layer(const Layer&) = delete; + Layer(Layer&&) = delete; + Layer& operator=(const Layer&) = delete; + Layer& operator=(Layer&&) = delete; + + /** + * @brief Get the region that this layer operates on. + */ + const Region& getRegion() const noexcept { return *region_; } + /** + * @brief Get the region that this layer operates on. + */ + Region& getRegion() noexcept { return *region_; } + + /** + * @brief Reset the activations to their initial states. + */ + void initializeActivations(); + + /** + * @brief Given the current midi state, is the region switched on? + * + * @return true + * @return false + */ + bool isSwitchedOn() const noexcept; + /** + * @brief Register a new note on event. The region may be switched on or off using keys so + * this function updates the keyswitches state. + * + * @param noteNumber + * @param velocity + * @param randValue a random value between 0 and 1 used to randomize a bit the region activations + * and vary the samples + * @return true if the region should trigger on this event. + * @return false + */ + bool registerNoteOn(int noteNumber, float velocity, float randValue) noexcept; + /** + * @brief Register a new note off event. The region may be switched on or off using keys so + * this function updates the keyswitches state. + * + * @param noteNumber + * @param velocity + * @param randValue a random value between 0 and 1 used to randomize a bit the region activations + * and vary the samples + * @return true if the region should trigger on this event. + * @return false + */ + bool registerNoteOff(int noteNumber, float velocity, float randValue) noexcept; + /** + * @brief Register a new CC event. The region may be switched on or off using CCs so + * this function checks if it indeeds need to activate or not. + * + * @param ccNumber + * @param ccValue + * @return true if the region should trigger on this event + * @return false + */ + bool registerCC(int ccNumber, float ccValue) noexcept; + /** + * @brief Register a new pitch wheel event. + * + * @param pitch + */ + void registerPitchWheel(float pitch) noexcept; + /** + * @brief Register a new aftertouch event. + * + * @param aftertouch + */ + void registerAftertouch(float aftertouch) noexcept; + /** + * @brief Register tempo + * + * @param secondsPerQuarter + */ + void registerTempo(float secondsPerQuarter) noexcept; + + Region* const region_ {}; + bool regionOwned_ = false; + + // Started notes + bool sustainPressed_ { false }; + bool sostenutoPressed_ { false }; + std::vector> delayedSustainReleases_; + std::vector> delayedSostenutoReleases_; + void delaySustainRelease(int noteNumber, float velocity) noexcept; + void delaySostenutoRelease(int noteNumber, float velocity) noexcept; + void storeSostenutoNotes() noexcept; + void removeFromSostenutoReleases(int noteNumber) noexcept; + bool isNoteSustained(int noteNumber) const noexcept; + bool isNoteSostenutoed(int noteNumber) const noexcept; + + const MidiState& midiState_; + bool keySwitched_ {}; + bool previousKeySwitched_ {}; + bool sequenceSwitched_ {}; + bool pitchSwitched_ {}; + bool bpmSwitched_ {}; + bool aftertouchSwitched_ {}; + std::bitset ccSwitched_; + + int sequenceCounter_ { 0 }; + + LEAK_DETECTOR(Layer); +}; + +} // namespace sfz diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 90f4b046..cfc84355 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -6,6 +6,7 @@ #include "Region.h" #include "Opcode.h" +#include "MidiState.h" #include "MathHelpers.h" #include "utility/SwapAndPop.h" #include "utility/StringViewHelpers.h" @@ -35,11 +36,9 @@ bool extendIfNecessary(std::vector& vec, unsigned size, unsigned defaultCapac return true; } -sfz::Region::Region(int regionNumber, const MidiState& midiState, absl::string_view defaultPath) -: id{regionNumber}, midiState(midiState), defaultPath(defaultPath) +sfz::Region::Region(int regionNumber, absl::string_view defaultPath) +: id{regionNumber}, defaultPath(defaultPath) { - ccSwitched.set(); - gainToEffect.reserve(5); // sufficient room for main and fx1-4 gainToEffect.push_back(1.0); // contribute 100% into the main bus @@ -260,7 +259,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("sw_last"): if (!lastKeyswitchRange) { lastKeyswitch = opcode.readOptional(Default::key); - keySwitched = !lastKeyswitch.has_value(); + usesKeySwitches = lastKeyswitch.has_value(); } break; case hash("sw_lolast"): @@ -271,7 +270,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) else lastKeyswitchRange->setStart(value); - keySwitched = false; + usesKeySwitches = true; lastKeyswitch = absl::nullopt; } break; @@ -283,7 +282,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) else lastKeyswitchRange->setEnd(value); - keySwitched = false; + usesKeySwitches = true; lastKeyswitch = absl::nullopt; } break; @@ -292,14 +291,14 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) break; case hash("sw_down"): downKeyswitch = opcode.readOptional(Default::key); - keySwitched = !downKeyswitch.has_value(); + usesKeySwitches = downKeyswitch.has_value(); break; case hash("sw_up"): upKeyswitch = opcode.readOptional(Default::key); break; case hash("sw_previous"): previousKeyswitch = opcode.readOptional(Default::key); - previousKeySwitched = !previousKeyswitch.has_value(); + usesPreviousKeySwitches = previousKeyswitch.has_value(); break; case hash("sw_vel"): velocityOverride = @@ -348,7 +347,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) break; case hash("seq_position"): sequencePosition = opcode.read(Default::sequence); - sequenceSwitched = false; + usesSequenceSwitches = true; break; // Region logic: triggers case hash("trigger"): @@ -1499,191 +1498,6 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec spec, return true; } -bool sfz::Region::isSwitchedOn() const noexcept -{ - return keySwitched && previousKeySwitched && sequenceSwitched && pitchSwitched && bpmSwitched && aftertouchSwitched && ccSwitched.all(); -} - -void sfz::Region::delaySustainRelease(int noteNumber, float velocity) noexcept -{ - if (delayedSustainReleases.size() == delayedSustainReleases.capacity()) - return; - - delayedSustainReleases.emplace_back(noteNumber, velocity); -} - -void sfz::Region::delaySostenutoRelease(int noteNumber, float velocity) noexcept -{ - if (delayedSostenutoReleases.size() == delayedSostenutoReleases.capacity()) - return; - - delayedSostenutoReleases.emplace_back(noteNumber, velocity); -} - -void sfz::Region::removeFromSostenutoReleases(int noteNumber) noexcept -{ - swapAndPopFirst(delayedSostenutoReleases, [=](const std::pair& p) { - return p.first == noteNumber; - }); -} - -void sfz::Region::storeSostenutoNotes() noexcept -{ - ASSERT(delayedSostenutoReleases.empty()); - for (int note = keyRange.getStart(); note <= keyRange.getEnd(); ++note) { - if (midiState.isNotePressed(note)) - delaySostenutoRelease(note, midiState.getNoteVelocity(note)); - } -} - - -bool sfz::Region::isNoteSustained(int noteNumber) const noexcept -{ - return absl::c_find_if(delayedSustainReleases, [=](const std::pair& p) { - return p.first == noteNumber; - }) != delayedSustainReleases.end(); -} - -bool sfz::Region::isNoteSostenutoed(int noteNumber) const noexcept -{ - return absl::c_find_if(delayedSostenutoReleases, [=](const std::pair& p) { - return p.first == noteNumber; - }) != delayedSostenutoReleases.end(); -} - -bool sfz::Region::registerNoteOn(int noteNumber, float velocity, float randValue) noexcept -{ - ASSERT(velocity >= 0.0f && velocity <= 1.0f); - - const bool keyOk = keyRange.containsWithEnd(noteNumber); - if (keyOk) { - // Sequence activation - sequenceSwitched = - ((sequenceCounter++ % sequenceLength) == sequencePosition - 1); - } - - if (!isSwitchedOn()) - return false; - - if (!triggerOnNote) - return false; - - if (velocityOverride == VelocityOverride::previous) - velocity = midiState.getLastVelocity(); - - const bool velOk = velocityRange.containsWithEnd(velocity); - const bool randOk = randRange.contains(randValue) || (randValue >= 1.0f && randRange.isValid() && randRange.getEnd() >= 1.0f); - const bool firstLegatoNote = (trigger == Trigger::first && midiState.getActiveNotes() == 1); - const bool attackTrigger = (trigger == Trigger::attack); - const bool notFirstLegatoNote = (trigger == Trigger::legato && midiState.getActiveNotes() > 1); - - return keyOk && velOk && randOk && (attackTrigger || firstLegatoNote || notFirstLegatoNote); -} - -bool sfz::Region::registerNoteOff(int noteNumber, float velocity, float randValue) noexcept -{ - ASSERT(velocity >= 0.0f && velocity <= 1.0f); - - if (!isSwitchedOn()) - return false; - - if (!triggerOnNote) - return false; - - // Prerequisites - - const bool keyOk = keyRange.containsWithEnd(noteNumber); - const bool velOk = velocityRange.containsWithEnd(velocity); - const bool randOk = randRange.contains(randValue) || (randValue >= 1.0f && randRange.isValid() && randRange.getEnd() >= 1.0f); - - if (!(velOk && keyOk && randOk)) - return false; - - // Release logic - - if (trigger == Trigger::release_key) - return true; - - if (trigger == Trigger::release) { - const bool sostenutoed = isNoteSostenutoed(noteNumber); - - if (sostenutoed && !sostenutoPressed) { - removeFromSostenutoReleases(noteNumber); - if (sustainPressed) - delaySustainRelease(noteNumber, midiState.getNoteVelocity(noteNumber)); - } - - if (!sostenutoPressed || !sostenutoed) { - if (sustainPressed) - delaySustainRelease(noteNumber, midiState.getNoteVelocity(noteNumber)); - else - return true; - } - } - - return false; -} - -bool sfz::Region::registerCC(int ccNumber, float ccValue) noexcept -{ - ASSERT(ccValue >= 0.0f && ccValue <= 1.0f); - - if (ccNumber == sustainCC) - sustainPressed = checkSustain && ccValue >= sustainThreshold; - - if (ccNumber == sostenutoCC) { - const bool newState = checkSostenuto && ccValue >= sostenutoThreshold; - if (!sostenutoPressed && newState) - storeSostenutoNotes(); - - if (!newState && sostenutoPressed) - delayedSostenutoReleases.clear(); - - sostenutoPressed = newState; - } - - if (ccConditions.getWithDefault(ccNumber).containsWithEnd(ccValue)) - ccSwitched.set(ccNumber, true); - else - ccSwitched.set(ccNumber, false); - - if (!isSwitchedOn()) - return false; - - if (!triggerOnCC) - return false; - - if (ccTriggers.contains(ccNumber) && ccTriggers[ccNumber].containsWithEnd(ccValue)) - return true; - - return false; -} - -void sfz::Region::registerPitchWheel(float pitch) noexcept -{ - if (bendRange.containsWithEnd(pitch)) - pitchSwitched = true; - else - pitchSwitched = false; -} - -void sfz::Region::registerAftertouch(float aftertouch) noexcept -{ - if (aftertouchRange.containsWithEnd(aftertouch)) - aftertouchSwitched = true; - else - aftertouchSwitched = false; -} - -void sfz::Region::registerTempo(float secondsPerQuarter) noexcept -{ - const float bpm = 60.0f / secondsPerQuarter; - if (bpmRange.containsWithEnd(bpm)) - bpmSwitched = true; - else - bpmSwitched = false; -} - float sfz::Region::getBasePitchVariation(float noteNumber, float velocity) const noexcept { ASSERT(velocity >= 0.0f && velocity <= 1.0f); @@ -1697,7 +1511,7 @@ float sfz::Region::getBasePitchVariation(float noteNumber, float velocity) const return centsFactor(pitchVariationInCents); } -float sfz::Region::getBaseVolumedB(int noteNumber) const noexcept +float sfz::Region::getBaseVolumedB(const MidiState& midiState, int noteNumber) const noexcept { fast_real_distribution volumeDistribution { 0.0f, ampRandom }; auto baseVolumedB = volume + volumeDistribution(Random::randomGenerator); @@ -1732,7 +1546,7 @@ float sfz::Region::getPhase() const noexcept return phase; } -uint64_t sfz::Region::getOffset(Oversampling factor) const noexcept +uint64_t sfz::Region::getOffset(const MidiState& midiState, Oversampling factor) const noexcept { std::uniform_int_distribution offsetDistribution { 0, offsetRandom }; uint64_t finalOffset = offset + offsetDistribution(Random::randomGenerator); @@ -1741,7 +1555,7 @@ uint64_t sfz::Region::getOffset(Oversampling factor) const noexcept return Default::offset.bounds.clamp(finalOffset) * static_cast(factor); } -float sfz::Region::getDelay() const noexcept +float sfz::Region::getDelay(const MidiState& midiState) const noexcept { fast_real_distribution delayDistribution { 0, delayRandom }; float finalDelay { delay }; @@ -1792,7 +1606,7 @@ float sfz::Region::getNoteGain(int noteNumber, float velocity) const noexcept return baseGain; } -float sfz::Region::getCrossfadeGain() const noexcept +float sfz::Region::getCrossfadeGain(const MidiState& midiState) const noexcept { float gain { 1.0f }; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 4b3f106d..8c77d145 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -15,7 +15,6 @@ #include "LFODescription.h" #include "Opcode.h" #include "AudioBuffer.h" -#include "MidiState.h" #include "FileId.h" #include "utility/NumericId.h" #include "utility/LeakDetector.h" @@ -29,6 +28,7 @@ namespace sfz { class RegionSet; +class MidiState; /** * @brief Regions are the basic building blocks for the SFZ parsing and handling code. @@ -43,7 +43,7 @@ class RegionSet; * */ struct Region { - Region(int regionNumber, const MidiState& midiState, absl::string_view defaultPath = ""); + explicit Region(int regionNumber, absl::string_view defaultPath = ""); Region(const Region&) = default; ~Region() = default; @@ -98,65 +98,6 @@ struct Region { * @return false */ bool shouldLoop() const noexcept { return (loopMode == LoopMode::loop_continuous || loopMode == LoopMode::loop_sustain); } - /** - * @brief Given the current midi state, is the region switched on? - * - * @return true - * @return false - */ - bool isSwitchedOn() const noexcept; - /** - * @brief Register a new note on event. The region may be switched on or off using keys so - * this function updates the keyswitches state. - * - * @param noteNumber - * @param velocity - * @param randValue a random value between 0 and 1 used to randomize a bit the region activations - * and vary the samples - * @return true if the region should trigger on this event. - * @return false - */ - bool registerNoteOn(int noteNumber, float velocity, float randValue) noexcept; - /** - * @brief Register a new note off event. The region may be switched on or off using keys so - * this function updates the keyswitches state. - * - * @param noteNumber - * @param velocity - * @param randValue a random value between 0 and 1 used to randomize a bit the region activations - * and vary the samples - * @return true if the region should trigger on this event. - * @return false - */ - bool registerNoteOff(int noteNumber, float velocity, float randValue) noexcept; - /** - * @brief Register a new CC event. The region may be switched on or off using CCs so - * this function checks if it indeeds need to activate or not. - * - * @param ccNumber - * @param ccValue - * @return true if the region should trigger on this event - * @return false - */ - bool registerCC(int ccNumber, float ccValue) noexcept; - /** - * @brief Register a new pitch wheel event. - * - * @param pitch - */ - void registerPitchWheel(float pitch) noexcept; - /** - * @brief Register a new aftertouch event. - * - * @param aftertouch - */ - void registerAftertouch(float aftertouch) noexcept; - /** - * @brief Register tempo - * - * @param secondsPerQuarter - */ - void registerTempo(float secondsPerQuarter) noexcept; /** * @brief Get the base pitch of the region depending on which note has been @@ -180,18 +121,19 @@ struct Region { * @brief Get the additional crossfade gain of the region depending on the * CC values * - * @param ccState + * @param midiState * @return float */ - float getCrossfadeGain() const noexcept; + float getCrossfadeGain(const MidiState& midiState) const noexcept; /** * @brief Get the base volume of the region depending on which note has been * pressed to trigger the region. * + * @param midiState * @param noteNumber * @return float */ - float getBaseVolumedB(int noteNumber) const noexcept; + float getBaseVolumedB(const MidiState& midiState, int noteNumber) const noexcept; /** * @brief Get the base gain of the region. * @@ -221,15 +163,17 @@ struct Region { /** * @brief Get the region offset in samples * + * @param midiState * @return uint32_t */ - uint64_t getOffset(Oversampling factor = Oversampling::x1) const noexcept; + uint64_t getOffset(const MidiState& midiState, Oversampling factor = Oversampling::x1) const noexcept; /** * @brief Get the region delay in seconds * + * @param midiState * @return float */ - float getDelay() const noexcept; + float getDelay(const MidiState& midiState) const noexcept; /** * @brief Get the index of the sample end, either natural end or forced * loop. @@ -410,6 +354,9 @@ struct Region { float sustainThreshold { Default::sustainThreshold }; // sustain_cc float sostenutoThreshold { Default::sostenutoThreshold }; // sustain_cc + bool usesKeySwitches { false }; + bool usesPreviousKeySwitches { false }; + // Region logic: internal conditions UncheckedRange aftertouchRange { Default::loChannelAftertouch, Default::hiChannelAftertouch }; // hichanaft and lochanaft UncheckedRange bpmRange { Default::loBPM, Default::hiBPM }; // hibpm and lobpm @@ -417,6 +364,8 @@ struct Region { uint8_t sequenceLength { Default::sequence }; // seq_length uint8_t sequencePosition { Default::sequence }; // seq_position + bool usesSequenceSwitches { false }; + // Region logic: triggers Trigger trigger { Default::trigger }; // trigger CCMap> ccTriggers {{ Default::loCC, Default::hiCC }}; // on_loccN on_hiccN @@ -507,29 +456,8 @@ struct Region { // Parent RegionSet* parent { nullptr }; - // Started notes - bool sustainPressed { false }; - bool sostenutoPressed { false }; - std::vector> delayedSustainReleases; - std::vector> delayedSostenutoReleases; - void delaySustainRelease(int noteNumber, float velocity) noexcept; - void delaySostenutoRelease(int noteNumber, float velocity) noexcept; - void storeSostenutoNotes() noexcept; - void removeFromSostenutoReleases(int noteNumber) noexcept; - bool isNoteSustained(int noteNumber) const noexcept; - bool isNoteSostenutoed(int noteNumber) const noexcept; + std::string defaultPath; - const MidiState& midiState; - bool keySwitched { true }; - bool previousKeySwitched { true }; - bool sequenceSwitched { true }; - bool pitchSwitched { true }; - bool bpmSwitched { true }; - bool aftertouchSwitched { true }; - std::bitset ccSwitched; - std::string defaultPath { "" }; - - int sequenceCounter { 0 }; LEAK_DETECTOR(Region); }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 0bcdfaf9..ca3b0245 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -136,8 +136,10 @@ void Synth::Impl::onParseWarning(const SourceRange& range, const std::string& me void Synth::Impl::buildRegion(const std::vector& regionOpcodes) { - int regionNumber = static_cast(regions_.size()); - auto lastRegion = absl::make_unique(regionNumber, resources_.midiState, defaultPath_); + int regionNumber = static_cast(layers_.size()); + Region* lastRegion = new Region(regionNumber, defaultPath_); + Layer* lastLayer = new Layer(std::unique_ptr(lastRegion), resources_.midiState); + layers_.emplace_back(lastLayer); // auto parseOpcodes = [&](const std::vector& opcodes) { @@ -171,22 +173,22 @@ void Synth::Impl::buildRegion(const std::vector& regionOpcodes) lastRegion->offsetAllKeys(octaveOffset_ * 12 + noteOffset_); if (lastRegion->lastKeyswitch) - lastKeyswitchLists_[*lastRegion->lastKeyswitch].push_back(lastRegion.get()); + lastKeyswitchLists_[*lastRegion->lastKeyswitch].push_back(lastLayer); if (lastRegion->lastKeyswitchRange) { auto& range = *lastRegion->lastKeyswitchRange; for (uint8_t note = range.getStart(), end = range.getEnd(); note <= end; note++) - lastKeyswitchLists_[note].push_back(lastRegion.get()); + lastKeyswitchLists_[note].push_back(lastLayer); } if (lastRegion->upKeyswitch) - upKeyswitchLists_[*lastRegion->upKeyswitch].push_back(lastRegion.get()); + upKeyswitchLists_[*lastRegion->upKeyswitch].push_back(lastLayer); if (lastRegion->downKeyswitch) - downKeyswitchLists_[*lastRegion->downKeyswitch].push_back(lastRegion.get()); + downKeyswitchLists_[*lastRegion->downKeyswitch].push_back(lastLayer); if (lastRegion->previousKeyswitch) - previousKeyswitchLists_.push_back(lastRegion.get()); + previousKeyswitchLists_.push_back(lastLayer); if (lastRegion->defaultSwitch) currentSwitch_ = *lastRegion->defaultSwitch; @@ -201,18 +203,19 @@ void Synth::Impl::buildRegion(const std::vector& regionOpcodes) if (currentSet_ != nullptr) { lastRegion->parent = currentSet_; - currentSet_->addRegion(lastRegion.get()); + currentSet_->addRegion(lastRegion); } // Adapt the size of the delayed releases to avoid allocating later on if (lastRegion->trigger == Trigger::release) { const auto keyLength = static_cast(lastRegion->keyRange.length()); const auto size = max(config::delayedReleaseVoices, keyLength); - lastRegion->delayedSustainReleases.reserve(size); - lastRegion->delayedSostenutoReleases.reserve(size); + lastLayer->delayedSustainReleases_.reserve(size); + lastLayer->delayedSostenutoReleases_.reserve(size); } - regions_.push_back(std::move(lastRegion)); + // Initialize status of Key switches, CC switches, etc + lastLayer->initializeActivations(); } void Synth::Impl::clear() @@ -235,7 +238,7 @@ void Synth::Impl::clear() currentSet_ = nullptr; sets_.clear(); - regions_.clear(); + layers_.clear(); effectBuses_.clear(); effectBuses_.emplace_back(new EffectBus); effectBuses_[0]->setGainToMain(1.0); @@ -502,7 +505,7 @@ bool Synth::loadSfzFile(const fs::path& file) if (!loaderParsesPermissively) success = parser.getErrorCount() == 0; - success = success && !impl.regions_.empty(); + success = success && !impl.layers_.empty(); if (!success) { parser.clear(); @@ -527,7 +530,7 @@ bool Synth::loadSfzString(const fs::path& path, absl::string_view text) if (!loaderParsesPermissively) success = parser.getErrorCount() == 0; - success = success && !impl.regions_.empty(); + success = success && !impl.layers_.empty(); if (!success) { parser.clear(); @@ -543,11 +546,12 @@ void Synth::Impl::finalizeSfzLoad() resources_.filePool.setRootDirectory(parser_.originalDirectory()); size_t currentRegionIndex = 0; - size_t currentRegionCount = regions_.size(); + size_t currentRegionCount = layers_.size(); auto removeCurrentRegion = [this, ¤tRegionIndex, ¤tRegionCount]() { - DBG("Removing the region with sample " << *regions_[currentRegionIndex]->sampleId); - regions_.erase(regions_.begin() + currentRegionIndex); + const Region& region = layers_[currentRegionIndex]->getRegion(); + DBG("Removing the region with sample " << *region.sampleId); + layers_.erase(layers_.begin() + currentRegionIndex); --currentRegionCount; }; @@ -564,155 +568,156 @@ void Synth::Impl::finalizeSfzLoad() FlexEGs::clearUnusedCurves(); while (currentRegionIndex < currentRegionCount) { - auto region = regions_[currentRegionIndex].get(); + Layer& layer = *layers_[currentRegionIndex]; + Region& region = layer.getRegion(); absl::optional fileInformation; - if (!region->isGenerator()) { - if (!resources_.filePool.checkSampleId(*region->sampleId)) { + if (!region.isGenerator()) { + if (!resources_.filePool.checkSampleId(*region.sampleId)) { removeCurrentRegion(); continue; } - fileInformation = resources_.filePool.getFileInformation(*region->sampleId); + fileInformation = resources_.filePool.getFileInformation(*region.sampleId); if (!fileInformation) { removeCurrentRegion(); continue; } - region->hasWavetableSample = fileInformation->wavetable || + region.hasWavetableSample = fileInformation->wavetable || fileInformation->end < config::wavetableMaxFrames; } - if (!region->isOscillator()) { - region->sampleEnd = std::min(region->sampleEnd, fileInformation->end); + if (!region.isOscillator()) { + region.sampleEnd = std::min(region.sampleEnd, fileInformation->end); if (fileInformation->hasLoop) { - if (region->loopRange.getStart() == Default::loopStart) - region->loopRange.setStart(fileInformation->loopStart); + if (region.loopRange.getStart() == Default::loopStart) + region.loopRange.setStart(fileInformation->loopStart); - if (region->loopRange.getEnd() == Default::loopEnd) - region->loopRange.setEnd(fileInformation->loopEnd); + if (region.loopRange.getEnd() == Default::loopEnd) + region.loopRange.setEnd(fileInformation->loopEnd); - if (!region->loopMode) - region->loopMode = LoopMode::loop_continuous; + if (!region.loopMode) + region.loopMode = LoopMode::loop_continuous; } - if (region->isRelease() && !region->loopMode) - region->loopMode = LoopMode::one_shot; + if (region.isRelease() && !region.loopMode) + region.loopMode = LoopMode::one_shot; - if (region->loopRange.getEnd() == Default::loopEnd) - region->loopRange.setEnd(region->sampleEnd); + if (region.loopRange.getEnd() == Default::loopEnd) + region.loopRange.setEnd(region.sampleEnd); // if range is invalid, disable the loop - if (!region->loopRange.isValid()) - region->loopMode = absl::nullopt; + if (!region.loopRange.isValid()) + region.loopMode = absl::nullopt; if (fileInformation->numChannels == 2) - region->hasStereoSample = true; + region.hasStereoSample = true; - if (region->pitchKeycenterFromSample) - region->pitchKeycenter = fileInformation->rootKey; + if (region.pitchKeycenterFromSample) + region.pitchKeycenter = fileInformation->rootKey; // TODO: adjust with LFO targets const auto maxOffset = [region]() { - uint64_t sumOffsetCC = region->offset + region->offsetRandom; - for (const auto& offsets : region->offsetCC) + uint64_t sumOffsetCC = region.offset + region.offsetRandom; + for (const auto& offsets : region.offsetCC) sumOffsetCC += offsets.data; return Default::offsetMod.bounds.clamp(sumOffsetCC); }(); - if (!resources_.filePool.preloadFile(*region->sampleId, maxOffset)) + if (!resources_.filePool.preloadFile(*region.sampleId, maxOffset)) removeCurrentRegion(); } - else if (!region->isGenerator()) { - if (!resources_.wavePool.createFileWave(resources_.filePool, std::string(region->sampleId->filename()))) { + else if (!region.isGenerator()) { + if (!resources_.wavePool.createFileWave(resources_.filePool, std::string(region.sampleId->filename()))) { removeCurrentRegion(); continue; } } - if (region->lastKeyswitch) { + if (region.lastKeyswitch) { if (currentSwitch_) - region->keySwitched = (*currentSwitch_ == *region->lastKeyswitch); + layer.keySwitched_ = (*currentSwitch_ == *region.lastKeyswitch); - if (region->keyswitchLabel) - setKeyswitchLabel(*region->lastKeyswitch, *region->keyswitchLabel); + if (region.keyswitchLabel) + setKeyswitchLabel(*region.lastKeyswitch, *region.keyswitchLabel); } - if (region->lastKeyswitchRange) { - auto& range = *region->lastKeyswitchRange; + if (region.lastKeyswitchRange) { + auto& range = *region.lastKeyswitchRange; if (currentSwitch_) - region->keySwitched = range.containsWithEnd(*currentSwitch_); + layer.keySwitched_ = range.containsWithEnd(*currentSwitch_); - if (region->keyswitchLabel) { + if (region.keyswitchLabel) { for (uint8_t note = range.getStart(), end = range.getEnd(); note <= end; note++) - setKeyswitchLabel(note, *region->keyswitchLabel); + setKeyswitchLabel(note, *region.keyswitchLabel); } } for (auto note = 0; note < 128; note++) { - if (region->keyRange.containsWithEnd(note)) - noteActivationLists_[note].push_back(region); + if (region.keyRange.containsWithEnd(note)) + noteActivationLists_[note].push_back(&layer); } for (int cc = 0; cc < config::numCCs; cc++) { - if (region->ccTriggers.contains(cc) - || region->ccConditions.contains(cc) - || (cc == region->sustainCC && region->trigger == Trigger::release) - || (cc == region->sostenutoCC && region->trigger == Trigger::release)) - ccActivationLists_[cc].push_back(region); + if (region.ccTriggers.contains(cc) + || region.ccConditions.contains(cc) + || (cc == region.sustainCC && region.trigger == Trigger::release) + || (cc == region.sostenutoCC && region.trigger == Trigger::release)) + ccActivationLists_[cc].push_back(&layer); } // Defaults for (int cc = 0; cc < config::numCCs; cc++) { - region->registerCC(cc, resources_.midiState.getCCValue(cc)); + layer.registerCC(cc, resources_.midiState.getCCValue(cc)); } // Set the default frequencies on equalizers if needed - if (region->equalizers.size() > 0 - && region->equalizers[0].frequency == Default::eqFrequency) { - region->equalizers[0].frequency = Default::defaultEQFreq[0]; - if (region->equalizers.size() > 1 - && region->equalizers[1].frequency == Default::eqFrequency) { - region->equalizers[1].frequency = Default::defaultEQFreq[1]; - if (region->equalizers.size() > 2 - && region->equalizers[2].frequency == Default::eqFrequency) { - region->equalizers[2].frequency = Default::defaultEQFreq[2]; + if (region.equalizers.size() > 0 + && region.equalizers[0].frequency == Default::eqFrequency) { + region.equalizers[0].frequency = Default::defaultEQFreq[0]; + if (region.equalizers.size() > 1 + && region.equalizers[1].frequency == Default::eqFrequency) { + region.equalizers[1].frequency = Default::defaultEQFreq[1]; + if (region.equalizers.size() > 2 + && region.equalizers[2].frequency == Default::eqFrequency) { + region.equalizers[2].frequency = Default::defaultEQFreq[2]; } } } - if (!region->velocityPoints.empty()) - region->velCurve = Curve::buildFromVelcurvePoints( - region->velocityPoints, Curve::Interpolator::Linear); + if (!region.velocityPoints.empty()) + region.velCurve = Curve::buildFromVelcurvePoints( + region.velocityPoints, Curve::Interpolator::Linear); - region->registerPitchWheel(0); - region->registerAftertouch(0); - region->registerTempo(2.0f); - maxFilters = max(maxFilters, region->filters.size()); - maxEQs = max(maxEQs, region->equalizers.size()); - maxLFOs = max(maxLFOs, region->lfos.size()); - maxFlexEGs = max(maxFlexEGs, region->flexEGs.size()); - havePitchEG = havePitchEG || region->pitchEG != absl::nullopt; - haveFilterEG = haveFilterEG || region->filterEG != absl::nullopt; - haveAmplitudeLFO = haveAmplitudeLFO || region->amplitudeLFO != absl::nullopt; - havePitchLFO = havePitchLFO || region->pitchLFO != absl::nullopt; - haveFilterLFO = haveFilterLFO || region->filterLFO != absl::nullopt; + layer.registerPitchWheel(0); + layer.registerAftertouch(0); + layer.registerTempo(2.0f); + maxFilters = max(maxFilters, region.filters.size()); + maxEQs = max(maxEQs, region.equalizers.size()); + maxLFOs = max(maxLFOs, region.lfos.size()); + maxFlexEGs = max(maxFlexEGs, region.flexEGs.size()); + havePitchEG = havePitchEG || region.pitchEG != absl::nullopt; + haveFilterEG = haveFilterEG || region.filterEG != absl::nullopt; + haveAmplitudeLFO = haveAmplitudeLFO || region.amplitudeLFO != absl::nullopt; + havePitchLFO = havePitchLFO || region.pitchLFO != absl::nullopt; + haveFilterLFO = haveFilterLFO || region.filterLFO != absl::nullopt; ++currentRegionIndex; } - if (currentRegionCount < regions_.size()) { - DBG("Removing " << (regions_.size() - currentRegionCount) - << " out of " << regions_.size() << " regions"); + if (currentRegionCount < layers_.size()) { + DBG("Removing " << (layers_.size() - currentRegionCount) + << " out of " << layers_.size() << " regions"); } - regions_.resize(currentRegionCount); + layers_.resize(currentRegionCount); // collect all CCs used in regions, with matrix not yet connected BitArray usedCCs; - for (const RegionPtr& regionPtr : regions_) { - const Region& region = *regionPtr; + for (const LayerPtr& layerPtr : layers_) { + const Region& region = layerPtr->getRegion(); collectUsedCCsFromRegion(usedCCs, region); for (const Region::Connection& connection : region.connections) { if (connection.source.id() == ModId::Controller) @@ -720,8 +725,8 @@ void Synth::Impl::finalizeSfzLoad() } } // connect default controllers, except if these CC are already used - for (const RegionPtr& regionPtr : regions_) { - Region& region = *regionPtr; + for (const LayerPtr& layerPtr : layers_) { + Region& region = layerPtr->getRegion(); constexpr unsigned defaultSmoothness = 10; if (!usedCCs.test(7)) { region.getOrCreateConnection( @@ -760,19 +765,21 @@ void Synth::Impl::finalizeSfzLoad() currentUsedCCs_ = collectAllUsedCCs(); // cache the set of keys assigned - for (const RegionPtr& regionPtr : regions_) { - UncheckedRange keyRange = regionPtr->keyRange; + for (const LayerPtr& layerPtr : layers_) { + const Region& region = layerPtr->getRegion(); + UncheckedRange keyRange = region.keyRange; unsigned loKey = keyRange.getStart(); unsigned hiKey = keyRange.getEnd(); for (unsigned key = loKey; key <= hiKey; ++key) keySlots_.set(key); } // cache the set of keyswitches assigned - for (const RegionPtr& regionPtr : regions_) { - if (absl::optional sw = regionPtr->lastKeyswitch) { + for (const LayerPtr& layerPtr : layers_) { + const Region& region = layerPtr->getRegion(); + if (absl::optional sw = region.lastKeyswitch) { swLastSlots_.set(*sw); } - else if (absl::optional> swRange = regionPtr->lastKeyswitchRange) { + else if (absl::optional> swRange = region.lastKeyswitchRange) { unsigned loKey = swRange->getStart(); unsigned hiKey = swRange->getEnd(); for (unsigned key = loKey; key <= hiKey; ++key) @@ -1075,15 +1082,17 @@ void Synth::noteOff(int delay, int noteNumber, uint8_t velocity) noexcept impl.resources_.midiState.noteOffEvent(delay, noteNumber, normalizedVelocity); } -void Synth::Impl::startVoice(Region* region, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept +void Synth::Impl::startVoice(Layer* layer, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept { - voiceManager_.checkPolyphony(region, delay, triggerEvent); + const Region& region = layer->getRegion(); + + voiceManager_.checkPolyphony(®ion, delay, triggerEvent); Voice* selectedVoice = voiceManager_.findFreeVoice(); if (selectedVoice == nullptr) return; ASSERT(selectedVoice->isFree()); - if (selectedVoice->startVoice(region, delay, triggerEvent)) + if (selectedVoice->startVoice(layer, delay, triggerEvent)) ring.addVoiceToRing(selectedVoice); } @@ -1093,18 +1102,19 @@ void Synth::Impl::noteOffDispatch(int delay, int noteNumber, float velocity) noe SisterVoiceRingBuilder ring; const TriggerEvent triggerEvent { TriggerEventType::NoteOff, noteNumber, velocity }; - for (auto& region : upKeyswitchLists_[noteNumber]) - region->keySwitched = true; + for (Layer* layer : upKeyswitchLists_[noteNumber]) + layer->keySwitched_ = true; - for (auto& region : downKeyswitchLists_[noteNumber]) - region->keySwitched = false; + for (Layer* layer : downKeyswitchLists_[noteNumber]) + layer->keySwitched_ = false; - for (auto& region : noteActivationLists_[noteNumber]) { - if (region->registerNoteOff(noteNumber, velocity, randValue)) { - if (region->trigger == Trigger::release && !region->rtDead && !voiceManager_.playingAttackVoice(region)) + for (Layer* layer : noteActivationLists_[noteNumber]) { + const Region& region = layer->getRegion(); + if (layer->registerNoteOff(noteNumber, velocity, randValue)) { + if (region.trigger == Trigger::release && !region.rtDead && !voiceManager_.playingAttackVoice(®ion)) continue; - startVoice(region, delay, triggerEvent, ring); + startVoice(layer, delay, triggerEvent, ring); } } } @@ -1116,70 +1126,77 @@ void Synth::Impl::noteOnDispatch(int delay, int noteNumber, float velocity) noex if (!lastKeyswitchLists_[noteNumber].empty()) { if (currentSwitch_ && *currentSwitch_ != noteNumber) { - for (auto& region : lastKeyswitchLists_[*currentSwitch_]) - region->keySwitched = false; + for (Layer* layer : lastKeyswitchLists_[*currentSwitch_]) + layer->keySwitched_ = false; } currentSwitch_ = noteNumber; currentSwitchChanged_ = true; } - for (auto& region : lastKeyswitchLists_[noteNumber]) - region->keySwitched = true; + for (Layer* layer : lastKeyswitchLists_[noteNumber]) + layer->keySwitched_ = true; - for (auto& region : upKeyswitchLists_[noteNumber]) - region->keySwitched = false; + for (Layer* layer : upKeyswitchLists_[noteNumber]) + layer->keySwitched_ = false; - for (auto& region : downKeyswitchLists_[noteNumber]) - region->keySwitched = true; + for (Layer* layer : downKeyswitchLists_[noteNumber]) + layer->keySwitched_ = true; - for (auto& region : noteActivationLists_[noteNumber]) { - if (region->registerNoteOn(noteNumber, velocity, randValue)) { + for (Layer* layer : noteActivationLists_[noteNumber]) { + if (layer->registerNoteOn(noteNumber, velocity, randValue)) { + const Region& region = layer->getRegion(); for (auto& voice : voiceManager_) { - if (voice.checkOffGroup(region, delay, noteNumber)) { + if (voice.checkOffGroup(®ion, delay, noteNumber)) { const TriggerEvent& event = voice.getTriggerEvent(); noteOffDispatch(delay, event.number, event.value); } } TriggerEvent triggerEvent { TriggerEventType::NoteOn, noteNumber, velocity }; - if (region->velocityOverride == VelocityOverride::previous) + if (region.velocityOverride == VelocityOverride::previous) triggerEvent.value = resources_.midiState.getLastVelocity(); - startVoice(region, delay, triggerEvent, ring); + startVoice(layer, delay, triggerEvent, ring); } } - for (auto& region : previousKeyswitchLists_) - region->previousKeySwitched = (*region->previousKeyswitch == noteNumber); + for (Layer* layer : previousKeyswitchLists_) { + const Region& region = layer->getRegion(); + layer->previousKeySwitched_ = (region.previousKeyswitch == noteNumber); + } } -void Synth::Impl::startDelayedSustainReleases(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept +void Synth::Impl::startDelayedSustainReleases(Layer* layer, int delay, SisterVoiceRingBuilder& ring) noexcept { - if (!region->rtDead && !voiceManager_.playingAttackVoice(region)) { - region->delayedSustainReleases.clear(); + const Region& region = layer->getRegion(); + + if (!region.rtDead && !voiceManager_.playingAttackVoice(®ion)) { + layer->delayedSustainReleases_.clear(); return; } - for (auto& note: region->delayedSustainReleases) { + for (auto& note: layer->delayedSustainReleases_) { const TriggerEvent noteOffEvent { TriggerEventType::NoteOff, note.first, note.second }; - startVoice(region, delay, noteOffEvent, ring); + startVoice(layer, delay, noteOffEvent, ring); } - region->delayedSustainReleases.clear(); + layer->delayedSustainReleases_.clear(); } -void Synth::Impl::startDelayedSostenutoReleases(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept +void Synth::Impl::startDelayedSostenutoReleases(Layer* layer, int delay, SisterVoiceRingBuilder& ring) noexcept { - if (!region->rtDead && !voiceManager_.playingAttackVoice(region)) { - region->delayedSostenutoReleases.clear(); + const Region& region = layer->getRegion(); + + if (!region.rtDead && !voiceManager_.playingAttackVoice(®ion)) { + layer->delayedSostenutoReleases_.clear(); return; } - for (auto& note: region->delayedSostenutoReleases) { + for (auto& note: layer->delayedSostenutoReleases_) { const TriggerEvent noteOffEvent { TriggerEventType::NoteOff, note.first, note.second }; - startVoice(region, delay, noteOffEvent, ring); + startVoice(layer, delay, noteOffEvent, ring); } - region->delayedSostenutoReleases.clear(); + layer->delayedSostenutoReleases_.clear(); } void Synth::cc(int delay, int ccNumber, uint8_t ccValue) noexcept @@ -1192,23 +1209,25 @@ void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept { SisterVoiceRingBuilder ring; const TriggerEvent triggerEvent { TriggerEventType::CC, ccNumber, value }; - for (auto& region : ccActivationLists_[ccNumber]) { - if (region->checkSustain && ccNumber == region->sustainCC && value < region->sustainThreshold) - startDelayedSustainReleases(region, delay, ring); + for (Layer* layer : ccActivationLists_[ccNumber]) { + const Region& region = layer->getRegion(); - if (region->checkSostenuto && ccNumber == region->sostenutoCC && value < region->sostenutoThreshold) { - if (region->sustainPressed) { - for (const auto& v: region->delayedSostenutoReleases) - region->delaySustainRelease(v.first, v.second); + if (region.checkSustain && ccNumber == region.sustainCC && value < region.sustainThreshold) + startDelayedSustainReleases(layer, delay, ring); - region->delayedSostenutoReleases.clear(); + if (region.checkSostenuto && ccNumber == region.sostenutoCC && value < region.sostenutoThreshold) { + if (layer->sustainPressed_) { + for (const auto& v: layer->delayedSostenutoReleases_) + layer->delaySustainRelease(v.first, v.second); + + layer->delayedSostenutoReleases_.clear(); } else { - startDelayedSostenutoReleases(region, delay, ring); + startDelayedSostenutoReleases(layer, delay, ring); } } - if (region->registerCC(ccNumber, value)) - startVoice(region, delay, triggerEvent, ring); + if (layer->registerCC(ccNumber, value)) + startVoice(layer, delay, triggerEvent, ring); } } @@ -1288,8 +1307,8 @@ void Synth::pitchWheel(int delay, int pitch) noexcept ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; impl.resources_.midiState.pitchBendEvent(delay, normalizedPitch); - for (auto& region : impl.regions_) { - region->registerPitchWheel(normalizedPitch); + for (const Impl::LayerPtr& layer : impl.layers_) { + layer->registerPitchWheel(normalizedPitch); } for (auto& voice : impl.voiceManager_) { @@ -1310,8 +1329,8 @@ void Synth::hdAftertouch(int delay, float normAftertouch) noexcept impl.resources_.midiState.channelAftertouchEvent(delay, normAftertouch); - for (auto& region : impl.regions_) { - region->registerAftertouch(normAftertouch); + for (const Impl::LayerPtr& layerPtr : impl.layers_) { + layerPtr->registerAftertouch(normAftertouch); } for (auto& voice : impl.voiceManager_) { @@ -1365,7 +1384,7 @@ void Synth::playbackState(int delay, int playbackState) int Synth::getNumRegions() const noexcept { Impl& impl = *impl_; - return static_cast(impl.regions_.size()); + return static_cast(impl.layers_.size()); } int Synth::getNumGroups() const noexcept @@ -1490,10 +1509,16 @@ std::string Synth::exportMidnam(absl::string_view model) const return std::move(writer.str()); } -const Region* Synth::getRegionView(int idx) const noexcept +const Layer* Synth::getLayerView(int idx) const noexcept { Impl& impl = *impl_; - return (size_t)idx < impl.regions_.size() ? impl.regions_[idx].get() : nullptr; + return (size_t)idx < impl.layers_.size() ? impl.layers_[idx].get() : nullptr; +} + +const Region* Synth::getRegionView(int idx) const noexcept +{ + const Layer* layer = getLayerView(idx); + return layer ? &layer->getRegion() : nullptr; } const EffectBus* Synth::getEffectBusView(int idx) const noexcept @@ -1514,10 +1539,10 @@ const PolyphonyGroup* Synth::getPolyphonyGroupView(int idx) const noexcept return impl.voiceManager_.getPolyphonyGroupView(idx); } -const Region* Synth::getRegionById(NumericId id) const noexcept +Layer* Synth::getLayerById(NumericId id) noexcept { Impl& impl = *impl_; - const size_t size = impl.regions_.size(); + const size_t size = impl.layers_.size(); if (size == 0 || !id.valid()) return nullptr; @@ -1526,10 +1551,17 @@ const Region* Synth::getRegionById(NumericId id) const noexcept size_t index = static_cast(id.number()); index = std::min(index, size - 1); - while (index > 0 && impl.regions_[index]->getId().number() > id.number()) + while (index > 0 && impl.layers_[index]->getRegion().getId().number() > id.number()) --index; - return (impl.regions_[index]->getId() == id) ? impl.regions_[index].get() : nullptr; + return (impl.layers_[index]->getRegion().getId() == id) ? + impl.layers_[index].get() : nullptr; +} + +const Region* Synth::getRegionById(NumericId id) const noexcept +{ + Layer* layer = const_cast(this)->getLayerById(id); + return layer ? &layer->getRegion() : nullptr; } const Voice* Synth::getVoiceView(int idx) const noexcept @@ -1653,8 +1685,10 @@ void Synth::Impl::setupModMatrix() { ModMatrix& mm = resources_.modMatrix; - for (const RegionPtr& region : regions_) { - for (const Region::Connection& conn : region->connections) { + for (const LayerPtr& layerPtr : layers_) { + const Region& region = layerPtr->getRegion(); + + for (const Region::Connection& conn : region.connections) { ModGenerator* gen = nullptr; ModKey sourceKey = conn.source; @@ -1789,9 +1823,10 @@ void Synth::Impl::resetAllControllers(int delay) noexcept voice.registerCC(delay, cc, 0.0f); } - for (auto& region : regions_) { + for (const LayerPtr& layerPtr : layers_) { + Layer& layer = *layerPtr; for (int cc = 0; cc < config::numCCs; ++cc) - region->registerCC(cc, 0.0f); + layer.registerCC(cc, 0.0f); } } @@ -1929,8 +1964,8 @@ void Synth::Impl::collectUsedCCsFromModulations(BitArray& usedCC BitArray Synth::Impl::collectAllUsedCCs() { BitArray used; - for (const Impl::RegionPtr& region : regions_) - collectUsedCCsFromRegion(used, *region); + for (const LayerPtr& layerPtr : layers_) + collectUsedCCsFromRegion(used, layerPtr->getRegion()); collectUsedCCsFromModulations(used, resources_.modMatrix); return used; } diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index d314b9a0..5bdf3ba4 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -26,6 +26,7 @@ class RegionSet; class PolyphonyGroup; class EffectBus; struct Region; +struct Layer; class Voice; /** @@ -189,6 +190,13 @@ public: * @brief Export a MIDI Name document describing the loaded instrument */ std::string exportMidnam(absl::string_view model = {}) const; + /** + * @brief Find the layer which is associated with the given identifier. + * + * @param id + * @return Layer* + */ + Layer* getLayerById(NumericId id) noexcept; /** * @brief Find the region which is associated with the given identifier. * @@ -196,6 +204,14 @@ public: * @return const Region* */ const Region* getRegionById(NumericId id) const noexcept; + /** + * @brief Get a raw view into a specific layer. This is mostly used + * for testing. + * + * @param idx + * @return const Layer* + */ + const Layer* getLayerView(int idx) const noexcept; /** * @brief Get a raw view into a specific region. This is mostly used * for testing. diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index a91b9447..24de4b96 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -28,9 +28,10 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co if (extractMessage(p, path, indices) && !strcmp(sig, s)) #define GET_REGION_OR_BREAK(idx) \ - if (idx >= impl.regions_.size()) \ + if (idx >= impl.layers_.size()) \ break; \ - const auto& region = *impl.regions_[idx]; + Layer& layer = *impl.layers_[idx]; \ + const Region& region = layer.getRegion(); MATCH("/hello", "") { client.receive(delay, "/hello", "", nullptr); diff --git a/src/sfizz/SynthPrivate.h b/src/sfizz/SynthPrivate.h index b40ebcc7..350a12fd 100644 --- a/src/sfizz/SynthPrivate.h +++ b/src/sfizz/SynthPrivate.h @@ -5,6 +5,7 @@ #include "SisterVoiceRing.h" #include "TriggerEvent.h" #include "VoiceManager.h" +#include "Layer.h" #include "BitArray.h" #include "modulations/sources/ADSREnvelope.h" #include "modulations/sources/Controller.h" @@ -147,30 +148,30 @@ struct Synth::Impl final: public Parser::Listener { * @brief Start a voice for a specific region. * This will do the needed polyphony checks and voice stealing. * - * @param region + * @param layer * @param delay * @param triggerEvent * @param ring */ - void startVoice(Region* region, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept; + void startVoice(Layer* layer, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept; /** * @brief Start all delayed sustain release voices of the region if necessary * - * @param region + * @param layer * @param delay * @param ring */ - void startDelayedSustainReleases(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept; + void startDelayedSustainReleases(Layer* layer, int delay, SisterVoiceRingBuilder& ring) noexcept; /** * @brief Start all delayed sostenuto release voices of the region if necessary * - * @param region + * @param layer * @param delay * @param ring */ - void startDelayedSostenutoReleases(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept; + void startDelayedSostenutoReleases(Layer* layer, int delay, SisterVoiceRingBuilder& ring) noexcept; /** * @brief Finalize SFZ loading, following a successful execution of the @@ -242,22 +243,24 @@ struct Synth::Impl final: public Parser::Listener { bool currentSwitchChanged_ = true; std::vector unknownOpcodes_; using RegionViewVector = std::vector; + using LayerViewVector = std::vector; using VoiceViewVector = std::vector; + using LayerPtr = std::unique_ptr; using RegionPtr = std::unique_ptr; using RegionSetPtr = std::unique_ptr; - std::vector regions_; + std::vector layers_; VoiceManager voiceManager_; // These are more general "groups" than sfz and encapsulates the full hierarchy RegionSet* currentSet_ { nullptr }; std::vector sets_; - std::array lastKeyswitchLists_; - std::array downKeyswitchLists_; - std::array upKeyswitchLists_; - RegionViewVector previousKeyswitchLists_; - std::array noteActivationLists_; - std::array ccActivationLists_; + std::array lastKeyswitchLists_; + std::array downKeyswitchLists_; + std::array upKeyswitchLists_; + LayerViewVector previousKeyswitchLists_; + std::array noteActivationLists_; + std::array ccActivationLists_; // Effect factory and buses EffectFactory effectFactory_; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index e2eb1f30..06fcfa59 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -5,6 +5,7 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "Voice.h" +#include "Layer.h" #include "AudioBuffer.h" #include "Config.h" #include "Defaults.h" @@ -198,7 +199,7 @@ struct Voice::Impl const NumericId id_; StateListener* stateListener_ = nullptr; - Region* region_ { nullptr }; + const Region* region_ { nullptr }; State state_ { State::idle }; bool noteIsOff_ { false }; @@ -362,18 +363,21 @@ Voice::Impl::Impl(int voiceNumber, Resources& resources) getSCurve(); } -bool Voice::startVoice(Region* region, int delay, const TriggerEvent& event) noexcept +bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexcept { Impl& impl = *impl_; ASSERT(event.value >= 0.0f && event.value <= 1.0f); - impl.region_ = region; + Resources& resources = impl.resources_; + + const Region& region = layer->getRegion(); + impl.region_ = ®ion; impl.triggerEvent_ = event; if (impl.triggerEvent_.type == TriggerEventType::CC) - impl.triggerEvent_.number = region->pitchKeycenter; + impl.triggerEvent_.number = region.pitchKeycenter; - if (region->disabled()) { + if (region.disabled()) { impl.switchState(State::cleanMeUp); return false; } @@ -384,33 +388,33 @@ bool Voice::startVoice(Region* region, int delay, const TriggerEvent& event) noe if (delay < 0) delay = 0; - if (region->isOscillator()) { + if (region.isOscillator()) { const WavetableMulti* wave = nullptr; - if (!region->isGenerator()) - wave = impl.resources_.wavePool.getFileWave(region->sampleId->filename()); + if (!region.isGenerator()) + wave = resources.wavePool.getFileWave(region.sampleId->filename()); else { - switch (hash(region->sampleId->filename())) { + switch (hash(region.sampleId->filename())) { default: case hash("*silence"): break; case hash("*sine"): - wave = impl.resources_.wavePool.getWaveSin(); + wave = resources.wavePool.getWaveSin(); break; case hash("*triangle"): // fallthrough case hash("*tri"): - wave = impl.resources_.wavePool.getWaveTriangle(); + wave = resources.wavePool.getWaveTriangle(); break; case hash("*square"): - wave = impl.resources_.wavePool.getWaveSquare(); + wave = resources.wavePool.getWaveSquare(); break; case hash("*saw"): - wave = impl.resources_.wavePool.getWaveSaw(); + wave = resources.wavePool.getWaveSaw(); break; } } - const float phase = region->getPhase(); + const float phase = region.getPhase(); const int quality = - region->oscillatorQuality.value_or(Default::oscillatorQuality); + region.oscillatorQuality.value_or(Default::oscillatorQuality); for (WavetableOscillator& osc : impl.waveOscillators_) { osc.setWavetable(wave); osc.setPhase(phase); @@ -418,61 +422,61 @@ bool Voice::startVoice(Region* region, int delay, const TriggerEvent& event) noe } impl.setupOscillatorUnison(); } else { - impl.currentPromise_ = impl.resources_.filePool.getFilePromise(region->sampleId); + impl.currentPromise_ = resources.filePool.getFilePromise(region.sampleId); if (!impl.currentPromise_) { impl.switchState(State::cleanMeUp); return false; } impl.updateLoopInformation(); impl.speedRatio_ = static_cast(impl.currentPromise_->information.sampleRate / impl.sampleRate_); - impl.sourcePosition_ = region->getOffset(impl.resources_.filePool.getOversamplingFactor()); + impl.sourcePosition_ = region.getOffset(resources.midiState, resources.filePool.getOversamplingFactor()); } // do Scala retuning and reconvert the frequency into a 12TET key number - const float numberRetuned = impl.resources_.tuning.getKeyFractional12TET(impl.triggerEvent_.number); + const float numberRetuned = resources.tuning.getKeyFractional12TET(impl.triggerEvent_.number); - impl.pitchRatio_ = region->getBasePitchVariation(numberRetuned, impl.triggerEvent_.value); + impl.pitchRatio_ = region.getBasePitchVariation(numberRetuned, impl.triggerEvent_.value); // apply stretch tuning if set - if (impl.resources_.stretch) - impl.pitchRatio_ *= impl.resources_.stretch->getRatioForFractionalKey(numberRetuned); + if (resources.stretch) + impl.pitchRatio_ *= resources.stretch->getRatioForFractionalKey(numberRetuned); - impl.baseVolumedB_ = region->getBaseVolumedB(impl.triggerEvent_.number); - impl.baseGain_ = region->getBaseGain(); + impl.baseVolumedB_ = region.getBaseVolumedB(resources.midiState, impl.triggerEvent_.number); + impl.baseGain_ = region.getBaseGain(); if (impl.triggerEvent_.type != TriggerEventType::CC) - impl.baseGain_ *= region->getNoteGain(impl.triggerEvent_.number, impl.triggerEvent_.value); + impl.baseGain_ *= region.getNoteGain(impl.triggerEvent_.number, impl.triggerEvent_.value); impl.gainSmoother_.reset(); impl.resetCrossfades(); - for (unsigned i = 0; i < region->filters.size(); ++i) { - impl.filters_[i].setup(*region, i, impl.triggerEvent_.number, impl.triggerEvent_.value); + for (unsigned i = 0; i < region.filters.size(); ++i) { + impl.filters_[i].setup(region, i, impl.triggerEvent_.number, impl.triggerEvent_.value); } - for (unsigned i = 0; i < region->equalizers.size(); ++i) { - impl.equalizers_[i].setup(*region, i, impl.triggerEvent_.value); + for (unsigned i = 0; i < region.equalizers.size(); ++i) { + impl.equalizers_[i].setup(region, i, impl.triggerEvent_.value); } impl.triggerDelay_ = delay; - impl.initialDelay_ = delay + static_cast(region->getDelay() * impl.sampleRate_); - impl.baseFrequency_ = impl.resources_.tuning.getFrequencyOfKey(impl.triggerEvent_.number); - impl.sampleSize_ = region->trueSampleEnd(impl.resources_.filePool.getOversamplingFactor()) - impl.sourcePosition_ - 1; - impl.bendStepFactor_ = centsFactor(region->bendStep); - impl.bendSmoother_.setSmoothing(region->bendSmooth, impl.sampleRate_); - impl.bendSmoother_.reset(centsFactor(region->getBendInCents(impl.resources_.midiState.getPitchBend()))); + impl.initialDelay_ = delay + static_cast(region.getDelay(resources.midiState) * impl.sampleRate_); + impl.baseFrequency_ = resources.tuning.getFrequencyOfKey(impl.triggerEvent_.number); + impl.sampleSize_ = region.trueSampleEnd(resources.filePool.getOversamplingFactor()) - impl.sourcePosition_ - 1; + impl.bendStepFactor_ = centsFactor(region.bendStep); + impl.bendSmoother_.setSmoothing(region.bendSmooth, impl.sampleRate_); + impl.bendSmoother_.reset(centsFactor(region.getBendInCents(resources.midiState.getPitchBend()))); - impl.resources_.modMatrix.initVoice(impl.id_, region->getId(), impl.initialDelay_); - impl.saveModulationTargets(region); + resources.modMatrix.initVoice(impl.id_, region.getId(), impl.initialDelay_); + impl.saveModulationTargets(®ion); - if (region->checkSustain) { + if (region.checkSustain) { const bool sustainPressed = - impl.resources_.midiState.getCCValue(region->sustainCC) >= region->sustainThreshold; + resources.midiState.getCCValue(region.sustainCC) >= region.sustainThreshold; impl.sustainState_ = sustainPressed ? Impl::SustainState::Sustaining : Impl::SustainState::Up; } - if (region->checkSostenuto) { + if (region.checkSostenuto) { const bool sostenutoPressed = - impl.resources_.midiState.getCCValue(region->sostenutoCC) >= region->sostenutoThreshold; + resources.midiState.getCCValue(region.sostenutoCC) >= region.sostenutoThreshold; impl.sostenutoState_ = sostenutoPressed ? Impl::SostenutoState::PreviouslyDown : Impl::SostenutoState::Up; } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index ceaf3588..d6412424 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -18,6 +18,7 @@ namespace sfz { enum InterpolatorModel : int; class LFO; class FlexEnvelope; +struct Layer; /** * @brief The SFZ voice are the polyphony holders. They get activated by the synth * and tasked to play a given region until the end, stopping on note-offs, off-groups @@ -99,12 +100,12 @@ public: /** * @brief Start playing a region after a short delay for different triggers (note on, off, cc) * - * @param region + * @param layer * @param delay * @param evebt * @return bool */ - bool startVoice(Region* region, int delay, const TriggerEvent& event) noexcept; + bool startVoice(Layer* layer, int delay, const TriggerEvent& event) noexcept; /** * @brief Get the sample quality determined by the active region. diff --git a/tests/DirectRegionT.cpp b/tests/DirectRegionT.cpp index 8f5bf6a6..f0d63c94 100644 --- a/tests/DirectRegionT.cpp +++ b/tests/DirectRegionT.cpp @@ -7,6 +7,7 @@ #include "TestHelpers.h" #include "sfizz/MidiState.h" #include "sfizz/Region.h" +#include "sfizz/Layer.h" #include "sfizz/SfzHelpers.h" #include "sfizz/modulations/ModId.h" #include "sfizz/modulations/ModKey.h" @@ -18,8 +19,7 @@ using namespace sfz; TEST_CASE("[Direct Region Tests] amp_velcurve") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "amp_velcurve_6", "0.4" }); REQUIRE(region.velocityPoints.back() == std::pair(6, 0.4f)); region.parseOpcode({ "amp_velcurve_127", "-1.0" }); @@ -33,87 +33,98 @@ TEST_CASE("[Direct Region Tests] amp_velcurve") TEST_CASE("[Direct Region Tests] Release and release key") { MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "lokey", "63" }); region.parseOpcode({ "hikey", "65" }); region.parseOpcode({ "sample", "*sine" }); - region.delayedSustainReleases.reserve(config::delayedReleaseVoices); SECTION("Release key without sustain") { region.parseOpcode({ "trigger", "release_key" }); + Layer layer { ®ion, midiState }; + layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 0.0f); - region.registerCC(64, 0.0f); - REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); - REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); + layer.registerCC(64, 0.0f); + REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( layer.registerNoteOff(63, 0.5f, 0.0f) ); } SECTION("Release key with sustain") { region.parseOpcode({ "trigger", "release_key" }); + Layer layer { ®ion, midiState }; + layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 1.0f); - region.registerCC(64, 1.0f); - REQUIRE( !region.registerCC(64, 1.0f) ); - REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); - REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); + layer.registerCC(64, 1.0f); + REQUIRE( !layer.registerCC(64, 1.0f) ); + REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( layer.registerNoteOff(63, 0.5f, 0.0f) ); } SECTION("Release without sustain") { region.parseOpcode({ "trigger", "release" }); + Layer layer { ®ion, midiState }; + layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 0.0f); - region.registerCC(64, 0.0f); - REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); - REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); + layer.registerCC(64, 0.0f); + REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( layer.registerNoteOff(63, 0.5f, 0.0f) ); } SECTION("Release with sustain") { region.parseOpcode({ "trigger", "release" }); + Layer layer { ®ion, midiState }; + layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 1.0f); - region.registerCC(64, 1.0f); + layer.registerCC(64, 1.0f); midiState.noteOnEvent(0, 63, 0.5f); - REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); - REQUIRE( !region.registerNoteOff(63, 0.5f, 0.0f) ); - REQUIRE( region.delayedSustainReleases.size() == 1 ); + REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( !layer.registerNoteOff(63, 0.5f, 0.0f) ); + REQUIRE( layer.delayedSustainReleases_.size() == 1 ); std::vector> expected = { { 63, 0.5f } }; - REQUIRE( region.delayedSustainReleases == expected ); + REQUIRE( layer.delayedSustainReleases_ == expected ); } SECTION("Release with sustain and 2 notes") { region.parseOpcode({ "trigger", "release" }); + Layer layer { ®ion, midiState }; + layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 1.0f); - region.registerCC(64, 1.0f); + layer.registerCC(64, 1.0f); midiState.noteOnEvent(0, 63, 0.5f); - REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); midiState.noteOnEvent(0, 64, 0.6f); - REQUIRE( !region.registerNoteOn(64, 0.6f, 0.0f) ); - REQUIRE( !region.registerNoteOff(63, 0.0f, 0.0f) ); - REQUIRE( !region.registerNoteOff(64, 0.2f, 0.0f) ); - REQUIRE( region.delayedSustainReleases.size() == 2 ); + REQUIRE( !layer.registerNoteOn(64, 0.6f, 0.0f) ); + REQUIRE( !layer.registerNoteOff(63, 0.0f, 0.0f) ); + REQUIRE( !layer.registerNoteOff(64, 0.2f, 0.0f) ); + REQUIRE( layer.delayedSustainReleases_.size() == 2 ); std::vector> expected = { { 63, 0.5f }, { 64, 0.6f } }; - REQUIRE( region.delayedSustainReleases == expected ); + REQUIRE( layer.delayedSustainReleases_ == expected ); } SECTION("Release with sustain and 2 notes but 1 outside") { region.parseOpcode({ "trigger", "release" }); + Layer layer { ®ion, midiState }; + layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 1.0f); - region.registerCC(64, 1.0f); + layer.registerCC(64, 1.0f); midiState.noteOnEvent(0, 63, 0.5f); - REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); midiState.noteOnEvent(0, 66, 0.6f); - REQUIRE( !region.registerNoteOn(66, 0.6f, 0.0f) ); - REQUIRE( !region.registerNoteOff(63, 0.0f, 0.0f) ); - REQUIRE( !region.registerNoteOff(66, 0.2f, 0.0f) ); - REQUIRE( region.delayedSustainReleases.size() == 1 ); + REQUIRE( !layer.registerNoteOn(66, 0.6f, 0.0f) ); + REQUIRE( !layer.registerNoteOff(63, 0.0f, 0.0f) ); + REQUIRE( !layer.registerNoteOff(66, 0.2f, 0.0f) ); + REQUIRE( layer.delayedSustainReleases_.size() == 1 ); std::vector> expected = { { 63, 0.5f } }; - REQUIRE( region.delayedSustainReleases == expected ); + REQUIRE( layer.delayedSustainReleases_ == expected ); } } diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp index 13f6b660..cca98f99 100644 --- a/tests/RegionActivationT.cpp +++ b/tests/RegionActivationT.cpp @@ -4,6 +4,7 @@ // license. You should have receive a LICENSE.md file along with the code. // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz +#include "sfizz/Layer.h" #include "sfizz/Region.h" #include "sfizz/Synth.h" #include "sfizz/SfzHelpers.h" @@ -14,33 +15,35 @@ using namespace sfz::literals; TEST_CASE("Region activation", "Region tests") { sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + sfz::Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); SECTION("Basic state") { - region.registerCC(4, 0_norm); - REQUIRE(region.isSwitchedOn()); + sfz::Layer layer { ®ion, midiState }; + layer.registerCC(4, 0_norm); + REQUIRE(layer.isSwitchedOn()); } SECTION("Single CC range") { region.parseOpcode({ "locc4", "56" }); region.parseOpcode({ "hicc4", "59" }); - region.registerCC(4, 0_norm); - REQUIRE(!region.isSwitchedOn()); - region.registerCC(4, 57_norm); - REQUIRE(region.isSwitchedOn()); - region.registerCC(4, 56_norm); - REQUIRE(region.isSwitchedOn()); - region.registerCC(4, 59_norm); - REQUIRE(region.isSwitchedOn()); - region.registerCC(4, 43_norm); - REQUIRE(!region.isSwitchedOn()); - region.registerCC(4, 65_norm); - REQUIRE(!region.isSwitchedOn()); - region.registerCC(6, 57_norm); - REQUIRE(!region.isSwitchedOn()); + sfz::Layer layer { ®ion, midiState }; + layer.registerCC(4, 0_norm); + REQUIRE(!layer.isSwitchedOn()); + layer.registerCC(4, 57_norm); + REQUIRE(layer.isSwitchedOn()); + layer.registerCC(4, 56_norm); + REQUIRE(layer.isSwitchedOn()); + layer.registerCC(4, 59_norm); + REQUIRE(layer.isSwitchedOn()); + layer.registerCC(4, 43_norm); + REQUIRE(!layer.isSwitchedOn()); + layer.registerCC(4, 65_norm); + REQUIRE(!layer.isSwitchedOn()); + layer.registerCC(6, 57_norm); + REQUIRE(!layer.isSwitchedOn()); } SECTION("Multiple CC ranges") @@ -49,69 +52,73 @@ TEST_CASE("Region activation", "Region tests") region.parseOpcode({ "hicc4", "59" }); region.parseOpcode({ "locc54", "18" }); region.parseOpcode({ "hicc54", "27" }); - region.registerCC(4, 0_norm); - region.registerCC(54, 0_norm); - REQUIRE(!region.isSwitchedOn()); - region.registerCC(4, 57_norm); - REQUIRE(!region.isSwitchedOn()); - region.registerCC(54, 19_norm); - REQUIRE(region.isSwitchedOn()); - region.registerCC(54, 17_norm); - REQUIRE(!region.isSwitchedOn()); - region.registerCC(54, 27_norm); - REQUIRE(region.isSwitchedOn()); - region.registerCC(4, 56_norm); - REQUIRE(region.isSwitchedOn()); - region.registerCC(4, 59_norm); - REQUIRE(region.isSwitchedOn()); - region.registerCC(54, 2_norm); - REQUIRE(!region.isSwitchedOn()); - region.registerCC(54, 26_norm); - REQUIRE(region.isSwitchedOn()); - region.registerCC(4, 65_norm); - REQUIRE(!region.isSwitchedOn()); + sfz::Layer layer { ®ion, midiState }; + layer.registerCC(4, 0_norm); + layer.registerCC(54, 0_norm); + REQUIRE(!layer.isSwitchedOn()); + layer.registerCC(4, 57_norm); + REQUIRE(!layer.isSwitchedOn()); + layer.registerCC(54, 19_norm); + REQUIRE(layer.isSwitchedOn()); + layer.registerCC(54, 17_norm); + REQUIRE(!layer.isSwitchedOn()); + layer.registerCC(54, 27_norm); + REQUIRE(layer.isSwitchedOn()); + layer.registerCC(4, 56_norm); + REQUIRE(layer.isSwitchedOn()); + layer.registerCC(4, 59_norm); + REQUIRE(layer.isSwitchedOn()); + layer.registerCC(54, 2_norm); + REQUIRE(!layer.isSwitchedOn()); + layer.registerCC(54, 26_norm); + REQUIRE(layer.isSwitchedOn()); + layer.registerCC(4, 65_norm); + REQUIRE(!layer.isSwitchedOn()); } SECTION("Bend ranges") { region.parseOpcode({ "lobend", "56" }); region.parseOpcode({ "hibend", "243" }); - region.registerPitchWheel(0); - REQUIRE(!region.isSwitchedOn()); - region.registerPitchWheel(sfz::normalizeBend(56)); - REQUIRE(region.isSwitchedOn()); - region.registerPitchWheel(sfz::normalizeBend(243)); - REQUIRE(region.isSwitchedOn()); - region.registerPitchWheel(sfz::normalizeBend(245)); - REQUIRE(!region.isSwitchedOn()); + sfz::Layer layer { ®ion, midiState }; + layer.registerPitchWheel(0); + REQUIRE(!layer.isSwitchedOn()); + layer.registerPitchWheel(sfz::normalizeBend(56)); + REQUIRE(layer.isSwitchedOn()); + layer.registerPitchWheel(sfz::normalizeBend(243)); + REQUIRE(layer.isSwitchedOn()); + layer.registerPitchWheel(sfz::normalizeBend(245)); + REQUIRE(!layer.isSwitchedOn()); } SECTION("Aftertouch ranges") { region.parseOpcode({ "lochanaft", "56" }); region.parseOpcode({ "hichanaft", "68" }); - region.registerAftertouch(sfz::normalize7Bits(0)); - REQUIRE(!region.isSwitchedOn()); - region.registerAftertouch(sfz::normalize7Bits(56)); - REQUIRE(region.isSwitchedOn()); - region.registerAftertouch(sfz::normalize7Bits(68)); - REQUIRE(region.isSwitchedOn()); - region.registerAftertouch(sfz::normalize7Bits(98)); - REQUIRE(!region.isSwitchedOn()); + sfz::Layer layer { ®ion, midiState }; + layer.registerAftertouch(sfz::normalize7Bits(0)); + REQUIRE(!layer.isSwitchedOn()); + layer.registerAftertouch(sfz::normalize7Bits(56)); + REQUIRE(layer.isSwitchedOn()); + layer.registerAftertouch(sfz::normalize7Bits(68)); + REQUIRE(layer.isSwitchedOn()); + layer.registerAftertouch(sfz::normalize7Bits(98)); + REQUIRE(!layer.isSwitchedOn()); } SECTION("BPM ranges") { region.parseOpcode({ "lobpm", "56" }); region.parseOpcode({ "hibpm", "68" }); - region.registerTempo(2.0f); - REQUIRE(!region.isSwitchedOn()); - region.registerTempo(0.90f); - REQUIRE(region.isSwitchedOn()); - region.registerTempo(1.01f); - REQUIRE(region.isSwitchedOn()); - region.registerTempo(1.1f); - REQUIRE(!region.isSwitchedOn()); + sfz::Layer layer { ®ion, midiState }; + layer.registerTempo(2.0f); + REQUIRE(!layer.isSwitchedOn()); + layer.registerTempo(0.90f); + REQUIRE(layer.isSwitchedOn()); + layer.registerTempo(1.01f); + REQUIRE(layer.isSwitchedOn()); + layer.registerTempo(1.1f); + REQUIRE(!layer.isSwitchedOn()); } SECTION("Sequences: length 2, default position") @@ -119,61 +126,64 @@ TEST_CASE("Region activation", "Region tests") region.parseOpcode({ "seq_length", "2" }); region.parseOpcode({ "seq_position", "1" }); region.parseOpcode({ "key", "40" }); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOn(40, 64_norm, 0.5f); - REQUIRE(region.isSwitchedOn()); - region.registerNoteOff(40, 0_norm, 0.5f); - REQUIRE(region.isSwitchedOn()); - region.registerNoteOn(40, 64_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOff(40, 0_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOn(40, 64_norm, 0.5f); - REQUIRE(region.isSwitchedOn()); - region.registerNoteOff(40, 0_norm, 0.5f); - REQUIRE(region.isSwitchedOn()); + sfz::Layer layer { ®ion, midiState }; + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOn(40, 64_norm, 0.5f); + REQUIRE(layer.isSwitchedOn()); + layer.registerNoteOff(40, 0_norm, 0.5f); + REQUIRE(layer.isSwitchedOn()); + layer.registerNoteOn(40, 64_norm, 0.5f); + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOff(40, 0_norm, 0.5f); + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOn(40, 64_norm, 0.5f); + REQUIRE(layer.isSwitchedOn()); + layer.registerNoteOff(40, 0_norm, 0.5f); + REQUIRE(layer.isSwitchedOn()); } SECTION("Sequences: length 2, position 2") { region.parseOpcode({ "seq_length", "2" }); region.parseOpcode({ "seq_position", "2" }); region.parseOpcode({ "key", "40" }); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOn(40, 64_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOff(40, 0_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOn(40, 64_norm, 0.5f); - REQUIRE(region.isSwitchedOn()); - region.registerNoteOff(40, 0_norm, 0.5f); - REQUIRE(region.isSwitchedOn()); - region.registerNoteOn(40, 64_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOff(40, 0_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); + sfz::Layer layer { ®ion, midiState }; + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOn(40, 64_norm, 0.5f); + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOff(40, 0_norm, 0.5f); + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOn(40, 64_norm, 0.5f); + REQUIRE(layer.isSwitchedOn()); + layer.registerNoteOff(40, 0_norm, 0.5f); + REQUIRE(layer.isSwitchedOn()); + layer.registerNoteOn(40, 64_norm, 0.5f); + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOff(40, 0_norm, 0.5f); + REQUIRE(!layer.isSwitchedOn()); } SECTION("Sequences: length 3, position 2") { region.parseOpcode({ "seq_length", "3" }); region.parseOpcode({ "seq_position", "2" }); region.parseOpcode({ "key", "40" }); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOn(40, 64_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOff(40, 0_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOn(40, 64_norm, 0.5f); - REQUIRE(region.isSwitchedOn()); - region.registerNoteOff(40, 0_norm, 0.5f); - REQUIRE(region.isSwitchedOn()); - region.registerNoteOn(40, 64_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOff(40, 0_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOn(40, 64_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); - region.registerNoteOff(40, 0_norm, 0.5f); - REQUIRE(!region.isSwitchedOn()); + sfz::Layer layer { ®ion, midiState }; + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOn(40, 64_norm, 0.5f); + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOff(40, 0_norm, 0.5f); + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOn(40, 64_norm, 0.5f); + REQUIRE(layer.isSwitchedOn()); + layer.registerNoteOff(40, 0_norm, 0.5f); + REQUIRE(layer.isSwitchedOn()); + layer.registerNoteOn(40, 64_norm, 0.5f); + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOff(40, 0_norm, 0.5f); + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOn(40, 64_norm, 0.5f); + REQUIRE(!layer.isSwitchedOn()); + layer.registerNoteOff(40, 0_norm, 0.5f); + REQUIRE(!layer.isSwitchedOn()); } } @@ -341,10 +351,10 @@ TEST_CASE("[Keyswitches] sw_default") sw_last=40 key=54 sample=*sine )"); REQUIRE( synth.getNumRegions() == 4 ); - REQUIRE( !synth.getRegionView(0)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(1)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(3)->isSwitchedOn() ); + REQUIRE( !synth.getLayerView(0)->isSwitchedOn() ); + REQUIRE( synth.getLayerView(1)->isSwitchedOn() ); + REQUIRE( !synth.getLayerView(2)->isSwitchedOn() ); + REQUIRE( synth.getLayerView(3)->isSwitchedOn() ); } TEST_CASE("[Keyswitches] sw_default and playing with switches") @@ -358,22 +368,22 @@ TEST_CASE("[Keyswitches] sw_default and playing with switches") sw_last=40 key=54 sample=*sine )"); REQUIRE( synth.getNumRegions() == 4 ); - REQUIRE( !synth.getRegionView(0)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(1)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(3)->isSwitchedOn() ); + REQUIRE( !synth.getLayerView(0)->isSwitchedOn() ); + REQUIRE( synth.getLayerView(1)->isSwitchedOn() ); + REQUIRE( !synth.getLayerView(2)->isSwitchedOn() ); + REQUIRE( synth.getLayerView(3)->isSwitchedOn() ); synth.noteOn(0, 41, 64); synth.noteOff(0, 41, 0); - REQUIRE( synth.getRegionView(0)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(1)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(2)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(3)->isSwitchedOn() ); + REQUIRE( synth.getLayerView(0)->isSwitchedOn() ); + REQUIRE( !synth.getLayerView(1)->isSwitchedOn() ); + REQUIRE( synth.getLayerView(2)->isSwitchedOn() ); + REQUIRE( !synth.getLayerView(3)->isSwitchedOn() ); synth.noteOn(0, 40, 64); synth.noteOff(0, 40, 64); - REQUIRE( !synth.getRegionView(0)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(1)->isSwitchedOn() ); - REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); - REQUIRE( synth.getRegionView(3)->isSwitchedOn() ); + REQUIRE( !synth.getLayerView(0)->isSwitchedOn() ); + REQUIRE( synth.getLayerView(1)->isSwitchedOn() ); + REQUIRE( !synth.getLayerView(2)->isSwitchedOn() ); + REQUIRE( synth.getLayerView(3)->isSwitchedOn() ); } TEST_CASE("[Keyswitches] sw_previous in range") @@ -385,21 +395,21 @@ TEST_CASE("[Keyswitches] sw_previous in range") // Note: sforzando seems to activate by default if sw_previous is indeed 60, // but not any other value. As it does not seem really useful at this point // the test assumes that sw_previous regions are disabled by default - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 51, 64); REQUIRE(synth.getNumActiveVoices() == 0); synth.noteOn(0, 60, 64); REQUIRE(synth.getNumActiveVoices() == 0); - REQUIRE(synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 51, 64); REQUIRE(synth.getNumActiveVoices() == 1); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 60, 64); REQUIRE(synth.getNumActiveVoices() == 1); - REQUIRE(synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 60, 64); REQUIRE(synth.getNumActiveVoices() == 2); - REQUIRE(synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(synth.getLayerView(0)->isSwitchedOn()); } TEST_CASE("[Keyswitches] sw_previous out of range") @@ -409,20 +419,20 @@ TEST_CASE("[Keyswitches] sw_previous out of range") synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( sample=*saw sw_previous=60 lokey=50 hikey=55 )"); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 51, 64); REQUIRE(synth.getNumActiveVoices() == 0); synth.noteOn(0, 60, 64); REQUIRE(synth.getNumActiveVoices() == 0); - REQUIRE(synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 51, 64); REQUIRE(synth.getNumActiveVoices() == 1); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 60, 64); REQUIRE(synth.getNumActiveVoices() == 1); - REQUIRE(synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 61, 64); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); } TEST_CASE("[Keyswitches] sw_lolast and sw_hilast") @@ -432,29 +442,29 @@ TEST_CASE("[Keyswitches] sw_lolast and sw_hilast") sw_lolast=57 sw_hilast=59 key=70 sample=*saw sw_lolast=60 sw_hilast=62 key=72 sample=*sine )"); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 51, 64); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 57, 64); - REQUIRE(synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 60, 64); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 58, 64); - REQUIRE(synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 61, 64); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 59, 64); - REQUIRE(synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 62, 64); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(synth.getLayerView(1)->isSwitchedOn()); } TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_last") @@ -464,26 +474,26 @@ TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_last") sw_last=40 sw_lolast=57 sw_hilast=59 key=70 sample=*saw sw_lolast=60 sw_hilast=62 sw_last=41 key=72 sample=*sine )"); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 40, 64); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 41, 64); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 57, 64); - REQUIRE(synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 41, 64); - REQUIRE(synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 60, 64); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 40, 64); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(synth.getLayerView(1)->isSwitchedOn()); } TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_default") @@ -494,8 +504,8 @@ TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_default") sw_lolast=57 sw_hilast=59 key=70 sample=*saw sw_lolast=60 sw_hilast=62 key=72 sample=*sine )"); - REQUIRE(synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); } TEST_CASE("[Keyswitches] Multiple sw_default") @@ -509,10 +519,10 @@ TEST_CASE("[Keyswitches] Multiple sw_default") sw_default=59 sw_last=62 key=73 sample=*saw )"); - REQUIRE(!synth.getRegionView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); // Only the last one is taken into account - REQUIRE(synth.getRegionView(1)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(2)->isSwitchedOn()); + REQUIRE(synth.getLayerView(1)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(2)->isSwitchedOn()); } TEST_CASE("[Keyswitches] Multiple sw_default, in region") @@ -523,7 +533,7 @@ TEST_CASE("[Keyswitches] Multiple sw_default, in region") sw_last=58 key=70 sample=*saw sw_default=58 sw_last=59 key=72 sample=*saw )"); - REQUIRE(synth.getRegionView(0)->isSwitchedOn()); - REQUIRE(!synth.getRegionView(1)->isSwitchedOn()); + REQUIRE(synth.getLayerView(0)->isSwitchedOn()); + REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); } diff --git a/tests/RegionTriggersT.cpp b/tests/RegionTriggersT.cpp index 6817acb5..30b1cad5 100644 --- a/tests/RegionTriggersT.cpp +++ b/tests/RegionTriggersT.cpp @@ -7,6 +7,7 @@ #include "TestHelpers.h" #include "sfizz/Synth.h" #include "sfizz/Region.h" +#include "sfizz/Layer.h" #include "sfizz/SfzHelpers.h" #include "catch2/catch.hpp" using namespace Catch::literals; @@ -16,50 +17,54 @@ using namespace sfz; TEST_CASE("Basic triggers", "Region triggers") { MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); SECTION("key") { region.parseOpcode({ "key", "40" }); - REQUIRE(region.registerNoteOn(40, 64_norm, 0.5f)); - REQUIRE(!region.registerNoteOff(40, 64_norm, 0.5f)); - REQUIRE(!region.registerNoteOn(41, 64_norm, 0.5f)); - REQUIRE(!region.registerCC(63, 64_norm)); + Layer layer { ®ion, midiState }; + REQUIRE(layer.registerNoteOn(40, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOff(40, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOn(41, 64_norm, 0.5f)); + REQUIRE(!layer.registerCC(63, 64_norm)); } SECTION("lokey and hikey") { region.parseOpcode({ "lokey", "40" }); region.parseOpcode({ "hikey", "42" }); - REQUIRE(!region.registerNoteOn(39, 64_norm, 0.5f)); - REQUIRE(region.registerNoteOn(40, 64_norm, 0.5f)); - REQUIRE(!region.registerNoteOff(40, 64_norm, 0.5f)); - REQUIRE(region.registerNoteOn(41, 64_norm, 0.5f)); - REQUIRE(region.registerNoteOn(42, 64_norm, 0.5f)); - REQUIRE(!region.registerNoteOn(43, 64_norm, 0.5f)); - REQUIRE(!region.registerNoteOff(42, 64_norm, 0.5f)); - REQUIRE(!region.registerNoteOff(42, 64_norm, 0.5f)); - REQUIRE(!region.registerCC(63, 64_norm)); + Layer layer { ®ion, midiState }; + REQUIRE(!layer.registerNoteOn(39, 64_norm, 0.5f)); + REQUIRE(layer.registerNoteOn(40, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOff(40, 64_norm, 0.5f)); + REQUIRE(layer.registerNoteOn(41, 64_norm, 0.5f)); + REQUIRE(layer.registerNoteOn(42, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOn(43, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOff(42, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOff(42, 64_norm, 0.5f)); + REQUIRE(!layer.registerCC(63, 64_norm)); } SECTION("key and release trigger") { region.parseOpcode({ "key", "40" }); region.parseOpcode({ "trigger", "release" }); - REQUIRE(!region.registerNoteOn(40, 64_norm, 0.5f)); - REQUIRE(region.registerNoteOff(40, 64_norm, 0.5f)); - REQUIRE(!region.registerNoteOn(41, 64_norm, 0.5f)); - REQUIRE(!region.registerNoteOff(41, 64_norm, 0.5f)); - REQUIRE(!region.registerCC(63, 64_norm)); + Layer layer { ®ion, midiState }; + REQUIRE(!layer.registerNoteOn(40, 64_norm, 0.5f)); + REQUIRE(layer.registerNoteOff(40, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOn(41, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOff(41, 64_norm, 0.5f)); + REQUIRE(!layer.registerCC(63, 64_norm)); } SECTION("key and release_key trigger") { region.parseOpcode({ "key", "40" }); region.parseOpcode({ "trigger", "release_key" }); - REQUIRE(!region.registerNoteOn(40, 64_norm, 0.5f)); - REQUIRE(region.registerNoteOff(40, 64_norm, 0.5f)); - REQUIRE(!region.registerNoteOn(41, 64_norm, 0.5f)); - REQUIRE(!region.registerNoteOff(41, 64_norm, 0.5f)); - REQUIRE(!region.registerCC(63, 64_norm)); + Layer layer { ®ion, midiState }; + REQUIRE(!layer.registerNoteOn(40, 64_norm, 0.5f)); + REQUIRE(layer.registerNoteOff(40, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOn(41, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOff(41, 64_norm, 0.5f)); + REQUIRE(!layer.registerCC(63, 64_norm)); } // TODO: first and legato triggers SECTION("lovel and hivel") @@ -67,11 +72,12 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "key", "40" }); region.parseOpcode({ "lovel", "60" }); region.parseOpcode({ "hivel", "70" }); - REQUIRE(region.registerNoteOn(40, 64_norm, 0.5f)); - REQUIRE(region.registerNoteOn(40, 60_norm, 0.5f)); - REQUIRE(region.registerNoteOn(40, 70_norm, 0.5f)); - REQUIRE(!region.registerNoteOn(41, 71_norm, 0.5f)); - REQUIRE(!region.registerNoteOn(41, 59_norm, 0.5f)); + Layer layer { ®ion, midiState }; + REQUIRE(layer.registerNoteOn(40, 64_norm, 0.5f)); + REQUIRE(layer.registerNoteOn(40, 60_norm, 0.5f)); + REQUIRE(layer.registerNoteOn(40, 70_norm, 0.5f)); + REQUIRE(!layer.registerNoteOn(41, 71_norm, 0.5f)); + REQUIRE(!layer.registerNoteOn(41, 59_norm, 0.5f)); } SECTION("lorand and hirand") @@ -79,54 +85,63 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "key", "40" }); region.parseOpcode({ "lorand", "0.35" }); region.parseOpcode({ "hirand", "0.40" }); - REQUIRE(!region.registerNoteOn(40, 64_norm, 0.34f)); - REQUIRE(region.registerNoteOn(40, 64_norm, 0.35f)); - REQUIRE(region.registerNoteOn(40, 64_norm, 0.36f)); - REQUIRE(region.registerNoteOn(40, 64_norm, 0.37f)); - REQUIRE(region.registerNoteOn(40, 64_norm, 0.38f)); - REQUIRE(region.registerNoteOn(40, 64_norm, 0.39f)); - REQUIRE(!region.registerNoteOn(40, 64_norm, 0.40f)); - REQUIRE(!region.registerNoteOn(40, 64_norm, 0.41f)); + Layer layer { ®ion, midiState }; + REQUIRE(!layer.registerNoteOn(40, 64_norm, 0.34f)); + REQUIRE(layer.registerNoteOn(40, 64_norm, 0.35f)); + REQUIRE(layer.registerNoteOn(40, 64_norm, 0.36f)); + REQUIRE(layer.registerNoteOn(40, 64_norm, 0.37f)); + REQUIRE(layer.registerNoteOn(40, 64_norm, 0.38f)); + REQUIRE(layer.registerNoteOn(40, 64_norm, 0.39f)); + REQUIRE(!layer.registerNoteOn(40, 64_norm, 0.40f)); + REQUIRE(!layer.registerNoteOn(40, 64_norm, 0.41f)); } SECTION("lorand and hirand on 1.0f") { region.parseOpcode({ "key", "40" }); region.parseOpcode({ "lorand", "0.35" }); - REQUIRE(!region.registerNoteOn(40, 64_norm, 0.34f)); - REQUIRE(region.registerNoteOn(40, 64_norm, 0.35f)); - REQUIRE(region.registerNoteOn(40, 64_norm, 1.0f)); + Layer layer { ®ion, midiState }; + REQUIRE(!layer.registerNoteOn(40, 64_norm, 0.34f)); + REQUIRE(layer.registerNoteOn(40, 64_norm, 0.35f)); + REQUIRE(layer.registerNoteOn(40, 64_norm, 1.0f)); } SECTION("Disable key trigger") { region.parseOpcode({ "key", "40" }); - REQUIRE(region.registerNoteOn(40, 64_norm, 1.0f)); + Layer layer1 { ®ion, midiState }; + REQUIRE(layer1.registerNoteOn(40, 64_norm, 1.0f)); region.parseOpcode({ "hikey", "-1" }); - REQUIRE(!region.registerNoteOn(40, 64_norm, 1.0f)); + Layer layer2 { ®ion, midiState }; + REQUIRE(!layer2.registerNoteOn(40, 64_norm, 1.0f)); region.parseOpcode({ "hikey", "40" }); - REQUIRE(region.registerNoteOn(40, 64_norm, 1.0f)); + Layer layer3 { ®ion, midiState }; + REQUIRE(layer3.registerNoteOn(40, 64_norm, 1.0f)); + Layer layer4 { ®ion, midiState }; region.parseOpcode({ "key", "-1" }); - REQUIRE(!region.registerNoteOn(40, 64_norm, 1.0f)); + REQUIRE(!layer4.registerNoteOn(40, 64_norm, 1.0f)); + Layer layer5 { ®ion, midiState }; region.parseOpcode({ "key", "40" }); - REQUIRE(region.registerNoteOn(40, 64_norm, 1.0f)); + REQUIRE(layer5.registerNoteOn(40, 64_norm, 1.0f)); } SECTION("on_loccN, on_hiccN") { region.parseOpcode({ "on_locc47", "64" }); region.parseOpcode({ "on_hicc47", "68" }); - REQUIRE(!region.registerCC(47, 63_norm)); - REQUIRE(region.registerCC(47, 64_norm)); - REQUIRE(region.registerCC(47, 65_norm)); + Layer layer1 { ®ion, midiState }; + REQUIRE(!layer1.registerCC(47, 63_norm)); + REQUIRE(layer1.registerCC(47, 64_norm)); + REQUIRE(layer1.registerCC(47, 65_norm)); region.parseOpcode({ "hikey", "-1" }); - REQUIRE(region.registerCC(47, 64_norm)); - REQUIRE(region.registerCC(47, 65_norm)); - REQUIRE(region.registerCC(47, 66_norm)); - REQUIRE(region.registerCC(47, 67_norm)); - REQUIRE(region.registerCC(47, 68_norm)); - REQUIRE(!region.registerCC(47, 69_norm)); - REQUIRE(!region.registerCC(40, 64_norm)); + Layer layer2 { ®ion, midiState }; + REQUIRE(layer2.registerCC(47, 64_norm)); + REQUIRE(layer2.registerCC(47, 65_norm)); + REQUIRE(layer2.registerCC(47, 66_norm)); + REQUIRE(layer2.registerCC(47, 67_norm)); + REQUIRE(layer2.registerCC(47, 68_norm)); + REQUIRE(!layer2.registerCC(47, 69_norm)); + REQUIRE(!layer2.registerCC(40, 64_norm)); } SECTION("on_loccN does not disable key triggering") @@ -134,10 +149,11 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "on_locc1", "127" }); region.parseOpcode({ "on_hicc1", "127" }); - REQUIRE(!region.registerCC(1, 126_norm)); - REQUIRE(!region.registerCC(2, 127_norm)); - REQUIRE(region.registerCC(1, 127_norm)); - REQUIRE(region.registerNoteOn(64, 127_norm, 0.5f)); + Layer layer { ®ion, midiState }; + REQUIRE(!layer.registerCC(1, 126_norm)); + REQUIRE(!layer.registerCC(2, 127_norm)); + REQUIRE(layer.registerCC(1, 127_norm)); + REQUIRE(layer.registerNoteOn(64, 127_norm, 0.5f)); } SECTION("on_loccN does not disable key triggering, but adding key=-1 does") @@ -146,9 +162,10 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "on_locc1", "127" }); region.parseOpcode({ "on_hicc1", "127" }); region.parseOpcode({ "key", "-1" }); - REQUIRE(!region.registerCC(1, 126_norm)); - REQUIRE(region.registerCC(1, 127_norm)); - REQUIRE(!region.registerNoteOn(64, 127_norm, 0.5f)); + Layer layer { ®ion, midiState }; + REQUIRE(!layer.registerCC(1, 126_norm)); + REQUIRE(layer.registerCC(1, 127_norm)); + REQUIRE(!layer.registerNoteOn(64, 127_norm, 0.5f)); } SECTION("on_loccN does not disable key triggering, but adding hikey=-1 does") @@ -157,33 +174,35 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "on_locc1", "127" }); region.parseOpcode({ "on_hicc1", "127" }); region.parseOpcode({ "hikey", "-1" }); - REQUIRE(!region.registerCC(1, 126_norm)); - REQUIRE(!region.registerCC(2, 127_norm)); - REQUIRE(region.registerCC(1, 127_norm)); - REQUIRE(!region.registerNoteOn(64, 127_norm, 0.5f)); + Layer layer { ®ion, midiState }; + REQUIRE(!layer.registerCC(1, 126_norm)); + REQUIRE(!layer.registerCC(2, 127_norm)); + REQUIRE(layer.registerCC(1, 127_norm)); + REQUIRE(!layer.registerNoteOn(64, 127_norm, 0.5f)); } } TEST_CASE("Legato triggers", "Region triggers") { MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); SECTION("First note playing") { region.parseOpcode({ "lokey", "40" }); region.parseOpcode({ "hikey", "50" }); region.parseOpcode({ "trigger", "first" }); + Layer layer { ®ion, midiState }; midiState.noteOnEvent(0, 40, 64_norm); - REQUIRE(region.registerNoteOn(40, 64_norm, 0.5f)); + REQUIRE(layer.registerNoteOn(40, 64_norm, 0.5f)); midiState.noteOnEvent(0, 41, 64_norm); - REQUIRE(!region.registerNoteOn(41, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOn(41, 64_norm, 0.5f)); midiState.noteOffEvent(0, 40, 0_norm); - region.registerNoteOff(40, 0_norm, 0.5f); + layer.registerNoteOff(40, 0_norm, 0.5f); midiState.noteOffEvent(0, 41, 0_norm); - region.registerNoteOff(41, 0_norm, 0.5f); + layer.registerNoteOff(41, 0_norm, 0.5f); midiState.noteOnEvent(0, 42, 64_norm); - REQUIRE(region.registerNoteOn(42, 64_norm, 0.5f)); + REQUIRE(layer.registerNoteOn(42, 64_norm, 0.5f)); } SECTION("Second note playing") @@ -191,16 +210,17 @@ TEST_CASE("Legato triggers", "Region triggers") region.parseOpcode({ "lokey", "40" }); region.parseOpcode({ "hikey", "50" }); region.parseOpcode({ "trigger", "legato" }); + Layer layer { ®ion, midiState }; midiState.noteOnEvent(0, 40, 64_norm); - REQUIRE(!region.registerNoteOn(40, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOn(40, 64_norm, 0.5f)); midiState.noteOnEvent(0, 41, 64_norm); - REQUIRE(region.registerNoteOn(41, 64_norm, 0.5f)); + REQUIRE(layer.registerNoteOn(41, 64_norm, 0.5f)); midiState.noteOffEvent(0, 40, 64_norm); - region.registerNoteOff(40, 0_norm, 0.5f); + layer.registerNoteOff(40, 0_norm, 0.5f); midiState.noteOffEvent(0, 41, 64_norm); - region.registerNoteOff(41, 0_norm, 0.5f); + layer.registerNoteOff(41, 0_norm, 0.5f); midiState.noteOnEvent(0, 42, 64_norm); - REQUIRE(!region.registerNoteOn(42, 64_norm, 0.5f)); + REQUIRE(!layer.registerNoteOn(42, 64_norm, 0.5f)); } } diff --git a/tests/RegionValueComputationsT.cpp b/tests/RegionValueComputationsT.cpp index f3b1abca..c7516830 100644 --- a/tests/RegionValueComputationsT.cpp +++ b/tests/RegionValueComputationsT.cpp @@ -18,8 +18,7 @@ constexpr int numRandomTests { 64 }; TEST_CASE("[Region] Crossfade in on key") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "3" }); @@ -30,8 +29,7 @@ TEST_CASE("[Region] Crossfade in on key") TEST_CASE("[Region] Crossfade in on key - 2") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "5" }); @@ -45,8 +43,7 @@ TEST_CASE("[Region] Crossfade in on key - 2") TEST_CASE("[Region] Crossfade in on key - gain") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "5" }); @@ -60,8 +57,7 @@ TEST_CASE("[Region] Crossfade in on key - gain") TEST_CASE("[Region] Crossfade out on key") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lokey", "51" }); region.parseOpcode({ "xfout_hikey", "55" }); @@ -76,8 +72,7 @@ TEST_CASE("[Region] Crossfade out on key") TEST_CASE("[Region] Crossfade out on key - gain") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lokey", "51" }); region.parseOpcode({ "xfout_hikey", "55" }); @@ -93,8 +88,7 @@ TEST_CASE("[Region] Crossfade out on key - gain") TEST_CASE("[Region] Crossfade in on velocity") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lovel", "20" }); region.parseOpcode({ "xfin_hivel", "24" }); @@ -110,8 +104,7 @@ TEST_CASE("[Region] Crossfade in on velocity") TEST_CASE("[Region] Crossfade in on vel - gain") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lovel", "20" }); region.parseOpcode({ "xfin_hivel", "24" }); @@ -128,8 +121,7 @@ TEST_CASE("[Region] Crossfade in on vel - gain") TEST_CASE("[Region] Crossfade out on vel") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lovel", "51" }); region.parseOpcode({ "xfout_hivel", "55" }); @@ -145,8 +137,7 @@ TEST_CASE("[Region] Crossfade out on vel") TEST_CASE("[Region] Crossfade out on vel - gain") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lovel", "51" }); region.parseOpcode({ "xfout_hivel", "55" }); @@ -164,104 +155,103 @@ TEST_CASE("[Region] Crossfade out on vel - gain") TEST_CASE("[Region] Crossfade in on CC") { MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_locc24", "20" }); region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); midiState.ccEvent(0, 24, 19_norm); - REQUIRE(region.getCrossfadeGain() == 0.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); midiState.ccEvent(0, 24, 20_norm); - REQUIRE(region.getCrossfadeGain() == 0.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); midiState.ccEvent(0, 24, 21_norm); - REQUIRE(region.getCrossfadeGain() == 0.5_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.5_a); midiState.ccEvent(0, 24, 22_norm); - REQUIRE(region.getCrossfadeGain() == 0.70711_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.70711_a); midiState.ccEvent(0, 24, 23_norm); - REQUIRE(region.getCrossfadeGain() == 0.86603_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.86603_a); midiState.ccEvent(0, 24, 24_norm); - REQUIRE(region.getCrossfadeGain() == 1.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); midiState.ccEvent(0, 24, 25_norm); - REQUIRE(region.getCrossfadeGain() == 1.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); } TEST_CASE("[Region] Crossfade in on CC - gain") { MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_locc24", "20" }); region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "xf_cccurve", "gain" }); midiState.ccEvent(0, 24, 19_norm); - REQUIRE(region.getCrossfadeGain() == 0.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); midiState.ccEvent(0, 24, 20_norm); - REQUIRE(region.getCrossfadeGain() == 0.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); midiState.ccEvent(0, 24, 21_norm); - REQUIRE(region.getCrossfadeGain() == 0.25_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.25_a); midiState.ccEvent(0, 24, 22_norm); - REQUIRE(region.getCrossfadeGain() == 0.5_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.5_a); midiState.ccEvent(0, 24, 23_norm); - REQUIRE(region.getCrossfadeGain() == 0.75_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.75_a); midiState.ccEvent(0, 24, 24_norm); - REQUIRE(region.getCrossfadeGain() == 1.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); midiState.ccEvent(0, 24, 25_norm); - REQUIRE(region.getCrossfadeGain() == 1.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); } TEST_CASE("[Region] Crossfade out on CC") { MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_locc24", "20" }); region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); midiState.ccEvent(0, 24, 19_norm); - REQUIRE(region.getCrossfadeGain() == 1.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); midiState.ccEvent(0, 24, 20_norm); - REQUIRE(region.getCrossfadeGain() == 1.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); midiState.ccEvent(0, 24, 21_norm); - REQUIRE(region.getCrossfadeGain() == 0.86603_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.86603_a); midiState.ccEvent(0, 24, 22_norm); - REQUIRE(region.getCrossfadeGain() == 0.70711_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.70711_a); midiState.ccEvent(0, 24, 23_norm); - REQUIRE(region.getCrossfadeGain() == 0.5_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.5_a); midiState.ccEvent(0, 24, 24_norm); - REQUIRE(region.getCrossfadeGain() == 0.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); midiState.ccEvent(0, 24, 25_norm); - REQUIRE(region.getCrossfadeGain() == 0.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); } TEST_CASE("[Region] Crossfade out on CC - gain") { MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_locc24", "20" }); region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "xf_cccurve", "gain" }); midiState.ccEvent(0, 24, 19_norm); - REQUIRE(region.getCrossfadeGain() == 1.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); midiState.ccEvent(0, 24, 20_norm); - REQUIRE(region.getCrossfadeGain() == 1.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); midiState.ccEvent(0, 24, 21_norm); - REQUIRE(region.getCrossfadeGain() == 0.75_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.75_a); midiState.ccEvent(0, 24, 22_norm); - REQUIRE(region.getCrossfadeGain() == 0.5_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.5_a); midiState.ccEvent(0, 24, 23_norm); - REQUIRE(region.getCrossfadeGain() == 0.25_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.25_a); midiState.ccEvent(0, 24, 24_norm); - REQUIRE(region.getCrossfadeGain() == 0.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); midiState.ccEvent(0, 24, 25_norm); - REQUIRE(region.getCrossfadeGain() == 0.0_a); + REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); } TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "0" }); REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a); @@ -271,8 +261,7 @@ TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "100" }); REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a); @@ -281,8 +270,7 @@ TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack") TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "-100" }); REQUIRE(region.getNoteGain(64, 127_norm) == Approx(0.0).margin(0.0001)); @@ -293,35 +281,35 @@ TEST_CASE("[Region] rt_decay") { MidiState midiState; midiState.setSampleRate(1000); - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "trigger", "release" }); region.parseOpcode({ "rt_decay", "10" }); midiState.noteOnEvent(0, 64, 64_norm); midiState.advanceTime(100); - REQUIRE( region.getBaseVolumedB(64) == Approx(Default::volume - 1.0f).margin(0.1) ); + REQUIRE( region.getBaseVolumedB(midiState, 64) == Approx(Default::volume - 1.0f).margin(0.1) ); region.parseOpcode({ "rt_decay", "20" }); midiState.noteOnEvent(0, 64, 64_norm); midiState.advanceTime(100); - REQUIRE( region.getBaseVolumedB(64) == Approx(Default::volume - 2.0f).margin(0.1) ); + REQUIRE( region.getBaseVolumedB(midiState, 64) == Approx(Default::volume - 2.0f).margin(0.1) ); region.parseOpcode({ "trigger", "attack" }); midiState.noteOnEvent(0, 64, 64_norm); midiState.advanceTime(100); - REQUIRE( region.getBaseVolumedB(64) == Approx(Default::volume).margin(0.1) ); + REQUIRE( region.getBaseVolumedB(midiState, 64) == Approx(Default::volume).margin(0.1) ); } TEST_CASE("[Region] Base delay") { MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "delay", "10" }); - REQUIRE( region.getDelay() == 10.0f ); + REQUIRE( region.getDelay(midiState) == 10.0f ); region.parseOpcode({ "delay_random", "10" }); Random::randomGenerator.seed(42); for (int i = 0; i < numRandomTests; ++i) { - auto delay = region.getDelay(); + auto delay = region.getDelay(midiState); REQUIRE( (delay >= 10.0 && delay <= 20.0) ); } } @@ -329,25 +317,24 @@ TEST_CASE("[Region] Base delay") TEST_CASE("[Region] Offsets with CCs") { MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; region.parseOpcode({ "offset_cc4", "255" }); region.parseOpcode({ "offset", "10" }); - REQUIRE( region.getOffset() == 10 ); + REQUIRE( region.getOffset(midiState) == 10 ); midiState.ccEvent(0, 4, 127_norm); - REQUIRE( region.getOffset() == 265 ); + REQUIRE( region.getOffset(midiState) == 265 ); midiState.ccEvent(0, 4, 100_norm); - REQUIRE( region.getOffset() == 210 ); + REQUIRE( region.getOffset(midiState) == 210 ); midiState.ccEvent(0, 4, 10_norm); - REQUIRE( region.getOffset() == 30 ); + REQUIRE( region.getOffset(midiState) == 30 ); midiState.ccEvent(0, 4, 0); - REQUIRE( region.getOffset() == 10 ); + REQUIRE( region.getOffset(midiState) == 10 ); } TEST_CASE("[Region] Pitch variation with veltrack") { - MidiState midiState; - Region region { 0, midiState }; + Region region { 0 }; REQUIRE(region.getBasePitchVariation(60.0, 0_norm) == 1.0); REQUIRE(region.getBasePitchVariation(60.0, 64_norm) == 1.0); @@ -357,4 +344,3 @@ TEST_CASE("[Region] Pitch variation with veltrack") REQUIRE(region.getBasePitchVariation(60.0, 64_norm) == Approx(centsFactor(600.0)).margin(0.01f)); REQUIRE(region.getBasePitchVariation(60.0, 127_norm) == Approx(centsFactor(1200.0)).margin(0.01f)); } - diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index cb97a0f7..5ae2cb51 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -5,13 +5,15 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "sfizz/Synth.h" +#include "sfizz/Region.h" +#include "sfizz/Layer.h" #include "sfizz/SisterVoiceRing.h" #include "sfizz/SfzHelpers.h" #include "sfizz/utility/NumericId.h" #include "BitArray.h" #include "TestHelpers.h" -#include #include "catch2/catch.hpp" +#include using namespace Catch::literals; using namespace sfz::literals; @@ -1111,7 +1113,7 @@ TEST_CASE("[Synth] Release (Multiple notes, release, cleared the delayed voices sortAll(requiredVelocities, actualVelocities); REQUIRE( requiredVelocities == actualVelocities ); - REQUIRE( synth.getRegionView(1)->delayedSustainReleases.empty() ); + REQUIRE( synth.getLayerView(1)->delayedSustainReleases_.empty() ); } TEST_CASE("[Synth] Release (Multiple notes after pedal is down, release, cleared the delayed voices after)") @@ -1141,7 +1143,7 @@ TEST_CASE("[Synth] Release (Multiple notes after pedal is down, release, cleared sortAll(requiredVelocities, actualVelocities); REQUIRE( requiredVelocities == actualVelocities ); - REQUIRE( synth.getRegionView(1)->delayedSustainReleases.empty() ); + REQUIRE( synth.getLayerView(1)->delayedSustainReleases_.empty() ); } TEST_CASE("[Synth] Release (Multiple note ons during pedal down)") @@ -1168,7 +1170,7 @@ TEST_CASE("[Synth] Release (Multiple note ons during pedal down)") } sortAll(requiredVelocities, actualVelocities); REQUIRE( requiredVelocities == actualVelocities ); - REQUIRE( synth.getRegionView(1)->delayedSustainReleases.empty() ); + REQUIRE( synth.getLayerView(1)->delayedSustainReleases_.empty() ); } TEST_CASE("[Synth] No release sample after the main sample stopped sounding by default") @@ -1203,7 +1205,7 @@ TEST_CASE("[Synth] No release sample after the main sample stopped sounding by d synth.cc(0, 64, 0); REQUIRE( synth.getNumActiveVoices() == 0 ); - REQUIRE( synth.getRegionView(1)->delayedSustainReleases.empty() ); + REQUIRE( synth.getLayerView(1)->delayedSustainReleases_.empty() ); } TEST_CASE("[Synth] If rt_dead is active the release sample can sound after the attack sample died") @@ -1238,7 +1240,7 @@ TEST_CASE("[Synth] If rt_dead is active the release sample can sound after the a synth.cc(0, 64, 0); REQUIRE( synth.getNumActiveVoices() == 0 ); - REQUIRE( synth.getRegionView(1)->delayedSustainReleases.empty() ); + REQUIRE( synth.getLayerView(1)->delayedSustainReleases_.empty() ); } TEST_CASE("[Synth] sw_default works at a global level")