Merge pull request #166 from paulfd/curvecc-stepcc
Add support for curvecc and stepcc
This commit is contained in:
commit
1feb073d0f
27 changed files with 860 additions and 435 deletions
|
|
@ -10,7 +10,7 @@
|
|||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include "SfzHelpers.h"
|
||||
#include "ModifierHelpers.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
class EnvelopeFixture : public benchmark::Fixture {
|
||||
|
|
|
|||
|
|
@ -44,11 +44,11 @@ public:
|
|||
*/
|
||||
const ValueType& getWithDefault(int index) const noexcept
|
||||
{
|
||||
auto it = absl::c_lower_bound(container, index, CCValuePairComparator<ValueType>{});
|
||||
auto it = absl::c_lower_bound(container, index, CCDataComparator<ValueType>{});
|
||||
if (it == container.end() || it->cc != index) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return it->value;
|
||||
return it->data;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,12 +60,12 @@ public:
|
|||
*/
|
||||
ValueType& operator[](const int& index) noexcept
|
||||
{
|
||||
auto it = absl::c_lower_bound(container, index, CCValuePairComparator<ValueType>{});
|
||||
auto it = absl::c_lower_bound(container, index, CCDataComparator<ValueType>{});
|
||||
if (it == container.end() || it->cc != index) {
|
||||
auto inserted = container.insert(it, { index, defaultValue });
|
||||
return inserted->value;
|
||||
return inserted->data;
|
||||
} else {
|
||||
return it->value;
|
||||
return it->data;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,16 +85,16 @@ public:
|
|||
*/
|
||||
bool contains(int index) const noexcept
|
||||
{
|
||||
return absl::c_binary_search(container, index, CCValuePairComparator<ValueType>{});
|
||||
return absl::c_binary_search(container, index, CCDataComparator<ValueType>{});
|
||||
}
|
||||
typename std::vector<CCValuePair<ValueType>>::const_iterator begin() const { return container.cbegin(); }
|
||||
typename std::vector<CCValuePair<ValueType>>::const_iterator end() const { return container.cend(); }
|
||||
typename std::vector<CCData<ValueType>>::const_iterator begin() const { return container.cbegin(); }
|
||||
typename std::vector<CCData<ValueType>>::const_iterator end() const { return container.cend(); }
|
||||
private:
|
||||
// typename std::vector<std::pair<int, ValueType>>::iterator begin() { return container.begin(); }
|
||||
// typename std::vector<std::pair<int, ValueType>>::iterator end() { return container.end(); }
|
||||
|
||||
const ValueType defaultValue;
|
||||
std::vector<CCValuePair<ValueType>> container;
|
||||
std::vector<CCData<ValueType>> container;
|
||||
LEAK_DETECTOR(CCMap);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ namespace config {
|
|||
constexpr size_t powerHistoryLength { 16 };
|
||||
constexpr float voiceStealingThreshold { 0.00001f };
|
||||
constexpr uint16_t numCCs { 512 };
|
||||
constexpr int maxCurves { 256 };
|
||||
constexpr int chunkSize { 1024 };
|
||||
constexpr int filtersInPool { maxVoices * 2 };
|
||||
constexpr int filtersPerVoice { 2 };
|
||||
|
|
|
|||
|
|
@ -196,6 +196,12 @@ void CurveSet::addCurve(const Curve& curve, int explicitIndex)
|
|||
{
|
||||
std::unique_ptr<Curve>* slot;
|
||||
|
||||
if (explicitIndex < -1)
|
||||
return;
|
||||
|
||||
if (explicitIndex >= config::maxCurves)
|
||||
return;
|
||||
|
||||
if (explicitIndex == -1) {
|
||||
if (_useExplicitIndexing)
|
||||
return; // reject implicit indices if any were explicit before
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public:
|
|||
*/
|
||||
float evalNormalized(float value) const
|
||||
{
|
||||
return evalCC7(denormalize7Bits<int>(value));
|
||||
return evalCC7(value * 127.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ namespace Default
|
|||
constexpr Range<uint8_t> midi7Range { 0, 127 };
|
||||
constexpr Range<float> normalizedRange { 0.0f, 1.0f };
|
||||
constexpr Range<float> symmetricNormalizedRange { -1.0, 1.0 };
|
||||
constexpr float zeroModifier { 0.0f };
|
||||
|
||||
// Wavetable oscillator
|
||||
constexpr float oscillatorPhase { 0.0 };
|
||||
|
|
@ -82,6 +81,9 @@ namespace Default
|
|||
// Region logic: MIDI conditions
|
||||
constexpr Range<uint8_t> channelRange { 1, 16 };
|
||||
constexpr Range<uint8_t> midiChannelRange { 0, 15 };
|
||||
constexpr Range<uint8_t> stepCCRange { 0, 127 };
|
||||
constexpr Range<uint8_t> smoothCCRange { 0, 127 };
|
||||
constexpr Range<uint8_t> curveCCRange { 0, 255 };
|
||||
constexpr Range<uint16_t> ccNumberRange { 0, config::numCCs };
|
||||
constexpr auto ccValueRange = normalizedRange;
|
||||
constexpr Range<int> bendRange = { -8192, 8192 };
|
||||
|
|
@ -108,17 +110,21 @@ namespace Default
|
|||
constexpr float volume { 0.0f };
|
||||
constexpr Range<float> volumeRange { -144.0, 6.0 };
|
||||
constexpr Range<float> volumeCCRange { -144.0, 48.0 };
|
||||
constexpr Range<float> volumeStepRange { 0, 48.0 };
|
||||
constexpr float amplitude { 100.0 };
|
||||
constexpr Range<float> amplitudeRange { 0.0, 100.0 };
|
||||
constexpr float pan { 0.0 };
|
||||
constexpr Range<float> panRange { -100.0, 100.0 };
|
||||
constexpr Range<float> panCCRange { -200.0, 200.0 };
|
||||
constexpr Range<float> panStepRange { 0.0, 200.0 };
|
||||
constexpr float position { 0.0 };
|
||||
constexpr Range<float> positionRange { -100.0, 100.0 };
|
||||
constexpr Range<float> positionCCRange { -200.0, 200.0 };
|
||||
constexpr Range<float> positionStepRange { 0.0, 200.0 };
|
||||
constexpr float width { 100.0 };
|
||||
constexpr Range<float> widthRange { -100.0, 100.0 };
|
||||
constexpr Range<float> widthCCRange { -200.0, 200.0 };
|
||||
constexpr Range<float> widthStepRange { 0.0, 200.0 };
|
||||
constexpr uint8_t ampKeycenter { 60 };
|
||||
constexpr float ampKeytrack { 0.0 };
|
||||
constexpr Range<float> ampKeytrackRange { -96, 12 };
|
||||
|
|
@ -194,6 +200,7 @@ namespace Default
|
|||
constexpr int tune { 0 };
|
||||
constexpr Range<int> tuneRange { -9600, 9600 }; // ±100 in SFZv1, more in ARIA
|
||||
constexpr Range<int> tuneCCRange { -9600, 9600 };
|
||||
constexpr Range<int> tuneStepRange { 0, 9600 };
|
||||
constexpr Range<int> bendBoundRange { -9600, 9600 };
|
||||
constexpr Range<int> bendStepRange { 1, 1200 };
|
||||
constexpr int bendUp { 200 }; // No range here because the bounds can be inverted
|
||||
|
|
|
|||
|
|
@ -50,10 +50,10 @@ namespace sfz {
|
|||
* @param value
|
||||
* @return float
|
||||
*/
|
||||
inline float ccSwitchedValue(const MidiState& state, const absl::optional<CCValuePair<float>>& ccSwitch, float value) noexcept
|
||||
inline float ccSwitchedValue(const MidiState& state, const absl::optional<CCData<float>>& ccSwitch, float value) noexcept
|
||||
{
|
||||
if (ccSwitch)
|
||||
return value + ccSwitch->value * state.getCCValue(ccSwitch->cc);
|
||||
return value + ccSwitch->data * state.getCCValue(ccSwitch->cc);
|
||||
else
|
||||
return value;
|
||||
}
|
||||
|
|
@ -80,13 +80,13 @@ struct EGDescription {
|
|||
float vel2sustain { Default::vel2sustain };
|
||||
int vel2depth { Default::depth };
|
||||
|
||||
absl::optional<CCValuePair<float>> ccAttack;
|
||||
absl::optional<CCValuePair<float>> ccDecay;
|
||||
absl::optional<CCValuePair<float>> ccDelay;
|
||||
absl::optional<CCValuePair<float>> ccHold;
|
||||
absl::optional<CCValuePair<float>> ccRelease;
|
||||
absl::optional<CCValuePair<float>> ccStart;
|
||||
absl::optional<CCValuePair<float>> ccSustain;
|
||||
absl::optional<CCData<float>> ccAttack;
|
||||
absl::optional<CCData<float>> ccDecay;
|
||||
absl::optional<CCData<float>> ccDelay;
|
||||
absl::optional<CCData<float>> ccHold;
|
||||
absl::optional<CCData<float>> ccRelease;
|
||||
absl::optional<CCData<float>> ccStart;
|
||||
absl::optional<CCData<float>> ccSustain;
|
||||
|
||||
/**
|
||||
* @brief Get the attack with possibly a CC modifier and a velocity modifier
|
||||
|
|
|
|||
|
|
@ -29,17 +29,17 @@ void sfz::EQHolder::setup(const EQDescription& description, unsigned numChannels
|
|||
// Setup the modulated values
|
||||
lastFrequency = baseFrequency;
|
||||
for (const auto& mod : description.frequencyCC)
|
||||
lastFrequency += midiState.getCCValue(mod.cc) * mod.value;
|
||||
lastFrequency += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);
|
||||
|
||||
lastBandwidth = baseBandwidth;
|
||||
for (const auto& mod : description.bandwidthCC)
|
||||
lastBandwidth += midiState.getCCValue(mod.cc) * mod.value;
|
||||
lastBandwidth += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth);
|
||||
|
||||
lastGain = baseGain;
|
||||
for (const auto& mod : description.gainCC)
|
||||
lastGain += midiState.getCCValue(mod.cc) * mod.value;
|
||||
lastGain += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||
|
||||
// Initialize the EQ
|
||||
|
|
@ -62,17 +62,17 @@ void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numF
|
|||
// For now we take the last value
|
||||
lastFrequency = baseFrequency;
|
||||
for (const auto& mod : description->frequencyCC)
|
||||
lastFrequency += midiState.getCCValue(mod.cc) * mod.value;
|
||||
lastFrequency += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);
|
||||
|
||||
lastBandwidth = baseBandwidth;
|
||||
for (const auto& mod : description->bandwidthCC)
|
||||
lastBandwidth += midiState.getCCValue(mod.cc) * mod.value;
|
||||
lastBandwidth += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth);
|
||||
|
||||
lastGain = baseGain;
|
||||
for (const auto& mod : description->gainCC)
|
||||
lastGain += midiState.getCCValue(mod.cc) * mod.value;
|
||||
lastGain += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||
|
||||
if (lastGain == 0.0f) {
|
||||
|
|
|
|||
|
|
@ -41,17 +41,17 @@ void sfz::FilterHolder::setup(const FilterDescription& description, unsigned num
|
|||
// Setup the modulated values
|
||||
lastCutoff = baseCutoff;
|
||||
for (const auto& mod : description.cutoffCC)
|
||||
lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.value);
|
||||
lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.data);
|
||||
lastCutoff = Default::filterCutoffRange.clamp(lastCutoff);
|
||||
|
||||
lastResonance = baseResonance;
|
||||
for (const auto& mod : description.resonanceCC)
|
||||
lastResonance += midiState.getCCValue(mod.cc) * mod.value;
|
||||
lastResonance += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastResonance = Default::filterResonanceRange.clamp(lastResonance);
|
||||
|
||||
lastGain = baseGain;
|
||||
for (const auto& mod : description.gainCC)
|
||||
lastGain += midiState.getCCValue(mod.cc) * mod.value;
|
||||
lastGain += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||
|
||||
// Initialize the filter
|
||||
|
|
@ -71,17 +71,17 @@ void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned
|
|||
// TODO: the template deduction could be automatic here?
|
||||
lastCutoff = baseCutoff;
|
||||
for (const auto& mod : description->cutoffCC)
|
||||
lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.value);
|
||||
lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.data);
|
||||
lastCutoff = Default::filterCutoffRange.clamp(lastCutoff);
|
||||
|
||||
lastResonance = baseResonance;
|
||||
for (const auto& mod : description->resonanceCC)
|
||||
lastResonance += midiState.getCCValue(mod.cc) * mod.value;
|
||||
lastResonance += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastResonance = Default::filterResonanceRange.clamp(lastResonance);
|
||||
|
||||
lastGain = baseGain;
|
||||
for (const auto& mod : description->gainCC)
|
||||
lastGain += midiState.getCCValue(mod.cc) * mod.value;
|
||||
lastGain += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||
|
||||
filter.process(inputs, outputs, lastCutoff, lastResonance, lastGain, numFrames);
|
||||
|
|
|
|||
|
|
@ -16,3 +16,10 @@
|
|||
#define CXX11_MOVE(x) std::move(x)
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#define IF_CONSTEXPR if constexpr
|
||||
#else
|
||||
#define IF_CONSTEXPR if
|
||||
#endif
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,26 +15,27 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <random>
|
||||
#include <cfenv>
|
||||
|
||||
template<class T>
|
||||
template <class T>
|
||||
constexpr T max(T op1, T op2)
|
||||
{
|
||||
return op1 > op2 ? op1 : op2;
|
||||
}
|
||||
|
||||
template<class T, class... Args>
|
||||
template <class T, class... Args>
|
||||
constexpr T max(T op1, Args... rest)
|
||||
{
|
||||
return max(op1, max(rest...));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
template <class T>
|
||||
constexpr T min(T op1, T op2)
|
||||
{
|
||||
return op1 > op2 ? op2 : op1;
|
||||
}
|
||||
|
||||
template<class T, class... Args>
|
||||
template <class T, class... Args>
|
||||
constexpr T min(T op1, Args... rest)
|
||||
{
|
||||
return min(op1, min(rest...));
|
||||
|
|
@ -46,7 +47,7 @@ constexpr T min(T op1, Args... rest)
|
|||
* @param op
|
||||
* @return T
|
||||
*/
|
||||
template<class T>
|
||||
template <class T>
|
||||
constexpr T power2(T in)
|
||||
{
|
||||
return in * in;
|
||||
|
|
@ -111,8 +112,8 @@ constexpr Type mag2db(Type in)
|
|||
*
|
||||
*/
|
||||
namespace Random {
|
||||
static std::random_device randomDevice;
|
||||
static std::minstd_rand randomGenerator { randomDevice() };
|
||||
static std::random_device randomDevice;
|
||||
static std::minstd_rand randomGenerator { randomDevice() };
|
||||
} // namespace Random
|
||||
|
||||
/**
|
||||
|
|
@ -135,42 +136,42 @@ inline float midiNoteFrequency(const int noteNumber)
|
|||
* @param hi
|
||||
* @return T
|
||||
*/
|
||||
template<class T>
|
||||
constexpr T clamp( T v, T lo, T hi )
|
||||
template <class T>
|
||||
constexpr T clamp(T v, T lo, T hi)
|
||||
{
|
||||
return max(min(v, hi), lo);
|
||||
}
|
||||
|
||||
template<int Increment = 1, class T>
|
||||
template <int Increment = 1, class T>
|
||||
inline CXX14_CONSTEXPR void incrementAll(T& only)
|
||||
{
|
||||
only += Increment;
|
||||
}
|
||||
|
||||
template<int Increment = 1, class T, class... Args>
|
||||
template <int Increment = 1, class T, class... Args>
|
||||
inline CXX14_CONSTEXPR void incrementAll(T& first, Args&... rest)
|
||||
{
|
||||
first += Increment;
|
||||
incrementAll<Increment>(rest...);
|
||||
}
|
||||
|
||||
template<class ValueType>
|
||||
template <class ValueType>
|
||||
constexpr ValueType linearInterpolation(ValueType left, ValueType right, ValueType leftCoeff, ValueType rightCoeff)
|
||||
{
|
||||
return left * leftCoeff + right * rightCoeff;
|
||||
}
|
||||
|
||||
template<class Type>
|
||||
template <class Type>
|
||||
constexpr Type pi() { return static_cast<Type>(3.141592653589793238462643383279502884); };
|
||||
template<class Type>
|
||||
template <class Type>
|
||||
constexpr Type twoPi() { return pi<Type>() * 2; };
|
||||
template<class Type>
|
||||
template <class Type>
|
||||
constexpr Type piTwo() { return pi<Type>() / 2; };
|
||||
template<class Type>
|
||||
template <class Type>
|
||||
constexpr Type piFour() { return pi<Type>() / 4; };
|
||||
template<class Type>
|
||||
template <class Type>
|
||||
constexpr Type sqrtTwo() { return static_cast<Type>(1.414213562373095048801688724209698078569671875376948073176); };
|
||||
template<class Type>
|
||||
template <class Type>
|
||||
constexpr Type sqrtTwoInv() { return static_cast<Type>(0.707106781186547524400844362104849039284835937688474036588); };
|
||||
|
||||
/**
|
||||
|
|
@ -205,23 +206,23 @@ inline Fraction<I>::operator float() const noexcept
|
|||
template <class F>
|
||||
struct FP_traits;
|
||||
|
||||
template <> struct FP_traits<double>
|
||||
{
|
||||
template <>
|
||||
struct FP_traits<double> {
|
||||
typedef double type;
|
||||
typedef uint64_t same_size_int;
|
||||
static_assert(sizeof(type) == sizeof(same_size_int),
|
||||
"Unexpected size of floating point type");
|
||||
"Unexpected size of floating point type");
|
||||
static constexpr int e_bits = 11;
|
||||
static constexpr int m_bits = 52;
|
||||
static constexpr int e_offset = -1023;
|
||||
};
|
||||
|
||||
template <> struct FP_traits<float>
|
||||
{
|
||||
template <>
|
||||
struct FP_traits<float> {
|
||||
typedef float type;
|
||||
typedef uint32_t same_size_int;
|
||||
static_assert(sizeof(type) == sizeof(same_size_int),
|
||||
"Unexpected size of floating point type");
|
||||
"Unexpected size of floating point type");
|
||||
static constexpr int e_bits = 8;
|
||||
static constexpr int m_bits = 23;
|
||||
static constexpr int e_offset = -127;
|
||||
|
|
@ -237,7 +238,10 @@ template <class F>
|
|||
inline bool fp_sign(F x)
|
||||
{
|
||||
typedef FP_traits<F> T;
|
||||
union { F real; typename T::same_size_int integer; } u;
|
||||
union {
|
||||
F real;
|
||||
typename T::same_size_int integer;
|
||||
} u;
|
||||
u.real = x;
|
||||
return ((u.integer >> (T::e_bits + T::m_bits)) & 1) != 0;
|
||||
}
|
||||
|
|
@ -254,7 +258,10 @@ template <class F>
|
|||
inline int fp_exponent(F x)
|
||||
{
|
||||
typedef FP_traits<F> T;
|
||||
union { F real; typename T::same_size_int integer; } u;
|
||||
union {
|
||||
F real;
|
||||
typename T::same_size_int integer;
|
||||
} u;
|
||||
u.real = x;
|
||||
int ex = (u.integer >> T::m_bits) & ((1u << T::e_bits) - 1);
|
||||
return ex + T::e_offset;
|
||||
|
|
@ -269,10 +276,13 @@ template <class F>
|
|||
inline Fraction<uint64_t> fp_mantissa(F x)
|
||||
{
|
||||
typedef FP_traits<F> T;
|
||||
union { F real; typename T::same_size_int integer; } u;
|
||||
union {
|
||||
F real;
|
||||
typename T::same_size_int integer;
|
||||
} u;
|
||||
u.real = x;
|
||||
Fraction<uint64_t> f;
|
||||
f.den = uint64_t{1} << T::m_bits;
|
||||
f.den = uint64_t { 1 } << T::m_bits;
|
||||
f.num = u.integer & (f.den - 1);
|
||||
return f;
|
||||
}
|
||||
|
|
@ -287,10 +297,11 @@ inline F fp_from_parts(bool sgn, int ex, uint64_t mant)
|
|||
{
|
||||
typedef FP_traits<F> T;
|
||||
typedef typename T::same_size_int I;
|
||||
union { F real; I integer; } u;
|
||||
u.integer = mant |
|
||||
(static_cast<I>(ex - T::e_offset) << T::m_bits) |
|
||||
(static_cast<I>(sgn) << (T::e_bits + T::m_bits));
|
||||
union {
|
||||
F real;
|
||||
I integer;
|
||||
} u;
|
||||
u.integer = mant | (static_cast<I>(ex - T::e_offset) << T::m_bits) | (static_cast<I>(sgn) << (T::e_bits + T::m_bits));
|
||||
return u.real;
|
||||
}
|
||||
|
||||
|
|
@ -328,3 +339,20 @@ bool isValidAudio(absl::Span<Type> span)
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
class ScopedRoundingMode {
|
||||
public:
|
||||
ScopedRoundingMode() = delete;
|
||||
ScopedRoundingMode(int newRoundingMode)
|
||||
: savedFloatMode(std::fegetround())
|
||||
{
|
||||
std::fesetround(newRoundingMode);
|
||||
}
|
||||
~ScopedRoundingMode()
|
||||
{
|
||||
std::fesetround(savedFloatMode);
|
||||
}
|
||||
|
||||
private:
|
||||
const int savedFloatMode;
|
||||
};
|
||||
|
|
|
|||
244
src/sfizz/ModifierHelpers.h
Normal file
244
src/sfizz/ModifierHelpers.h
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
#pragma once
|
||||
|
||||
#include "Range.h"
|
||||
#include "Defaults.h"
|
||||
#include "SfzHelpers.h"
|
||||
#include "Resources.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
namespace sfz {
|
||||
|
||||
/**
|
||||
* @brief Compute a crossfade in value with respect to a crossfade range (note, velocity, cc, ...)
|
||||
*/
|
||||
template <class T, class U>
|
||||
float crossfadeIn(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
||||
{
|
||||
if (value < crossfadeRange.getStart())
|
||||
return 0.0f;
|
||||
|
||||
const auto length = static_cast<float>(crossfadeRange.length());
|
||||
if (length == 0.0f)
|
||||
return 1.0f;
|
||||
|
||||
else if (value < crossfadeRange.getEnd()) {
|
||||
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
||||
if (curve == SfzCrossfadeCurve::power)
|
||||
return sqrt(crossfadePosition);
|
||||
if (curve == SfzCrossfadeCurve::gain)
|
||||
return crossfadePosition;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute a crossfade out value with respect to a crossfade range (note, velocity, cc, ...)
|
||||
*/
|
||||
template <class T, class U>
|
||||
float crossfadeOut(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
||||
{
|
||||
if (value > crossfadeRange.getEnd())
|
||||
return 0.0f;
|
||||
|
||||
const auto length = static_cast<float>(crossfadeRange.length());
|
||||
if (length == 0.0f)
|
||||
return 1.0f;
|
||||
|
||||
else if (value > crossfadeRange.getStart()) {
|
||||
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
||||
if (curve == SfzCrossfadeCurve::power)
|
||||
return std::sqrt(1 - crossfadePosition);
|
||||
if (curve == SfzCrossfadeCurve::gain)
|
||||
return 1 - crossfadePosition;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
|
||||
{
|
||||
ASSERT(events.size() > 0);
|
||||
ASSERT(events[0].delay == 0);
|
||||
if (envelope.size() == 0)
|
||||
return;
|
||||
|
||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||
|
||||
auto lastValue = lambda(events[0].value);
|
||||
auto lastDelay = events[0].delay;
|
||||
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||
const auto step = (lambda(events[i].value) - lastValue) / length;
|
||||
lastValue = linearRamp<float>(envelope.subspan(lastDelay, length), lastValue, step);
|
||||
lastDelay += length;
|
||||
}
|
||||
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
|
||||
{
|
||||
ASSERT(events.size() > 0);
|
||||
ASSERT(events[0].delay == 0);
|
||||
ASSERT(step != 0.0);
|
||||
|
||||
if (envelope.size() == 0)
|
||||
return;
|
||||
|
||||
auto quantize = [step](float value) -> float {
|
||||
return std::trunc(value / step) * step;
|
||||
};
|
||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||
|
||||
auto lastValue = quantize(lambda(events[0].value));
|
||||
auto lastDelay = events[0].delay;
|
||||
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||
const auto nextValue = quantize(lambda(events[i].value));
|
||||
const auto difference = std::abs(nextValue - lastValue);
|
||||
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||
|
||||
if (difference < step) {
|
||||
fill<float>(envelope.subspan(lastDelay, length), lastValue);
|
||||
lastValue = nextValue;
|
||||
lastDelay += length;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto numSteps = static_cast<int>(difference / step);
|
||||
const auto stepLength = static_cast<int>(length / numSteps);
|
||||
for (int i = 0; i < numSteps; ++i) {
|
||||
fill<float>(envelope.subspan(lastDelay, stepLength), lastValue);
|
||||
lastValue += lastValue <= nextValue ? step : -step;
|
||||
lastDelay += stepLength;
|
||||
}
|
||||
}
|
||||
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
|
||||
{
|
||||
ASSERT(events.size() > 0);
|
||||
ASSERT(events[0].delay == 0);
|
||||
|
||||
if (envelope.size() == 0)
|
||||
return;
|
||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||
|
||||
auto lastValue = lambda(events[0].value);
|
||||
auto lastDelay = events[0].delay;
|
||||
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||
const auto nextValue = lambda(events[i].value);
|
||||
const auto step = std::exp((std::log(nextValue) - std::log(lastValue)) / length);
|
||||
multiplicativeRamp<float>(envelope.subspan(lastDelay, length), lastValue, step);
|
||||
lastValue = nextValue;
|
||||
lastDelay += length;
|
||||
}
|
||||
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||
}
|
||||
|
||||
template <class F, bool Round = false>
|
||||
void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
|
||||
{
|
||||
ASSERT(events.size() > 0);
|
||||
ASSERT(events[0].delay == 0);
|
||||
ASSERT(step != 0.0f);
|
||||
|
||||
ScopedRoundingMode roundingMode { Round ? FE_TONEAREST : FE_TOWARDZERO };
|
||||
|
||||
if (envelope.size() == 0)
|
||||
return;
|
||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||
const auto logStep = std::log(step);
|
||||
// If we assume that a = b.q^r for b in (1, q) then
|
||||
// log a log b
|
||||
// ----- = ----- + r
|
||||
// log q log q
|
||||
// and log(b)\log(q) is between 0 and 1.
|
||||
auto quantize = [logStep](float value) -> float {
|
||||
return std::exp(logStep * std::rint(std::log(value) / logStep));
|
||||
};
|
||||
|
||||
auto lastValue = quantize(lambda(events[0].value));
|
||||
auto lastDelay = events[0].delay;
|
||||
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||
const auto nextValue = quantize(lambda(events[i].value));
|
||||
const auto difference = nextValue > lastValue ? nextValue / lastValue : lastValue / nextValue;
|
||||
|
||||
if (difference < step) {
|
||||
fill<float>(envelope.subspan(lastDelay, length), lastValue);
|
||||
lastValue = nextValue;
|
||||
lastDelay += length;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto numSteps = std::round(std::log(difference) / logStep);
|
||||
const auto stepLength = static_cast<int>(length / numSteps);
|
||||
for (int i = 0; i < static_cast<int>(numSteps); ++i) {
|
||||
fill<float>(envelope.subspan(lastDelay, stepLength), lastValue);
|
||||
lastValue = nextValue > lastValue ? lastValue * step : lastValue / step;
|
||||
lastDelay += stepLength;
|
||||
}
|
||||
}
|
||||
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void pitchBendEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
|
||||
{
|
||||
multiplicativeEnvelope<F, true>(events, envelope, std::forward<F>(lambda), step);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void pitchBendEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
|
||||
{
|
||||
multiplicativeEnvelope<F>(events, envelope, std::forward<F>(lambda));
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void linearModifier(const sfz::Resources& resources, absl::Span<float> span, const sfz::CCData<sfz::Modifier>& ccData, F&& lambda)
|
||||
{
|
||||
const auto events = resources.midiState.getCCEvents(ccData.cc);
|
||||
const auto curve = resources.curves.getCurve(ccData.data.curve);
|
||||
if (ccData.data.step == 0.0f) {
|
||||
linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) {
|
||||
return lambda(curve.evalNormalized(x) * ccData.data.value);
|
||||
});
|
||||
} else {
|
||||
const float stepSize { lambda(ccData.data.step) };
|
||||
linearEnvelope(
|
||||
events, span, [&ccData, &curve, &lambda](float x) {
|
||||
return lambda(curve.evalNormalized(x) * ccData.data.value);
|
||||
},
|
||||
stepSize);
|
||||
}
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void multiplicativeModifier(const sfz::Resources& resources, absl::Span<float> span, const sfz::CCData<sfz::Modifier>& ccData, F&& lambda)
|
||||
{
|
||||
const auto events = resources.midiState.getCCEvents(ccData.cc);
|
||||
const auto curve = resources.curves.getCurve(ccData.data.curve);
|
||||
if (ccData.data.step == 0.0f) {
|
||||
multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) {
|
||||
return lambda(curve.evalNormalized(x) * ccData.data.value);
|
||||
});
|
||||
} else {
|
||||
const float stepSize { lambda(ccData.data.step) };
|
||||
multiplicativeEnvelope(
|
||||
events, span, [&ccData, &curve, &lambda](float x) {
|
||||
return lambda(curve.evalNormalized(x) * ccData.data.value);
|
||||
},
|
||||
stepSize);
|
||||
}
|
||||
}
|
||||
|
||||
inline void linearModifier(const sfz::Resources& resources, absl::Span<float> span, const sfz::CCData<sfz::Modifier>& ccData)
|
||||
{
|
||||
linearModifier(resources, span, ccData, [](float x) { return x; });
|
||||
}
|
||||
}
|
||||
|
|
@ -185,7 +185,7 @@ inline void setRangeStartFromOpcode(const Opcode& opcode, Range<ValueType>& targ
|
|||
* @param validRange the range of admitted values used to clamp the opcode
|
||||
*/
|
||||
template <class ValueType>
|
||||
inline void setCCPairFromOpcode(const Opcode& opcode, absl::optional<CCValuePair<ValueType>>& target, const Range<ValueType>& validRange)
|
||||
inline void setCCPairFromOpcode(const Opcode& opcode, absl::optional<CCData<ValueType>>& target, const Range<ValueType>& validRange)
|
||||
{
|
||||
auto value = readOpcode(opcode.value, validRange);
|
||||
if (value && Default::ccNumberRange.containsWithEnd(opcode.parameters.back()))
|
||||
|
|
|
|||
|
|
@ -5,13 +5,12 @@
|
|||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#include "Region.h"
|
||||
#include "Defaults.h"
|
||||
#include "MathHelpers.h"
|
||||
#include "Macros.h"
|
||||
#include "Debug.h"
|
||||
#include "Opcode.h"
|
||||
#include "StringViewHelpers.h"
|
||||
#include "MidiState.h"
|
||||
#include "ModifierHelpers.h"
|
||||
#include "absl/strings/str_replace.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
|
|
@ -297,55 +296,146 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
|||
case hash("volume"):
|
||||
setValueFromOpcode(opcode, volume, Default::volumeRange);
|
||||
break;
|
||||
case hash("volume_curvecc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
|
||||
volumeCC[opcode.parameters.back()].curve = *value;
|
||||
break;
|
||||
case hash("volume_stepcc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::volumeStepRange))
|
||||
volumeCC[opcode.parameters.back()].step = *value;
|
||||
break;
|
||||
case hash("volume_smoothcc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
|
||||
volumeCC[opcode.parameters.back()].smooth = *value;
|
||||
break;
|
||||
case hash("gain_cc&"):
|
||||
case hash("gain_oncc&"): // fallthrough
|
||||
case hash("volume_oncc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::volumeCCRange))
|
||||
volumeCC[opcode.parameters.back()] = *value;
|
||||
volumeCC[opcode.parameters.back()].value = *value;
|
||||
break;
|
||||
case hash("amplitude"):
|
||||
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
|
||||
amplitude = normalizePercents(*value);
|
||||
break;
|
||||
case hash("amplitude_curvecc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
|
||||
amplitudeCC[opcode.parameters.back()].curve = *value;
|
||||
break;
|
||||
case hash("amplitude_stepcc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
|
||||
amplitudeCC[opcode.parameters.back()].step = normalizePercents(*value);
|
||||
break;
|
||||
case hash("amplitude_smoothcc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
|
||||
amplitudeCC[opcode.parameters.back()].smooth = *value;
|
||||
break;
|
||||
case hash("amplitude_cc&"): // fallthrough
|
||||
case hash("amplitude_oncc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
|
||||
amplitudeCC[opcode.parameters.back()] = normalizePercents(*value);
|
||||
amplitudeCC[opcode.parameters.back()].value = normalizePercents(*value);
|
||||
break;
|
||||
case hash("pan"):
|
||||
if (auto value = readOpcode(opcode.value, Default::panRange))
|
||||
pan = normalizePercents(*value);
|
||||
break;
|
||||
case hash("pan_curvecc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
|
||||
panCC[opcode.parameters.back()].curve = *value;
|
||||
break;
|
||||
case hash("pan_stepcc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::panStepRange))
|
||||
panCC[opcode.parameters.back()].step = normalizePercents(*value);
|
||||
break;
|
||||
case hash("pan_smoothcc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
|
||||
panCC[opcode.parameters.back()].smooth = *value;
|
||||
break;
|
||||
case hash("pan_cc&"):
|
||||
case hash("pan_oncc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::panCCRange))
|
||||
panCC[opcode.parameters.back()] = normalizePercents(*value);
|
||||
panCC[opcode.parameters.back()].value = normalizePercents(*value);
|
||||
break;
|
||||
case hash("position"):
|
||||
if (auto value = readOpcode(opcode.value, Default::positionRange))
|
||||
position = normalizePercents(*value);
|
||||
break;
|
||||
case hash("position_curvecc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
|
||||
positionCC[opcode.parameters.back()].curve = *value;
|
||||
break;
|
||||
case hash("position_stepcc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::positionStepRange))
|
||||
positionCC[opcode.parameters.back()].step = normalizePercents(*value);
|
||||
break;
|
||||
case hash("position_smoothcc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
|
||||
positionCC[opcode.parameters.back()].smooth = *value;
|
||||
break;
|
||||
case hash("position_cc&"): // fallthrough
|
||||
case hash("position_oncc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::positionCCRange))
|
||||
positionCC[opcode.parameters.back()] = normalizePercents(*value);
|
||||
positionCC[opcode.parameters.back()].value = normalizePercents(*value);
|
||||
break;
|
||||
case hash("width"):
|
||||
if (auto value = readOpcode(opcode.value, Default::widthRange))
|
||||
width = normalizePercents(*value);
|
||||
break;
|
||||
case hash("width_curvecc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
|
||||
widthCC[opcode.parameters.back()].curve = *value;
|
||||
break;
|
||||
case hash("width_stepcc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::widthStepRange))
|
||||
widthCC[opcode.parameters.back()].step = normalizePercents(*value);
|
||||
break;
|
||||
case hash("width_smoothcc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
|
||||
widthCC[opcode.parameters.back()].smooth = *value;
|
||||
break;
|
||||
case hash("width_oncc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::widthCCRange))
|
||||
widthCC[opcode.parameters.back()] = normalizePercents(*value);
|
||||
widthCC[opcode.parameters.back()].value = normalizePercents(*value);
|
||||
break;
|
||||
case hash("amp_keycenter"):
|
||||
setValueFromOpcode(opcode, ampKeycenter, Default::keyRange);
|
||||
|
|
@ -726,6 +816,27 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
|||
case hash("pitch"):
|
||||
setValueFromOpcode(opcode, tune, Default::tuneRange);
|
||||
break;
|
||||
case hash("pitch_curvecc&"): // fallthrough
|
||||
case hash("tune_curvecc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
|
||||
tuneCC[opcode.parameters.back()].curve = *value;
|
||||
break;
|
||||
case hash("pitch_stepcc&"): // fallthrough
|
||||
case hash("tune_stepcc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::tuneStepRange))
|
||||
tuneCC[opcode.parameters.back()].step = *value;
|
||||
break;
|
||||
case hash("pitch_smoothcc&"): // fallthrough
|
||||
case hash("tune_smoothcc&"):
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
|
||||
tuneCC[opcode.parameters.back()].smooth = *value;
|
||||
break;
|
||||
case hash("tune_cc&"):
|
||||
case hash("tune_oncc&"):
|
||||
case hash("pitch_cc&"):
|
||||
|
|
@ -733,7 +844,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
|||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
if (auto value = readOpcode(opcode.value, Default::tuneCCRange))
|
||||
tuneCC[opcode.parameters.back()] = *value;
|
||||
tuneCC[opcode.parameters.back()].value = *value;
|
||||
break;
|
||||
case hash("bend_up"):
|
||||
setValueFromOpcode(opcode, bendUp, Default::bendBoundRange);
|
||||
|
|
@ -1078,15 +1189,15 @@ float sfz::Region::getCrossfadeGain() const noexcept
|
|||
float gain { 1.0f };
|
||||
|
||||
// Crossfades due to CC states
|
||||
for (const auto& valuePair : crossfadeCCInRange) {
|
||||
const auto ccValue = midiState.getCCValue(valuePair.cc);
|
||||
const auto crossfadeRange = valuePair.value;
|
||||
for (const auto& ccData : crossfadeCCInRange) {
|
||||
const auto ccValue = midiState.getCCValue(ccData.cc);
|
||||
const auto crossfadeRange = ccData.data;
|
||||
gain *= crossfadeIn(crossfadeRange, ccValue, crossfadeCCCurve);
|
||||
}
|
||||
|
||||
for (const auto& valuePair : crossfadeCCOutRange) {
|
||||
const auto ccValue = midiState.getCCValue(valuePair.cc);
|
||||
const auto crossfadeRange = valuePair.value;
|
||||
for (const auto& ccData : crossfadeCCOutRange) {
|
||||
const auto ccValue = midiState.getCCValue(ccData.cc);
|
||||
const auto crossfadeRange = ccData.data;
|
||||
gain *= crossfadeOut(crossfadeRange, ccValue, crossfadeCCCurve);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,8 @@
|
|||
#include "Opcode.h"
|
||||
#include "AudioBuffer.h"
|
||||
#include "MidiState.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include <bitset>
|
||||
#include <absl/types/optional.h>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -285,11 +283,11 @@ struct Region {
|
|||
float pan { normalizePercents(Default::pan) }; // pan
|
||||
float width { normalizePercents(Default::width) }; // width
|
||||
float position { normalizePercents(Default::position) }; // position
|
||||
CCMap<float> volumeCC { Default::zeroModifier }; // volume_oncc
|
||||
CCMap<float> amplitudeCC { Default::zeroModifier }; // amplitude_oncc
|
||||
CCMap<float> panCC { Default::zeroModifier }; // pan_oncc
|
||||
CCMap<float> widthCC { Default::zeroModifier }; // width_oncc
|
||||
CCMap<float> positionCC { Default::zeroModifier }; // position_oncc
|
||||
CCMap<Modifier> volumeCC { {} }; // volume_oncc
|
||||
CCMap<Modifier> amplitudeCC { {} }; // amplitude_oncc
|
||||
CCMap<Modifier> panCC { {} }; // pan_oncc
|
||||
CCMap<Modifier> widthCC { {} }; // width_oncc
|
||||
CCMap<Modifier> positionCC { {} }; // position_oncc
|
||||
uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter
|
||||
float ampKeytrack { Default::ampKeytrack }; // amp_keytrack
|
||||
float ampVeltrack { Default::ampVeltrack }; // amp_keytrack
|
||||
|
|
@ -317,7 +315,7 @@ struct Region {
|
|||
int pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack
|
||||
int transpose { Default::transpose }; // transpose
|
||||
int tune { Default::tune }; // tune
|
||||
CCMap<int> tuneCC { Default::tune };
|
||||
CCMap<Modifier> tuneCC { {} };
|
||||
int bendUp { Default::bendUp };
|
||||
int bendDown { Default::bendDown };
|
||||
int bendStep { Default::bendStep };
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "EQPool.h"
|
||||
#include "Logger.h"
|
||||
#include "Wavetables.h"
|
||||
#include "Curve.h"
|
||||
|
||||
namespace sfz
|
||||
{
|
||||
|
|
@ -21,6 +22,7 @@ struct Resources
|
|||
BufferPool bufferPool;
|
||||
MidiState midiState;
|
||||
Logger logger;
|
||||
CurveSet curves;
|
||||
FilePool filePool { logger };
|
||||
FilterPool filterPool { midiState };
|
||||
EQPool eqPool { midiState };
|
||||
|
|
@ -38,5 +40,14 @@ struct Resources
|
|||
bufferPool.setBufferSize(samplesPerBlock);
|
||||
midiState.setSamplesPerBlock(samplesPerBlock);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
curves = CurveSet::createPredefined();
|
||||
filePool.clear();
|
||||
wavePool.clearFileWaves();
|
||||
logger.clear();
|
||||
midiState.reset();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,44 +24,35 @@ using CCNamePair = std::pair<uint16_t, std::string>;
|
|||
template <class T>
|
||||
using MidiNoteArray = std::array<T, 128>;
|
||||
template<class ValueType>
|
||||
struct CCValuePair {
|
||||
struct CCData {
|
||||
int cc;
|
||||
ValueType value;
|
||||
ValueType data;
|
||||
static_assert(config::numCCs - 1 < std::numeric_limits<decltype(cc)>::max(), "The cc type in the CCData struct cannot support the required number of CCs");
|
||||
};
|
||||
|
||||
template<class ValueType, bool CompareValue = false>
|
||||
struct CCValuePairComparator {
|
||||
bool operator()(const CCValuePair<ValueType>& valuePair, const int& cc)
|
||||
{
|
||||
return (valuePair.cc < cc);
|
||||
}
|
||||
|
||||
bool operator()(const int& cc, const CCValuePair<ValueType>& valuePair)
|
||||
{
|
||||
return (cc < valuePair.cc);
|
||||
}
|
||||
|
||||
bool operator()(const CCValuePair<ValueType>& lhs, const CCValuePair<ValueType>& rhs)
|
||||
{
|
||||
return (lhs.cc < rhs.cc);
|
||||
}
|
||||
struct Modifier {
|
||||
float value { 0.0f };
|
||||
float step { 0.0f };
|
||||
uint8_t curve { 0 };
|
||||
uint8_t smooth { 0 };
|
||||
static_assert(config::maxCurves - 1 <= std::numeric_limits<decltype(curve)>::max(), "The curve type in the Modifier struct cannot support the required number of curves");
|
||||
};
|
||||
|
||||
template<class ValueType>
|
||||
struct CCValuePairComparator<ValueType, true> {
|
||||
bool operator()(const CCValuePair<ValueType>& valuePair, const ValueType& value)
|
||||
struct CCDataComparator {
|
||||
bool operator()(const CCData<ValueType>& ccData, const int& cc)
|
||||
{
|
||||
return (valuePair.value < value);
|
||||
return (ccData.cc < cc);
|
||||
}
|
||||
|
||||
bool operator()(const ValueType& value, const CCValuePair<ValueType>& valuePair)
|
||||
bool operator()(const int& cc, const CCData<ValueType>& ccData)
|
||||
{
|
||||
return (value < valuePair.value);
|
||||
return (cc < ccData.cc);
|
||||
}
|
||||
|
||||
bool operator()(const CCValuePair<ValueType>& lhs, const CCValuePair<ValueType>& rhs)
|
||||
bool operator()(const CCData<ValueType>& lhs, const CCData<ValueType>& rhs)
|
||||
{
|
||||
return (lhs.value < rhs.value);
|
||||
return (lhs.cc < rhs.cc);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -273,200 +264,5 @@ bool findDefine(absl::string_view line, absl::string_view& variable, absl::strin
|
|||
*/
|
||||
bool findInclude(absl::string_view line, std::string& path);
|
||||
|
||||
/**
|
||||
* @brief multiply a value by a factor, in cents. To be used for pitch variations.
|
||||
*
|
||||
* @param base
|
||||
* @param modifier
|
||||
*/
|
||||
inline CXX14_CONSTEXPR float multiplyByCentsModifier(int modifier, float base)
|
||||
{
|
||||
return base * centsFactor(modifier);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline CXX14_CONSTEXPR float gainModifier(T modifier, float value)
|
||||
{
|
||||
return value * modifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute a crossfade in value with respect to a crossfade range (note, velocity, cc, ...)
|
||||
*/
|
||||
template <class T, class U>
|
||||
float crossfadeIn(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
||||
{
|
||||
if (value < crossfadeRange.getStart())
|
||||
return 0.0f;
|
||||
|
||||
const auto length = static_cast<float>(crossfadeRange.length());
|
||||
if (length == 0.0f)
|
||||
return 1.0f;
|
||||
|
||||
else if (value < crossfadeRange.getEnd()) {
|
||||
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
||||
if (curve == SfzCrossfadeCurve::power)
|
||||
return sqrt(crossfadePosition);
|
||||
if (curve == SfzCrossfadeCurve::gain)
|
||||
return crossfadePosition;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute a crossfade out value with respect to a crossfade range (note, velocity, cc, ...)
|
||||
*/
|
||||
template <class T, class U>
|
||||
float crossfadeOut(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
||||
{
|
||||
if (value > crossfadeRange.getEnd())
|
||||
return 0.0f;
|
||||
|
||||
const auto length = static_cast<float>(crossfadeRange.length());
|
||||
if (length == 0.0f)
|
||||
return 1.0f;
|
||||
|
||||
else if (value > crossfadeRange.getStart()) {
|
||||
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
||||
if (curve == SfzCrossfadeCurve::power)
|
||||
return std::sqrt(1 - crossfadePosition);
|
||||
if (curve == SfzCrossfadeCurve::gain)
|
||||
return 1 - crossfadePosition;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
|
||||
{
|
||||
ASSERT(events.size() > 0);
|
||||
ASSERT(events[0].delay == 0);
|
||||
if (envelope.size() == 0)
|
||||
return;
|
||||
|
||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||
|
||||
auto lastValue = lambda(events[0].value);
|
||||
auto lastDelay = events[0].delay;
|
||||
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||
const auto step = (lambda(events[i].value) - lastValue) / length;
|
||||
lastValue = linearRamp<float>(envelope.subspan(lastDelay, length), lastValue, step);
|
||||
lastDelay += length;
|
||||
}
|
||||
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
|
||||
{
|
||||
ASSERT(events.size() > 0);
|
||||
ASSERT(events[0].delay == 0);
|
||||
ASSERT(step != 0.0);
|
||||
|
||||
if (envelope.size() == 0)
|
||||
return;
|
||||
|
||||
auto quantize = [step](float value) -> float {
|
||||
return std::round(value / step) * step;
|
||||
};
|
||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||
|
||||
auto lastValue = quantize(lambda(events[0].value));
|
||||
auto lastDelay = events[0].delay;
|
||||
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||
const auto nextValue = quantize(lambda(events[i].value));
|
||||
const auto difference = std::abs(nextValue - lastValue);
|
||||
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||
|
||||
if (difference < step) {
|
||||
fill<float>(envelope.subspan(lastDelay, length), lastValue);
|
||||
lastValue = nextValue;
|
||||
lastDelay += length;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto numSteps = static_cast<int>(difference / step);
|
||||
const auto stepLength = static_cast<int>(length / numSteps);
|
||||
for (int i = 0; i < numSteps; ++i) {
|
||||
fill<float>(envelope.subspan(lastDelay, stepLength), lastValue);
|
||||
lastValue += lastValue <= nextValue ? step : -step;
|
||||
lastDelay += stepLength;
|
||||
}
|
||||
}
|
||||
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
|
||||
{
|
||||
ASSERT(events.size() > 0);
|
||||
ASSERT(events[0].delay == 0);
|
||||
|
||||
if (envelope.size() == 0)
|
||||
return;
|
||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||
|
||||
auto lastValue = lambda(events[0].value);
|
||||
auto lastDelay = events[0].delay;
|
||||
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||
const auto nextValue = lambda(events[i].value);
|
||||
const auto step = std::exp((std::log(nextValue) - std::log(lastValue)) / length);
|
||||
multiplicativeRamp<float>(envelope.subspan(lastDelay, length), lastValue, step);
|
||||
lastValue = nextValue;
|
||||
lastDelay += length;
|
||||
}
|
||||
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
|
||||
{
|
||||
ASSERT(events.size() > 0);
|
||||
ASSERT(events[0].delay == 0);
|
||||
ASSERT(step != 0.0f);
|
||||
|
||||
if (envelope.size() == 0)
|
||||
return;
|
||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||
|
||||
const auto logStep = std::log(step);
|
||||
// If we assume that a = b.q^r for b in (1, q) then
|
||||
// log a log b
|
||||
// ----- = ----- + r
|
||||
// log q log q
|
||||
// and log(b)\log(q) is between 0 and 1.
|
||||
auto quantize = [logStep](float value) -> float {
|
||||
return std::exp(logStep * std::round(std::log(value) / logStep));
|
||||
};
|
||||
|
||||
auto lastValue = quantize(lambda(events[0].value));
|
||||
auto lastDelay = events[0].delay;
|
||||
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||
const auto nextValue = quantize(lambda(events[i].value));
|
||||
const auto difference = nextValue > lastValue ? nextValue / lastValue : lastValue / nextValue;
|
||||
|
||||
if (difference < step) {
|
||||
fill<float>(envelope.subspan(lastDelay, length), lastValue);
|
||||
lastValue = nextValue;
|
||||
lastDelay += length;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto numSteps = static_cast<int>(std::log(difference) / logStep);
|
||||
const auto stepLength = static_cast<int>(length / numSteps);
|
||||
for (int i = 0; i < numSteps; ++i) {
|
||||
fill<float>(envelope.subspan(lastDelay, stepLength), lastValue);
|
||||
lastValue = nextValue > lastValue ? lastValue * step : lastValue / step;
|
||||
lastDelay += stepLength;
|
||||
}
|
||||
}
|
||||
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||
}
|
||||
|
||||
} // namespace sfz
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ void sfz::Synth::onParseFullBlock(const std::string& header, const std::vector<O
|
|||
buildRegion(members);
|
||||
break;
|
||||
case hash("curve"):
|
||||
curves.addCurveFromHeader(members);
|
||||
resources.curves.addCurveFromHeader(members);
|
||||
break;
|
||||
case hash("effect"):
|
||||
handleEffectOpcodes(members);
|
||||
|
|
@ -130,22 +130,18 @@ void sfz::Synth::clear()
|
|||
list.clear();
|
||||
for (auto& list : ccActivationLists)
|
||||
list.clear();
|
||||
|
||||
regions.clear();
|
||||
effectBuses.clear();
|
||||
effectBuses.emplace_back(new EffectBus);
|
||||
effectBuses[0]->setGainToMain(1.0);
|
||||
effectBuses[0]->setSamplesPerBlock(samplesPerBlock);
|
||||
effectBuses[0]->setSampleRate(sampleRate);
|
||||
curves = CurveSet::createPredefined();
|
||||
resources.filePool.clear();
|
||||
resources.wavePool.clearFileWaves();
|
||||
resources.logger.clear();
|
||||
resources.clear();
|
||||
numGroups = 0;
|
||||
numMasters = 0;
|
||||
fileTicket = -1;
|
||||
defaultSwitch = absl::nullopt;
|
||||
defaultPath = "";
|
||||
resources.midiState.reset();
|
||||
ccNames.clear();
|
||||
globalOpcodes.clear();
|
||||
masterOpcodes.clear();
|
||||
|
|
@ -814,7 +810,7 @@ int sfz::Synth::getNumMasters() const noexcept
|
|||
}
|
||||
int sfz::Synth::getNumCurves() const noexcept
|
||||
{
|
||||
return static_cast<int>(curves.getNumCurves());
|
||||
return static_cast<int>(resources.curves.getNumCurves());
|
||||
}
|
||||
|
||||
std::string sfz::Synth::exportMidnam(absl::string_view model) const
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
#include "Voice.h"
|
||||
#include "Region.h"
|
||||
#include "Effects.h"
|
||||
#include "Curve.h"
|
||||
#include "LeakDetector.h"
|
||||
#include "MidiState.h"
|
||||
#include "AudioSpan.h"
|
||||
|
|
@ -340,7 +339,7 @@ public:
|
|||
*/
|
||||
void disableFreeWheeling() noexcept;
|
||||
|
||||
const MidiState& getMidiState() const noexcept { return resources.midiState; }
|
||||
const Resources& getResources() const noexcept { return resources; }
|
||||
|
||||
/**
|
||||
* @brief Check if the SFZ should be reloaded.
|
||||
|
|
@ -515,9 +514,6 @@ private:
|
|||
typedef std::unique_ptr<EffectBus> EffectBusPtr;
|
||||
std::vector<EffectBusPtr> effectBuses; // 0 is "main", 1-N are "fx1"-"fxN"
|
||||
|
||||
// Curves
|
||||
CurveSet curves;
|
||||
|
||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||
float sampleRate { config::defaultSampleRate };
|
||||
float volume { Default::globalVolume };
|
||||
|
|
@ -526,7 +522,6 @@ private:
|
|||
|
||||
// Distribution used to generate random value for the *rand opcodes
|
||||
std::uniform_real_distribution<float> randNoteDistribution { 0, 1 };
|
||||
unsigned fileTicket { 1 };
|
||||
|
||||
std::mutex callbackGuard;
|
||||
bool freeWheeling { false };
|
||||
|
|
|
|||
|
|
@ -4,16 +4,14 @@
|
|||
// 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 "Macros.h"
|
||||
#include "Voice.h"
|
||||
#include "AudioSpan.h"
|
||||
#include "Config.h"
|
||||
#include "Macros.h"
|
||||
#include "Defaults.h"
|
||||
#include "ModifierHelpers.h"
|
||||
#include "MathHelpers.h"
|
||||
#include "SIMDHelpers.h"
|
||||
#include "SfzHelpers.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
#include <memory>
|
||||
|
||||
sfz::Voice::Voice(sfz::Resources& resources)
|
||||
: resources(resources)
|
||||
|
|
@ -264,28 +262,32 @@ void sfz::Voice::amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept
|
|||
// Amplitude envelope
|
||||
applyGain<float>(baseGain, modulationSpan);
|
||||
for (const auto& mod : region->amplitudeCC) {
|
||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
||||
linearModifier(resources, *tempSpan, mod);
|
||||
applyGain<float>(*tempSpan, modulationSpan);
|
||||
}
|
||||
|
||||
// Crossfade envelopes
|
||||
for (const auto& mod : region->crossfadeCCInRange) {
|
||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||
linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.value, x, xfCurve); });
|
||||
linearEnvelope(events, *tempSpan, [&](float x) {
|
||||
return crossfadeIn(mod.data, x, xfCurve);
|
||||
});
|
||||
applyGain<float>(*tempSpan, modulationSpan);
|
||||
}
|
||||
for (const auto& mod : region->crossfadeCCOutRange) {
|
||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||
linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.value, x, xfCurve); });
|
||||
linearEnvelope(events, *tempSpan, [&](float x) {
|
||||
return crossfadeOut(mod.data, x, xfCurve);
|
||||
});
|
||||
applyGain<float>(*tempSpan, modulationSpan);
|
||||
}
|
||||
|
||||
// Volume envelope
|
||||
applyGain<float>(db2mag(baseVolumedB), modulationSpan);
|
||||
for (const auto& mod : region->volumeCC) {
|
||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||
multiplicativeEnvelope(events, *tempSpan, [&](float x) { return db2mag(x * mod.value); });
|
||||
multiplicativeModifier(resources, *tempSpan, mod, [](float x) {
|
||||
return db2mag(x);
|
||||
});
|
||||
applyGain<float>(*tempSpan, modulationSpan);
|
||||
}
|
||||
}
|
||||
|
|
@ -337,8 +339,7 @@ void sfz::Voice::panStageMono(AudioSpan<float> buffer) noexcept
|
|||
// Apply panning
|
||||
fill<float>(*modulationSpan, region->pan);
|
||||
for (const auto& mod : region->panCC) {
|
||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
||||
linearModifier(resources, *tempSpan, mod);
|
||||
add<float>(*tempSpan, *modulationSpan);
|
||||
}
|
||||
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
|
||||
|
|
@ -357,30 +358,24 @@ void sfz::Voice::panStageStereo(AudioSpan<float> buffer) noexcept
|
|||
return;
|
||||
|
||||
// Apply panning
|
||||
// panningModulation(*modulationSpan);
|
||||
fill<float>(*modulationSpan, region->pan);
|
||||
for (const auto& mod : region->panCC) {
|
||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
||||
linearModifier(resources, *tempSpan, mod);
|
||||
add<float>(*tempSpan, *modulationSpan);
|
||||
}
|
||||
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
|
||||
|
||||
// Apply the width/position process
|
||||
// widthModulation(*modulationSpan);
|
||||
fill<float>(*modulationSpan, region->width);
|
||||
for (const auto& mod : region->widthCC) {
|
||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
||||
linearModifier(resources, *tempSpan, mod);
|
||||
add<float>(*tempSpan, *modulationSpan);
|
||||
}
|
||||
width<float>(*modulationSpan, leftBuffer, rightBuffer);
|
||||
|
||||
// positionModulation(*modulationSpan);
|
||||
fill<float>(*modulationSpan, region->position);
|
||||
for (const auto& mod : region->positionCC) {
|
||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
||||
linearModifier(resources, *tempSpan, mod);
|
||||
add<float>(*tempSpan, *modulationSpan);
|
||||
}
|
||||
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
|
||||
|
|
@ -451,14 +446,13 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
|
|||
};
|
||||
|
||||
if (region->bendStep > 1)
|
||||
multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor);
|
||||
pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor);
|
||||
else
|
||||
multiplicativeEnvelope(events, *bends, bendLambda);
|
||||
pitchBendEnvelope(events, *bends, bendLambda);
|
||||
applyGain<float>(*bends, *jumps);
|
||||
|
||||
for (const auto& mod : region->tuneCC) {
|
||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||
multiplicativeEnvelope(events, *bends, [&](float x) { return centsFactor(x * mod.value); });
|
||||
multiplicativeModifier(resources, *bends, mod, [](float x) { return centsFactor(x); });
|
||||
applyGain<float>(*bends, *jumps);
|
||||
}
|
||||
|
||||
|
|
@ -555,14 +549,15 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
|
|||
return centsFactor(bendInCents);
|
||||
};
|
||||
if (region->bendStep > 1)
|
||||
multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor);
|
||||
pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor);
|
||||
else
|
||||
multiplicativeEnvelope(events, *bends, bendLambda);
|
||||
pitchBendEnvelope(events, *bends, bendLambda);
|
||||
applyGain<float>(*bends, *frequencies);
|
||||
|
||||
for (const auto& mod : region->tuneCC) {
|
||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||
multiplicativeEnvelope(events, *bends, [&](float x) { return centsFactor(x * mod.value); });
|
||||
multiplicativeModifier(resources, *bends, mod, [](float x) {
|
||||
return centsFactor(x);
|
||||
});
|
||||
applyGain<float>(*bends, *frequencies);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,13 +10,10 @@
|
|||
#include "HistoricalBuffer.h"
|
||||
#include "Region.h"
|
||||
#include "AudioBuffer.h"
|
||||
#include "MidiState.h"
|
||||
#include "Wavetables.h"
|
||||
#include "Resources.h"
|
||||
#include "AudioSpan.h"
|
||||
#include "LeakDetector.h"
|
||||
#include <absl/types/span.h>
|
||||
#include <atomic>
|
||||
#include "absl/types/span.h"
|
||||
#include <memory>
|
||||
#include <random>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ TEST_CASE("[Curve] Bipolar -1 to 1")
|
|||
REQUIRE( curve.evalCC7(85) == Approx(0.3386).margin(1e-3) );
|
||||
REQUIRE( curve.evalNormalized(0.0f) == -1.0f );
|
||||
REQUIRE( curve.evalNormalized(1.0f) == 1.0f );
|
||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(-0.402).margin(1e-3) );
|
||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(-0.4).margin(1e-3) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Bipolar 1 to 0")
|
||||
|
|
@ -59,7 +59,7 @@ TEST_CASE("[Curve] Bipolar 1 to -1")
|
|||
REQUIRE( curve.evalCC7(85) == Approx(-0.3386).margin(1e-3) );
|
||||
REQUIRE( curve.evalNormalized(0.0f) == 1.0f );
|
||||
REQUIRE( curve.evalNormalized(1.0f) == -1.0f );
|
||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(0.402).margin(1e-3) );
|
||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(0.4).margin(1e-3) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] x**2")
|
||||
|
|
@ -199,6 +199,17 @@ TEST_CASE("[Curve] Add curves to CurveSet")
|
|||
REQUIRE( curveSet.getCurve(4).evalCC7(0) == 1.0f );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Add bad indices")
|
||||
{
|
||||
sfz::CurveSet curveSet;
|
||||
curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0), -2);
|
||||
REQUIRE( curveSet.getNumCurves() == 0 );
|
||||
curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0), 256);
|
||||
REQUIRE( curveSet.getNumCurves() == 0 );
|
||||
curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0), 512);
|
||||
REQUIRE( curveSet.getNumCurves() == 0 );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Default CurveSet")
|
||||
{
|
||||
auto curveSet = sfz::CurveSet::createPredefined();
|
||||
|
|
@ -209,7 +220,7 @@ TEST_CASE("[Curve] Default CurveSet")
|
|||
|
||||
REQUIRE( curveSet.getCurve(1).evalNormalized(0.0f) == -1.0f );
|
||||
REQUIRE( curveSet.getCurve(1).evalNormalized(1.0f) == 1.0f );
|
||||
REQUIRE( curveSet.getCurve(1).evalNormalized(0.3f) == Approx(-0.402).margin(1e-3) );
|
||||
REQUIRE( curveSet.getCurve(1).evalNormalized(0.3f) == Approx(-0.4).margin(1e-3) );
|
||||
|
||||
REQUIRE( curveSet.getCurve(2).evalNormalized(0.0f) == 1.0f );
|
||||
REQUIRE( curveSet.getCurve(2).evalNormalized(1.0f) == 0.0f );
|
||||
|
|
@ -217,7 +228,7 @@ TEST_CASE("[Curve] Default CurveSet")
|
|||
|
||||
REQUIRE( curveSet.getCurve(3).evalNormalized(0.0f) == 1.0f );
|
||||
REQUIRE( curveSet.getCurve(3).evalNormalized(1.0f) == -1.0f );
|
||||
REQUIRE( curveSet.getCurve(3).evalNormalized(0.3f) == Approx(0.402).margin(1e-3) );
|
||||
REQUIRE( curveSet.getCurve(3).evalNormalized(0.3f) == Approx(0.4).margin(1e-3) );
|
||||
|
||||
REQUIRE( curveSet.getCurve(4).evalNormalized(0.0f) == 0.0f );
|
||||
REQUIRE( curveSet.getCurve(4).evalNormalized(1.0f) == 1.0f );
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#include "sfizz/SfzHelpers.h"
|
||||
#include "sfizz/ModifierHelpers.h"
|
||||
#include "sfizz/Buffer.h"
|
||||
#include "catch2/catch.hpp"
|
||||
#include <absl/algorithm/container.h>
|
||||
|
|
@ -42,13 +42,13 @@ TEST_CASE("[Envelopes] Empty")
|
|||
std::array<float, 5> expected { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
std::array<float, 5> expectedMul { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
multiplicativeEnvelope(events, absl::MakeSpan(output), expModifier);
|
||||
REQUIRE(output == expectedMul);
|
||||
REQUIRE(approxEqual<float>(output, expectedMul));
|
||||
multiplicativeEnvelope(events, absl::MakeSpan(output), expModifier, 2.0f);
|
||||
REQUIRE(output == expectedMul);
|
||||
REQUIRE(approxEqual<float>(output, expectedMul));
|
||||
}
|
||||
|
||||
TEST_CASE("[Envelopes] Linear basic")
|
||||
|
|
@ -60,7 +60,7 @@ TEST_CASE("[Envelopes] Linear basic")
|
|||
std::array<float, 9> output;
|
||||
std::array<float, 9> expected { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[LinearEnvelope] 2 events, close")
|
||||
|
|
@ -73,7 +73,7 @@ TEST_CASE("[LinearEnvelope] 2 events, close")
|
|||
std::array<float, 9> output;
|
||||
std::array<float, 9> expected { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f };
|
||||
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[LinearEnvelope] 2 events, far")
|
||||
|
|
@ -86,7 +86,7 @@ TEST_CASE("[LinearEnvelope] 2 events, far")
|
|||
std::array<float, 9> output;
|
||||
std::array<float, 9> expected { 0.0f, 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.0f, 2.0f };
|
||||
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[LinearEnvelope] 3 events, out of block")
|
||||
|
|
@ -100,7 +100,7 @@ TEST_CASE("[LinearEnvelope] 3 events, out of block")
|
|||
std::array<float, 9> output;
|
||||
std::array<float, 9> expected { 0.0f, 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.5f, 3.0f };
|
||||
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[LinearEnvelope] 2 events, function")
|
||||
|
|
@ -113,7 +113,7 @@ TEST_CASE("[LinearEnvelope] 2 events, function")
|
|||
std::array<float, 9> output;
|
||||
std::array<float, 9> expected { 0.0f, 1.0f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.0f, 4.0f };
|
||||
linearEnvelope(events, absl::MakeSpan(output), twiceModifier);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[LinearEnvelope] Get quantized")
|
||||
|
|
@ -126,7 +126,7 @@ TEST_CASE("[LinearEnvelope] Get quantized")
|
|||
std::array<float, 8> output;
|
||||
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f };
|
||||
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets")
|
||||
|
|
@ -137,9 +137,9 @@ TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets")
|
|||
{ 6, 1.9f }
|
||||
};
|
||||
std::array<float, 8> output;
|
||||
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f };
|
||||
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[LinearEnvelope] Get quantized with 2 steps")
|
||||
|
|
@ -152,7 +152,7 @@ TEST_CASE("[LinearEnvelope] Get quantized with 2 steps")
|
|||
std::array<float, 8> output;
|
||||
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f };
|
||||
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and an unquantized out of block step")
|
||||
|
|
@ -166,7 +166,7 @@ TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and an unquantized out of
|
|||
std::array<float, 8> output;
|
||||
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 4.0f };
|
||||
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ TEST_CASE("[LinearEnvelope] Going down quantized with 2 steps")
|
|||
std::array<float, 8> output;
|
||||
std::array<float, 8> expected { 3.0f, 3.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f };
|
||||
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[MultiplicativeEnvelope] Basic event")
|
||||
|
|
@ -224,52 +224,140 @@ TEST_CASE("[MultiplicativeEnvelope] 2 events, far")
|
|||
TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps")
|
||||
{
|
||||
sfz::EventVector events {
|
||||
{ 0, 1.0f },
|
||||
{ 2, 2.0f },
|
||||
{ 6, 4.0f }
|
||||
{ 0, 1.3f },
|
||||
{ 2, 2.1f },
|
||||
{ 6, 4.2f }
|
||||
};
|
||||
std::array<float, 8> output;
|
||||
std::array<float, 8> expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 4.0f };
|
||||
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[MultiplicativeEnvelope] Get quantized with an unquantized out of range step")
|
||||
{
|
||||
sfz::EventVector events {
|
||||
{ 0, 1.0f },
|
||||
{ 2, 2.0f },
|
||||
{ 6, 4.0f },
|
||||
{ 0, 1.3f },
|
||||
{ 2, 2.1f },
|
||||
{ 6, 4.1f },
|
||||
{ 10, 8.2f }
|
||||
};
|
||||
std::array<float, 8> output;
|
||||
std::array<float, 8> expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 8.0f };
|
||||
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps")
|
||||
{
|
||||
sfz::EventVector events {
|
||||
{ 0, 4.0f },
|
||||
{ 2, 2.0f },
|
||||
{ 6, 0.5f }
|
||||
{ 0, 4.1f },
|
||||
{ 2, 2.2f },
|
||||
{ 6, 0.4f }
|
||||
};
|
||||
std::array<float, 8> output;
|
||||
std::array<float, 8> expected { 4.0f, 4.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.5f, 0.5f };
|
||||
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
||||
REQUIRE(output == expected);
|
||||
REQUIRE(approxEqual<float>(output, expected));
|
||||
}
|
||||
|
||||
TEST_CASE("[MultiplicativeEnvelope] Get quantized with unclean events")
|
||||
TEST_CASE("[linearModifiers] Compare with envelopes")
|
||||
{
|
||||
sfz::EventVector events {
|
||||
{ 0, 1.0f },
|
||||
{ 2, 1.2f },
|
||||
{ 6, 2.5f }
|
||||
};
|
||||
std::array<float, 8> output;
|
||||
std::array<float, 8> expected { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f };
|
||||
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
||||
REQUIRE(output == expected);
|
||||
sfz::Resources resources;
|
||||
resources.curves = sfz::CurveSet::createPredefined();
|
||||
|
||||
sfz::CCData<sfz::Modifier> ccData;
|
||||
ccData.cc = 20;
|
||||
ccData.data.value = 100.0f;
|
||||
|
||||
resources.midiState.ccEvent(5, 20, 0.1);
|
||||
resources.midiState.ccEvent(10, 20, 0.8);
|
||||
|
||||
std::array<float, 16> output;
|
||||
std::array<float, 16> envelope;
|
||||
|
||||
linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||
return ccData.data.value * x;
|
||||
});
|
||||
linearModifier(resources, absl::MakeSpan(output), ccData);
|
||||
REQUIRE(approxEqual<float>(output, envelope));
|
||||
|
||||
ccData.data.curve = 1;
|
||||
linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||
return ccData.data.value * (2 * x - 1);
|
||||
});
|
||||
linearModifier(resources, absl::MakeSpan(output), ccData);
|
||||
REQUIRE(approxEqual<float>(output, envelope));
|
||||
|
||||
ccData.data.curve = 3;
|
||||
ccData.data.value = 10.0f;
|
||||
linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||
return ccData.data.value * (1 - 2 * x);
|
||||
});
|
||||
linearModifier(resources, absl::MakeSpan(output), ccData);
|
||||
REQUIRE(approxEqual<float>(output, envelope));
|
||||
|
||||
ccData.data.curve = 2;
|
||||
ccData.data.value = 20.0f;
|
||||
ccData.data.step = 2.5f;
|
||||
linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||
return ccData.data.value * (1 - x);
|
||||
}, ccData.data.step);
|
||||
linearModifier(resources, absl::MakeSpan(output), ccData);
|
||||
REQUIRE(approxEqual<float>(output, envelope));
|
||||
}
|
||||
|
||||
TEST_CASE("[multiplicativeModifiers] Compare with envelopes")
|
||||
{
|
||||
sfz::Resources resources;
|
||||
resources.curves = sfz::CurveSet::createPredefined();
|
||||
|
||||
sfz::CCData<sfz::Modifier> ccData;
|
||||
ccData.cc = 20;
|
||||
ccData.data.value = 100.0f;
|
||||
|
||||
resources.midiState.ccEvent(5, 20, 0.1);
|
||||
resources.midiState.ccEvent(15, 20, 0.8);
|
||||
|
||||
std::array<float, 16> output;
|
||||
std::array<float, 16> envelope;
|
||||
|
||||
multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||
return db2mag(ccData.data.value * x);
|
||||
});
|
||||
multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) {
|
||||
return db2mag(x);
|
||||
});
|
||||
REQUIRE(approxEqual<float>(output, envelope));
|
||||
|
||||
ccData.data.curve = 1;
|
||||
multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||
return db2mag(ccData.data.value * (2 * x - 1));
|
||||
});
|
||||
multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) {
|
||||
return db2mag(x);
|
||||
});
|
||||
REQUIRE(approxEqual<float>(output, envelope));
|
||||
|
||||
ccData.data.curve = 3;
|
||||
ccData.data.value = 10.0f;
|
||||
multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||
return db2mag(ccData.data.value * (1 - 2 * x));
|
||||
});
|
||||
multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) {
|
||||
return db2mag(x);
|
||||
});
|
||||
REQUIRE(approxEqual<float>(output, envelope));
|
||||
|
||||
ccData.data.curve = 2;
|
||||
ccData.data.value = 20.0f;
|
||||
ccData.data.step = 2.5f;
|
||||
multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||
return db2mag(ccData.data.value * (1 - x));
|
||||
}, db2mag(ccData.data.step) );
|
||||
multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) {
|
||||
return db2mag(x);
|
||||
});
|
||||
REQUIRE(approxEqual<float>(output, envelope));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines")
|
|||
REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 );
|
||||
REQUIRE(!synth.getRegionView(2)->amplitudeCC.empty());
|
||||
REQUIRE(synth.getRegionView(2)->amplitudeCC.contains(10));
|
||||
REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10) == 0.34f);
|
||||
REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10).value == 0.34f);
|
||||
}
|
||||
|
||||
TEST_CASE("[Files] Specific bug: relative path with backslashes")
|
||||
|
|
@ -394,9 +394,10 @@ TEST_CASE("[Files] Default path is ignored for generators")
|
|||
TEST_CASE("[Files] Set CC applies properly")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
const auto& midiState = synth.getResources().midiState;
|
||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/set_cc.sfz");
|
||||
REQUIRE(synth.getMidiState().getCCValue(142) == 63_norm);
|
||||
REQUIRE(synth.getMidiState().getCCValue(61) == 122_norm);
|
||||
REQUIRE(midiState.getCCValue(142) == 63_norm);
|
||||
REQUIRE(midiState.getCCValue(61) == 122_norm);
|
||||
}
|
||||
|
||||
TEST_CASE("[Files] Note and octave offsets")
|
||||
|
|
|
|||
|
|
@ -470,7 +470,25 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.panCC.empty());
|
||||
region.parseOpcode({ "pan_oncc45", "4.2" });
|
||||
REQUIRE(region.panCC.contains(45));
|
||||
REQUIRE(region.panCC[45] == 0.042_a);
|
||||
REQUIRE(region.panCC[45].value == 0.042_a);
|
||||
region.parseOpcode({ "pan_curvecc17", "18" });
|
||||
REQUIRE(region.panCC[17].curve == 18);
|
||||
region.parseOpcode({ "pan_curvecc17", "15482" });
|
||||
REQUIRE(region.panCC[17].curve == 255);
|
||||
region.parseOpcode({ "pan_curvecc17", "-2" });
|
||||
REQUIRE(region.panCC[17].curve == 0);
|
||||
region.parseOpcode({ "pan_smoothcc14", "85" });
|
||||
REQUIRE(region.panCC[14].smooth == 85);
|
||||
region.parseOpcode({ "pan_smoothcc14", "15482" });
|
||||
REQUIRE(region.panCC[14].smooth == 127);
|
||||
region.parseOpcode({ "pan_smoothcc14", "-2" });
|
||||
REQUIRE(region.panCC[14].smooth == 0);
|
||||
region.parseOpcode({ "pan_stepcc120", "24" });
|
||||
REQUIRE(region.panCC[120].step == 0.24_a);
|
||||
region.parseOpcode({ "pan_stepcc120", "15482" });
|
||||
REQUIRE(region.panCC[120].step == 2.0_a);
|
||||
region.parseOpcode({ "pan_stepcc120", "-2" });
|
||||
REQUIRE(region.panCC[120].step == 0.0f);
|
||||
}
|
||||
|
||||
SECTION("width")
|
||||
|
|
@ -491,7 +509,25 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.widthCC.empty());
|
||||
region.parseOpcode({ "width_oncc45", "4.2" });
|
||||
REQUIRE(region.widthCC.contains(45));
|
||||
REQUIRE(region.widthCC[45] == 0.042_a);
|
||||
REQUIRE(region.widthCC[45].value == 0.042_a);
|
||||
region.parseOpcode({ "width_curvecc17", "18" });
|
||||
REQUIRE(region.widthCC[17].curve == 18);
|
||||
region.parseOpcode({ "width_curvecc17", "15482" });
|
||||
REQUIRE(region.widthCC[17].curve == 255);
|
||||
region.parseOpcode({ "width_curvecc17", "-2" });
|
||||
REQUIRE(region.widthCC[17].curve == 0);
|
||||
region.parseOpcode({ "width_smoothcc14", "85" });
|
||||
REQUIRE(region.widthCC[14].smooth == 85);
|
||||
region.parseOpcode({ "width_smoothcc14", "15482" });
|
||||
REQUIRE(region.widthCC[14].smooth == 127);
|
||||
region.parseOpcode({ "width_smoothcc14", "-2" });
|
||||
REQUIRE(region.widthCC[14].smooth == 0);
|
||||
region.parseOpcode({ "width_stepcc120", "24" });
|
||||
REQUIRE(region.widthCC[120].step == 0.24_a);
|
||||
region.parseOpcode({ "width_stepcc120", "15482" });
|
||||
REQUIRE(region.widthCC[120].step == 2.0_a);
|
||||
region.parseOpcode({ "width_stepcc120", "-20" });
|
||||
REQUIRE(region.widthCC[120].step == 0.0f);
|
||||
}
|
||||
|
||||
SECTION("position")
|
||||
|
|
@ -512,7 +548,25 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.positionCC.empty());
|
||||
region.parseOpcode({ "position_oncc45", "4.2" });
|
||||
REQUIRE(region.positionCC.contains(45));
|
||||
REQUIRE(region.positionCC[45] == 0.042_a);
|
||||
REQUIRE(region.positionCC[45].value == 0.042_a);
|
||||
region.parseOpcode({ "position_curvecc17", "18" });
|
||||
REQUIRE(region.positionCC[17].curve == 18);
|
||||
region.parseOpcode({ "position_curvecc17", "15482" });
|
||||
REQUIRE(region.positionCC[17].curve == 255);
|
||||
region.parseOpcode({ "position_curvecc17", "-2" });
|
||||
REQUIRE(region.positionCC[17].curve == 0);
|
||||
region.parseOpcode({ "position_smoothcc14", "85" });
|
||||
REQUIRE(region.positionCC[14].smooth == 85);
|
||||
region.parseOpcode({ "position_smoothcc14", "15482" });
|
||||
REQUIRE(region.positionCC[14].smooth == 127);
|
||||
region.parseOpcode({ "position_smoothcc14", "-2" });
|
||||
REQUIRE(region.positionCC[14].smooth == 0);
|
||||
region.parseOpcode({ "position_stepcc120", "24" });
|
||||
REQUIRE(region.positionCC[120].step == 0.24_a);
|
||||
region.parseOpcode({ "position_stepcc120", "15482" });
|
||||
REQUIRE(region.positionCC[120].step == 2.0_a);
|
||||
region.parseOpcode({ "position_stepcc120", "-2" });
|
||||
REQUIRE(region.positionCC[120].step == 0.0f);
|
||||
}
|
||||
|
||||
SECTION("amp_keycenter")
|
||||
|
|
@ -974,13 +1028,13 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.amplitudeEG.ccRelease->cc == 5);
|
||||
REQUIRE(region.amplitudeEG.ccStart->cc == 6);
|
||||
REQUIRE(region.amplitudeEG.ccSustain->cc == 7);
|
||||
REQUIRE(region.amplitudeEG.ccAttack->value == 1.0f);
|
||||
REQUIRE(region.amplitudeEG.ccDecay->value == 2.0f);
|
||||
REQUIRE(region.amplitudeEG.ccDelay->value == 3.0f);
|
||||
REQUIRE(region.amplitudeEG.ccHold->value == 4.0f);
|
||||
REQUIRE(region.amplitudeEG.ccRelease->value == 5.0f);
|
||||
REQUIRE(region.amplitudeEG.ccStart->value == 6.0f);
|
||||
REQUIRE(region.amplitudeEG.ccSustain->value == 7.0f);
|
||||
REQUIRE(region.amplitudeEG.ccAttack->data == 1.0f);
|
||||
REQUIRE(region.amplitudeEG.ccDecay->data == 2.0f);
|
||||
REQUIRE(region.amplitudeEG.ccDelay->data == 3.0f);
|
||||
REQUIRE(region.amplitudeEG.ccHold->data == 4.0f);
|
||||
REQUIRE(region.amplitudeEG.ccRelease->data == 5.0f);
|
||||
REQUIRE(region.amplitudeEG.ccStart->data == 6.0f);
|
||||
REQUIRE(region.amplitudeEG.ccSustain->data == 7.0f);
|
||||
//
|
||||
region.parseOpcode({ "ampeg_attack_oncc1", "101" });
|
||||
region.parseOpcode({ "ampeg_decay_oncc2", "101" });
|
||||
|
|
@ -989,13 +1043,13 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
region.parseOpcode({ "ampeg_release_oncc5", "101" });
|
||||
region.parseOpcode({ "ampeg_start_oncc6", "101" });
|
||||
region.parseOpcode({ "ampeg_sustain_oncc7", "101" });
|
||||
REQUIRE(region.amplitudeEG.ccAttack->value == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccDecay->value == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccDelay->value == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccHold->value == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccRelease->value == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccStart->value == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccSustain->value == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccAttack->data == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccDecay->data == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccDelay->data == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccHold->data == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccRelease->data == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccStart->data == 100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccSustain->data == 100.0f);
|
||||
//
|
||||
region.parseOpcode({ "ampeg_attack_oncc1", "-101" });
|
||||
region.parseOpcode({ "ampeg_decay_oncc2", "-101" });
|
||||
|
|
@ -1004,13 +1058,13 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
region.parseOpcode({ "ampeg_release_oncc5", "-101" });
|
||||
region.parseOpcode({ "ampeg_start_oncc6", "-101" });
|
||||
region.parseOpcode({ "ampeg_sustain_oncc7", "-101" });
|
||||
REQUIRE(region.amplitudeEG.ccAttack->value == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccDecay->value == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccDelay->value == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccHold->value == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccRelease->value == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccStart->value == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccSustain->value == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccAttack->data == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccDecay->data == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccDelay->data == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccHold->data == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccRelease->data == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccStart->data == -100.0f);
|
||||
REQUIRE(region.amplitudeEG.ccSustain->data == -100.0f);
|
||||
}
|
||||
|
||||
SECTION("sustain_sw and sostenuto_sw")
|
||||
|
|
@ -1448,10 +1502,28 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.amplitudeCC.empty());
|
||||
region.parseOpcode({ "amplitude_cc1", "40" });
|
||||
REQUIRE(region.amplitudeCC.contains(1));
|
||||
REQUIRE(region.amplitudeCC[1] == 0.40_a);
|
||||
REQUIRE(region.amplitudeCC[1].value == 0.40_a);
|
||||
region.parseOpcode({ "amplitude_oncc2", "30" });
|
||||
REQUIRE(region.amplitudeCC.contains(2));
|
||||
REQUIRE(region.amplitudeCC[2] == 0.30_a);
|
||||
REQUIRE(region.amplitudeCC[2].value == 0.30_a);
|
||||
region.parseOpcode({ "amplitude_curvecc17", "18" });
|
||||
REQUIRE(region.amplitudeCC[17].curve == 18);
|
||||
region.parseOpcode({ "amplitude_curvecc17", "15482" });
|
||||
REQUIRE(region.amplitudeCC[17].curve == 255);
|
||||
region.parseOpcode({ "amplitude_curvecc17", "-2" });
|
||||
REQUIRE(region.amplitudeCC[17].curve == 0);
|
||||
region.parseOpcode({ "amplitude_smoothcc14", "85" });
|
||||
REQUIRE(region.amplitudeCC[14].smooth == 85);
|
||||
region.parseOpcode({ "amplitude_smoothcc14", "15482" });
|
||||
REQUIRE(region.amplitudeCC[14].smooth == 127);
|
||||
region.parseOpcode({ "amplitude_smoothcc14", "-2" });
|
||||
REQUIRE(region.amplitudeCC[14].smooth == 0);
|
||||
region.parseOpcode({ "amplitude_stepcc120", "24" });
|
||||
REQUIRE(region.amplitudeCC[120].step == 0.24_a);
|
||||
region.parseOpcode({ "amplitude_stepcc120", "15482" });
|
||||
REQUIRE(region.amplitudeCC[120].step == 1.0_a);
|
||||
region.parseOpcode({ "amplitude_stepcc120", "-2" });
|
||||
REQUIRE(region.amplitudeCC[120].step == 0.0f);
|
||||
}
|
||||
|
||||
SECTION("volume_oncc/gain_cc")
|
||||
|
|
@ -1459,13 +1531,31 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.volumeCC.empty());
|
||||
region.parseOpcode({ "gain_cc1", "40" });
|
||||
REQUIRE(region.volumeCC.contains(1));
|
||||
REQUIRE(region.volumeCC[1] == 40_a);
|
||||
REQUIRE(region.volumeCC[1].value == 40_a);
|
||||
region.parseOpcode({ "volume_oncc2", "-76" });
|
||||
REQUIRE(region.volumeCC.contains(2));
|
||||
REQUIRE(region.volumeCC[2] == -76.0_a);
|
||||
REQUIRE(region.volumeCC[2].value == -76.0_a);
|
||||
region.parseOpcode({ "gain_oncc4", "-1" });
|
||||
REQUIRE(region.volumeCC.contains(4));
|
||||
REQUIRE(region.volumeCC[4] == -1.0_a);
|
||||
REQUIRE(region.volumeCC[4].value == -1.0_a);
|
||||
region.parseOpcode({ "volume_curvecc17", "18" });
|
||||
REQUIRE(region.volumeCC[17].curve == 18);
|
||||
region.parseOpcode({ "volume_curvecc17", "15482" });
|
||||
REQUIRE(region.volumeCC[17].curve == 255);
|
||||
region.parseOpcode({ "volume_curvecc17", "-2" });
|
||||
REQUIRE(region.volumeCC[17].curve == 0);
|
||||
region.parseOpcode({ "volume_smoothcc14", "85" });
|
||||
REQUIRE(region.volumeCC[14].smooth == 85);
|
||||
region.parseOpcode({ "volume_smoothcc14", "15482" });
|
||||
REQUIRE(region.volumeCC[14].smooth == 127);
|
||||
region.parseOpcode({ "volume_smoothcc14", "-2" });
|
||||
REQUIRE(region.volumeCC[14].smooth == 0);
|
||||
region.parseOpcode({ "volume_stepcc120", "24" });
|
||||
REQUIRE(region.volumeCC[120].step == 24.0f);
|
||||
region.parseOpcode({ "volume_stepcc120", "15482" });
|
||||
REQUIRE(region.volumeCC[120].step == 48.0f);
|
||||
region.parseOpcode({ "volume_stepcc120", "-2" });
|
||||
REQUIRE(region.volumeCC[120].step == 0.0f);
|
||||
}
|
||||
|
||||
SECTION("tune_cc/pitch_cc")
|
||||
|
|
@ -1473,13 +1563,31 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.tuneCC.empty());
|
||||
region.parseOpcode({ "pitch_cc1", "40" });
|
||||
REQUIRE(region.tuneCC.contains(1));
|
||||
REQUIRE(region.tuneCC[1] == 40);
|
||||
REQUIRE(region.tuneCC[1].value == 40.0);
|
||||
region.parseOpcode({ "tune_oncc2", "-76" });
|
||||
REQUIRE(region.tuneCC.contains(2));
|
||||
REQUIRE(region.tuneCC[2] == -76.0);
|
||||
REQUIRE(region.tuneCC[2].value == -76.0);
|
||||
region.parseOpcode({ "pitch_oncc4", "-1" });
|
||||
REQUIRE(region.tuneCC.contains(4));
|
||||
REQUIRE(region.tuneCC[4] == -1.0);
|
||||
REQUIRE(region.tuneCC[4].value == -1.0);
|
||||
region.parseOpcode({ "tune_curvecc17", "18" });
|
||||
REQUIRE(region.tuneCC[17].curve == 18);
|
||||
region.parseOpcode({ "pitch_curvecc17", "15482" });
|
||||
REQUIRE(region.tuneCC[17].curve == 255);
|
||||
region.parseOpcode({ "tune_curvecc17", "-2" });
|
||||
REQUIRE(region.tuneCC[17].curve == 0);
|
||||
region.parseOpcode({ "pitch_smoothcc14", "85" });
|
||||
REQUIRE(region.tuneCC[14].smooth == 85);
|
||||
region.parseOpcode({ "tune_smoothcc14", "15482" });
|
||||
REQUIRE(region.tuneCC[14].smooth == 127);
|
||||
region.parseOpcode({ "pitch_smoothcc14", "-2" });
|
||||
REQUIRE(region.tuneCC[14].smooth == 0);
|
||||
region.parseOpcode({ "tune_stepcc120", "24" });
|
||||
REQUIRE(region.tuneCC[120].step == 24.0f);
|
||||
region.parseOpcode({ "pitch_stepcc120", "15482" });
|
||||
REQUIRE(region.tuneCC[120].step == 9600.0f);
|
||||
region.parseOpcode({ "tune_stepcc120", "-2" });
|
||||
REQUIRE(region.tuneCC[120].step == 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -140,10 +140,11 @@ TEST_CASE("[Synth] All notes offs/all sounds off")
|
|||
TEST_CASE("[Synth] Reset all controllers")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
const auto& midiState = synth.getResources().midiState;
|
||||
synth.cc(0, 12, 64);
|
||||
REQUIRE(synth.getMidiState().getCCValue(12) == 64_norm);
|
||||
REQUIRE(midiState.getCCValue(12) == 64_norm);
|
||||
synth.cc(0, 121, 64);
|
||||
REQUIRE(synth.getMidiState().getCCValue(12) == 0_norm);
|
||||
REQUIRE(midiState.getCCValue(12) == 0_norm);
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kills the voice")
|
||||
|
|
@ -419,3 +420,17 @@ TEST_CASE("[Synth] Polyphony in master")
|
|||
synth.noteOn(0, 61, 64);
|
||||
REQUIRE(synth.getNumActiveVoices() == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Basic curves")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
const auto& curves = synth.getResources().curves;
|
||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/curves.sfz");
|
||||
REQUIRE( synth.getNumCurves() == 19 );
|
||||
REQUIRE( curves.getCurve(18).evalCC7(127) == 1.0f );
|
||||
REQUIRE( curves.getCurve(18).evalCC7(95) == 0.5f );
|
||||
REQUIRE( curves.getCurve(17).evalCC7(100) == 1.0f );
|
||||
REQUIRE( curves.getCurve(17).evalCC7(95) == 0.5f );
|
||||
// Default linear
|
||||
REQUIRE( curves.getCurve(16).evalCC7(63) == Approx(63_norm) );
|
||||
}
|
||||
|
|
|
|||
10
tests/TestFiles/curves.sfz
Normal file
10
tests/TestFiles/curves.sfz
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<region> sample=*sine
|
||||
<curve>curve_index=18
|
||||
v000=0
|
||||
v095=0.5
|
||||
v127=1
|
||||
|
||||
<curve>curve_index=17
|
||||
v000=0
|
||||
v095=0.5
|
||||
v100=1
|
||||
Loading…
Add table
Reference in a new issue