Crossfades and testing

This commit is contained in:
paulfd 2019-08-30 00:16:01 +02:00
parent 194ad0b8eb
commit de30501ed3
3 changed files with 256 additions and 57 deletions

View file

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

View file

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