Remove CCArray, and store floats internally

This commit is contained in:
Paul Fd 2020-03-17 00:17:18 +01:00 committed by Paul Ferrand
parent f8801e78e4
commit 9373f31945
11 changed files with 209 additions and 151 deletions

View file

@ -18,15 +18,14 @@ void ADSREnvelope<Type>::reset(const Region& region, const MidiState& state, int
return static_cast<int>(timeInSeconds * sampleRate); return static_cast<int>(timeInSeconds * sampleRate);
}; };
const auto ccArray = state.getCCArray(); this->delay = delay + secondsToSamples(region.amplitudeEG.getDelay(state, velocity));
this->delay = delay + secondsToSamples(region.amplitudeEG.getDelay(ccArray, velocity)); this->attack = secondsToSamples(region.amplitudeEG.getAttack(state, velocity));
this->attack = secondsToSamples(region.amplitudeEG.getAttack(ccArray, velocity)); this->decay = secondsToSamples(region.amplitudeEG.getDecay(state, velocity));
this->decay = secondsToSamples(region.amplitudeEG.getDecay(ccArray, velocity)); this->release = secondsToSamples(region.amplitudeEG.getRelease(state, velocity));
this->release = secondsToSamples(region.amplitudeEG.getRelease(ccArray, velocity)); this->hold = secondsToSamples(region.amplitudeEG.getHold(state, velocity));
this->hold = secondsToSamples(region.amplitudeEG.getHold(ccArray, velocity));
this->peak = 1.0; this->peak = 1.0;
this->sustain = normalizePercents(region.amplitudeEG.getSustain(ccArray, velocity)); this->sustain = normalizePercents(region.amplitudeEG.getSustain(state, velocity));
this->start = this->peak * normalizePercents(region.amplitudeEG.getStart(ccArray, velocity)); this->start = this->peak * normalizePercents(region.amplitudeEG.getStart(state, velocity));
releaseDelay = 0; releaseDelay = 0;
shouldRelease = false; shouldRelease = false;

View file

@ -29,6 +29,7 @@
#include "Macros.h" #include "Macros.h"
#include "LeakDetector.h" #include "LeakDetector.h"
#include "SfzHelpers.h" #include "SfzHelpers.h"
#include "MidiState.h"
#include <absl/types/optional.h> #include <absl/types/optional.h>
@ -42,6 +43,23 @@ namespace sfz
* TODO: should be updated for SFZ v2 * TODO: should be updated for SFZ v2
* *
*/ */
/**
* @brief If a cc switch exists for the value, returns the value with the CC modifier, otherwise returns the value alone.
*
* @param ccValues
* @param ccSwitch
* @param value
* @return float
*/
inline float ccSwitchedValue(const MidiState& state, const absl::optional<CCValuePair<float>>& ccSwitch, float value) noexcept
{
if (ccSwitch)
return value + ccSwitch->value * normalizeCC(state.getCCValue(ccSwitch->cc));
else
return value;
}
struct EGDescription struct EGDescription
{ {
EGDescription() = default; EGDescription() = default;
@ -76,80 +94,80 @@ struct EGDescription
/** /**
* @brief Get the attack with possibly a CC modifier and a velocity modifier * @brief Get the attack with possibly a CC modifier and a velocity modifier
* *
* @param ccValues * @param state
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getAttack(const SfzCCArray &ccValues, uint8_t velocity) const noexcept float getAttack(const MidiState &state, uint8_t velocity) const noexcept
{ {
return Default::egTimeRange.clamp(ccSwitchedValue(ccValues, ccAttack, attack) + normalizeVelocity(velocity)*vel2attack); return Default::egTimeRange.clamp(ccSwitchedValue(state, ccAttack, attack) + normalizeVelocity(velocity)*vel2attack);
} }
/** /**
* @brief Get the decay with possibly a CC modifier and a velocity modifier * @brief Get the decay with possibly a CC modifier and a velocity modifier
* *
* @param ccValues * @param state
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getDecay(const SfzCCArray &ccValues, uint8_t velocity) const noexcept float getDecay(const MidiState &state, uint8_t velocity) const noexcept
{ {
return Default::egTimeRange.clamp(ccSwitchedValue(ccValues, ccDecay, decay) + normalizeVelocity(velocity)*vel2decay); return Default::egTimeRange.clamp(ccSwitchedValue(state, ccDecay, decay) + normalizeVelocity(velocity)*vel2decay);
} }
/** /**
* @brief Get the delay with possibly a CC modifier and a velocity modifier * @brief Get the delay with possibly a CC modifier and a velocity modifier
* *
* @param ccValues * @param state
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getDelay(const SfzCCArray &ccValues, uint8_t velocity) const noexcept float getDelay(const MidiState &state, uint8_t velocity) const noexcept
{ {
return Default::egTimeRange.clamp(ccSwitchedValue(ccValues, ccDelay, delay) + normalizeVelocity(velocity)*vel2delay); return Default::egTimeRange.clamp(ccSwitchedValue(state, ccDelay, delay) + normalizeVelocity(velocity)*vel2delay);
} }
/** /**
* @brief Get the holding duration with possibly a CC modifier and a velocity modifier * @brief Get the holding duration with possibly a CC modifier and a velocity modifier
* *
* @param ccValues * @param state
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getHold(const SfzCCArray &ccValues, uint8_t velocity) const noexcept float getHold(const MidiState &state, uint8_t velocity) const noexcept
{ {
return Default::egTimeRange.clamp(ccSwitchedValue(ccValues, ccHold, hold) + normalizeVelocity(velocity)*vel2hold); return Default::egTimeRange.clamp(ccSwitchedValue(state, ccHold, hold) + normalizeVelocity(velocity)*vel2hold);
} }
/** /**
* @brief Get the release duration with possibly a CC modifier and a velocity modifier * @brief Get the release duration with possibly a CC modifier and a velocity modifier
* *
* @param ccValues * @param state
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getRelease(const SfzCCArray &ccValues, uint8_t velocity) const noexcept float getRelease(const MidiState &state, uint8_t velocity) const noexcept
{ {
return Default::egTimeRange.clamp(ccSwitchedValue(ccValues, ccRelease, release) + normalizeVelocity(velocity)*vel2release); return Default::egTimeRange.clamp(ccSwitchedValue(state, ccRelease, release) + normalizeVelocity(velocity)*vel2release);
} }
/** /**
* @brief Get the starting level with possibly a CC modifier and a velocity modifier * @brief Get the starting level with possibly a CC modifier and a velocity modifier
* *
* @param ccValues * @param state
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getStart(const SfzCCArray &ccValues, uint8_t velocity) const noexcept float getStart(const MidiState &state, uint8_t velocity) const noexcept
{ {
UNUSED(velocity); UNUSED(velocity);
return Default::egPercentRange.clamp(ccSwitchedValue(ccValues, ccStart, start)); return Default::egPercentRange.clamp(ccSwitchedValue(state, ccStart, start));
} }
/** /**
* @brief Get the sustain level with possibly a CC modifier and a velocity modifier * @brief Get the sustain level with possibly a CC modifier and a velocity modifier
* *
* @param ccValues * @param state
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getSustain(const SfzCCArray &ccValues, uint8_t velocity) const noexcept float getSustain(const MidiState &state, uint8_t velocity) const noexcept
{ {
return Default::egPercentRange.clamp(ccSwitchedValue(ccValues, ccSustain, sustain) + normalizeVelocity(velocity)*vel2sustain); return Default::egPercentRange.clamp(ccSwitchedValue(state, ccSustain, sustain) + normalizeVelocity(velocity)*vel2sustain);
} }
LEAK_DETECTOR(EGDescription); LEAK_DETECTOR(EGDescription);
}; };

View file

@ -14,9 +14,19 @@ sfz::MidiState::MidiState()
} }
void sfz::MidiState::noteOnEvent(int delay, int noteNumber, uint8_t velocity) noexcept void sfz::MidiState::noteOnEvent(int delay, int noteNumber, uint8_t velocity) noexcept
{
noteOnEventNormalized(delay, noteNumber, normalizeVelocity(velocity));
}
void sfz::MidiState::noteOffEvent(int delay, int noteNumber, uint8_t velocity) noexcept
{
noteOffEventNormalized(delay, noteNumber, normalizeVelocity(velocity));
}
void sfz::MidiState::noteOnEventNormalized(int delay, int noteNumber, float velocity) noexcept
{ {
ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(noteNumber >= 0 && noteNumber <= 127);
ASSERT(velocity >= 0 && velocity <= 127); ASSERT(velocity >= 0 && velocity <= 1.0);
if (noteNumber >= 0 && noteNumber < 128) { if (noteNumber >= 0 && noteNumber < 128) {
lastNoteVelocities[noteNumber] = velocity; lastNoteVelocities[noteNumber] = velocity;
@ -26,10 +36,10 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, uint8_t velocity) no
} }
void sfz::MidiState::noteOffEvent(int delay, int noteNumber, uint8_t velocity) noexcept void sfz::MidiState::noteOffEventNormalized(int delay, int noteNumber, float velocity) noexcept
{ {
ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(noteNumber >= 0 && noteNumber <= 127);
ASSERT(velocity >= 0 && velocity <= 127); ASSERT(velocity >= 0.0 && velocity <= 1.0);
UNUSED(velocity); UNUSED(velocity);
if (noteNumber >= 0 && noteNumber < 128) { if (noteNumber >= 0 && noteNumber < 128) {
if (activeNotes > 0) if (activeNotes > 0)
@ -52,12 +62,18 @@ float sfz::MidiState::getNoteDuration(int noteNumber) const
} }
uint8_t sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept uint8_t sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept
{
return denormalizeVelocity(getNoteVelocityNormalized(noteNumber));
}
float sfz::MidiState::getNoteVelocityNormalized(int noteNumber) const noexcept
{ {
ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(noteNumber >= 0 && noteNumber <= 127);
return lastNoteVelocities[noteNumber]; return lastNoteVelocities[noteNumber];
} }
void sfz::MidiState::pitchBendEvent(int delay, int pitchBendValue) noexcept void sfz::MidiState::pitchBendEvent(int delay, int pitchBendValue) noexcept
{ {
ASSERT(pitchBendValue >= -8192 && pitchBendValue <= 8192); ASSERT(pitchBendValue >= -8192 && pitchBendValue <= 8192);
@ -72,24 +88,28 @@ int sfz::MidiState::getPitchBend() const noexcept
void sfz::MidiState::ccEvent(int delay, int ccNumber, uint8_t ccValue) noexcept void sfz::MidiState::ccEvent(int delay, int ccNumber, uint8_t ccValue) noexcept
{ {
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs); ccEventNormalized(delay, ccNumber, normalizeCC(ccValue));
ASSERT(ccValue >= 0 && ccValue <= 127);
cc[ccNumber] = ccValue;
} }
uint8_t sfz::MidiState::getCCValue(int ccNumber) const noexcept uint8_t sfz::MidiState::getCCValue(int ccNumber) const noexcept
{
return denormalizeCC(getCCValueNormalized(ccNumber));
}
void sfz::MidiState::ccEventNormalized(int delay, int ccNumber, float ccValue) noexcept
{
ASSERT(ccValue >= 0.0 && ccValue <= 1.0);
cc[ccNumber] = ccValue;
}
float sfz::MidiState::getCCValueNormalized(int ccNumber) const noexcept
{ {
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs); ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
return cc[ccNumber]; return cc[ccNumber];
} }
const sfz::SfzCCArray& sfz::MidiState::getCCArray() const noexcept
{
return cc;
}
void sfz::MidiState::reset(int delay) noexcept void sfz::MidiState::reset(int delay) noexcept
{ {
for (auto& velocity: lastNoteVelocities) for (auto& velocity: lastNoteVelocities)

View file

@ -7,7 +7,6 @@
#pragma once #pragma once
#include <chrono> #include <chrono>
#include <array> #include <array>
#include "SfzHelpers.h"
#include "CCMap.h" #include "CCMap.h"
#include "Range.h" #include "Range.h"
@ -31,6 +30,14 @@ public:
*/ */
void noteOnEvent(int delay, int noteNumber, uint8_t velocity) noexcept; void noteOnEvent(int delay, int noteNumber, uint8_t velocity) noexcept;
/**
* @brief Update the state after a note on event
*
* @param noteNumber
* @param velocity
*/
void noteOnEventNormalized(int delay, int noteNumber, float velocity) noexcept;
/** /**
* @brief Update the state after a note off event * @brief Update the state after a note off event
* *
@ -39,6 +46,14 @@ public:
*/ */
void noteOffEvent(int delay, int noteNumber, uint8_t velocity) noexcept; void noteOffEvent(int delay, int noteNumber, uint8_t velocity) noexcept;
/**
* @brief Update the state after a note off event
*
* @param noteNumber
* @param velocity
*/
void noteOffEventNormalized(int delay, int noteNumber, float velocity) noexcept;
int getActiveNotes() const noexcept { return activeNotes; } int getActiveNotes() const noexcept { return activeNotes; }
/** /**
@ -57,6 +72,14 @@ public:
*/ */
uint8_t getNoteVelocity(int noteNumber) const noexcept; uint8_t getNoteVelocity(int noteNumber) const noexcept;
/**
* @brief Get the note on velocity for a given note
*
* @param noteNumber
* @return float
*/
float getNoteVelocityNormalized(int noteNumber) const noexcept;
/** /**
* @brief Register a pitch bend event * @brief Register a pitch bend event
* *
@ -79,6 +102,14 @@ public:
*/ */
void ccEvent(int delay, int ccNumber, uint8_t ccValue) noexcept; void ccEvent(int delay, int ccNumber, uint8_t ccValue) noexcept;
/**
* @brief Register a CC event
*
* @param ccNumber
* @param ccValue
*/
void ccEventNormalized(int delay, int ccNumber, float ccValue) noexcept;
/** /**
* @brief Get the CC value for CC number * @brief Get the CC value for CC number
* *
@ -88,11 +119,12 @@ public:
uint8_t getCCValue(int ccNumber) const noexcept; uint8_t getCCValue(int ccNumber) const noexcept;
/** /**
* @brief Get the full CC status * @brief Get the CC value for CC number
* *
* @return const SfzCCArray& * @param ccNumber
* @return float
*/ */
const SfzCCArray& getCCArray() const noexcept; float getCCValueNormalized(int ccNumber) const noexcept;
/** /**
* @brief Reset the midi state (does not impact the last note on time) * @brief Reset the midi state (does not impact the last note on time)
@ -140,12 +172,12 @@ private:
* depressed notes. * depressed notes.
* *
*/ */
MidiNoteArray<uint8_t> lastNoteVelocities; MidiNoteArray<float> lastNoteVelocities;
/** /**
* @brief Current known values for the CCs. * @brief Current known values for the CCs.
* *
*/ */
SfzCCArray cc; std::array<float, config::numCCs> cc;
/** /**
* Pitch bend status * Pitch bend status
*/ */

View file

@ -1009,19 +1009,19 @@ float sfz::Region::getNoteGain(int noteNumber, uint8_t velocity) const noexcept
return baseGain; return baseGain;
} }
float sfz::Region::getCrossfadeGain(const sfz::SfzCCArray& ccState) const noexcept float sfz::Region::getCrossfadeGain() const noexcept
{ {
float gain { 1.0f }; float gain { 1.0f };
// Crossfades due to CC states // Crossfades due to CC states
for (const auto& valuePair : crossfadeCCInRange) { for (const auto& valuePair : crossfadeCCInRange) {
const auto ccValue = ccState[valuePair.cc]; const auto ccValue = midiState.getCCValue(valuePair.cc);
const auto crossfadeRange = valuePair.value; const auto crossfadeRange = valuePair.value;
gain *= crossfadeIn(crossfadeRange, ccValue, crossfadeCCCurve); gain *= crossfadeIn(crossfadeRange, ccValue, crossfadeCCCurve);
} }
for (const auto& valuePair : crossfadeCCOutRange) { for (const auto& valuePair : crossfadeCCOutRange) {
const auto ccValue = ccState[valuePair.cc]; const auto ccValue = midiState.getCCValue(valuePair.cc);
const auto crossfadeRange = valuePair.value; const auto crossfadeRange = valuePair.value;
gain *= crossfadeOut(crossfadeRange, ccValue, crossfadeCCCurve); gain *= crossfadeOut(crossfadeRange, ccValue, crossfadeCCCurve);
} }

View file

@ -152,7 +152,7 @@ struct Region {
* @param ccState * @param ccState
* @return float * @return float
*/ */
float getCrossfadeGain(const SfzCCArray& ccState) const noexcept; float getCrossfadeGain() 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.

View file

@ -18,7 +18,6 @@
namespace sfz namespace sfz
{ {
using SfzCCArray = std::array<uint8_t, config::numCCs>;
using CCNamePair = std::pair<uint16_t, std::string>; using CCNamePair = std::pair<uint16_t, std::string>;
template<class ValueType> template<class ValueType>
@ -83,6 +82,16 @@ constexpr T denormalize7Bits(float value)
return static_cast<T>(value * 127.0f); return static_cast<T>(value * 127.0f);
} }
constexpr uint8_t denormalizeCC(float value)
{
return denormalize7Bits<uint8_t>(value);
}
constexpr uint8_t denormalizeVelocity(float value)
{
return denormalize7Bits<uint8_t>(value);
}
template<class T> template<class T>
constexpr float normalize7Bits(T value) constexpr float normalize7Bits(T value)
{ {
@ -141,22 +150,6 @@ constexpr float normalizeBend(float bendValue)
return min(max(bendValue, -8191.0f), 8191.0f) / 8191.0f; return min(max(bendValue, -8191.0f), 8191.0f) / 8191.0f;
} }
/**
* @brief If a cc switch exists for the value, returns the value with the CC modifier, otherwise returns the value alone.
*
* @param ccValues
* @param ccSwitch
* @param value
* @return float
*/
inline float ccSwitchedValue(const SfzCCArray& ccValues, const absl::optional<CCValuePair<float>>& ccSwitch, float value) noexcept
{
if (ccSwitch)
return value + ccSwitch->value * normalizeCC(ccValues[ccSwitch->cc]);
else
return value;
}
/** /**
* @brief Convert a note in string to its equivalent midi note number * @brief Convert a note in string to its equivalent midi note number
* *

View file

@ -89,7 +89,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value
gain += normalizeCC(resources.midiState.getCCValue(region->amplitudeCC->cc)) * normalizePercents(region->amplitudeCC->value); gain += normalizeCC(resources.midiState.getCCValue(region->amplitudeCC->cc)) * normalizePercents(region->amplitudeCC->value);
amplitudeEnvelope.reset(Default::normalizedRange.clamp(gain)); amplitudeEnvelope.reset(Default::normalizedRange.clamp(gain));
float crossfadeGain { region->getCrossfadeGain(resources.midiState.getCCArray()) }; float crossfadeGain { region->getCrossfadeGain() };
crossfadeEnvelope.reset(Default::normalizedRange.clamp(crossfadeGain)); crossfadeEnvelope.reset(Default::normalizedRange.clamp(crossfadeGain));
basePan = normalizePercents(region->pan); basePan = normalizePercents(region->pan);
@ -225,7 +225,7 @@ void sfz::Voice::registerCC(int delay, int ccNumber, uint8_t ccValue) noexcept
} }
if (region->crossfadeCCInRange.contains(ccNumber) || region->crossfadeCCOutRange.contains(ccNumber)) { if (region->crossfadeCCInRange.contains(ccNumber) || region->crossfadeCCOutRange.contains(ccNumber)) {
const float crossfadeGain = region->getCrossfadeGain(resources.midiState.getCCArray()); const float crossfadeGain = region->getCrossfadeGain();
crossfadeEnvelope.registerEvent(delay, Default::normalizedRange.clamp(crossfadeGain)); crossfadeEnvelope.registerEvent(delay, Default::normalizedRange.clamp(crossfadeGain));
} }
} }

View file

@ -12,108 +12,108 @@ using namespace Catch::literals;
TEST_CASE("[EGDescription] Attack range") TEST_CASE("[EGDescription] Attack range")
{ {
sfz::EGDescription eg; sfz::EGDescription eg;
sfz::SfzCCArray ccArray { 0 }; sfz::MidiState state;
eg.attack = 1; eg.attack = 1;
eg.vel2attack = -1.27f; eg.vel2attack = -1.27f;
eg.ccAttack = { 63, 1.27f }; eg.ccAttack = { 63, 1.27f };
REQUIRE( eg.getAttack(ccArray, 0) == 1.0f ); REQUIRE( eg.getAttack(state, 0) == 1.0f );
REQUIRE( eg.getAttack(ccArray, 127) == 0.0f ); REQUIRE( eg.getAttack(state, 127) == 0.0f );
ccArray[63] = 127; state.ccEvent(0, 63, 127);
REQUIRE( eg.getAttack(ccArray, 127) == 1.0f ); REQUIRE( eg.getAttack(state, 127) == 1.0f );
REQUIRE( eg.getAttack(ccArray, 0) == 2.27f ); REQUIRE( eg.getAttack(state, 0) == 2.27f );
eg.ccAttack = { 63, 127.0f }; eg.ccAttack = { 63, 127.0f };
REQUIRE( eg.getAttack(ccArray, 0) == 100.0f ); REQUIRE( eg.getAttack(state, 0) == 100.0f );
} }
TEST_CASE("[EGDescription] Delay range") TEST_CASE("[EGDescription] Delay range")
{ {
sfz::EGDescription eg; sfz::EGDescription eg;
sfz::SfzCCArray ccArray { 0 }; sfz::MidiState state;
eg.delay = 1; eg.delay = 1;
eg.vel2delay = -1.27f; eg.vel2delay = -1.27f;
eg.ccDelay = { 63, 1.27f }; eg.ccDelay = { 63, 1.27f };
REQUIRE( eg.getDelay(ccArray, 0) == 1.0f ); REQUIRE( eg.getDelay(state, 0) == 1.0f );
REQUIRE( eg.getDelay(ccArray, 127) == 0.0f ); REQUIRE( eg.getDelay(state, 127) == 0.0f );
ccArray[63] = 127; state.ccEvent(0, 63, 127);
REQUIRE( eg.getDelay(ccArray, 127) == 1.0f ); REQUIRE( eg.getDelay(state, 127) == 1.0f );
REQUIRE( eg.getDelay(ccArray, 0) == 2.27f ); REQUIRE( eg.getDelay(state, 0) == 2.27f );
eg.ccDelay = { 63, 127.0f }; eg.ccDelay = { 63, 127.0f };
REQUIRE( eg.getDelay(ccArray, 0) == 100.0f ); REQUIRE( eg.getDelay(state, 0) == 100.0f );
} }
TEST_CASE("[EGDescription] Decay range") TEST_CASE("[EGDescription] Decay range")
{ {
sfz::EGDescription eg; sfz::EGDescription eg;
sfz::SfzCCArray ccArray { 0 }; sfz::MidiState state;
eg.decay = 1.0f; eg.decay = 1.0f;
eg.vel2decay = -1.27f; eg.vel2decay = -1.27f;
eg.ccDecay = { 63, 1.27f }; eg.ccDecay = { 63, 1.27f };
REQUIRE( eg.getDecay(ccArray, 0) == 1.0f ); REQUIRE( eg.getDecay(state, 0) == 1.0f );
REQUIRE( eg.getDecay(ccArray, 127) == 0.0f ); REQUIRE( eg.getDecay(state, 127) == 0.0f );
ccArray[63] = 127; state.ccEvent(0, 63, 127);
REQUIRE( eg.getDecay(ccArray, 127) == 1.0f ); REQUIRE( eg.getDecay(state, 127) == 1.0f );
REQUIRE( eg.getDecay(ccArray, 0) == 2.27f ); REQUIRE( eg.getDecay(state, 0) == 2.27f );
eg.ccDecay = { 63, 127.0f }; eg.ccDecay = { 63, 127.0f };
REQUIRE( eg.getDecay(ccArray, 0) == 100.0f ); REQUIRE( eg.getDecay(state, 0) == 100.0f );
} }
TEST_CASE("[EGDescription] Release range") TEST_CASE("[EGDescription] Release range")
{ {
sfz::EGDescription eg; sfz::EGDescription eg;
sfz::SfzCCArray ccArray { 0 }; sfz::MidiState state;
eg.release = 1; eg.release = 1;
eg.vel2release = -1.27f; eg.vel2release = -1.27f;
eg.ccRelease = { 63, 1.27f }; eg.ccRelease = { 63, 1.27f };
REQUIRE( eg.getRelease(ccArray, 0) == 1.0f ); REQUIRE( eg.getRelease(state, 0) == 1.0f );
REQUIRE( eg.getRelease(ccArray, 127) == 0.0f ); REQUIRE( eg.getRelease(state, 127) == 0.0f );
ccArray[63] = 127; state.ccEvent(0, 63, 127);
REQUIRE( eg.getRelease(ccArray, 127) == 1.0f ); REQUIRE( eg.getRelease(state, 127) == 1.0f );
REQUIRE( eg.getRelease(ccArray, 0) == 2.27f ); REQUIRE( eg.getRelease(state, 0) == 2.27f );
eg.ccRelease = { 63, 127.0f }; eg.ccRelease = { 63, 127.0f };
REQUIRE( eg.getRelease(ccArray, 0) == 100.0f ); REQUIRE( eg.getRelease(state, 0) == 100.0f );
} }
TEST_CASE("[EGDescription] Hold range") TEST_CASE("[EGDescription] Hold range")
{ {
sfz::EGDescription eg; sfz::EGDescription eg;
sfz::SfzCCArray ccArray { 0 }; sfz::MidiState state;
eg.hold = 1; eg.hold = 1;
eg.vel2hold = -1.27f; eg.vel2hold = -1.27f;
eg.ccHold = { 63, 1.27f }; eg.ccHold = { 63, 1.27f };
REQUIRE( eg.getHold(ccArray, 0) == 1.0f ); REQUIRE( eg.getHold(state, 0) == 1.0f );
REQUIRE( eg.getHold(ccArray, 127) == 0.0f ); REQUIRE( eg.getHold(state, 127) == 0.0f );
ccArray[63] = 127; state.ccEvent(0, 63, 127);
REQUIRE( eg.getHold(ccArray, 127) == 1.0f ); REQUIRE( eg.getHold(state, 127) == 1.0f );
REQUIRE( eg.getHold(ccArray, 0) == 2.27f ); REQUIRE( eg.getHold(state, 0) == 2.27f );
eg.ccHold = { 63, 127.0f }; eg.ccHold = { 63, 127.0f };
REQUIRE( eg.getHold(ccArray, 0) == 100.0f ); REQUIRE( eg.getHold(state, 0) == 100.0f );
} }
TEST_CASE("[EGDescription] Sustain level") TEST_CASE("[EGDescription] Sustain level")
{ {
sfz::EGDescription eg; sfz::EGDescription eg;
sfz::SfzCCArray ccArray { 0 }; sfz::MidiState state;
eg.sustain = 50; eg.sustain = 50;
eg.vel2sustain = -100; eg.vel2sustain = -100;
eg.ccSustain = { 63, 100.0f }; eg.ccSustain = { 63, 100.0f };
REQUIRE( eg.getSustain(ccArray, 0) == 50.0f ); REQUIRE( eg.getSustain(state, 0) == 50.0f );
REQUIRE( eg.getSustain(ccArray, 127) == 0.0f ); REQUIRE( eg.getSustain(state, 127) == 0.0f );
ccArray[63] = 127; state.ccEvent(0, 63, 127);
REQUIRE( eg.getSustain(ccArray, 127) == 50.0f ); REQUIRE( eg.getSustain(state, 127) == 50.0f );
eg.ccSustain = { 63, 200.0f }; eg.ccSustain = { 63, 200.0f };
REQUIRE( eg.getSustain(ccArray, 0) == 100.0f ); REQUIRE( eg.getSustain(state, 0) == 100.0f );
} }
TEST_CASE("[EGDescription] Start level") TEST_CASE("[EGDescription] Start level")
{ {
sfz::EGDescription eg; sfz::EGDescription eg;
sfz::SfzCCArray ccArray { 0 }; sfz::MidiState state;
eg.start = 0; eg.start = 0;
eg.ccStart = { 63, 127.0f }; eg.ccStart = { 63, 127.0f };
REQUIRE( eg.getStart(ccArray, 0) == 0.0f ); REQUIRE( eg.getStart(state, 0) == 0.0f );
REQUIRE( eg.getStart(ccArray, 127) == 0.0f ); REQUIRE( eg.getStart(state, 127) == 0.0f );
ccArray[63] = 127; state.ccEvent(0, 63, 127);
REQUIRE( eg.getStart(ccArray, 0) == 100.0f ); REQUIRE( eg.getStart(state, 0) == 100.0f );
eg.ccStart = { 63, -127.0f }; eg.ccStart = { 63, -127.0f };
REQUIRE( eg.getStart(ccArray, 0) == 0.0f ); REQUIRE( eg.getStart(state, 0) == 0.0f );
} }

View file

@ -18,21 +18,18 @@ using namespace Catch::literals;
TEST_CASE("[MidiState] Initial values") TEST_CASE("[MidiState] Initial values")
{ {
sfz::MidiState state; sfz::MidiState state;
for (auto& cc: state.getCCArray()) for (unsigned cc = 0; cc < sfz::config::numCCs; cc++)
REQUIRE( cc == 0 ); REQUIRE( state.getCCValue(cc) == 0 );
REQUIRE( state.getPitchBend() == 0 ); REQUIRE( state.getPitchBend() == 0 );
} }
TEST_CASE("[MidiState] Set and get CCs") TEST_CASE("[MidiState] Set and get CCs")
{ {
sfz::MidiState state; sfz::MidiState state;
const auto& cc = state.getCCArray();
state.ccEvent(0, 24, 23); state.ccEvent(0, 24, 23);
state.ccEvent(0, 123, 124); state.ccEvent(0, 123, 124);
REQUIRE(state.getCCValue(24) == 23); REQUIRE(state.getCCValue(24) == 23);
REQUIRE(cc[24] == 23);
REQUIRE(state.getCCValue(123) == 124); REQUIRE(state.getCCValue(123) == 124);
REQUIRE(cc[123] == 124);
} }
TEST_CASE("[MidiState] Set and get pitch bends") TEST_CASE("[MidiState] Set and get pitch bends")
@ -68,6 +65,5 @@ TEST_CASE("[MidiState] Set and get note velocities")
TEST_CASE("[MidiState] Extended CCs") TEST_CASE("[MidiState] Extended CCs")
{ {
sfz::MidiState state; sfz::MidiState state;
REQUIRE(state.getCCArray().size() >= 142);
state.ccEvent(0, 142, 64); // should not trap state.ccEvent(0, 142, 64); // should not trap
} }

View file

@ -166,13 +166,13 @@ TEST_CASE("[Region] Crossfade in on CC")
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); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); midiState.ccEvent(0, 24, 19); REQUIRE( region.getCrossfadeGain() == 0.0_a );
midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain() == 0.0_a );
midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain() == 0.5_a );
midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.70711_a ); midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain() == 0.70711_a );
midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.86603_a ); midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain() == 0.86603_a );
midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain() == 1.0_a );
midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain() == 1.0_a );
} }
TEST_CASE("[Region] Crossfade in on CC - gain") TEST_CASE("[Region] Crossfade in on CC - gain")
@ -184,13 +184,13 @@ TEST_CASE("[Region] Crossfade in on CC - gain")
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); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); midiState.ccEvent(0, 24, 19); REQUIRE( region.getCrossfadeGain() == 0.0_a );
midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain() == 0.0_a );
midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.25_a ); midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain() == 0.25_a );
midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain() == 0.5_a );
midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.75_a ); midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain() == 0.75_a );
midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain() == 1.0_a );
midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain() == 1.0_a );
} }
TEST_CASE("[Region] Crossfade out on CC") TEST_CASE("[Region] Crossfade out on CC")
{ {
@ -200,13 +200,13 @@ TEST_CASE("[Region] Crossfade out on CC")
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); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); midiState.ccEvent(0, 24, 19); REQUIRE( region.getCrossfadeGain() == 1.0_a );
midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain() == 1.0_a );
midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.86603_a ); midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain() == 0.86603_a );
midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.70711_a ); midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain() == 0.70711_a );
midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain() == 0.5_a );
midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain() == 0.0_a );
midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain() == 0.0_a );
} }
TEST_CASE("[Region] Crossfade out on CC - gain") TEST_CASE("[Region] Crossfade out on CC - gain")
@ -218,13 +218,13 @@ TEST_CASE("[Region] Crossfade out on CC - gain")
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); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); midiState.ccEvent(0, 24, 19); REQUIRE( region.getCrossfadeGain() == 1.0_a );
midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain() == 1.0_a );
midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.75_a ); midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain() == 0.75_a );
midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain() == 0.5_a );
midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.25_a ); midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain() == 0.25_a );
midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain() == 0.0_a );
midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain() == 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")