Region and Synth use float velocities internally
This commit is contained in:
parent
2655418406
commit
0e1e65135e
6 changed files with 206 additions and 103 deletions
|
|
@ -68,7 +68,7 @@ namespace Default
|
||||||
|
|
||||||
// Region logic: key mapping
|
// Region logic: key mapping
|
||||||
constexpr Range<uint8_t> keyRange { 0, 127 };
|
constexpr Range<uint8_t> keyRange { 0, 127 };
|
||||||
constexpr Range<uint8_t> velocityRange { 0, 127 };
|
constexpr Range<float> velocityRange { 0.0f, 1.0f };
|
||||||
|
|
||||||
// Region logic: MIDI conditions
|
// Region logic: MIDI conditions
|
||||||
constexpr Range<uint8_t> channelRange { 1, 16 };
|
constexpr Range<uint8_t> channelRange { 1, 16 };
|
||||||
|
|
@ -121,8 +121,8 @@ namespace Default
|
||||||
constexpr Range<float> ampRandomRange { 0.0, 24.0 };
|
constexpr Range<float> ampRandomRange { 0.0, 24.0 };
|
||||||
constexpr Range<uint8_t> crossfadeKeyInRange { 0, 0 };
|
constexpr Range<uint8_t> crossfadeKeyInRange { 0, 0 };
|
||||||
constexpr Range<uint8_t> crossfadeKeyOutRange { 127, 127 };
|
constexpr Range<uint8_t> crossfadeKeyOutRange { 127, 127 };
|
||||||
constexpr Range<uint8_t> crossfadeVelInRange { 0, 0 };
|
constexpr Range<float> crossfadeVelInRange { 0.0f, 0.0f };
|
||||||
constexpr Range<uint8_t> crossfadeVelOutRange { 127, 127 };
|
constexpr Range<float> crossfadeVelOutRange { 1.0f, 1.0f };
|
||||||
constexpr Range<uint8_t> crossfadeCCInRange { 0, 0 };
|
constexpr Range<uint8_t> crossfadeCCInRange { 0, 0 };
|
||||||
constexpr Range<uint8_t> crossfadeCCOutRange { 127, 127 };
|
constexpr Range<uint8_t> crossfadeCCOutRange { 127, 127 };
|
||||||
constexpr SfzCrossfadeCurve crossfadeKeyCurve { SfzCrossfadeCurve::power };
|
constexpr SfzCrossfadeCurve crossfadeKeyCurve { SfzCrossfadeCurve::power };
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include "MidiState.h"
|
#include "MidiState.h"
|
||||||
#include "absl/strings/str_replace.h"
|
#include "absl/strings/str_replace.h"
|
||||||
#include "absl/strings/str_cat.h"
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/algorithm/container.h"
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
|
|
@ -138,10 +139,12 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
||||||
setValueFromOpcode(opcode, pitchKeycenter, Default::keyRange);
|
setValueFromOpcode(opcode, pitchKeycenter, Default::keyRange);
|
||||||
break;
|
break;
|
||||||
case hash("lovel"):
|
case hash("lovel"):
|
||||||
setRangeStartFromOpcode(opcode, velocityRange, Default::velocityRange);
|
if (auto value = readOpcode(opcode.value, Default::midi7Range))
|
||||||
|
velocityRange.setStart(normalizeVelocity(*value));
|
||||||
break;
|
break;
|
||||||
case hash("hivel"):
|
case hash("hivel"):
|
||||||
setRangeEndFromOpcode(opcode, velocityRange, Default::velocityRange);
|
if (auto value = readOpcode(opcode.value, Default::midi7Range))
|
||||||
|
velocityRange.setEnd(normalizeVelocity(*value));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Region logic: MIDI conditions
|
// Region logic: MIDI conditions
|
||||||
|
|
@ -303,8 +306,11 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
||||||
case hash("amp_velcurve_&"):
|
case hash("amp_velcurve_&"):
|
||||||
{
|
{
|
||||||
auto value = readOpcode(opcode.value, Default::ampVelcurveRange);
|
auto value = readOpcode(opcode.value, Default::ampVelcurveRange);
|
||||||
|
if (opcode.parameters.back() > 127)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (value)
|
if (value)
|
||||||
velocityPoints.emplace_back(opcode.parameters.back(), *value);
|
velocityPoints.emplace_back(normalizeVelocity(opcode.parameters.back()), *value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case hash("xfin_lokey"):
|
case hash("xfin_lokey"):
|
||||||
|
|
@ -320,16 +326,20 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
||||||
setRangeEndFromOpcode(opcode, crossfadeKeyOutRange, Default::keyRange);
|
setRangeEndFromOpcode(opcode, crossfadeKeyOutRange, Default::keyRange);
|
||||||
break;
|
break;
|
||||||
case hash("xfin_lovel"):
|
case hash("xfin_lovel"):
|
||||||
setRangeStartFromOpcode(opcode, crossfadeVelInRange, Default::velocityRange);
|
if (auto value = readOpcode(opcode.value, Default::midi7Range))
|
||||||
|
crossfadeVelInRange.setStart(normalizeVelocity(*value));
|
||||||
break;
|
break;
|
||||||
case hash("xfin_hivel"):
|
case hash("xfin_hivel"):
|
||||||
setRangeEndFromOpcode(opcode, crossfadeVelInRange, Default::velocityRange);
|
if (auto value = readOpcode(opcode.value, Default::midi7Range))
|
||||||
|
crossfadeVelInRange.setEnd(normalizeVelocity(*value));
|
||||||
break;
|
break;
|
||||||
case hash("xfout_lovel"):
|
case hash("xfout_lovel"):
|
||||||
setRangeStartFromOpcode(opcode, crossfadeVelOutRange, Default::velocityRange);
|
if (auto value = readOpcode(opcode.value, Default::midi7Range))
|
||||||
|
crossfadeVelOutRange.setStart(normalizeVelocity(*value));
|
||||||
break;
|
break;
|
||||||
case hash("xfout_hivel"):
|
case hash("xfout_hivel"):
|
||||||
setRangeEndFromOpcode(opcode, crossfadeVelOutRange, Default::velocityRange);
|
if (auto value = readOpcode(opcode.value, Default::midi7Range))
|
||||||
|
crossfadeVelOutRange.setEnd(normalizeVelocity(*value));
|
||||||
break;
|
break;
|
||||||
case hash("xf_keycurve"):
|
case hash("xf_keycurve"):
|
||||||
switch (hash(opcode.value)) {
|
switch (hash(opcode.value)) {
|
||||||
|
|
@ -773,6 +783,13 @@ bool sfz::Region::isSwitchedOn() const noexcept
|
||||||
|
|
||||||
bool sfz::Region::registerNoteOn(int noteNumber, uint8_t velocity, float randValue) noexcept
|
bool sfz::Region::registerNoteOn(int noteNumber, uint8_t velocity, float randValue) noexcept
|
||||||
{
|
{
|
||||||
|
return registerNoteOnNormalized(noteNumber, normalizeVelocity(velocity), randValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sfz::Region::registerNoteOnNormalized(int noteNumber, float velocity, float randValue) noexcept
|
||||||
|
{
|
||||||
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
|
|
||||||
if (keyswitchRange.containsWithEnd(noteNumber)) {
|
if (keyswitchRange.containsWithEnd(noteNumber)) {
|
||||||
if (keyswitch) {
|
if (keyswitch) {
|
||||||
if (*keyswitch == noteNumber)
|
if (*keyswitch == noteNumber)
|
||||||
|
|
@ -825,6 +842,13 @@ bool sfz::Region::registerNoteOn(int noteNumber, uint8_t velocity, float randVal
|
||||||
|
|
||||||
bool sfz::Region::registerNoteOff(int noteNumber, uint8_t velocity, float randValue) noexcept
|
bool sfz::Region::registerNoteOff(int noteNumber, uint8_t velocity, float randValue) noexcept
|
||||||
{
|
{
|
||||||
|
return registerNoteOffNormalized(noteNumber, normalizeVelocity(velocity), randValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sfz::Region::registerNoteOffNormalized(int noteNumber, float velocity, float randValue) noexcept
|
||||||
|
{
|
||||||
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
|
|
||||||
if (keyswitchRange.containsWithEnd(noteNumber)) {
|
if (keyswitchRange.containsWithEnd(noteNumber)) {
|
||||||
if (keyswitchDown && *keyswitchDown == noteNumber)
|
if (keyswitchDown && *keyswitchDown == noteNumber)
|
||||||
keySwitched = false;
|
keySwitched = false;
|
||||||
|
|
@ -893,11 +917,18 @@ void sfz::Region::registerTempo(float secondsPerQuarter) noexcept
|
||||||
|
|
||||||
float sfz::Region::getBasePitchVariation(int noteNumber, uint8_t velocity) const noexcept
|
float sfz::Region::getBasePitchVariation(int noteNumber, uint8_t velocity) const noexcept
|
||||||
{
|
{
|
||||||
|
return getBasePitchVariationNormalized(noteNumber, normalizeVelocity(velocity));
|
||||||
|
}
|
||||||
|
|
||||||
|
float sfz::Region::getBasePitchVariationNormalized(int noteNumber, float velocity) const noexcept
|
||||||
|
{
|
||||||
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
|
|
||||||
std::uniform_int_distribution<int> pitchDistribution { -pitchRandom, pitchRandom };
|
std::uniform_int_distribution<int> pitchDistribution { -pitchRandom, pitchRandom };
|
||||||
auto pitchVariationInCents = pitchKeytrack * (noteNumber - (int)pitchKeycenter); // note difference with pitch center
|
auto pitchVariationInCents = pitchKeytrack * (noteNumber - (int)pitchKeycenter); // note difference with pitch center
|
||||||
pitchVariationInCents += tune; // sample tuning
|
pitchVariationInCents += tune; // sample tuning
|
||||||
pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose
|
pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose
|
||||||
pitchVariationInCents += velocity / 127 * pitchVeltrack; // track velocity
|
pitchVariationInCents += velocity * pitchVeltrack; // track velocity
|
||||||
pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes
|
pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes
|
||||||
return centsFactor(pitchVariationInCents);
|
return centsFactor(pitchVariationInCents);
|
||||||
}
|
}
|
||||||
|
|
@ -961,8 +992,13 @@ float crossfadeIn(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurv
|
||||||
{
|
{
|
||||||
if (value < crossfadeRange.getStart())
|
if (value < crossfadeRange.getStart())
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
|
|
||||||
|
const auto length = static_cast<float>(crossfadeRange.length());
|
||||||
|
if (length == 0.0f)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
else if (value < crossfadeRange.getEnd()) {
|
else if (value < crossfadeRange.getEnd()) {
|
||||||
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / std::max(static_cast<float>(crossfadeRange.length()), 1.0f);
|
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
||||||
if (curve == SfzCrossfadeCurve::power)
|
if (curve == SfzCrossfadeCurve::power)
|
||||||
return sqrt(crossfadePosition);
|
return sqrt(crossfadePosition);
|
||||||
if (curve == SfzCrossfadeCurve::gain)
|
if (curve == SfzCrossfadeCurve::gain)
|
||||||
|
|
@ -977,8 +1013,13 @@ float crossfadeOut(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCur
|
||||||
{
|
{
|
||||||
if (value > crossfadeRange.getEnd())
|
if (value > crossfadeRange.getEnd())
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
|
|
||||||
|
const auto length = static_cast<float>(crossfadeRange.length());
|
||||||
|
if (length == 0.0f)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
else if (value > crossfadeRange.getStart()) {
|
else if (value > crossfadeRange.getStart()) {
|
||||||
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / std::max(static_cast<float>(crossfadeRange.length()), 1.0f);
|
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
||||||
if (curve == SfzCrossfadeCurve::power)
|
if (curve == SfzCrossfadeCurve::power)
|
||||||
return std::sqrt(1 - crossfadePosition);
|
return std::sqrt(1 - crossfadePosition);
|
||||||
if (curve == SfzCrossfadeCurve::gain)
|
if (curve == SfzCrossfadeCurve::gain)
|
||||||
|
|
@ -990,6 +1031,13 @@ float crossfadeOut(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCur
|
||||||
|
|
||||||
float sfz::Region::getNoteGain(int noteNumber, uint8_t velocity) const noexcept
|
float sfz::Region::getNoteGain(int noteNumber, uint8_t velocity) const noexcept
|
||||||
{
|
{
|
||||||
|
return getNoteGainNormalized(noteNumber, normalizeVelocity(velocity));
|
||||||
|
}
|
||||||
|
|
||||||
|
float sfz::Region::getNoteGainNormalized(int noteNumber, float velocity) const noexcept
|
||||||
|
{
|
||||||
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
|
|
||||||
float baseGain { 1.0f };
|
float baseGain { 1.0f };
|
||||||
|
|
||||||
// Amplitude key tracking
|
// Amplitude key tracking
|
||||||
|
|
@ -1000,7 +1048,7 @@ float sfz::Region::getNoteGain(int noteNumber, uint8_t velocity) const noexcept
|
||||||
baseGain *= crossfadeOut(crossfadeKeyOutRange, noteNumber, crossfadeKeyCurve);
|
baseGain *= crossfadeOut(crossfadeKeyOutRange, noteNumber, crossfadeKeyCurve);
|
||||||
|
|
||||||
// Amplitude velocity tracking
|
// Amplitude velocity tracking
|
||||||
baseGain *= velocityCurve(velocity);
|
baseGain *= velocityCurveNormalized(velocity);
|
||||||
|
|
||||||
// Crossfades related to velocity
|
// Crossfades related to velocity
|
||||||
baseGain *= crossfadeIn(crossfadeVelInRange, velocity, crossfadeVelCurve);
|
baseGain *= crossfadeIn(crossfadeVelInRange, velocity, crossfadeVelCurve);
|
||||||
|
|
@ -1031,25 +1079,30 @@ float sfz::Region::getCrossfadeGain() const noexcept
|
||||||
|
|
||||||
float sfz::Region::velocityCurve(uint8_t velocity) const noexcept
|
float sfz::Region::velocityCurve(uint8_t velocity) const noexcept
|
||||||
{
|
{
|
||||||
float gain { 1.0f };
|
return velocityCurveNormalized(normalizeVelocity(velocity));
|
||||||
|
}
|
||||||
|
|
||||||
|
float sfz::Region::velocityCurveNormalized(float velocity) const noexcept
|
||||||
|
{
|
||||||
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
|
|
||||||
|
float gain { 1.0f };
|
||||||
if (velocityPoints.size() > 0) { // Custom velocity curve
|
if (velocityPoints.size() > 0) { // Custom velocity curve
|
||||||
auto after = std::find_if(velocityPoints.begin(), velocityPoints.end(), [velocity](const std::pair<int, float>& val) { return val.first >= velocity; });
|
auto after = absl::c_find_if(velocityPoints, [velocity](const std::pair<float, float>& val) { return val.first >= velocity; });
|
||||||
auto before = after == velocityPoints.begin() ? velocityPoints.begin() : after - 1;
|
auto before = after == velocityPoints.begin() ? velocityPoints.begin() : after - 1;
|
||||||
// Linear interpolation
|
// Linear interpolation
|
||||||
float relativePositionInSegment {
|
float relativePositionInSegment {
|
||||||
static_cast<float>(velocity - before->first) / static_cast<float>(after->first - before->first)
|
(velocity - before->first) / (after->first - before->first)
|
||||||
};
|
};
|
||||||
float segmentEndpoints { after->second - before->second };
|
float segmentEndpoints { after->second - before->second };
|
||||||
gain *= relativePositionInSegment * segmentEndpoints;
|
gain *= relativePositionInSegment * segmentEndpoints;
|
||||||
} else { // Standard velocity curve
|
} else { // Standard velocity curve
|
||||||
const float floatVelocity { static_cast<float>(velocity) / 127.0f };
|
|
||||||
// FIXME: Maybe there's a prettier way to check the boundaries?
|
// FIXME: Maybe there's a prettier way to check the boundaries?
|
||||||
const float gaindB = [&]() {
|
const float gaindB = [&]() {
|
||||||
if (ampVeltrack >= 0)
|
if (ampVeltrack >= 0)
|
||||||
return floatVelocity == 0.0f ? -90.0f : 40 * std::log(floatVelocity) / std::log(10.0f);
|
return velocity == 0.0f ? -90.0f : 40 * std::log(velocity) / std::log(10.0f);
|
||||||
else
|
else
|
||||||
return floatVelocity == 1.0f ? -90.0f : 40 * std::log(1 - floatVelocity) / std::log(10.0f);
|
return velocity == 1.0f ? -90.0f : 40 * std::log(1 - velocity) / std::log(10.0f);
|
||||||
}();
|
}();
|
||||||
gain *= db2mag( gaindB * std::abs(ampVeltrack) / sfz::Default::ampVeltrackRange.getEnd());
|
gain *= db2mag( gaindB * std::abs(ampVeltrack) / sfz::Default::ampVeltrackRange.getEnd());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,30 @@ struct Region {
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
bool registerNoteOff(int noteNumber, uint8_t velocity, float randValue) noexcept;
|
bool registerNoteOff(int noteNumber, uint8_t velocity, float randValue) 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 registerNoteOnNormalized(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 registerNoteOffNormalized(int noteNumber, float velocity, float randValue) noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Register a new CC event. The region may be switched on or off using CCs so
|
* @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.
|
* this function checks if it indeeds need to activate or not.
|
||||||
|
|
@ -136,6 +160,15 @@ struct Region {
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getBasePitchVariation(int noteNumber, uint8_t velocity) const noexcept;
|
float getBasePitchVariation(int noteNumber, uint8_t velocity) const noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Get the base pitch of the region depending on which note has been
|
||||||
|
* pressed and at which velocity.
|
||||||
|
*
|
||||||
|
* @param noteNumber
|
||||||
|
* @param velocity
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
float getBasePitchVariationNormalized(int noteNumber, float velocity) const noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Get the note-related gain of the region depending on which note has been
|
* @brief Get the note-related gain of the region depending on which note has been
|
||||||
* pressed and at which velocity.
|
* pressed and at which velocity.
|
||||||
|
|
@ -145,6 +178,15 @@ struct Region {
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getNoteGain(int noteNumber, uint8_t velocity) const noexcept;
|
float getNoteGain(int noteNumber, uint8_t velocity) const noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Get the note-related gain of the region depending on which note has been
|
||||||
|
* pressed and at which velocity.
|
||||||
|
*
|
||||||
|
* @param noteNumber
|
||||||
|
* @param velocity
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
float getNoteGainNormalized(int noteNumber, float velocity) const noexcept;
|
||||||
/**
|
/**
|
||||||
* @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
|
||||||
|
|
@ -179,6 +221,12 @@ struct Region {
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float velocityCurve(uint8_t velocity) const noexcept;
|
float velocityCurve(uint8_t velocity) const noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Computes the gain value related to the velocity of the note
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
float velocityCurveNormalized(float velocity) const noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Get the region offset in samples
|
* @brief Get the region offset in samples
|
||||||
*
|
*
|
||||||
|
|
@ -243,7 +291,7 @@ struct Region {
|
||||||
|
|
||||||
// Region logic: key mapping
|
// Region logic: key mapping
|
||||||
Range<uint8_t> keyRange { Default::keyRange }; //lokey, hikey and key
|
Range<uint8_t> keyRange { Default::keyRange }; //lokey, hikey and key
|
||||||
Range<uint8_t> velocityRange { Default::velocityRange }; // hivel and lovel
|
Range<float> velocityRange { Default::velocityRange }; // hivel and lovel
|
||||||
|
|
||||||
// Region logic: MIDI conditions
|
// Region logic: MIDI conditions
|
||||||
Range<int> bendRange { Default::bendRange }; // hibend and lobend
|
Range<int> bendRange { Default::bendRange }; // hibend and lobend
|
||||||
|
|
@ -282,12 +330,12 @@ struct Region {
|
||||||
uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter
|
uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter
|
||||||
float ampKeytrack { Default::ampKeytrack }; // amp_keytrack
|
float ampKeytrack { Default::ampKeytrack }; // amp_keytrack
|
||||||
float ampVeltrack { Default::ampVeltrack }; // amp_keytrack
|
float ampVeltrack { Default::ampVeltrack }; // amp_keytrack
|
||||||
std::vector<std::pair<int, float>> velocityPoints; // amp_velcurve_N
|
std::vector<std::pair<float, float>> velocityPoints; // amp_velcurve_N
|
||||||
float ampRandom { Default::ampRandom }; // amp_random
|
float ampRandom { Default::ampRandom }; // amp_random
|
||||||
Range<uint8_t> crossfadeKeyInRange { Default::crossfadeKeyInRange };
|
Range<uint8_t> crossfadeKeyInRange { Default::crossfadeKeyInRange };
|
||||||
Range<uint8_t> crossfadeKeyOutRange { Default::crossfadeKeyOutRange };
|
Range<uint8_t> crossfadeKeyOutRange { Default::crossfadeKeyOutRange };
|
||||||
Range<uint8_t> crossfadeVelInRange { Default::crossfadeVelInRange };
|
Range<float> crossfadeVelInRange { Default::crossfadeVelInRange };
|
||||||
Range<uint8_t> crossfadeVelOutRange { Default::crossfadeVelOutRange };
|
Range<float> crossfadeVelOutRange { Default::crossfadeVelOutRange };
|
||||||
SfzCrossfadeCurve crossfadeKeyCurve { Default::crossfadeKeyCurve };
|
SfzCrossfadeCurve crossfadeKeyCurve { Default::crossfadeKeyCurve };
|
||||||
SfzCrossfadeCurve crossfadeVelCurve { Default::crossfadeVelCurve };
|
SfzCrossfadeCurve crossfadeVelCurve { Default::crossfadeVelCurve };
|
||||||
SfzCrossfadeCurve crossfadeCCCurve { Default::crossfadeCCCurve };
|
SfzCrossfadeCurve crossfadeCCCurve { Default::crossfadeCCCurve };
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ sfz::Synth::~Synth()
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& voice: voices)
|
for (auto& voice : voices)
|
||||||
voice->reset();
|
voice->reset();
|
||||||
|
|
||||||
resources.filePool.emptyFileLoadingQueues();
|
resources.filePool.emptyFileLoadingQueues();
|
||||||
|
|
@ -129,11 +129,11 @@ void sfz::Synth::clear()
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &voice: voices)
|
for (auto& voice : voices)
|
||||||
voice->reset();
|
voice->reset();
|
||||||
for (auto& list: noteActivationLists)
|
for (auto& list : noteActivationLists)
|
||||||
list.clear();
|
list.clear();
|
||||||
for (auto& list: ccActivationLists)
|
for (auto& list : ccActivationLists)
|
||||||
list.clear();
|
list.clear();
|
||||||
regions.clear();
|
regions.clear();
|
||||||
effectBuses.clear();
|
effectBuses.clear();
|
||||||
|
|
@ -275,17 +275,19 @@ void sfz::Synth::handleEffectOpcodes(const std::vector<Opcode>& members)
|
||||||
void addEndpointsToVelocityCurve(sfz::Region& region)
|
void addEndpointsToVelocityCurve(sfz::Region& region)
|
||||||
{
|
{
|
||||||
if (region.velocityPoints.size() > 0) {
|
if (region.velocityPoints.size() > 0) {
|
||||||
absl::c_sort(region.velocityPoints, [](const std::pair<int, float>& lhs, const std::pair<int, float>& rhs) { return lhs.first < rhs.first; });
|
const auto velocityStart = sfz::Default::velocityRange.getStart();
|
||||||
|
const auto velocityEnd = sfz::Default::velocityRange.getEnd();
|
||||||
|
absl::c_sort(region.velocityPoints, [](const std::pair<float, float>& lhs, const std::pair<float, float>& rhs) { return lhs.first < rhs.first; });
|
||||||
if (region.ampVeltrack > 0) {
|
if (region.ampVeltrack > 0) {
|
||||||
if (region.velocityPoints.back().first != sfz::Default::velocityRange.getEnd())
|
if (region.velocityPoints.front().first != velocityStart)
|
||||||
region.velocityPoints.push_back(std::make_pair<int, float>(127, 1.0f));
|
region.velocityPoints.insert(region.velocityPoints.begin(), std::make_pair(velocityStart, velocityStart));
|
||||||
if (region.velocityPoints.front().first != sfz::Default::velocityRange.getStart())
|
if (region.velocityPoints.back().first != velocityEnd)
|
||||||
region.velocityPoints.insert(region.velocityPoints.begin(), std::make_pair<int, float>(0, 0.0f));
|
region.velocityPoints.push_back(std::make_pair(velocityEnd, velocityEnd));
|
||||||
} else {
|
} else {
|
||||||
if (region.velocityPoints.front().first != sfz::Default::velocityRange.getEnd())
|
if (region.velocityPoints.front().first != velocityEnd)
|
||||||
region.velocityPoints.insert(region.velocityPoints.begin(), std::make_pair<int, float>(127, 0.0f));
|
region.velocityPoints.insert(region.velocityPoints.begin(), std::make_pair(velocityEnd, velocityStart));
|
||||||
if (region.velocityPoints.back().first != sfz::Default::velocityRange.getStart())
|
if (region.velocityPoints.back().first != velocityStart)
|
||||||
region.velocityPoints.push_back(std::make_pair<int, float>(0, 1.0f));
|
region.velocityPoints.push_back(std::make_pair(velocityStart, velocityEnd));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -341,8 +343,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
|
||||||
if (region->loopRange.getEnd() == Default::loopRange.getEnd())
|
if (region->loopRange.getEnd() == Default::loopRange.getEnd())
|
||||||
region->loopRange.setEnd(region->sampleEnd);
|
region->loopRange.setEnd(region->sampleEnd);
|
||||||
|
|
||||||
if (fileInformation->loopBegin != Default::loopRange.getStart() &&
|
if (fileInformation->loopBegin != Default::loopRange.getStart() && fileInformation->loopEnd != Default::loopRange.getEnd()) {
|
||||||
fileInformation->loopEnd != Default::loopRange.getEnd()) {
|
|
||||||
if (region->loopRange.getStart() == Default::loopRange.getStart())
|
if (region->loopRange.getStart() == Default::loopRange.getStart())
|
||||||
region->loopRange.setStart(fileInformation->loopBegin);
|
region->loopRange.setStart(fileInformation->loopBegin);
|
||||||
|
|
||||||
|
|
@ -374,8 +375,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto note = 0; note < 128; note++) {
|
for (auto note = 0; note < 128; note++) {
|
||||||
if (region->keyRange.containsWithEnd(note) ||
|
if (region->keyRange.containsWithEnd(note) || (region->hasKeyswitches() && region->keyswitchRange.containsWithEnd(note)))
|
||||||
(region->hasKeyswitches() && region->keyswitchRange.containsWithEnd(note)))
|
|
||||||
noteActivationLists[note].push_back(region);
|
noteActivationLists[note].push_back(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,16 +396,13 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
|
||||||
|
|
||||||
// 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::eqFrequencyUnset)
|
&& region->equalizers[0].frequency == Default::eqFrequencyUnset) {
|
||||||
{
|
|
||||||
region->equalizers[0].frequency = Default::eqFrequency1;
|
region->equalizers[0].frequency = Default::eqFrequency1;
|
||||||
if (region->equalizers.size() > 1
|
if (region->equalizers.size() > 1
|
||||||
&& region->equalizers[1].frequency == Default::eqFrequencyUnset)
|
&& region->equalizers[1].frequency == Default::eqFrequencyUnset) {
|
||||||
{
|
|
||||||
region->equalizers[1].frequency = Default::eqFrequency2;
|
region->equalizers[1].frequency = Default::eqFrequency2;
|
||||||
if (region->equalizers.size() > 2
|
if (region->equalizers.size() > 2
|
||||||
&& region->equalizers[2].frequency == Default::eqFrequencyUnset)
|
&& region->equalizers[2].frequency == Default::eqFrequencyUnset) {
|
||||||
{
|
|
||||||
region->equalizers[2].frequency = Default::eqFrequency3;
|
region->equalizers[2].frequency = Default::eqFrequency3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -425,7 +422,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
|
||||||
regions.resize(remainingRegions);
|
regions.resize(remainingRegions);
|
||||||
modificationTime = checkModificationTime();
|
modificationTime = checkModificationTime();
|
||||||
|
|
||||||
for (auto& voice: voices) {
|
for (auto& voice : voices) {
|
||||||
voice->setMaxFiltersPerVoice(maxFilters);
|
voice->setMaxFiltersPerVoice(maxFilters);
|
||||||
voice->setMaxEQsPerVoice(maxEQs);
|
voice->setMaxEQsPerVoice(maxEQs);
|
||||||
}
|
}
|
||||||
|
|
@ -468,7 +465,6 @@ int sfz::Synth::getNumActiveVoices() const noexcept
|
||||||
|
|
||||||
void sfz::Synth::garbageCollect() noexcept
|
void sfz::Synth::garbageCollect() noexcept
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
|
void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
|
|
@ -487,7 +483,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
for (auto& voice : voices)
|
for (auto& voice : voices)
|
||||||
voice->setSamplesPerBlock(samplesPerBlock);
|
voice->setSamplesPerBlock(samplesPerBlock);
|
||||||
|
|
||||||
for (auto& bus: effectBuses) {
|
for (auto& bus : effectBuses) {
|
||||||
if (bus)
|
if (bus)
|
||||||
bus->setSamplesPerBlock(samplesPerBlock);
|
bus->setSamplesPerBlock(samplesPerBlock);
|
||||||
}
|
}
|
||||||
|
|
@ -507,7 +503,7 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept
|
||||||
resources.filterPool.setSampleRate(sampleRate);
|
resources.filterPool.setSampleRate(sampleRate);
|
||||||
resources.eqPool.setSampleRate(sampleRate);
|
resources.eqPool.setSampleRate(sampleRate);
|
||||||
|
|
||||||
for (auto& bus: effectBuses) {
|
for (auto& bus : effectBuses) {
|
||||||
if (bus)
|
if (bus)
|
||||||
bus->setSampleRate(sampleRate);
|
bus->setSampleRate(sampleRate);
|
||||||
}
|
}
|
||||||
|
|
@ -532,7 +528,7 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
|
|
||||||
{ // Prepare the effect inputs. They are mixes of per-region outputs.
|
{ // Prepare the effect inputs. They are mixes of per-region outputs.
|
||||||
ScopedTiming logger { callbackBreakdown.effects };
|
ScopedTiming logger { callbackBreakdown.effects };
|
||||||
for (auto& bus: effectBuses) {
|
for (auto& bus : effectBuses) {
|
||||||
if (bus)
|
if (bus)
|
||||||
bus->clearInputs(numFrames);
|
bus->clearInputs(numFrames);
|
||||||
}
|
}
|
||||||
|
|
@ -576,7 +572,7 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
// without any <effect>, the signal is just going to flow through it.
|
// without any <effect>, the signal is just going to flow through it.
|
||||||
ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration };
|
||||||
|
|
||||||
for (auto& bus: effectBuses) {
|
for (auto& bus : effectBuses) {
|
||||||
if (bus) {
|
if (bus) {
|
||||||
bus->process(numFrames);
|
bus->process(numFrames);
|
||||||
bus->mixOutputsTo(buffer, tempMixNode, numFrames);
|
bus->mixOutputsTo(buffer, tempMixNode, numFrames);
|
||||||
|
|
@ -711,11 +707,11 @@ void sfz::Synth::pitchWheel(int delay, int pitch) noexcept
|
||||||
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
||||||
resources.midiState.pitchBendEvent(delay, pitch);
|
resources.midiState.pitchBendEvent(delay, pitch);
|
||||||
|
|
||||||
for (auto& region: regions) {
|
for (auto& region : regions) {
|
||||||
region->registerPitchWheel(pitch);
|
region->registerPitchWheel(pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& voice: voices) {
|
for (auto& voice : voices) {
|
||||||
voice->registerPitchWheel(delay, pitch);
|
voice->registerPitchWheel(delay, pitch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -753,10 +749,9 @@ std::string sfz::Synth::exportMidnam(absl::string_view model) const
|
||||||
if (model.empty())
|
if (model.empty())
|
||||||
model = config::midnamModel;
|
model = config::midnamModel;
|
||||||
|
|
||||||
doc.append_child(pugi::node_doctype).set_value(
|
doc.append_child(pugi::node_doctype).set_value("MIDINameDocument PUBLIC"
|
||||||
"MIDINameDocument PUBLIC"
|
" \"-//MIDI Manufacturers Association//DTD MIDINameDocument 1.0//EN\""
|
||||||
" \"-//MIDI Manufacturers Association//DTD MIDINameDocument 1.0//EN\""
|
" \"http://www.midi.org/dtds/MIDINameDocument10.dtd\"");
|
||||||
" \"http://www.midi.org/dtds/MIDINameDocument10.dtd\"");
|
|
||||||
|
|
||||||
pugi::xml_node root = doc.append_child("MIDINameDocument");
|
pugi::xml_node root = doc.append_child("MIDINameDocument");
|
||||||
|
|
||||||
|
|
@ -767,9 +762,11 @@ std::string sfz::Synth::exportMidnam(absl::string_view model) const
|
||||||
|
|
||||||
pugi::xml_node device = root.append_child("MasterDeviceNames");
|
pugi::xml_node device = root.append_child("MasterDeviceNames");
|
||||||
device.append_child("Manufacturer")
|
device.append_child("Manufacturer")
|
||||||
.append_child(pugi::node_pcdata).set_value(std::string(manufacturer).c_str());
|
.append_child(pugi::node_pcdata)
|
||||||
|
.set_value(std::string(manufacturer).c_str());
|
||||||
device.append_child("Model")
|
device.append_child("Model")
|
||||||
.append_child(pugi::node_pcdata).set_value(std::string(model).c_str());
|
.append_child(pugi::node_pcdata)
|
||||||
|
.set_value(std::string(model).c_str());
|
||||||
|
|
||||||
{
|
{
|
||||||
pugi::xml_node devmode = device.append_child("CustomDeviceMode");
|
pugi::xml_node devmode = device.append_child("CustomDeviceMode");
|
||||||
|
|
@ -795,7 +792,8 @@ std::string sfz::Synth::exportMidnam(absl::string_view model) const
|
||||||
}
|
}
|
||||||
|
|
||||||
chns.append_child("UsesControlNameList")
|
chns.append_child("UsesControlNameList")
|
||||||
.append_attribute("Name").set_value("Controls");
|
.append_attribute("Name")
|
||||||
|
.set_value("Controls");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -876,7 +874,7 @@ void sfz::Synth::setNumVoices(int numVoices) noexcept
|
||||||
|
|
||||||
void sfz::Synth::resetVoices(int numVoices)
|
void sfz::Synth::resetVoices(int numVoices)
|
||||||
{
|
{
|
||||||
AtomicDisabler callbackDisabler{ canEnterCallback };
|
AtomicDisabler callbackDisabler { canEnterCallback };
|
||||||
while (inCallback) {
|
while (inCallback) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
|
|
@ -885,7 +883,7 @@ void sfz::Synth::resetVoices(int numVoices)
|
||||||
for (int i = 0; i < numVoices; ++i)
|
for (int i = 0; i < numVoices; ++i)
|
||||||
voices.push_back(absl::make_unique<Voice>(resources));
|
voices.push_back(absl::make_unique<Voice>(resources));
|
||||||
|
|
||||||
for (auto& voice: voices) {
|
for (auto& voice : voices) {
|
||||||
voice->setSampleRate(this->sampleRate);
|
voice->setSampleRate(this->sampleRate);
|
||||||
voice->setSamplesPerBlock(this->samplesPerBlock);
|
voice->setSamplesPerBlock(this->samplesPerBlock);
|
||||||
}
|
}
|
||||||
|
|
@ -896,12 +894,12 @@ void sfz::Synth::resetVoices(int numVoices)
|
||||||
|
|
||||||
void sfz::Synth::setOversamplingFactor(sfz::Oversampling factor) noexcept
|
void sfz::Synth::setOversamplingFactor(sfz::Oversampling factor) noexcept
|
||||||
{
|
{
|
||||||
AtomicDisabler callbackDisabler{ canEnterCallback };
|
AtomicDisabler callbackDisabler { canEnterCallback };
|
||||||
while (inCallback) {
|
while (inCallback) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& voice: voices)
|
for (auto& voice : voices)
|
||||||
voice->reset();
|
voice->reset();
|
||||||
|
|
||||||
resources.filePool.emptyFileLoadingQueues();
|
resources.filePool.emptyFileLoadingQueues();
|
||||||
|
|
@ -916,7 +914,7 @@ sfz::Oversampling sfz::Synth::getOversamplingFactor() const noexcept
|
||||||
|
|
||||||
void sfz::Synth::setPreloadSize(uint32_t preloadSize) noexcept
|
void sfz::Synth::setPreloadSize(uint32_t preloadSize) noexcept
|
||||||
{
|
{
|
||||||
AtomicDisabler callbackDisabler{ canEnterCallback };
|
AtomicDisabler callbackDisabler { canEnterCallback };
|
||||||
while (inCallback) {
|
while (inCallback) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
|
|
@ -951,13 +949,13 @@ void sfz::Synth::resetAllControllers(int delay) noexcept
|
||||||
return;
|
return;
|
||||||
|
|
||||||
resources.midiState.resetAllControllers(delay);
|
resources.midiState.resetAllControllers(delay);
|
||||||
for (auto& voice: voices) {
|
for (auto& voice : voices) {
|
||||||
voice->registerPitchWheel(delay, 0);
|
voice->registerPitchWheel(delay, 0);
|
||||||
for (unsigned cc = 0; cc < config::numCCs; ++cc)
|
for (unsigned cc = 0; cc < config::numCCs; ++cc)
|
||||||
voice->registerCC(delay, cc, 0);
|
voice->registerCC(delay, cc, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& region: regions) {
|
for (auto& region : regions) {
|
||||||
for (unsigned cc = 0; cc < config::numCCs; ++cc)
|
for (unsigned cc = 0; cc < config::numCCs; ++cc)
|
||||||
region->registerCC(cc, 0);
|
region->registerCC(cc, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -996,13 +994,13 @@ void sfz::Synth::disableLogging() noexcept
|
||||||
|
|
||||||
void sfz::Synth::allSoundOff() noexcept
|
void sfz::Synth::allSoundOff() noexcept
|
||||||
{
|
{
|
||||||
AtomicDisabler callbackDisabler{ canEnterCallback };
|
AtomicDisabler callbackDisabler { canEnterCallback };
|
||||||
while (inCallback) {
|
while (inCallback) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &voice: voices)
|
for (auto& voice : voices)
|
||||||
voice->reset();
|
voice->reset();
|
||||||
for (auto& effectBus: effectBuses)
|
for (auto& effectBus : effectBuses)
|
||||||
effectBus->clear();
|
effectBus->clear();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@
|
||||||
// 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/SfzHelpers.h"
|
||||||
#include "catch2/catch.hpp"
|
#include "catch2/catch.hpp"
|
||||||
#include "ghc/fs_std.hpp"
|
#include "ghc/fs_std.hpp"
|
||||||
using namespace Catch::literals;
|
using namespace Catch::literals;
|
||||||
|
using namespace sfz::literals;
|
||||||
|
|
||||||
TEST_CASE("[Files] Single region (regions_one.sfz)")
|
TEST_CASE("[Files] Single region (regions_one.sfz)")
|
||||||
{
|
{
|
||||||
|
|
@ -137,11 +139,11 @@ TEST_CASE("[Files] Group from AVL")
|
||||||
REQUIRE(synth.getRegionView(i)->volume == 6.0f);
|
REQUIRE(synth.getRegionView(i)->volume == 6.0f);
|
||||||
REQUIRE(synth.getRegionView(i)->keyRange == sfz::Range<uint8_t>(36, 36));
|
REQUIRE(synth.getRegionView(i)->keyRange == sfz::Range<uint8_t>(36, 36));
|
||||||
}
|
}
|
||||||
REQUIRE(synth.getRegionView(0)->velocityRange == sfz::Range<uint8_t>(1, 26));
|
REQUIRE(synth.getRegionView(0)->velocityRange == sfz::Range<float>(1_norm, 26_norm));
|
||||||
REQUIRE(synth.getRegionView(1)->velocityRange == sfz::Range<uint8_t>(27, 52));
|
REQUIRE(synth.getRegionView(1)->velocityRange == sfz::Range<float>(27_norm, 52_norm));
|
||||||
REQUIRE(synth.getRegionView(2)->velocityRange == sfz::Range<uint8_t>(53, 77));
|
REQUIRE(synth.getRegionView(2)->velocityRange == sfz::Range<float>(53_norm, 77_norm));
|
||||||
REQUIRE(synth.getRegionView(3)->velocityRange == sfz::Range<uint8_t>(78, 102));
|
REQUIRE(synth.getRegionView(3)->velocityRange == sfz::Range<float>(78_norm, 102_norm));
|
||||||
REQUIRE(synth.getRegionView(4)->velocityRange == sfz::Range<uint8_t>(103, 127));
|
REQUIRE(synth.getRegionView(4)->velocityRange == sfz::Range<float>(103_norm, 127_norm));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Full hierarchy")
|
TEST_CASE("[Files] Full hierarchy")
|
||||||
|
|
@ -232,7 +234,7 @@ TEST_CASE("[Files] Pizz basic")
|
||||||
REQUIRE(synth.getNumRegions() == 4);
|
REQUIRE(synth.getNumRegions() == 4);
|
||||||
for (int i = 0; i < synth.getNumRegions(); ++i) {
|
for (int i = 0; i < synth.getNumRegions(); ++i) {
|
||||||
REQUIRE(synth.getRegionView(i)->keyRange == sfz::Range<uint8_t>(12, 22));
|
REQUIRE(synth.getRegionView(i)->keyRange == sfz::Range<uint8_t>(12, 22));
|
||||||
REQUIRE(synth.getRegionView(i)->velocityRange == sfz::Range<uint8_t>(97, 127));
|
REQUIRE(synth.getRegionView(i)->velocityRange == sfz::Range<float>(97_norm, 127_norm));
|
||||||
REQUIRE(synth.getRegionView(i)->pitchKeycenter == 21);
|
REQUIRE(synth.getRegionView(i)->pitchKeycenter == 21);
|
||||||
REQUIRE(synth.getRegionView(i)->ccConditions.getWithDefault(107) == sfz::Range<uint8_t>(0, 13));
|
REQUIRE(synth.getRegionView(i)->ccConditions.getWithDefault(107) == sfz::Range<uint8_t>(0, 13));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,10 @@
|
||||||
|
|
||||||
#include "sfizz/MidiState.h"
|
#include "sfizz/MidiState.h"
|
||||||
#include "sfizz/Region.h"
|
#include "sfizz/Region.h"
|
||||||
|
#include "sfizz/SfzHelpers.h"
|
||||||
#include "catch2/catch.hpp"
|
#include "catch2/catch.hpp"
|
||||||
using namespace Catch::literals;
|
using namespace Catch::literals;
|
||||||
|
using namespace sfz::literals;
|
||||||
|
|
||||||
TEST_CASE("[Region] Parsing opcodes")
|
TEST_CASE("[Region] Parsing opcodes")
|
||||||
{
|
{
|
||||||
|
|
@ -204,19 +206,19 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
|
|
||||||
SECTION("lovel, hivel")
|
SECTION("lovel, hivel")
|
||||||
{
|
{
|
||||||
REQUIRE(region.velocityRange == sfz::Range<uint8_t>(0, 127));
|
REQUIRE(region.velocityRange == sfz::Range<float>(0_norm, 127_norm));
|
||||||
region.parseOpcode({ "lovel", "37" });
|
region.parseOpcode({ "lovel", "37" });
|
||||||
REQUIRE(region.velocityRange == sfz::Range<uint8_t>(37, 127));
|
REQUIRE(region.velocityRange == sfz::Range<float>(37_norm, 127_norm));
|
||||||
region.parseOpcode({ "lovel", "128" });
|
region.parseOpcode({ "lovel", "128" });
|
||||||
REQUIRE(region.velocityRange == sfz::Range<uint8_t>(127, 127));
|
REQUIRE(region.velocityRange == sfz::Range<float>(127_norm, 127_norm));
|
||||||
region.parseOpcode({ "lovel", "-3" });
|
region.parseOpcode({ "lovel", "-3" });
|
||||||
REQUIRE(region.velocityRange == sfz::Range<uint8_t>(0, 127));
|
REQUIRE(region.velocityRange == sfz::Range<float>(0_norm, 127_norm));
|
||||||
region.parseOpcode({ "hivel", "65" });
|
region.parseOpcode({ "hivel", "65" });
|
||||||
REQUIRE(region.velocityRange == sfz::Range<uint8_t>(0, 65));
|
REQUIRE(region.velocityRange == sfz::Range<float>(0_norm, 65_norm));
|
||||||
region.parseOpcode({ "hivel", "-1" });
|
region.parseOpcode({ "hivel", "-1" });
|
||||||
REQUIRE(region.velocityRange == sfz::Range<uint8_t>(0, 0));
|
REQUIRE(region.velocityRange == sfz::Range<float>(0_norm, 0_norm));
|
||||||
region.parseOpcode({ "hivel", "128" });
|
region.parseOpcode({ "hivel", "128" });
|
||||||
REQUIRE(region.velocityRange == sfz::Range<uint8_t>(0, 127));
|
REQUIRE(region.velocityRange == sfz::Range<float>(0_norm, 127_norm));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("lobend, hibend")
|
SECTION("lobend, hibend")
|
||||||
|
|
@ -563,9 +565,9 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
SECTION("amp_velcurve")
|
SECTION("amp_velcurve")
|
||||||
{
|
{
|
||||||
region.parseOpcode({ "amp_velcurve_6", "0.4" });
|
region.parseOpcode({ "amp_velcurve_6", "0.4" });
|
||||||
REQUIRE(region.velocityPoints.back() == std::make_pair<int, float>(6, 0.4f));
|
REQUIRE(region.velocityPoints.back() == std::make_pair<float, float>(6_norm, 0.4f));
|
||||||
region.parseOpcode({ "amp_velcurve_127", "-1.0" });
|
region.parseOpcode({ "amp_velcurve_127", "-1.0" });
|
||||||
REQUIRE(region.velocityPoints.back() == std::make_pair<int, float>(127, 0.0f));
|
REQUIRE(region.velocityPoints.back() == std::make_pair<float, float>(127_norm, 0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("xfin_lokey, xfin_hikey")
|
SECTION("xfin_lokey, xfin_hikey")
|
||||||
|
|
@ -589,21 +591,21 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
|
|
||||||
SECTION("xfin_lovel, xfin_hivel")
|
SECTION("xfin_lovel, xfin_hivel")
|
||||||
{
|
{
|
||||||
REQUIRE(region.crossfadeVelInRange == sfz::Range<uint8_t>(0, 0));
|
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(0_norm, 0_norm));
|
||||||
region.parseOpcode({ "xfin_lovel", "4" });
|
region.parseOpcode({ "xfin_lovel", "4" });
|
||||||
REQUIRE(region.crossfadeVelInRange == sfz::Range<uint8_t>(4, 4));
|
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(4_norm, 4_norm));
|
||||||
region.parseOpcode({ "xfin_lovel", "128" });
|
region.parseOpcode({ "xfin_lovel", "128" });
|
||||||
REQUIRE(region.crossfadeVelInRange == sfz::Range<uint8_t>(127, 127));
|
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(127_norm, 127_norm));
|
||||||
region.parseOpcode({ "xfin_lovel", "59" });
|
region.parseOpcode({ "xfin_lovel", "59" });
|
||||||
REQUIRE(region.crossfadeVelInRange == sfz::Range<uint8_t>(59, 127));
|
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(59_norm, 127_norm));
|
||||||
region.parseOpcode({ "xfin_hivel", "59" });
|
region.parseOpcode({ "xfin_hivel", "59" });
|
||||||
REQUIRE(region.crossfadeVelInRange == sfz::Range<uint8_t>(59, 59));
|
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(59_norm, 59_norm));
|
||||||
region.parseOpcode({ "xfin_hivel", "128" });
|
region.parseOpcode({ "xfin_hivel", "128" });
|
||||||
REQUIRE(region.crossfadeVelInRange == sfz::Range<uint8_t>(59, 127));
|
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(59_norm, 127_norm));
|
||||||
region.parseOpcode({ "xfin_hivel", "0" });
|
region.parseOpcode({ "xfin_hivel", "0" });
|
||||||
REQUIRE(region.crossfadeVelInRange == sfz::Range<uint8_t>(0, 0));
|
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(0_norm, 0_norm));
|
||||||
region.parseOpcode({ "xfin_hivel", "-1" });
|
region.parseOpcode({ "xfin_hivel", "-1" });
|
||||||
REQUIRE(region.crossfadeVelInRange == sfz::Range<uint8_t>(0, 0));
|
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(0_norm, 0_norm));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("xfout_lokey, xfout_hikey")
|
SECTION("xfout_lokey, xfout_hikey")
|
||||||
|
|
@ -627,21 +629,21 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
|
|
||||||
SECTION("xfout_lovel, xfout_hivel")
|
SECTION("xfout_lovel, xfout_hivel")
|
||||||
{
|
{
|
||||||
REQUIRE(region.crossfadeVelOutRange == sfz::Range<uint8_t>(127, 127));
|
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(127_norm, 127_norm));
|
||||||
region.parseOpcode({ "xfout_lovel", "4" });
|
region.parseOpcode({ "xfout_lovel", "4" });
|
||||||
REQUIRE(region.crossfadeVelOutRange == sfz::Range<uint8_t>(4, 127));
|
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(4_norm, 127_norm));
|
||||||
region.parseOpcode({ "xfout_lovel", "128" });
|
region.parseOpcode({ "xfout_lovel", "128" });
|
||||||
REQUIRE(region.crossfadeVelOutRange == sfz::Range<uint8_t>(127, 127));
|
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(127_norm, 127_norm));
|
||||||
region.parseOpcode({ "xfout_lovel", "59" });
|
region.parseOpcode({ "xfout_lovel", "59" });
|
||||||
REQUIRE(region.crossfadeVelOutRange == sfz::Range<uint8_t>(59, 127));
|
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(59_norm, 127_norm));
|
||||||
region.parseOpcode({ "xfout_hivel", "59" });
|
region.parseOpcode({ "xfout_hivel", "59" });
|
||||||
REQUIRE(region.crossfadeVelOutRange == sfz::Range<uint8_t>(59, 59));
|
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(59_norm, 59_norm));
|
||||||
region.parseOpcode({ "xfout_hivel", "128" });
|
region.parseOpcode({ "xfout_hivel", "128" });
|
||||||
REQUIRE(region.crossfadeVelOutRange == sfz::Range<uint8_t>(59, 127));
|
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(59_norm, 127_norm));
|
||||||
region.parseOpcode({ "xfout_hivel", "0" });
|
region.parseOpcode({ "xfout_hivel", "0" });
|
||||||
REQUIRE(region.crossfadeVelOutRange == sfz::Range<uint8_t>(0, 0));
|
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(0_norm, 0_norm));
|
||||||
region.parseOpcode({ "xfout_hivel", "-1" });
|
region.parseOpcode({ "xfout_hivel", "-1" });
|
||||||
REQUIRE(region.crossfadeVelOutRange == sfz::Range<uint8_t>(0, 0));
|
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(0_norm, 0_norm));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("xfin_locc, xfin_hicc")
|
SECTION("xfin_locc, xfin_hicc")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue