Merge pull request #938 from paulfd/veltrack-oncc
veltrack_oncc and veltrack_curvecc
This commit is contained in:
commit
af902ee8b0
17 changed files with 754 additions and 438 deletions
|
|
@ -107,6 +107,7 @@ SFIZZ_SOURCES = \
|
|||
src/sfizz/PowerFollower.cpp \
|
||||
src/sfizz/Region.cpp \
|
||||
src/sfizz/RegionSet.cpp \
|
||||
src/sfizz/RegionStateful.cpp \
|
||||
src/sfizz/Resources.cpp \
|
||||
src/sfizz/RTSemaphore.cpp \
|
||||
src/sfizz/ScopedFTZ.cpp \
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ clang-tidy \
|
|||
src/sfizz/Panning.cpp \
|
||||
src/sfizz/sfizz.cpp \
|
||||
src/sfizz/Region.cpp \
|
||||
src/sfizz/RegionStateful.cpp \
|
||||
src/sfizz/SIMDHelpers.cpp \
|
||||
src/sfizz/simd/HelpersSSE.cpp \
|
||||
src/sfizz/simd/HelpersAVX.cpp \
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ set(SFIZZ_HEADERS
|
|||
sfizz/railsback/4-1.h
|
||||
sfizz/railsback/4-2.h
|
||||
sfizz/Region.h
|
||||
sfizz/RegionStateful.h
|
||||
sfizz/RegionSet.h
|
||||
sfizz/Resources.h
|
||||
sfizz/RTSemaphore.h
|
||||
|
|
@ -131,6 +132,7 @@ set(SFIZZ_SOURCES
|
|||
sfizz/AudioReader.cpp
|
||||
sfizz/FilterPool.cpp
|
||||
sfizz/EQPool.cpp
|
||||
sfizz/RegionStateful.cpp
|
||||
sfizz/Region.cpp
|
||||
sfizz/Voice.cpp
|
||||
sfizz/ScopedFTZ.cpp
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ FloatSpec width { 100.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds
|
|||
FloatSpec widthMod { 0.0f, {-200.0f, 200.0f}, kNormalizePercent|kPermissiveBounds };
|
||||
FloatSpec ampKeytrack { 0.0f, {-96.0f, 12.0f}, kPermissiveBounds };
|
||||
FloatSpec ampVeltrack { 100.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };
|
||||
FloatSpec ampVeltrackMod { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };
|
||||
FloatSpec ampVelcurve { 0.0f, {0.0f, 1.0f}, kPermissiveBounds };
|
||||
FloatSpec ampRandom { 0.0f, {-24.0f, 24.0f}, kPermissiveBounds };
|
||||
BoolSpec rtDead { false, {false, true}, kEnforceBounds };
|
||||
|
|
@ -103,6 +104,7 @@ FloatSpec filterGainMod { 0.0f, {-96.0f, 96.0f}, kPermissiveBounds };
|
|||
FloatSpec filterRandom { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
|
||||
FloatSpec filterKeytrack { 0, {0, 1200}, kPermissiveBounds };
|
||||
FloatSpec filterVeltrack { 0, {-12000, 12000}, kPermissiveBounds };
|
||||
FloatSpec filterVeltrackMod { 0.0f, {-12000, 12000}, kPermissiveBounds };
|
||||
FloatSpec eqBandwidth { 1.0f, {0.001f, 4.0f}, kPermissiveBounds };
|
||||
FloatSpec eqBandwidthMod { 0.0f, {-4.0f, 4.0f}, kPermissiveBounds };
|
||||
FloatSpec eqFrequency { 0.0f, {0.0f, 20000.0f}, kPermissiveBounds };
|
||||
|
|
@ -114,6 +116,7 @@ FloatSpec eqVel2Gain { 0.0f, {-96.0f, 96.0f}, kPermissiveBounds };
|
|||
FloatSpec pitchKeytrack { 100, {-1200, 1200}, kPermissiveBounds };
|
||||
FloatSpec pitchRandom { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
|
||||
FloatSpec pitchVeltrack { 0, {-12000, 12000}, kPermissiveBounds };
|
||||
FloatSpec pitchVeltrackMod { 0.0f, {-12000, 12000}, kPermissiveBounds };
|
||||
FloatSpec transpose { 0, {-127, 127}, kPermissiveBounds };
|
||||
FloatSpec pitch { 0.0f, {-2400.0f, 2400.0f}, kPermissiveBounds };
|
||||
FloatSpec pitchMod { 0.0f, {-2400.0f, 2400.0f}, kPermissiveBounds };
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ namespace Default
|
|||
extern const OpcodeSpec<float> widthMod;
|
||||
extern const OpcodeSpec<float> ampKeytrack;
|
||||
extern const OpcodeSpec<float> ampVeltrack;
|
||||
extern const OpcodeSpec<float> ampVeltrackMod;
|
||||
extern const OpcodeSpec<float> ampVelcurve;
|
||||
extern const OpcodeSpec<float> ampRandom;
|
||||
extern const OpcodeSpec<bool> rtDead;
|
||||
|
|
@ -215,6 +216,7 @@ namespace Default
|
|||
extern const OpcodeSpec<float> filterRandom;
|
||||
extern const OpcodeSpec<float> filterKeytrack;
|
||||
extern const OpcodeSpec<float> filterVeltrack;
|
||||
extern const OpcodeSpec<float> filterVeltrackMod;
|
||||
extern const OpcodeSpec<float> eqBandwidth;
|
||||
extern const OpcodeSpec<float> eqBandwidthMod;
|
||||
extern const OpcodeSpec<float> eqFrequency;
|
||||
|
|
@ -226,6 +228,7 @@ namespace Default
|
|||
extern const OpcodeSpec<float> pitchKeytrack;
|
||||
extern const OpcodeSpec<float> pitchRandom;
|
||||
extern const OpcodeSpec<float> pitchVeltrack;
|
||||
extern const OpcodeSpec<float> pitchVeltrackMod;
|
||||
extern const OpcodeSpec<float> transpose;
|
||||
extern const OpcodeSpec<float> pitch;
|
||||
extern const OpcodeSpec<float> pitchMod;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ struct FilterDescription
|
|||
float keytrack { Default::filterKeytrack };
|
||||
uint8_t keycenter { Default::key };
|
||||
float veltrack { Default::filterVeltrack };
|
||||
CCMap<ModifierCurvePair<float>> veltrackCC { ModifierCurvePair<float>{ Default::filterVeltrackMod, Default::curveCC } };
|
||||
float random { Default::filterRandom };
|
||||
FilterType type { FilterType::kFilterLpf2p };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,8 +38,15 @@ void sfz::FilterHolder::setup(const Region& region, unsigned filterId, int noteN
|
|||
}
|
||||
const auto keytrack = description->keytrack * float(noteNumber - description->keycenter);
|
||||
baseCutoff *= centsFactor(keytrack);
|
||||
const auto veltrack = description->veltrack * velocity;
|
||||
baseCutoff *= centsFactor(veltrack);
|
||||
auto veltrack = description->veltrack;
|
||||
|
||||
for (const auto& mod : description->veltrackCC) {
|
||||
const auto& curve = resources.getCurves().getCurve(mod.data.curve);
|
||||
const float value = resources.getMidiState().getCCValue(mod.cc);
|
||||
veltrack += curve.evalNormalized(value) * mod.data.modifier;
|
||||
}
|
||||
|
||||
baseCutoff *= centsFactor(veltrack * velocity);
|
||||
baseCutoff = Default::filterCutoff.bounds.clamp(baseCutoff);
|
||||
|
||||
baseGain = description->gain;
|
||||
|
|
|
|||
|
|
@ -6,13 +6,11 @@
|
|||
|
||||
#include "Region.h"
|
||||
#include "Opcode.h"
|
||||
#include "MidiState.h"
|
||||
#include "MathHelpers.h"
|
||||
#include "utility/SwapAndPop.h"
|
||||
#include "utility/StringViewHelpers.h"
|
||||
#include "utility/Macros.h"
|
||||
#include "utility/Debug.h"
|
||||
#include "ModifierHelpers.h"
|
||||
#include "modulations/ModId.h"
|
||||
#include "absl/strings/str_replace.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
|
@ -453,6 +451,18 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode)
|
|||
case hash("amp_veltrack"):
|
||||
ampVeltrack = opcode.read(Default::ampVeltrack);
|
||||
break;
|
||||
case hash("amp_veltrack_oncc&"):
|
||||
if (opcode.parameters.back() >= config::numCCs)
|
||||
return false;
|
||||
|
||||
ampVeltrackCC[opcode.parameters.back()].modifier = opcode.read(Default::ampVeltrackMod);
|
||||
break;
|
||||
case hash("amp_veltrack_curvecc&"):
|
||||
if (opcode.parameters.back() >= config::numCCs)
|
||||
return false;
|
||||
|
||||
ampVeltrackCC[opcode.parameters.back()].curve = opcode.read(Default::curveCC);
|
||||
break;
|
||||
case hash("amp_random"):
|
||||
ampRandom = opcode.read(Default::ampRandom);
|
||||
break;
|
||||
|
|
@ -629,6 +639,32 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode)
|
|||
filters[filterIndex].veltrack = opcode.read(Default::filterVeltrack);
|
||||
}
|
||||
break;
|
||||
case hash("fil&_veltrack_oncc&"):
|
||||
{
|
||||
const auto filterIndex = opcode.parameters.front() - 1;
|
||||
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
|
||||
return false;
|
||||
|
||||
const auto cc = opcode.parameters.back();
|
||||
if (cc >= config::numCCs)
|
||||
return false;
|
||||
|
||||
filters[filterIndex].veltrackCC[cc].modifier = opcode.read(Default::filterVeltrackMod);
|
||||
}
|
||||
break;
|
||||
case hash("fil&_veltrack_curvecc&"):
|
||||
{
|
||||
const auto filterIndex = opcode.parameters.front() - 1;
|
||||
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
|
||||
return false;
|
||||
|
||||
const auto cc = opcode.parameters.back();
|
||||
if (cc >= config::numCCs)
|
||||
return false;
|
||||
|
||||
filters[filterIndex].veltrackCC[cc].curve = opcode.read(Default::curveCC);
|
||||
}
|
||||
break;
|
||||
case hash("fil&_random"): // also fil_random, cutoff_random, cutoff&_random
|
||||
{
|
||||
const auto filterIndex = opcode.parameters.front() - 1;
|
||||
|
|
@ -759,6 +795,18 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode)
|
|||
case hash("pitch_veltrack"):
|
||||
pitchVeltrack = opcode.read(Default::pitchVeltrack);
|
||||
break;
|
||||
case hash("pitch_veltrack_oncc&"):
|
||||
if (opcode.parameters.back() >= config::numCCs)
|
||||
return false;
|
||||
|
||||
pitchVeltrackCC[opcode.parameters.back()].modifier = opcode.read(Default::pitchVeltrackMod);
|
||||
break;
|
||||
case hash("pitch_veltrack_curvecc&"):
|
||||
if (opcode.parameters.back() >= config::numCCs)
|
||||
return false;
|
||||
|
||||
pitchVeltrackCC[opcode.parameters.back()].curve = opcode.read(Default::curveCC);
|
||||
break;
|
||||
case hash("pitch_random"):
|
||||
pitchRandom = opcode.read(Default::pitchRandom);
|
||||
break;
|
||||
|
|
@ -1680,31 +1728,6 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec<float> spec,
|
|||
return true;
|
||||
}
|
||||
|
||||
float sfz::Region::getBasePitchVariation(float noteNumber, float velocity) const noexcept
|
||||
{
|
||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||
|
||||
fast_real_distribution<float> pitchDistribution { 0.0f, pitchRandom };
|
||||
float pitchVariationInCents = pitchKeytrack * (noteNumber - float(pitchKeycenter)); // note difference with pitch center
|
||||
pitchVariationInCents += pitch; // sample tuning
|
||||
pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose
|
||||
pitchVariationInCents += velocity * pitchVeltrack; // track velocity
|
||||
pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes
|
||||
return centsFactor(pitchVariationInCents);
|
||||
}
|
||||
|
||||
float sfz::Region::getBaseVolumedB(const MidiState& midiState, int noteNumber) const noexcept
|
||||
{
|
||||
fast_real_distribution<float> volumeDistribution { 0.0f, ampRandom };
|
||||
auto baseVolumedB = volume + volumeDistribution(Random::randomGenerator);
|
||||
baseVolumedB += globalVolume;
|
||||
baseVolumedB += masterVolume;
|
||||
baseVolumedB += groupVolume;
|
||||
if (trigger == Trigger::release || trigger == Trigger::release_key)
|
||||
baseVolumedB -= rtDecay * midiState.getNoteDuration(noteNumber);
|
||||
return baseVolumedB;
|
||||
}
|
||||
|
||||
float sfz::Region::getBaseGain() const noexcept
|
||||
{
|
||||
float baseGain = amplitude;
|
||||
|
|
@ -1728,115 +1751,6 @@ float sfz::Region::getPhase() const noexcept
|
|||
return phase;
|
||||
}
|
||||
|
||||
uint64_t sfz::Region::getOffset(const MidiState& midiState) const noexcept
|
||||
{
|
||||
std::uniform_int_distribution<int64_t> offsetDistribution { 0, offsetRandom };
|
||||
uint64_t finalOffset = offset + offsetDistribution(Random::randomGenerator);
|
||||
for (const auto& mod: offsetCC)
|
||||
finalOffset += static_cast<uint64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
return Default::offset.bounds.clamp(finalOffset);
|
||||
}
|
||||
|
||||
float sfz::Region::getDelay(const MidiState& midiState) const noexcept
|
||||
{
|
||||
fast_real_distribution<float> delayDistribution { 0, delayRandom };
|
||||
float finalDelay { delay };
|
||||
finalDelay += delayDistribution(Random::randomGenerator);
|
||||
for (const auto& mod: delayCC)
|
||||
finalDelay += mod.data * midiState.getCCValue(mod.cc);
|
||||
|
||||
return Default::delay.bounds.clamp(finalDelay);
|
||||
}
|
||||
|
||||
uint32_t sfz::Region::getSampleEnd(MidiState& midiState) const noexcept
|
||||
{
|
||||
int64_t end = sampleEnd;
|
||||
for (const auto& mod: endCC)
|
||||
end += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
end = clamp(end, int64_t { 0 }, sampleEnd);
|
||||
return static_cast<uint32_t>(end);
|
||||
}
|
||||
|
||||
uint32_t sfz::Region::loopStart(MidiState& midiState) const noexcept
|
||||
{
|
||||
auto start = loopRange.getStart();
|
||||
for (const auto& mod: loopStartCC)
|
||||
start += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
start = clamp(start, int64_t { 0 }, sampleEnd);
|
||||
return static_cast<uint32_t>(start);
|
||||
}
|
||||
|
||||
uint32_t sfz::Region::loopEnd(MidiState& midiState) const noexcept
|
||||
{
|
||||
auto end = loopRange.getEnd();
|
||||
for (const auto& mod: loopEndCC)
|
||||
end += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
end = clamp(end, int64_t { 0 }, sampleEnd);
|
||||
return static_cast<uint32_t>(end);
|
||||
}
|
||||
|
||||
float sfz::Region::getNoteGain(int noteNumber, float velocity) const noexcept
|
||||
{
|
||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||
|
||||
float baseGain { 1.0f };
|
||||
|
||||
// Amplitude key tracking
|
||||
baseGain *= db2mag(ampKeytrack * static_cast<float>(noteNumber - ampKeycenter));
|
||||
|
||||
// Crossfades related to the note number
|
||||
baseGain *= crossfadeIn(crossfadeKeyInRange, noteNumber, crossfadeKeyCurve);
|
||||
baseGain *= crossfadeOut(crossfadeKeyOutRange, noteNumber, crossfadeKeyCurve);
|
||||
|
||||
// Amplitude velocity tracking
|
||||
baseGain *= velocityCurve(velocity);
|
||||
|
||||
// Crossfades related to velocity
|
||||
baseGain *= crossfadeIn(crossfadeVelInRange, velocity, crossfadeVelCurve);
|
||||
baseGain *= crossfadeOut(crossfadeVelOutRange, velocity, crossfadeVelCurve);
|
||||
|
||||
return baseGain;
|
||||
}
|
||||
|
||||
float sfz::Region::getCrossfadeGain(const MidiState& midiState) const noexcept
|
||||
{
|
||||
float gain { 1.0f };
|
||||
|
||||
// Crossfades due to CC states
|
||||
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& ccData : crossfadeCCOutRange) {
|
||||
const auto ccValue = midiState.getCCValue(ccData.cc);
|
||||
const auto crossfadeRange = ccData.data;
|
||||
gain *= crossfadeOut(crossfadeRange, ccValue, crossfadeCCCurve);
|
||||
}
|
||||
|
||||
return gain;
|
||||
}
|
||||
|
||||
float sfz::Region::velocityCurve(float velocity) const noexcept
|
||||
{
|
||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||
|
||||
float gain;
|
||||
if (velCurve)
|
||||
gain = velCurve->evalNormalized(velocity);
|
||||
else
|
||||
gain = velocity * velocity;
|
||||
|
||||
gain = std::fabs(ampVeltrack) * (1.0f - gain);
|
||||
gain = (ampVeltrack < 0) ? gain : (1.0f - gain);
|
||||
|
||||
return gain;
|
||||
}
|
||||
|
||||
void sfz::Region::offsetAllKeys(int offset) noexcept
|
||||
{
|
||||
// Offset key range
|
||||
|
|
|
|||
|
|
@ -99,41 +99,6 @@ struct Region {
|
|||
*/
|
||||
bool shouldLoop() const noexcept { return (loopMode == LoopMode::loop_continuous || loopMode == LoopMode::loop_sustain); }
|
||||
|
||||
/**
|
||||
* @brief Get the base pitch of the region depending on which note has been
|
||||
* pressed and at which velocity.
|
||||
*
|
||||
* @param noteNumber
|
||||
* @param velocity
|
||||
* @return float
|
||||
*/
|
||||
float getBasePitchVariation(float noteNumber, float velocity) const noexcept;
|
||||
/**
|
||||
* @brief Get the note-related gain of the region depending on which note has been
|
||||
* pressed and at which velocity.
|
||||
*
|
||||
* @param noteNumber
|
||||
* @param velocity
|
||||
* @return float
|
||||
*/
|
||||
float getNoteGain(int noteNumber, float velocity) const noexcept;
|
||||
/**
|
||||
* @brief Get the additional crossfade gain of the region depending on the
|
||||
* CC values
|
||||
*
|
||||
* @param midiState
|
||||
* @return float
|
||||
*/
|
||||
float getCrossfadeGain(const MidiState& midiState) const noexcept;
|
||||
/**
|
||||
* @brief Get the base volume of the region depending on which note has been
|
||||
* pressed to trigger the region.
|
||||
*
|
||||
* @param midiState
|
||||
* @param noteNumber
|
||||
* @return float
|
||||
*/
|
||||
float getBaseVolumedB(const MidiState& midiState, int noteNumber) const noexcept;
|
||||
/**
|
||||
* @brief Get the base gain of the region.
|
||||
*
|
||||
|
|
@ -146,12 +111,6 @@ struct Region {
|
|||
* @return float
|
||||
*/
|
||||
float getPhase() const noexcept;
|
||||
/**
|
||||
* @brief Computes the gain value related to the velocity of the note
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
float velocityCurve(float velocity) const noexcept;
|
||||
/**
|
||||
* @brief Get the detuning in cents for a given bend value between -1 and 1
|
||||
*
|
||||
|
|
@ -160,27 +119,6 @@ struct Region {
|
|||
*/
|
||||
float getBendInCents(float bend) const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the region offset in samples
|
||||
*
|
||||
* @param midiState
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint64_t getOffset(const MidiState& midiState) const noexcept;
|
||||
/**
|
||||
* @brief Get the region delay in seconds
|
||||
*
|
||||
* @param midiState
|
||||
* @return float
|
||||
*/
|
||||
float getDelay(const MidiState& midiState) const noexcept;
|
||||
/**
|
||||
* @brief Get the index of the sample end, either natural end or forced
|
||||
* loop.
|
||||
*
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint32_t getSampleEnd(MidiState& midiState) const noexcept;
|
||||
/**
|
||||
* @brief Parse a new opcode into the region to fill in the proper parameters.
|
||||
* This must be called multiple times for each opcode applying to this region.
|
||||
|
|
@ -260,9 +198,6 @@ struct Region {
|
|||
|
||||
void offsetAllKeys(int offset) noexcept;
|
||||
|
||||
uint32_t loopStart(MidiState& midiState) const noexcept;
|
||||
uint32_t loopEnd(MidiState& midiState) const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the gain this region contributes into the input of the Nth
|
||||
* effect bus
|
||||
|
|
@ -384,6 +319,7 @@ struct Region {
|
|||
uint8_t ampKeycenter { Default::key }; // amp_keycenter
|
||||
float ampKeytrack { Default::ampKeytrack }; // amp_keytrack
|
||||
float ampVeltrack { Default::ampVeltrack }; // amp_veltrack
|
||||
CCMap<ModifierCurvePair<float>> ampVeltrackCC { ModifierCurvePair<float>{ Default::ampVeltrackMod, Default::curveCC } };
|
||||
std::vector<std::pair<uint8_t, float>> velocityPoints; // amp_velcurve_N
|
||||
absl::optional<Curve> velCurve {};
|
||||
float ampRandom { Default::ampRandom }; // amp_random
|
||||
|
|
@ -415,6 +351,7 @@ struct Region {
|
|||
float pitchKeytrack { Default::pitchKeytrack }; // pitch_keytrack
|
||||
float pitchRandom { Default::pitchRandom }; // pitch_random
|
||||
float pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack
|
||||
CCMap<ModifierCurvePair<float>> pitchVeltrackCC { ModifierCurvePair<float>{ Default::pitchVeltrackMod, Default::curveCC } };
|
||||
float transpose { Default::transpose }; // transpose
|
||||
float pitch { Default::pitch }; // tune
|
||||
float bendUp { Default::bendUp };
|
||||
|
|
|
|||
164
src/sfizz/RegionStateful.cpp
Normal file
164
src/sfizz/RegionStateful.cpp
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#include "RegionStateful.h"
|
||||
#include "ModifierHelpers.h"
|
||||
|
||||
namespace sfz {
|
||||
|
||||
float baseVolumedB(const Region& region, const MidiState& midiState, int noteNumber) noexcept
|
||||
{
|
||||
fast_real_distribution<float> volumeDistribution { 0.0f, region.ampRandom };
|
||||
auto baseVolumedB = region.volume + volumeDistribution(Random::randomGenerator);
|
||||
baseVolumedB += region.globalVolume;
|
||||
baseVolumedB += region.masterVolume;
|
||||
baseVolumedB += region.groupVolume;
|
||||
if (region.trigger == Trigger::release || region.trigger == Trigger::release_key)
|
||||
baseVolumedB -= region.rtDecay * midiState.getNoteDuration(noteNumber);
|
||||
return baseVolumedB;
|
||||
}
|
||||
|
||||
|
||||
uint64_t sampleOffset(const Region& region, const MidiState& midiState) noexcept
|
||||
{
|
||||
std::uniform_int_distribution<int64_t> offsetDistribution { 0, region.offsetRandom };
|
||||
uint64_t finalOffset = region.offset + offsetDistribution(Random::randomGenerator);
|
||||
for (const auto& mod: region.offsetCC)
|
||||
finalOffset += static_cast<uint64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
return Default::offset.bounds.clamp(finalOffset);
|
||||
}
|
||||
|
||||
float regionDelay(const Region& region, const MidiState& midiState) noexcept
|
||||
{
|
||||
fast_real_distribution<float> delayDistribution { 0, region.delayRandom };
|
||||
float finalDelay { region.delay };
|
||||
finalDelay += delayDistribution(Random::randomGenerator);
|
||||
for (const auto& mod: region.delayCC)
|
||||
finalDelay += mod.data * midiState.getCCValue(mod.cc);
|
||||
|
||||
return Default::delay.bounds.clamp(finalDelay);
|
||||
}
|
||||
|
||||
uint32_t sampleEnd(const Region& region, MidiState& midiState) noexcept
|
||||
{
|
||||
int64_t end = region.sampleEnd;
|
||||
for (const auto& mod: region.endCC)
|
||||
end += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
end = clamp(end, int64_t { 0 }, region.sampleEnd);
|
||||
return static_cast<uint32_t>(end);
|
||||
}
|
||||
|
||||
uint32_t loopStart(const Region& region, MidiState& midiState) noexcept
|
||||
{
|
||||
auto start = region.loopRange.getStart();
|
||||
for (const auto& mod: region.loopStartCC)
|
||||
start += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
start = clamp(start, int64_t { 0 }, region.sampleEnd);
|
||||
return static_cast<uint32_t>(start);
|
||||
}
|
||||
|
||||
uint32_t loopEnd(const Region& region, MidiState& midiState) noexcept
|
||||
{
|
||||
auto end = region.loopRange.getEnd();
|
||||
for (const auto& mod: region.loopEndCC)
|
||||
end += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
end = clamp(end, int64_t { 0 }, region.sampleEnd);
|
||||
return static_cast<uint32_t>(end);
|
||||
}
|
||||
|
||||
float noteGain(const Region& region, int noteNumber, float velocity, const MidiState& midiState, const CurveSet& curveSet) noexcept
|
||||
{
|
||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||
|
||||
float baseGain { 1.0f };
|
||||
|
||||
// Amplitude key tracking
|
||||
baseGain *= db2mag(region.ampKeytrack * static_cast<float>(noteNumber - region.ampKeycenter));
|
||||
|
||||
// Crossfades related to the note number
|
||||
baseGain *= crossfadeIn(region.crossfadeKeyInRange, noteNumber, region.crossfadeKeyCurve);
|
||||
baseGain *= crossfadeOut(region.crossfadeKeyOutRange, noteNumber, region.crossfadeKeyCurve);
|
||||
|
||||
// Amplitude velocity tracking
|
||||
baseGain *= velocityCurve(region, velocity, midiState, curveSet);
|
||||
|
||||
// Crossfades related to velocity
|
||||
baseGain *= crossfadeIn(region.crossfadeVelInRange, velocity, region.crossfadeVelCurve);
|
||||
baseGain *= crossfadeOut(region.crossfadeVelOutRange, velocity, region.crossfadeVelCurve);
|
||||
|
||||
return baseGain;
|
||||
}
|
||||
|
||||
float crossfadeGain(const Region& region, const MidiState& midiState) noexcept
|
||||
{
|
||||
float gain { 1.0f };
|
||||
|
||||
// Crossfades due to CC states
|
||||
for (const auto& ccData : region.crossfadeCCInRange) {
|
||||
const auto ccValue = midiState.getCCValue(ccData.cc);
|
||||
const auto crossfadeRange = ccData.data;
|
||||
gain *= crossfadeIn(crossfadeRange, ccValue, region.crossfadeCCCurve);
|
||||
}
|
||||
|
||||
for (const auto& ccData : region.crossfadeCCOutRange) {
|
||||
const auto ccValue = midiState.getCCValue(ccData.cc);
|
||||
const auto crossfadeRange = ccData.data;
|
||||
gain *= crossfadeOut(crossfadeRange, ccValue, region.crossfadeCCCurve);
|
||||
}
|
||||
|
||||
return gain;
|
||||
}
|
||||
|
||||
float velocityCurve(const Region& region, float velocity, const MidiState& midiState, const CurveSet& curveSet) noexcept
|
||||
{
|
||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||
|
||||
float gain;
|
||||
if (region.velCurve)
|
||||
gain = region.velCurve->evalNormalized(velocity);
|
||||
else
|
||||
gain = velocity * velocity;
|
||||
|
||||
float veltrack = region.ampVeltrack;
|
||||
|
||||
for (const auto& mod : region.ampVeltrackCC) {
|
||||
const auto& curve = curveSet.getCurve(mod.data.curve);
|
||||
const float value = midiState.getCCValue(mod.cc);
|
||||
veltrack += curve.evalNormalized(value) * mod.data.modifier;
|
||||
}
|
||||
|
||||
gain = std::fabs(veltrack) * (1.0f - gain);
|
||||
gain = (veltrack < 0) ? gain : (1.0f - gain);
|
||||
|
||||
return gain;
|
||||
}
|
||||
|
||||
float basePitchVariation(const Region& region, float noteNumber, float velocity, const MidiState& midiState, const CurveSet& curveSet) noexcept
|
||||
{
|
||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||
|
||||
fast_real_distribution<float> pitchDistribution { 0.0f, region.pitchRandom };
|
||||
float pitchVariationInCents = region.pitchKeytrack * (noteNumber - float(region.pitchKeycenter)); // note difference with pitch center
|
||||
pitchVariationInCents += region.pitch; // sample tuning
|
||||
pitchVariationInCents += config::centPerSemitone * region.transpose; // sample transpose
|
||||
|
||||
float veltrack = region.pitchVeltrack;
|
||||
|
||||
for (const auto& mod : region.pitchVeltrackCC) {
|
||||
const auto& curve = curveSet.getCurve(mod.data.curve);
|
||||
const float value = midiState.getCCValue(mod.cc);
|
||||
veltrack += curve.evalNormalized(value) * mod.data.modifier;
|
||||
}
|
||||
|
||||
pitchVariationInCents += velocity * veltrack; // track velocity
|
||||
pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes
|
||||
return centsFactor(pitchVariationInCents);
|
||||
}
|
||||
|
||||
}
|
||||
110
src/sfizz/RegionStateful.h
Normal file
110
src/sfizz/RegionStateful.h
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#include "Region.h"
|
||||
#include "MidiState.h"
|
||||
#include "Curve.h"
|
||||
|
||||
namespace sfz {
|
||||
/**
|
||||
* @brief Get the note-related gain of the region depending on which note has been
|
||||
* pressed and at which velocity.
|
||||
*
|
||||
* @param region
|
||||
* @param noteNumber
|
||||
* @param velocity
|
||||
* @param midiState
|
||||
* @param curveSet
|
||||
* @return float
|
||||
*/
|
||||
float noteGain(const Region& region, int noteNumber, float velocity, const MidiState& midiState, const CurveSet& curveSet) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the additional crossfade gain of the region depending on the
|
||||
* CC values
|
||||
*
|
||||
* @param region
|
||||
* @param midiState
|
||||
* @return float
|
||||
*/
|
||||
float crossfadeGain(const Region& region, const MidiState& midiState) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the base volume of the region depending on which note has been
|
||||
* pressed to trigger the region.
|
||||
*
|
||||
* @param region
|
||||
* @param midiState
|
||||
* @param noteNumber
|
||||
* @return float
|
||||
*/
|
||||
float baseVolumedB(const Region& region, const MidiState& midiState, int noteNumber) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the region offset in samples
|
||||
*
|
||||
* @param region
|
||||
* @param midiState
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint64_t sampleOffset(const Region& region, const MidiState& midiState) noexcept;
|
||||
/**
|
||||
* @brief Get the region delay in seconds
|
||||
*
|
||||
* @param region
|
||||
* @param midiState
|
||||
* @return float
|
||||
*/
|
||||
float regionDelay(const Region& region, const MidiState& midiState) noexcept;
|
||||
/**
|
||||
* @brief Get the index of the sample end, either natural end or forced
|
||||
* loop.
|
||||
*
|
||||
* @param region
|
||||
* @param midiState
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint32_t sampleEnd(const Region& region, MidiState& midiState) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Computes the gain value related to the velocity of the note
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
float velocityCurve(const Region& region, float velocity, const MidiState& midiState, const CurveSet& curveSet) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Returns the start of the loop for a given region
|
||||
*
|
||||
* @param region
|
||||
* @param midiState
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint32_t loopStart(const Region& region, MidiState& midiState) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Returns the end of the loop for a given region
|
||||
*
|
||||
* @param region
|
||||
* @param midiState
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint32_t loopEnd(const Region& region, MidiState& midiState) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the base pitch of the region depending on which note has been
|
||||
* pressed and at which velocity.
|
||||
*
|
||||
* @param region
|
||||
* @param noteNumber
|
||||
* @param velocity
|
||||
* @param midiState
|
||||
* @param curveSet
|
||||
* @return float
|
||||
*/
|
||||
float basePitchVariation(const Region& region, float noteNumber, float velocity, const MidiState& midiState, const CurveSet& curveSet) noexcept;
|
||||
|
||||
}
|
||||
|
|
@ -22,6 +22,15 @@ namespace sfz {
|
|||
using CCNamePair = std::pair<uint16_t, std::string>;
|
||||
using NoteNamePair = std::pair<uint8_t, std::string>;
|
||||
|
||||
template<class T>
|
||||
struct ModifierCurvePair
|
||||
{
|
||||
ModifierCurvePair(const T& modifier, uint8_t curve)
|
||||
: modifier(modifier), curve(curve) {}
|
||||
T modifier {};
|
||||
uint8_t curve {};
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using MidiNoteArray = std::array<T, 128>;
|
||||
template <class ValueType>
|
||||
|
|
|
|||
|
|
@ -782,6 +782,26 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
|||
client.receive<'f'>(delay, path, region.ampVeltrack * 100.0f);
|
||||
} break;
|
||||
|
||||
MATCH("/region&/amp_veltrack_cc&", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
if (region.ampVeltrackCC.contains(indices[1])) {
|
||||
const auto& cc = region.ampVeltrackCC.getWithDefault(indices[1]);
|
||||
client.receive<'f'>(delay, path, cc.modifier * 100.0f);
|
||||
} else {
|
||||
client.receive<'N'>(delay, path, {});
|
||||
}
|
||||
} break;
|
||||
|
||||
MATCH("/region&/amp_veltrack_curvecc&", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
if (region.ampVeltrackCC.contains(indices[1])) {
|
||||
const auto& cc = region.ampVeltrackCC.getWithDefault(indices[1]);
|
||||
client.receive<'i'>(delay, path, cc.curve );
|
||||
} else {
|
||||
client.receive<'N'>(delay, path, {});
|
||||
}
|
||||
} break;
|
||||
|
||||
MATCH("/region&/amp_random", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
client.receive<'f'>(delay, path, region.ampRandom);
|
||||
|
|
@ -921,6 +941,26 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
|||
client.receive<'i'>(delay, path, region.pitchVeltrack);
|
||||
} break;
|
||||
|
||||
MATCH("/region&/pitch_veltrack_cc&", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
if (region.pitchVeltrackCC.contains(indices[1])) {
|
||||
const auto& cc = region.pitchVeltrackCC.getWithDefault(indices[1]);
|
||||
client.receive<'f'>(delay, path, cc.modifier);
|
||||
} else {
|
||||
client.receive<'N'>(delay, path, {});
|
||||
}
|
||||
} break;
|
||||
|
||||
MATCH("/region&/pitch_veltrack_curvecc&", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
if (region.pitchVeltrackCC.contains(indices[1])) {
|
||||
const auto& cc = region.pitchVeltrackCC.getWithDefault(indices[1]);
|
||||
client.receive<'i'>(delay, path, cc.curve );
|
||||
} else {
|
||||
client.receive<'N'>(delay, path, {});
|
||||
}
|
||||
} break;
|
||||
|
||||
MATCH("/region&/pitch_random", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
client.receive<'f'>(delay, path, region.pitchRandom);
|
||||
|
|
@ -1277,6 +1317,28 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
|||
client.receive<'i'>(delay, path, filter.veltrack);
|
||||
} break;
|
||||
|
||||
MATCH("/region&/filter&/veltrack_cc&", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
GET_FILTER_OR_BREAK(indices[1])
|
||||
if (filter.veltrackCC.contains(indices[2])) {
|
||||
const auto& cc = filter.veltrackCC.getWithDefault(indices[2]);
|
||||
client.receive<'f'>(delay, path, cc.modifier);
|
||||
} else {
|
||||
client.receive<'N'>(delay, path, {});
|
||||
}
|
||||
} break;
|
||||
|
||||
MATCH("/region&/filter&/veltrack_curvecc&", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
GET_FILTER_OR_BREAK(indices[1])
|
||||
if (filter.veltrackCC.contains(indices[2])) {
|
||||
const auto& cc = filter.veltrackCC.getWithDefault(indices[2]);
|
||||
client.receive<'i'>(delay, path, cc.curve );
|
||||
} else {
|
||||
client.receive<'N'>(delay, path, {});
|
||||
}
|
||||
} break;
|
||||
|
||||
MATCH("/region&/filter&/type", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
GET_FILTER_OR_BREAK(indices[1])
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "LFO.h"
|
||||
#include "MathHelpers.h"
|
||||
#include "ModifierHelpers.h"
|
||||
#include "RegionStateful.h"
|
||||
#include "TriggerEvent.h"
|
||||
#include "modulations/ModId.h"
|
||||
#include "modulations/ModKey.h"
|
||||
|
|
@ -407,6 +408,7 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
|
|||
|
||||
Resources& resources = impl.resources_;
|
||||
MidiState& midiState = resources.getMidiState();
|
||||
CurveSet& curveSet = resources.getCurves();
|
||||
|
||||
const Region& region = layer->getRegion();
|
||||
impl.region_ = ®ion;
|
||||
|
|
@ -474,24 +476,24 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
|
|||
}
|
||||
impl.updateLoopInformation();
|
||||
impl.speedRatio_ = static_cast<float>(impl.currentPromise_->information.sampleRate / impl.sampleRate_);
|
||||
impl.sourcePosition_ = region.getOffset(midiState);
|
||||
impl.sourcePosition_ = sampleOffset(region, midiState);
|
||||
}
|
||||
|
||||
// do Scala retuning and reconvert the frequency into a 12TET key number
|
||||
Tuning& tuning = resources.getTuning();
|
||||
const float numberRetuned = tuning.getKeyFractional12TET(impl.triggerEvent_.number);
|
||||
|
||||
impl.pitchRatio_ = region.getBasePitchVariation(numberRetuned, impl.triggerEvent_.value);
|
||||
impl.pitchRatio_ = basePitchVariation(region, numberRetuned, impl.triggerEvent_.value, midiState, curveSet);
|
||||
|
||||
// apply stretch tuning if set
|
||||
if (absl::optional<StretchTuning>& stretch = resources.getStretch())
|
||||
impl.pitchRatio_ *= stretch->getRatioForFractionalKey(numberRetuned);
|
||||
|
||||
impl.pitchKeycenter_ = region.pitchKeycenter;
|
||||
impl.baseVolumedB_ = region.getBaseVolumedB(midiState, impl.triggerEvent_.number);
|
||||
impl.baseVolumedB_ = baseVolumedB(region, midiState, impl.triggerEvent_.number);
|
||||
impl.baseGain_ = region.getBaseGain();
|
||||
if (impl.triggerEvent_.type != TriggerEventType::CC || region.velocityOverride == VelocityOverride::previous)
|
||||
impl.baseGain_ *= region.getNoteGain(impl.triggerEvent_.number, impl.triggerEvent_.value);
|
||||
impl.baseGain_ *= noteGain(region, impl.triggerEvent_.number, impl.triggerEvent_.value, midiState, curveSet);
|
||||
|
||||
impl.gainSmoother_.reset();
|
||||
impl.resetCrossfades();
|
||||
|
|
@ -505,9 +507,9 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
|
|||
}
|
||||
|
||||
impl.triggerDelay_ = delay;
|
||||
impl.initialDelay_ = delay + static_cast<int>(region.getDelay(midiState) * impl.sampleRate_);
|
||||
impl.initialDelay_ = delay + static_cast<int>(regionDelay(region, midiState) * impl.sampleRate_);
|
||||
impl.baseFrequency_ = tuning.getFrequencyOfKey(impl.triggerEvent_.number);
|
||||
impl.sampleEnd_ = int(region.getSampleEnd(midiState));
|
||||
impl.sampleEnd_ = int(sampleEnd(region, midiState));
|
||||
impl.sampleSize_ = impl.sampleEnd_- impl.sourcePosition_ - 1;
|
||||
impl.bendSmoother_.setSmoothing(region.bendSmooth, impl.sampleRate_);
|
||||
impl.bendSmoother_.reset(region.getBendInCents(midiState.getPitchBend()));
|
||||
|
|
@ -1724,14 +1726,15 @@ void Voice::Impl::updateLoopInformation() noexcept
|
|||
if (!region_->shouldLoop())
|
||||
return;
|
||||
|
||||
const Region& region = *region_;
|
||||
MidiState& midiState = resources_.getMidiState();
|
||||
const FileInformation& info = currentPromise_->information;
|
||||
const double rate = info.sampleRate;
|
||||
|
||||
loop_.start = static_cast<int>(region_->loopStart(midiState));
|
||||
loop_.end = max(static_cast<int>(region_->loopEnd(midiState)), loop_.start);
|
||||
loop_.start = static_cast<int>(loopStart(region, midiState));
|
||||
loop_.end = max(static_cast<int>(loopEnd(region, midiState)), loop_.start);
|
||||
loop_.size = loop_.end + 1 - loop_.start;
|
||||
loop_.xfSize = static_cast<int>(lroundPositive(region_->loopCrossfade * rate));
|
||||
loop_.xfSize = static_cast<int>(lroundPositive(region.loopCrossfade * rate));
|
||||
// Clamp the crossfade to the part available before the loop starts
|
||||
loop_.xfSize = min(loop_.start, loop_.xfSize);
|
||||
loop_.xfOutStart = loop_.end + 1 - loop_.xfSize;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "sfizz/Defaults.h"
|
||||
#include "sfizz/Region.h"
|
||||
#include "sfizz/RegionStateful.h"
|
||||
#include "sfizz/MidiState.h"
|
||||
#include "sfizz/SfzHelpers.h"
|
||||
#include "catch2/catch.hpp"
|
||||
|
|
@ -19,137 +20,155 @@ constexpr int numRandomTests { 64 };
|
|||
TEST_CASE("[Region] Crossfade in on key")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lokey", "1" });
|
||||
region.parseOpcode({ "xfin_hikey", "3" });
|
||||
REQUIRE(region.getNoteGain(2, 127_norm) == 0.70711_a);
|
||||
REQUIRE(region.getNoteGain(1, 127_norm) == 0.0_a);
|
||||
REQUIRE(region.getNoteGain(3, 127_norm) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 2, 127_norm, midiState, curveSet) == 0.70711_a);
|
||||
REQUIRE(noteGain(region, 1, 127_norm, midiState, curveSet) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 3, 127_norm, midiState, curveSet) == 1.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on key - 2")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lokey", "1" });
|
||||
region.parseOpcode({ "xfin_hikey", "5" });
|
||||
REQUIRE(region.getNoteGain(1, 127_norm) == 0.0_a);
|
||||
REQUIRE(region.getNoteGain(2, 127_norm) == 0.5_a);
|
||||
REQUIRE(region.getNoteGain(3, 127_norm) == 0.70711_a);
|
||||
REQUIRE(region.getNoteGain(4, 127_norm) == 0.86603_a);
|
||||
REQUIRE(region.getNoteGain(5, 127_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(6, 127_norm) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 1, 127_norm, midiState, curveSet) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 2, 127_norm, midiState, curveSet) == 0.5_a);
|
||||
REQUIRE(noteGain(region, 3, 127_norm, midiState, curveSet) == 0.70711_a);
|
||||
REQUIRE(noteGain(region, 4, 127_norm, midiState, curveSet) == 0.86603_a);
|
||||
REQUIRE(noteGain(region, 5, 127_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 6, 127_norm, midiState, curveSet) == 1.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on key - gain")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lokey", "1" });
|
||||
region.parseOpcode({ "xfin_hikey", "5" });
|
||||
region.parseOpcode({ "xf_keycurve", "gain" });
|
||||
REQUIRE(region.getNoteGain(1, 127_norm) == 0.0_a);
|
||||
REQUIRE(region.getNoteGain(2, 127_norm) == 0.25_a);
|
||||
REQUIRE(region.getNoteGain(3, 127_norm) == 0.5_a);
|
||||
REQUIRE(region.getNoteGain(4, 127_norm) == 0.75_a);
|
||||
REQUIRE(region.getNoteGain(5, 127_norm) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 1, 127_norm, midiState, curveSet) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 2, 127_norm, midiState, curveSet) == 0.25_a);
|
||||
REQUIRE(noteGain(region, 3, 127_norm, midiState, curveSet) == 0.5_a);
|
||||
REQUIRE(noteGain(region, 4, 127_norm, midiState, curveSet) == 0.75_a);
|
||||
REQUIRE(noteGain(region, 5, 127_norm, midiState, curveSet) == 1.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade out on key")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_lokey", "51" });
|
||||
region.parseOpcode({ "xfout_hikey", "55" });
|
||||
REQUIRE(region.getNoteGain(50, 127_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(51, 127_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(52, 127_norm) == 0.86603_a);
|
||||
REQUIRE(region.getNoteGain(53, 127_norm) == 0.70711_a);
|
||||
REQUIRE(region.getNoteGain(54, 127_norm) == 0.5_a);
|
||||
REQUIRE(region.getNoteGain(55, 127_norm) == 0.0_a);
|
||||
REQUIRE(region.getNoteGain(56, 127_norm) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 50, 127_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 51, 127_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 52, 127_norm, midiState, curveSet) == 0.86603_a);
|
||||
REQUIRE(noteGain(region, 53, 127_norm, midiState, curveSet) == 0.70711_a);
|
||||
REQUIRE(noteGain(region, 54, 127_norm, midiState, curveSet) == 0.5_a);
|
||||
REQUIRE(noteGain(region, 55, 127_norm, midiState, curveSet) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 56, 127_norm, midiState, curveSet) == 0.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade out on key - gain")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_lokey", "51" });
|
||||
region.parseOpcode({ "xfout_hikey", "55" });
|
||||
region.parseOpcode({ "xf_keycurve", "gain" });
|
||||
REQUIRE(region.getNoteGain(50, 127_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(51, 127_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(52, 127_norm) == 0.75_a);
|
||||
REQUIRE(region.getNoteGain(53, 127_norm) == 0.5_a);
|
||||
REQUIRE(region.getNoteGain(54, 127_norm) == 0.25_a);
|
||||
REQUIRE(region.getNoteGain(55, 127_norm) == 0.0_a);
|
||||
REQUIRE(region.getNoteGain(56, 127_norm) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 50, 127_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 51, 127_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 52, 127_norm, midiState, curveSet) == 0.75_a);
|
||||
REQUIRE(noteGain(region, 53, 127_norm, midiState, curveSet) == 0.5_a);
|
||||
REQUIRE(noteGain(region, 54, 127_norm, midiState, curveSet) == 0.25_a);
|
||||
REQUIRE(noteGain(region, 55, 127_norm, midiState, curveSet) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 56, 127_norm, midiState, curveSet) == 0.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on velocity")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lovel", "20" });
|
||||
region.parseOpcode({ "xfin_hivel", "24" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
REQUIRE(region.getNoteGain(1, 19_norm) == 0.0_a);
|
||||
REQUIRE(region.getNoteGain(1, 20_norm) == 0.0_a);
|
||||
REQUIRE(region.getNoteGain(2, 21_norm) == 0.5_a);
|
||||
REQUIRE(region.getNoteGain(3, 22_norm) == 0.70711_a);
|
||||
REQUIRE(region.getNoteGain(4, 23_norm) == 0.86603_a);
|
||||
REQUIRE(region.getNoteGain(5, 24_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(6, 25_norm) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 1, 19_norm, midiState, curveSet) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 1, 20_norm, midiState, curveSet) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 2, 21_norm, midiState, curveSet) == 0.5_a);
|
||||
REQUIRE(noteGain(region, 3, 22_norm, midiState, curveSet) == 0.70711_a);
|
||||
REQUIRE(noteGain(region, 4, 23_norm, midiState, curveSet) == 0.86603_a);
|
||||
REQUIRE(noteGain(region, 5, 24_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 6, 25_norm, midiState, curveSet) == 1.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on vel - gain")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lovel", "20" });
|
||||
region.parseOpcode({ "xfin_hivel", "24" });
|
||||
region.parseOpcode({ "xf_velcurve", "gain" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
REQUIRE(region.getNoteGain(1, 19_norm) == 0.0_a);
|
||||
REQUIRE(region.getNoteGain(1, 20_norm) == 0.0_a);
|
||||
REQUIRE(region.getNoteGain(2, 21_norm) == 0.25_a);
|
||||
REQUIRE(region.getNoteGain(3, 22_norm) == 0.5_a);
|
||||
REQUIRE(region.getNoteGain(4, 23_norm) == 0.75_a);
|
||||
REQUIRE(region.getNoteGain(5, 24_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(5, 25_norm) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 1, 19_norm, midiState, curveSet) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 1, 20_norm, midiState, curveSet) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 2, 21_norm, midiState, curveSet) == 0.25_a);
|
||||
REQUIRE(noteGain(region, 3, 22_norm, midiState, curveSet) == 0.5_a);
|
||||
REQUIRE(noteGain(region, 4, 23_norm, midiState, curveSet) == 0.75_a);
|
||||
REQUIRE(noteGain(region, 5, 24_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 5, 25_norm, midiState, curveSet) == 1.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade out on vel")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_lovel", "51" });
|
||||
region.parseOpcode({ "xfout_hivel", "55" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
REQUIRE(region.getNoteGain(5, 50_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(5, 51_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(5, 52_norm) == 0.86603_a);
|
||||
REQUIRE(region.getNoteGain(5, 53_norm) == 0.70711_a);
|
||||
REQUIRE(region.getNoteGain(5, 54_norm) == 0.5_a);
|
||||
REQUIRE(region.getNoteGain(5, 55_norm) == 0.0_a);
|
||||
REQUIRE(region.getNoteGain(5, 56_norm) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 5, 50_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 5, 51_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 5, 52_norm, midiState, curveSet) == 0.86603_a);
|
||||
REQUIRE(noteGain(region, 5, 53_norm, midiState, curveSet) == 0.70711_a);
|
||||
REQUIRE(noteGain(region, 5, 54_norm, midiState, curveSet) == 0.5_a);
|
||||
REQUIRE(noteGain(region, 5, 55_norm, midiState, curveSet) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 5, 56_norm, midiState, curveSet) == 0.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade out on vel - gain")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_lovel", "51" });
|
||||
region.parseOpcode({ "xfout_hivel", "55" });
|
||||
region.parseOpcode({ "xf_velcurve", "gain" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
REQUIRE(region.getNoteGain(56, 50_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(56, 51_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(56, 52_norm) == 0.75_a);
|
||||
REQUIRE(region.getNoteGain(56, 53_norm) == 0.5_a);
|
||||
REQUIRE(region.getNoteGain(56, 54_norm) == 0.25_a);
|
||||
REQUIRE(region.getNoteGain(56, 55_norm) == 0.0_a);
|
||||
REQUIRE(region.getNoteGain(56, 56_norm) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 56, 50_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 56, 51_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 56, 52_norm, midiState, curveSet) == 0.75_a);
|
||||
REQUIRE(noteGain(region, 56, 53_norm, midiState, curveSet) == 0.5_a);
|
||||
REQUIRE(noteGain(region, 56, 54_norm, midiState, curveSet) == 0.25_a);
|
||||
REQUIRE(noteGain(region, 56, 55_norm, midiState, curveSet) == 0.0_a);
|
||||
REQUIRE(noteGain(region, 56, 56_norm, midiState, curveSet) == 0.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on CC")
|
||||
|
|
@ -161,19 +180,19 @@ TEST_CASE("[Region] Crossfade in on CC")
|
|||
region.parseOpcode({ "xfin_hicc24", "24" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
midiState.ccEvent(0, 24, 19_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.0_a);
|
||||
midiState.ccEvent(0, 24, 20_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.0_a);
|
||||
midiState.ccEvent(0, 24, 21_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.5_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.5_a);
|
||||
midiState.ccEvent(0, 24, 22_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.70711_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.70711_a);
|
||||
midiState.ccEvent(0, 24, 23_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.86603_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.86603_a);
|
||||
midiState.ccEvent(0, 24, 24_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 1.0_a);
|
||||
midiState.ccEvent(0, 24, 25_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 1.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on CC - gain")
|
||||
|
|
@ -186,19 +205,19 @@ TEST_CASE("[Region] Crossfade in on CC - gain")
|
|||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
region.parseOpcode({ "xf_cccurve", "gain" });
|
||||
midiState.ccEvent(0, 24, 19_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.0_a);
|
||||
midiState.ccEvent(0, 24, 20_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.0_a);
|
||||
midiState.ccEvent(0, 24, 21_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.25_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.25_a);
|
||||
midiState.ccEvent(0, 24, 22_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.5_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.5_a);
|
||||
midiState.ccEvent(0, 24, 23_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.75_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.75_a);
|
||||
midiState.ccEvent(0, 24, 24_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 1.0_a);
|
||||
midiState.ccEvent(0, 24, 25_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 1.0_a);
|
||||
}
|
||||
TEST_CASE("[Region] Crossfade out on CC")
|
||||
{
|
||||
|
|
@ -209,19 +228,19 @@ TEST_CASE("[Region] Crossfade out on CC")
|
|||
region.parseOpcode({ "xfout_hicc24", "24" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
midiState.ccEvent(0, 24, 19_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 1.0_a);
|
||||
midiState.ccEvent(0, 24, 20_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 1.0_a);
|
||||
midiState.ccEvent(0, 24, 21_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.86603_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.86603_a);
|
||||
midiState.ccEvent(0, 24, 22_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.70711_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.70711_a);
|
||||
midiState.ccEvent(0, 24, 23_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.5_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.5_a);
|
||||
midiState.ccEvent(0, 24, 24_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.0_a);
|
||||
midiState.ccEvent(0, 24, 25_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade out on CC - gain")
|
||||
|
|
@ -234,47 +253,53 @@ TEST_CASE("[Region] Crossfade out on CC - gain")
|
|||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
region.parseOpcode({ "xf_cccurve", "gain" });
|
||||
midiState.ccEvent(0, 24, 19_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 1.0_a);
|
||||
midiState.ccEvent(0, 24, 20_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 1.0_a);
|
||||
midiState.ccEvent(0, 24, 21_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.75_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.75_a);
|
||||
midiState.ccEvent(0, 24, 22_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.5_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.5_a);
|
||||
midiState.ccEvent(0, 24, 23_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.25_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.25_a);
|
||||
midiState.ccEvent(0, 24, 24_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.0_a);
|
||||
midiState.ccEvent(0, 24, 25_norm);
|
||||
REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a);
|
||||
REQUIRE(crossfadeGain(region, midiState) == 0.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(64, 0_norm) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 64, 127_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 64, 0_norm, midiState, curveSet) == 1.0_a);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "amp_veltrack", "100" });
|
||||
REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a);
|
||||
REQUIRE(region.getNoteGain(64, 0_norm) == Approx(0.0).margin(0.0001));
|
||||
REQUIRE(noteGain(region, 64, 127_norm, midiState, curveSet) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 64, 0_norm, midiState, curveSet) == Approx(0.0).margin(0.0001));
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "amp_veltrack", "-100" });
|
||||
REQUIRE(region.getNoteGain(64, 127_norm) == Approx(0.0).margin(0.0001));
|
||||
REQUIRE(region.getNoteGain(64, 0_norm) == 1.0_a);
|
||||
REQUIRE(noteGain(region, 64, 127_norm, midiState, curveSet) == Approx(0.0).margin(0.0001));
|
||||
REQUIRE(noteGain(region, 64, 0_norm, midiState, curveSet) == 1.0_a);
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] rt_decay")
|
||||
|
|
@ -287,15 +312,15 @@ TEST_CASE("[Region] rt_decay")
|
|||
region.parseOpcode({ "rt_decay", "10" });
|
||||
midiState.noteOnEvent(0, 64, 64_norm);
|
||||
midiState.advanceTime(100);
|
||||
REQUIRE( region.getBaseVolumedB(midiState, 64) == Approx(Default::volume - 1.0f).margin(0.1) );
|
||||
REQUIRE( baseVolumedB(region, midiState, 64) == Approx(Default::volume - 1.0f).margin(0.1) );
|
||||
region.parseOpcode({ "rt_decay", "20" });
|
||||
midiState.noteOnEvent(0, 64, 64_norm);
|
||||
midiState.advanceTime(100);
|
||||
REQUIRE( region.getBaseVolumedB(midiState, 64) == Approx(Default::volume - 2.0f).margin(0.1) );
|
||||
REQUIRE( baseVolumedB(region, midiState, 64) == Approx(Default::volume - 2.0f).margin(0.1) );
|
||||
region.parseOpcode({ "trigger", "attack" });
|
||||
midiState.noteOnEvent(0, 64, 64_norm);
|
||||
midiState.advanceTime(100);
|
||||
REQUIRE( region.getBaseVolumedB(midiState, 64) == Approx(Default::volume).margin(0.1) );
|
||||
REQUIRE( baseVolumedB(region, midiState, 64) == Approx(Default::volume).margin(0.1) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Base delay")
|
||||
|
|
@ -304,12 +329,12 @@ TEST_CASE("[Region] Base delay")
|
|||
Region region { 0 };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "delay", "10" });
|
||||
REQUIRE( region.getDelay(midiState) == 10.0f );
|
||||
REQUIRE( regionDelay(region, midiState) == 10.0f );
|
||||
region.parseOpcode({ "delay_random", "10" });
|
||||
Random::randomGenerator.seed(42);
|
||||
for (int i = 0; i < numRandomTests; ++i)
|
||||
{
|
||||
auto delay = region.getDelay(midiState);
|
||||
auto delay = regionDelay(region, midiState);
|
||||
REQUIRE( (delay >= 10.0 && delay <= 20.0) );
|
||||
}
|
||||
}
|
||||
|
|
@ -321,26 +346,133 @@ TEST_CASE("[Region] Offsets with CCs")
|
|||
|
||||
region.parseOpcode({ "offset_cc4", "255" });
|
||||
region.parseOpcode({ "offset", "10" });
|
||||
REQUIRE( region.getOffset(midiState) == 10 );
|
||||
REQUIRE( sampleOffset(region, midiState) == 10 );
|
||||
midiState.ccEvent(0, 4, 127_norm);
|
||||
REQUIRE( region.getOffset(midiState) == 265 );
|
||||
REQUIRE( sampleOffset(region, midiState) == 265 );
|
||||
midiState.ccEvent(0, 4, 100_norm);
|
||||
REQUIRE( region.getOffset(midiState) == 210 );
|
||||
REQUIRE( sampleOffset(region, midiState) == 210 );
|
||||
midiState.ccEvent(0, 4, 10_norm);
|
||||
REQUIRE( region.getOffset(midiState) == 30 );
|
||||
REQUIRE( sampleOffset(region, midiState) == 30 );
|
||||
midiState.ccEvent(0, 4, 0);
|
||||
REQUIRE( region.getOffset(midiState) == 10 );
|
||||
REQUIRE( sampleOffset(region, midiState) == 10 );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Pitch variation with veltrack")
|
||||
{
|
||||
Region region { 0 };
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
|
||||
REQUIRE(region.getBasePitchVariation(60.0, 0_norm) == 1.0);
|
||||
REQUIRE(region.getBasePitchVariation(60.0, 64_norm) == 1.0);
|
||||
REQUIRE(region.getBasePitchVariation(60.0, 127_norm) == 1.0);
|
||||
REQUIRE(basePitchVariation(region, 60.0, 0_norm, midiState, curveSet) == 1.0);
|
||||
REQUIRE(basePitchVariation(region, 60.0, 64_norm, midiState, curveSet) == 1.0);
|
||||
REQUIRE(basePitchVariation(region, 60.0, 127_norm, midiState, curveSet) == 1.0);
|
||||
region.parseOpcode({ "pitch_veltrack", "1200" });
|
||||
REQUIRE(region.getBasePitchVariation(60.0, 0_norm) == 1.0);
|
||||
REQUIRE(region.getBasePitchVariation(60.0, 64_norm) == Approx(centsFactor(600.0)).margin(0.01f));
|
||||
REQUIRE(region.getBasePitchVariation(60.0, 127_norm) == Approx(centsFactor(1200.0)).margin(0.01f));
|
||||
REQUIRE(basePitchVariation(region, 60.0, 0_norm, midiState, curveSet) == 1.0);
|
||||
REQUIRE(basePitchVariation(region, 60.0, 64_norm, midiState, curveSet) == Approx(centsFactor(600.0)).margin(0.01f));
|
||||
REQUIRE(basePitchVariation(region, 60.0, 127_norm, midiState, curveSet) == Approx(centsFactor(1200.0)).margin(0.01f));
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] velcurve")
|
||||
{
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
|
||||
struct VelocityData { float velocity, gain; bool exact; };
|
||||
static const VelocityData veldata[] = {
|
||||
{ 0_norm, 0.0, true },
|
||||
{ 32_norm, 0.5f, false },
|
||||
{ 64_norm, 1.0, true },
|
||||
{ 96_norm, 1.0, true },
|
||||
{ 127_norm, 1.0, true },
|
||||
};
|
||||
|
||||
SECTION("Default veltrack")
|
||||
{
|
||||
sfz::Region region { 0 };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "amp_velcurve_064", "1" });
|
||||
region.velCurve = Curve::buildFromVelcurvePoints(
|
||||
region.velocityPoints, Curve::Interpolator::Linear);
|
||||
for (const VelocityData& vd : veldata) {
|
||||
if (vd.exact) {
|
||||
REQUIRE(velocityCurve(region, vd.velocity, midiState, curveSet) == vd.gain);
|
||||
} else {
|
||||
REQUIRE(velocityCurve(region, vd.velocity, midiState, curveSet) == Approx(vd.gain).margin(1e-2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Inverted veltrack")
|
||||
{
|
||||
sfz::Region region { 0 };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "amp_velcurve_064", "1" });
|
||||
region.parseOpcode({ "amp_veltrack", "-100" });
|
||||
region.velCurve = Curve::buildFromVelcurvePoints(
|
||||
region.velocityPoints, Curve::Interpolator::Linear);
|
||||
for (const VelocityData& vd : veldata) {
|
||||
if (vd.exact) {
|
||||
REQUIRE(velocityCurve(region, vd.velocity, midiState, curveSet) == 1.0f - vd.gain);
|
||||
} else {
|
||||
REQUIRE(velocityCurve(region, vd.velocity, midiState, curveSet) == Approx( 1.0f - vd.gain).margin(1e-2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] veltrack")
|
||||
{
|
||||
struct VelocityData { float velocity, dBGain; };
|
||||
struct VeltrackData { float veltrack; absl::Span<const VelocityData> veldata; };
|
||||
|
||||
MidiState midiState;
|
||||
CurveSet curveSet { CurveSet::createPredefined() };
|
||||
|
||||
// measured on ARIA
|
||||
const VelocityData veldata25[] = {
|
||||
{ 127_norm, 0.0 },
|
||||
{ 96_norm, -1 },
|
||||
{ 64_norm, -1.8 },
|
||||
{ 32_norm, -2.3 },
|
||||
{ 1_norm, -2.5 },
|
||||
};
|
||||
const VelocityData veldata50[] = {
|
||||
{ 127_norm, 0.0 },
|
||||
{ 96_norm, -2.1 },
|
||||
{ 64_norm, -4.1 },
|
||||
{ 32_norm, -5.5 },
|
||||
{ 1_norm, -6.0 },
|
||||
};
|
||||
const VelocityData veldata75[] = {
|
||||
{ 127_norm, 0.0 },
|
||||
{ 96_norm, -3.4 },
|
||||
{ 64_norm, -7.2 },
|
||||
{ 32_norm, -10.5 },
|
||||
{ 1_norm, -12.0 },
|
||||
};
|
||||
const VelocityData veldata100[] = {
|
||||
{ 127_norm, 0.0 },
|
||||
{ 96_norm, -4.9 },
|
||||
{ 64_norm, -12.0 },
|
||||
{ 32_norm, -24.0 },
|
||||
{ 1_norm, -84.1 },
|
||||
};
|
||||
|
||||
const VeltrackData veltrackdata[] = {
|
||||
{ 25, absl::MakeConstSpan(veldata25) },
|
||||
{ 50, absl::MakeConstSpan(veldata50) },
|
||||
{ 75, absl::MakeConstSpan(veldata75) },
|
||||
{ 100, absl::MakeConstSpan(veldata100) },
|
||||
};
|
||||
|
||||
for (const VeltrackData& vt : veltrackdata) {
|
||||
sfz::Region region { 0 };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "amp_veltrack", std::to_string(vt.veltrack) });
|
||||
|
||||
for (const VelocityData& vd : vt.veldata) {
|
||||
float dBGain = 20.0f * std::log10(velocityCurve(region, vd.velocity, midiState, curveSet));
|
||||
REQUIRE(dBGain == Approx(vd.dBGain).margin(0.1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1632,20 +1632,47 @@ TEST_CASE("[Values] Amp Veltrack")
|
|||
Client client(&messageList);
|
||||
client.setReceiveCallback(&simpleMessageReceiver);
|
||||
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||
<region> sample=kick.wav
|
||||
<region> sample=kick.wav amp_veltrack=10.1
|
||||
<region> sample=kick.wav amp_veltrack=-132
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/amp_veltrack", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/amp_veltrack", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/amp_veltrack", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/amp_veltrack,f : { 100 }",
|
||||
"/region1/amp_veltrack,f : { 10.1 }",
|
||||
"/region2/amp_veltrack,f : { -132 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
SECTION("Basic")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||
<region> sample=kick.wav
|
||||
<region> sample=kick.wav amp_veltrack=10.1
|
||||
<region> sample=kick.wav amp_veltrack=-132
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/amp_veltrack", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/amp_veltrack", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/amp_veltrack", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/amp_veltrack,f : { 100 }",
|
||||
"/region1/amp_veltrack,f : { 10.1 }",
|
||||
"/region2/amp_veltrack,f : { -132 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
|
||||
SECTION("CC")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||
<region> sample=kick.wav
|
||||
<region> sample=kick.wav amp_veltrack_cc1=10.1 amp_veltrack_curvecc1=3
|
||||
<region> sample=kick.wav amp_veltrack_oncc2=-40 amp_veltrack_curvecc3=4
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/amp_veltrack_cc1", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/amp_veltrack_cc1", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/amp_veltrack_curvecc1", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/amp_veltrack_cc2", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/amp_veltrack_curvecc3", "", nullptr);
|
||||
// TODO: activate for the new region parser ; accept oob
|
||||
// synth.dispatchMessage(client, 0, "/region2/amp_veltrack", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/amp_veltrack_cc1,N : { }",
|
||||
"/region1/amp_veltrack_cc1,f : { 10.1 }",
|
||||
"/region1/amp_veltrack_curvecc1,i : { 3 }",
|
||||
"/region2/amp_veltrack_cc2,f : { -40 }",
|
||||
"/region2/amp_veltrack_curvecc3,i : { 4 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Values] Amp Random")
|
||||
|
|
@ -1990,20 +2017,47 @@ TEST_CASE("[Values] Pitch Veltrack")
|
|||
Client client(&messageList);
|
||||
client.setReceiveCallback(&simpleMessageReceiver);
|
||||
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||
<region> sample=kick.wav
|
||||
<region> sample=kick.wav pitch_veltrack=10
|
||||
<region> sample=kick.wav pitch_veltrack=-132
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/pitch_veltrack", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/pitch_veltrack", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/pitch_veltrack", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/pitch_veltrack,i : { 0 }",
|
||||
"/region1/pitch_veltrack,i : { 10 }",
|
||||
"/region2/pitch_veltrack,i : { -132 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
SECTION("Basic")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||
<region> sample=kick.wav
|
||||
<region> sample=kick.wav pitch_veltrack=10
|
||||
<region> sample=kick.wav pitch_veltrack=-132
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/pitch_veltrack", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/pitch_veltrack", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/pitch_veltrack", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/pitch_veltrack,i : { 0 }",
|
||||
"/region1/pitch_veltrack,i : { 10 }",
|
||||
"/region2/pitch_veltrack,i : { -132 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
|
||||
SECTION("CC")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||
<region> sample=kick.wav
|
||||
<region> sample=kick.wav pitch_veltrack_cc1=10.1 pitch_veltrack_curvecc1=3
|
||||
<region> sample=kick.wav pitch_veltrack_oncc2=-40 pitch_veltrack_curvecc3=4
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/pitch_veltrack_cc1", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/pitch_veltrack_cc1", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/pitch_veltrack_curvecc1", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/pitch_veltrack_cc2", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/pitch_veltrack_curvecc3", "", nullptr);
|
||||
// TODO: activate for the new region parser ; accept oob
|
||||
// synth.dispatchMessage(client, 0, "/region2/pitch_veltrack", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/pitch_veltrack_cc1,N : { }",
|
||||
"/region1/pitch_veltrack_cc1,f : { 10.1 }",
|
||||
"/region1/pitch_veltrack_curvecc1,i : { 3 }",
|
||||
"/region2/pitch_veltrack_cc2,f : { -40 }",
|
||||
"/region2/pitch_veltrack_curvecc3,i : { 4 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Values] Pitch Random")
|
||||
|
|
@ -2995,6 +3049,7 @@ TEST_CASE("[Values] Filter dispatching")
|
|||
<region> sample=kick.wav
|
||||
cutoff3=50 resonance2=3 fil2_gain=-5 fil3_keytrack=100
|
||||
fil_gain=5 fil1_gain=-5 fil2_veltrack=-100
|
||||
fil4_veltrack_cc7=-100 fil5_veltrack_curvecc2=2
|
||||
)");
|
||||
|
||||
synth.dispatchMessage(client, 0, "/region0/filter2/cutoff", "", nullptr);
|
||||
|
|
@ -3003,6 +3058,8 @@ TEST_CASE("[Values] Filter dispatching")
|
|||
synth.dispatchMessage(client, 0, "/region0/filter2/keytrack", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region0/filter0/gain", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region0/filter1/veltrack", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region0/filter3/veltrack_cc7", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region0/filter4/veltrack_curvecc2", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/filter2/cutoff,f : { 50 }",
|
||||
"/region0/filter1/resonance,f : { 3 }",
|
||||
|
|
@ -3010,6 +3067,8 @@ TEST_CASE("[Values] Filter dispatching")
|
|||
"/region0/filter2/keytrack,i : { 100 }",
|
||||
"/region0/filter0/gain,f : { -5 }",
|
||||
"/region0/filter1/veltrack,i : { -100 }",
|
||||
"/region0/filter3/veltrack_cc7,f : { -100 }",
|
||||
"/region0/filter4/veltrack_curvecc2,i : { 2 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -431,98 +431,6 @@ TEST_CASE("[Synth] Velocity points")
|
|||
REQUIRE( synth.getRegionView(1)->velocityPoints[0].second == 1.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] velcurve")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/velocity_endpoints.sfz", R"(
|
||||
<region> amp_velcurve_064=1 sample=*sine
|
||||
<region> amp_velcurve_064=1 amp_veltrack=-100 sample=*sine
|
||||
)");
|
||||
|
||||
struct VelocityData { float velocity, gain; bool exact; };
|
||||
|
||||
static const VelocityData veldata[] = {
|
||||
{ 0_norm, 0.0, true },
|
||||
{ 32_norm, 0.5f, false },
|
||||
{ 64_norm, 1.0, true },
|
||||
{ 96_norm, 1.0, true },
|
||||
{ 127_norm, 1.0, true },
|
||||
};
|
||||
|
||||
REQUIRE(synth.getNumRegions() == 2);
|
||||
const sfz::Region* r1 = synth.getRegionView(0);
|
||||
const sfz::Region* r2 = synth.getRegionView(1);
|
||||
|
||||
for (const VelocityData& vd : veldata) {
|
||||
if (vd.exact) {
|
||||
REQUIRE(r1->velocityCurve(vd.velocity) == vd.gain);
|
||||
REQUIRE(r2->velocityCurve(vd.velocity) == 1.0f - vd.gain);
|
||||
}
|
||||
else {
|
||||
REQUIRE(r1->velocityCurve(vd.velocity) == Approx(vd.gain).margin(1e-2));
|
||||
REQUIRE(r2->velocityCurve(vd.velocity) == Approx(1.0f - vd.gain).margin(1e-2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] veltrack")
|
||||
{
|
||||
struct VelocityData { float velocity, dBGain; };
|
||||
struct VeltrackData { float veltrack; absl::Span<const VelocityData> veldata; };
|
||||
|
||||
// measured on ARIA
|
||||
const VelocityData veldata25[] = {
|
||||
{ 127_norm, 0.0 },
|
||||
{ 96_norm, -1 },
|
||||
{ 64_norm, -1.8 },
|
||||
{ 32_norm, -2.3 },
|
||||
{ 1_norm, -2.5 },
|
||||
};
|
||||
const VelocityData veldata50[] = {
|
||||
{ 127_norm, 0.0 },
|
||||
{ 96_norm, -2.1 },
|
||||
{ 64_norm, -4.1 },
|
||||
{ 32_norm, -5.5 },
|
||||
{ 1_norm, -6.0 },
|
||||
};
|
||||
const VelocityData veldata75[] = {
|
||||
{ 127_norm, 0.0 },
|
||||
{ 96_norm, -3.4 },
|
||||
{ 64_norm, -7.2 },
|
||||
{ 32_norm, -10.5 },
|
||||
{ 1_norm, -12.0 },
|
||||
};
|
||||
const VelocityData veldata100[] = {
|
||||
{ 127_norm, 0.0 },
|
||||
{ 96_norm, -4.9 },
|
||||
{ 64_norm, -12.0 },
|
||||
{ 32_norm, -24.0 },
|
||||
{ 1_norm, -84.1 },
|
||||
};
|
||||
|
||||
const VeltrackData veltrackdata[] = {
|
||||
{ 25, absl::MakeConstSpan(veldata25) },
|
||||
{ 50, absl::MakeConstSpan(veldata50) },
|
||||
{ 75, absl::MakeConstSpan(veldata75) },
|
||||
{ 100, absl::MakeConstSpan(veldata100) },
|
||||
};
|
||||
|
||||
for (const VeltrackData& vt : veltrackdata) {
|
||||
sfz::Synth synth;
|
||||
const std::string sfzCode = "<region>sample=*sine amp_veltrack=" +
|
||||
std::to_string(vt.veltrack);
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/veltrack.sfz", sfzCode);
|
||||
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
const sfz::Region* r = synth.getRegionView(0);
|
||||
|
||||
for (const VelocityData& vd : vt.veldata) {
|
||||
float dBGain = 20.0f * std::log10(r->velocityCurve(vd.velocity));
|
||||
REQUIRE(dBGain == Approx(vd.dBGain).margin(0.1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Region by identifier")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue