Merge pull request #166 from paulfd/curvecc-stepcc

Add support for curvecc and stepcc
This commit is contained in:
Paul Ferrand 2020-04-14 23:52:52 +02:00 committed by GitHub
commit 1feb073d0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 860 additions and 435 deletions

View file

@ -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 {

View file

@ -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);
};
}

View file

@ -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 };

View file

@ -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

View file

@ -45,7 +45,7 @@ public:
*/
float evalNormalized(float value) const
{
return evalCC7(denormalize7Bits<int>(value));
return evalCC7(value * 127.0f);
}
/**

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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);

View file

@ -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

View file

@ -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
View 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; });
}
}

View file

@ -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()))

View file

@ -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);
}

View file

@ -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 };

View file

@ -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();
}
};
}

View file

@ -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

View file

@ -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

View file

@ -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 };

View file

@ -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);
}

View file

@ -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>

View file

@ -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 );

View file

@ -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));
}

View file

@ -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")

View file

@ -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);
}
}

View file

@ -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) );
}

View 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