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