Crossfades and testing
This commit is contained in:
parent
194ad0b8eb
commit
de30501ed3
3 changed files with 256 additions and 57 deletions
|
|
@ -109,6 +109,7 @@ set(TEST_SOURCES
|
|||
tests/FilesT.cpp
|
||||
tests/OnePoleFilterT.cpp
|
||||
tests/RegionActivationT.cpp
|
||||
tests/RegionCrossfadesT.cpp
|
||||
tests/ADSREnvelopeT.cpp
|
||||
tests/LinearEnvelopeT.cpp
|
||||
tests/MainT.cpp
|
||||
|
|
|
|||
|
|
@ -631,55 +631,56 @@ bool sfz::Region::isStereo() const noexcept
|
|||
return this->numChannels == 2;
|
||||
}
|
||||
|
||||
// TODO: lots and lots of repetition here...
|
||||
template<class T, class U>
|
||||
float crossfadeIn(const Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
||||
{
|
||||
if (value < crossfadeRange.getStart())
|
||||
return 0.0f;
|
||||
else if (value < crossfadeRange.getEnd()) {
|
||||
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / std::max(static_cast<float>(crossfadeRange.length()), 1.0f);
|
||||
if (curve == SfzCrossfadeCurve::power)
|
||||
return sqrt(crossfadePosition);
|
||||
if (curve == SfzCrossfadeCurve::gain)
|
||||
return crossfadePosition;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
template<class T, class U>
|
||||
float crossfadeOut(const Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
||||
{
|
||||
if (value > crossfadeRange.getEnd())
|
||||
return 0.0f;
|
||||
else if (value > crossfadeRange.getStart()) {
|
||||
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / std::max(static_cast<float>(crossfadeRange.length()), 1.0f);
|
||||
if (curve == SfzCrossfadeCurve::power)
|
||||
return sqrt(1 - crossfadePosition);
|
||||
if (curve == SfzCrossfadeCurve::gain)
|
||||
return 1 - crossfadePosition;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float sfz::Region::getNoteGain(int noteNumber, uint8_t velocity) noexcept
|
||||
{
|
||||
float baseGain { 1.0f };
|
||||
|
||||
// Amplitude velocity tracking
|
||||
if (trigger == SfzTrigger::release_key)
|
||||
baseGain *= velocityGain(lastNoteVelocities[noteNumber]);
|
||||
else
|
||||
baseGain *= velocityGain(velocity);
|
||||
|
||||
if (noteNumber < crossfadeKeyInRange.getStart())
|
||||
baseGain = 0.0f;
|
||||
else if (noteNumber < crossfadeKeyInRange.getEnd()) {
|
||||
const auto crossfadePosition = static_cast<float>(noteNumber - crossfadeKeyInRange.getStart()) / (crossfadeKeyInRange.length() > 0 ? crossfadeKeyInRange.length() : 1);
|
||||
if (crossfadeKeyCurve == SfzCrossfadeCurve::power)
|
||||
baseGain *= sqrt(crossfadePosition);
|
||||
if (crossfadeKeyCurve == SfzCrossfadeCurve::gain)
|
||||
baseGain *= crossfadePosition;
|
||||
}
|
||||
// Amplitude key tracking
|
||||
baseGain *= db2pow(ampKeytrack * static_cast<float>(noteNumber - pitchKeycenter));
|
||||
|
||||
if (noteNumber > crossfadeKeyOutRange.getEnd())
|
||||
baseGain = 0.0f;
|
||||
else if (noteNumber > crossfadeKeyOutRange.getStart()) {
|
||||
const auto crossfadePosition = static_cast<float>(noteNumber - crossfadeKeyOutRange.getStart()) / (crossfadeKeyOutRange.length() > 0 ? crossfadeKeyOutRange.length() : 1);
|
||||
if (crossfadeKeyCurve == SfzCrossfadeCurve::power)
|
||||
baseGain *= sqrt(1 - crossfadePosition);
|
||||
if (crossfadeKeyCurve == SfzCrossfadeCurve::gain)
|
||||
baseGain *= 1 - crossfadePosition;
|
||||
}
|
||||
|
||||
if (velocity < crossfadeVelInRange.getStart())
|
||||
baseGain = 0;
|
||||
else if (velocity < crossfadeVelInRange.getEnd()) {
|
||||
const auto crossfadePosition = static_cast<float>(noteNumber - crossfadeVelInRange.getStart()) / (crossfadeVelInRange.length() > 0 ? crossfadeVelInRange.length() : 1);
|
||||
if (crossfadeVelCurve == SfzCrossfadeCurve::power)
|
||||
baseGain *= sqrt(crossfadePosition);
|
||||
if (crossfadeVelCurve == SfzCrossfadeCurve::gain)
|
||||
baseGain *= crossfadePosition;
|
||||
}
|
||||
|
||||
if (velocity > crossfadeVelOutRange.getEnd())
|
||||
baseGain = 0;
|
||||
else if (velocity > crossfadeVelOutRange.getStart()) {
|
||||
const auto crossfadePosition = static_cast<float>(noteNumber - crossfadeVelOutRange.getStart()) / (crossfadeVelOutRange.length() > 0 ? crossfadeVelOutRange.length() : 1);
|
||||
if (crossfadeVelCurve == SfzCrossfadeCurve::power)
|
||||
baseGain *= sqrt(1 - crossfadePosition);
|
||||
if (crossfadeVelCurve == SfzCrossfadeCurve::gain)
|
||||
baseGain *= 1 - crossfadePosition;
|
||||
}
|
||||
// Crossfades related to key and velocity
|
||||
baseGain *= crossfadeIn(crossfadeKeyInRange, noteNumber, crossfadeKeyCurve);
|
||||
baseGain *= crossfadeOut(crossfadeKeyOutRange, noteNumber, crossfadeKeyCurve);
|
||||
baseGain *= crossfadeIn(crossfadeVelInRange, velocity, crossfadeVelCurve);
|
||||
baseGain *= crossfadeOut(crossfadeVelOutRange, velocity, crossfadeVelCurve);
|
||||
|
||||
return baseGain;
|
||||
}
|
||||
|
|
@ -688,32 +689,20 @@ float sfz::Region::getCCGain(const sfz::CCValueArray& ccState) noexcept
|
|||
{
|
||||
float gain { 1.0f };
|
||||
|
||||
if (amplitudeCC)
|
||||
gain *= *amplitudeCC
|
||||
|
||||
// Crossfades due to CC states
|
||||
for (const auto& valuePair : crossfadeCCInRange) {
|
||||
const auto ccValue = ccState[valuePair.first];
|
||||
const auto crossfadeRange = valuePair.second;
|
||||
if (ccValue < crossfadeRange.getStart()) {
|
||||
gain = 0.0f;
|
||||
} else if (ccValue < crossfadeRange.getEnd()) {
|
||||
const auto crossfadePosition = static_cast<float>(ccValue - crossfadeRange.getStart()) / (crossfadeRange.length() > 0 ? crossfadeRange.length() : 1);
|
||||
if (crossfadeCCCurve == SfzCrossfadeCurve::power)
|
||||
gain *= sqrt(crossfadePosition);
|
||||
if (crossfadeVelCurve == SfzCrossfadeCurve::gain)
|
||||
gain *= crossfadePosition;
|
||||
}
|
||||
gain *= crossfadeIn(crossfadeRange, ccValue, crossfadeCCCurve);
|
||||
}
|
||||
|
||||
for (const auto& valuePair : crossfadeCCOutRange) {
|
||||
const auto ccValue = ccState[valuePair.first];
|
||||
const auto crossfadeRange = valuePair.second;
|
||||
if (ccValue > crossfadeRange.getEnd()) {
|
||||
gain = 0.0f;
|
||||
} else if (ccValue > crossfadeRange.getStart()) {
|
||||
const auto crossfadePosition = static_cast<float>(ccValue - crossfadeRange.getStart()) / (crossfadeRange.length() > 0 ? crossfadeRange.length() : 1);
|
||||
if (crossfadeCCCurve == SfzCrossfadeCurve::power)
|
||||
gain *= sqrt(1 - crossfadePosition);
|
||||
if (crossfadeVelCurve == SfzCrossfadeCurve::gain)
|
||||
gain *= 1 - crossfadePosition;
|
||||
}
|
||||
gain *= crossfadeOut(crossfadeRange, ccValue, crossfadeCCCurve);
|
||||
}
|
||||
|
||||
return gain;
|
||||
|
|
|
|||
209
tests/RegionCrossfadesT.cpp
Normal file
209
tests/RegionCrossfadesT.cpp
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
#include "../sources/Region.h"
|
||||
#include "catch2/catch.hpp"
|
||||
#include <SfzHelpers.h>
|
||||
using namespace Catch::literals;
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on key")
|
||||
{
|
||||
sfz::Region region {};
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lokey", "1" });
|
||||
region.parseOpcode({ "xfin_hikey", "3" });
|
||||
REQUIRE( region.getNoteGain(2, 127) == 0.70711_a );
|
||||
REQUIRE( region.getNoteGain(1, 127) == 0.0_a );
|
||||
REQUIRE( region.getNoteGain(3, 127) == 1.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on key - 2")
|
||||
{
|
||||
sfz::Region region {};
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lokey", "1" });
|
||||
region.parseOpcode({ "xfin_hikey", "5" });
|
||||
REQUIRE( region.getNoteGain(1, 127) == 0.0_a );
|
||||
REQUIRE( region.getNoteGain(2, 127) == 0.5_a );
|
||||
REQUIRE( region.getNoteGain(3, 127) == 0.70711_a );
|
||||
REQUIRE( region.getNoteGain(4, 127) == 0.86603_a );
|
||||
REQUIRE( region.getNoteGain(5, 127) == 1.0_a );
|
||||
REQUIRE( region.getNoteGain(6, 127) == 1.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on key - gain")
|
||||
{
|
||||
sfz::Region region {};
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lokey", "1" });
|
||||
region.parseOpcode({ "xfin_hikey", "5" });
|
||||
region.parseOpcode({ "xf_keycurve", "gain" });
|
||||
REQUIRE( region.getNoteGain(1, 127) == 0.0_a );
|
||||
REQUIRE( region.getNoteGain(2, 127) == 0.25_a );
|
||||
REQUIRE( region.getNoteGain(3, 127) == 0.5_a );
|
||||
REQUIRE( region.getNoteGain(4, 127) == 0.75_a );
|
||||
REQUIRE( region.getNoteGain(5, 127) == 1.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade out on key")
|
||||
{
|
||||
sfz::Region region {};
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_lokey", "51" });
|
||||
region.parseOpcode({ "xfout_hikey", "55" });
|
||||
REQUIRE( region.getNoteGain(50, 127) == 1.0_a );
|
||||
REQUIRE( region.getNoteGain(51, 127) == 1.0_a );
|
||||
REQUIRE( region.getNoteGain(52, 127) == 0.86603_a );
|
||||
REQUIRE( region.getNoteGain(53, 127) == 0.70711_a );
|
||||
REQUIRE( region.getNoteGain(54, 127) == 0.5_a );
|
||||
REQUIRE( region.getNoteGain(55, 127) == 0.0_a );
|
||||
REQUIRE( region.getNoteGain(56, 127) == 0.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade out on key - gain")
|
||||
{
|
||||
sfz::Region region {};
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_lokey", "51" });
|
||||
region.parseOpcode({ "xfout_hikey", "55" });
|
||||
region.parseOpcode({ "xf_keycurve", "gain" });
|
||||
REQUIRE( region.getNoteGain(50, 127) == 1.0_a );
|
||||
REQUIRE( region.getNoteGain(51, 127) == 1.0_a );
|
||||
REQUIRE( region.getNoteGain(52, 127) == 0.75_a );
|
||||
REQUIRE( region.getNoteGain(53, 127) == 0.5_a );
|
||||
REQUIRE( region.getNoteGain(54, 127) == 0.25_a );
|
||||
REQUIRE( region.getNoteGain(55, 127) == 0.0_a );
|
||||
REQUIRE( region.getNoteGain(56, 127) == 0.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on velocity")
|
||||
{
|
||||
sfz::Region region {};
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lovel", "20" });
|
||||
region.parseOpcode({ "xfin_hivel", "24" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
REQUIRE( region.getNoteGain(1, 19) == 0.0_a );
|
||||
REQUIRE( region.getNoteGain(1, 20) == 0.0_a );
|
||||
REQUIRE( region.getNoteGain(2, 21) == 0.5_a );
|
||||
REQUIRE( region.getNoteGain(3, 22) == 0.70711_a );
|
||||
REQUIRE( region.getNoteGain(4, 23) == 0.86603_a );
|
||||
REQUIRE( region.getNoteGain(5, 24) == 1.0_a );
|
||||
REQUIRE( region.getNoteGain(6, 25) == 1.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on vel - gain")
|
||||
{
|
||||
sfz::Region region {};
|
||||
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) == 0.0_a );
|
||||
REQUIRE( region.getNoteGain(1, 20) == 0.0_a );
|
||||
REQUIRE( region.getNoteGain(2, 21) == 0.25_a );
|
||||
REQUIRE( region.getNoteGain(3, 22) == 0.5_a );
|
||||
REQUIRE( region.getNoteGain(4, 23) == 0.75_a );
|
||||
REQUIRE( region.getNoteGain(5, 24) == 1.0_a );
|
||||
REQUIRE( region.getNoteGain(5, 25) == 1.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade out on vel")
|
||||
{
|
||||
sfz::Region region {};
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_lovel", "51" });
|
||||
region.parseOpcode({ "xfout_hivel", "55" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
REQUIRE( region.getNoteGain(5, 50) == 1.0_a );
|
||||
REQUIRE( region.getNoteGain(5, 51) == 1.0_a );
|
||||
REQUIRE( region.getNoteGain(5, 52) == 0.86603_a );
|
||||
REQUIRE( region.getNoteGain(5, 53) == 0.70711_a );
|
||||
REQUIRE( region.getNoteGain(5, 54) == 0.5_a );
|
||||
REQUIRE( region.getNoteGain(5, 55) == 0.0_a );
|
||||
REQUIRE( region.getNoteGain(5, 56) == 0.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade out on vel - gain")
|
||||
{
|
||||
sfz::Region region {};
|
||||
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) == 1.0_a );
|
||||
REQUIRE( region.getNoteGain(56, 51) == 1.0_a );
|
||||
REQUIRE( region.getNoteGain(56, 52) == 0.75_a );
|
||||
REQUIRE( region.getNoteGain(56, 53) == 0.5_a );
|
||||
REQUIRE( region.getNoteGain(56, 54) == 0.25_a );
|
||||
REQUIRE( region.getNoteGain(56, 55) == 0.0_a );
|
||||
REQUIRE( region.getNoteGain(56, 56) == 0.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on CC")
|
||||
{
|
||||
sfz::Region region {};
|
||||
sfz::CCValueArray ccState;
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_locc24", "20" });
|
||||
region.parseOpcode({ "xfin_hicc24", "24" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
ccState[24] = 19; REQUIRE( region.getCCGain(ccState) == 0.0_a );
|
||||
ccState[24] = 20; REQUIRE( region.getCCGain(ccState) == 0.0_a );
|
||||
ccState[24] = 21; REQUIRE( region.getCCGain(ccState) == 0.5_a );
|
||||
ccState[24] = 22; REQUIRE( region.getCCGain(ccState) == 0.70711_a );
|
||||
ccState[24] = 23; REQUIRE( region.getCCGain(ccState) == 0.86603_a );
|
||||
ccState[24] = 24; REQUIRE( region.getCCGain(ccState) == 1.0_a );
|
||||
ccState[24] = 25; REQUIRE( region.getCCGain(ccState) == 1.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade in on CC - gain")
|
||||
{
|
||||
sfz::Region region {};
|
||||
sfz::CCValueArray ccState;
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_locc24", "20" });
|
||||
region.parseOpcode({ "xfin_hicc24", "24" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
region.parseOpcode({ "xf_cccurve", "gain" });
|
||||
ccState[24] = 19; REQUIRE( region.getCCGain(ccState) == 0.0_a );
|
||||
ccState[24] = 20; REQUIRE( region.getCCGain(ccState) == 0.0_a );
|
||||
ccState[24] = 21; REQUIRE( region.getCCGain(ccState) == 0.25_a );
|
||||
ccState[24] = 22; REQUIRE( region.getCCGain(ccState) == 0.5_a );
|
||||
ccState[24] = 23; REQUIRE( region.getCCGain(ccState) == 0.75_a );
|
||||
ccState[24] = 24; REQUIRE( region.getCCGain(ccState) == 1.0_a );
|
||||
ccState[24] = 25; REQUIRE( region.getCCGain(ccState) == 1.0_a );
|
||||
}
|
||||
TEST_CASE("[Region] Crossfade out on CC")
|
||||
{
|
||||
sfz::Region region {};
|
||||
sfz::CCValueArray ccState;
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_locc24", "20" });
|
||||
region.parseOpcode({ "xfout_hicc24", "24" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
ccState[24] = 19; REQUIRE( region.getCCGain(ccState) == 1.0_a );
|
||||
ccState[24] = 20; REQUIRE( region.getCCGain(ccState) == 1.0_a );
|
||||
ccState[24] = 21; REQUIRE( region.getCCGain(ccState) == 0.86603_a );
|
||||
ccState[24] = 22; REQUIRE( region.getCCGain(ccState) == 0.70711_a );
|
||||
ccState[24] = 23; REQUIRE( region.getCCGain(ccState) == 0.5_a );
|
||||
ccState[24] = 24; REQUIRE( region.getCCGain(ccState) == 0.0_a );
|
||||
ccState[24] = 25; REQUIRE( region.getCCGain(ccState) == 0.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Region] Crossfade out on CC - gain")
|
||||
{
|
||||
sfz::Region region {};
|
||||
sfz::CCValueArray ccState;
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_locc24", "20" });
|
||||
region.parseOpcode({ "xfout_hicc24", "24" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
region.parseOpcode({ "xf_cccurve", "gain" });
|
||||
ccState[24] = 19; REQUIRE( region.getCCGain(ccState) == 1.0_a );
|
||||
ccState[24] = 20; REQUIRE( region.getCCGain(ccState) == 1.0_a );
|
||||
ccState[24] = 21; REQUIRE( region.getCCGain(ccState) == 0.75_a );
|
||||
ccState[24] = 22; REQUIRE( region.getCCGain(ccState) == 0.5_a );
|
||||
ccState[24] = 23; REQUIRE( region.getCCGain(ccState) == 0.25_a );
|
||||
ccState[24] = 24; REQUIRE( region.getCCGain(ccState) == 0.0_a );
|
||||
ccState[24] = 25; REQUIRE( region.getCCGain(ccState) == 0.0_a );
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue