ccState and the midiState globals become a single private member of the Synth object; read-only references are loaded on creation into the regions and voice.

This commit is contained in:
paulfd 2019-09-21 01:07:53 +02:00
parent 32865b5841
commit d4a1178233
12 changed files with 127 additions and 93 deletions

View file

@ -1,10 +1,13 @@
#pragma once
#include <chrono> #include <chrono>
#include <array> #include <array>
#include "SfzHelpers.h"
#include "compat/utils.h" #include "compat/utils.h"
namespace sfz namespace sfz
{ {
SFZ_INLINE std::array<std::chrono::steady_clock::time_point, 128> noteOnTimes { }; struct MidiState
SFZ_INLINE std::array<uint8_t, 128> lastNoteVelocities { }; {
inline void noteOn(int noteNumber, uint8_t velocity) inline void noteOn(int noteNumber, uint8_t velocity)
{ {
if (noteNumber >= 0 && noteNumber < 128) { if (noteNumber >= 0 && noteNumber < 128) {
@ -13,7 +16,7 @@ namespace sfz
} }
} }
inline float getNoteDuration(int noteNumber) inline float getNoteDuration(int noteNumber) const
{ {
if (noteNumber >= 0 && noteNumber < 128) { if (noteNumber >= 0 && noteNumber < 128) {
const auto noteOffTime = std::chrono::steady_clock::now(); const auto noteOffTime = std::chrono::steady_clock::now();
@ -24,11 +27,15 @@ namespace sfz
return 0.0f; return 0.0f;
} }
inline uint8_t getNoteVelocity(int noteNumber) inline uint8_t getNoteVelocity(int noteNumber) const
{ {
if (noteNumber >= 0 && noteNumber < 128) if (noteNumber >= 0 && noteNumber < 128)
return lastNoteVelocities[noteNumber]; return lastNoteVelocities[noteNumber];
return 0; return 0;
} }
std::array<std::chrono::steady_clock::time_point, 128> noteOnTimes { };
std::array<uint8_t, 128> lastNoteVelocities { };
CCValueArray cc;
};
} }

View file

@ -635,7 +635,7 @@ float sfz::Region::getBaseVolumedB(int noteNumber) noexcept
{ {
auto baseVolumedB = volume + volumeDistribution(Random::randomGenerator); auto baseVolumedB = volume + volumeDistribution(Random::randomGenerator);
if (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key) if (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key)
baseVolumedB -= rtDecay * getNoteDuration(noteNumber); baseVolumedB -= rtDecay * midiState.getNoteDuration(noteNumber);
return baseVolumedB; return baseVolumedB;
} }

View file

@ -28,6 +28,7 @@
#include "EGDescription.h" #include "EGDescription.h"
#include "Opcode.h" #include "Opcode.h"
#include "AudioBuffer.h" #include "AudioBuffer.h"
#include "MidiState.h"
#include <bitset> #include <bitset>
#include <absl/types/optional.h> #include <absl/types/optional.h>
#include <random> #include <random>
@ -36,7 +37,8 @@
namespace sfz { namespace sfz {
struct Region { struct Region {
Region() Region(const MidiState& midiState)
: midiState(midiState)
{ {
ccSwitched.set(); ccSwitched.set();
} }
@ -154,6 +156,7 @@ struct Region {
double sampleRate { config::defaultSampleRate }; double sampleRate { config::defaultSampleRate };
std::shared_ptr<AudioBuffer<float>> preloadedData { nullptr }; std::shared_ptr<AudioBuffer<float>> preloadedData { nullptr };
private: private:
const MidiState& midiState;
bool keySwitched { true }; bool keySwitched { true };
bool previousKeySwitched { true }; bool previousKeySwitched { true };
bool sequenceSwitched { true }; bool sequenceSwitched { true };

View file

@ -44,7 +44,7 @@ inline constexpr float centsFactor(T cents, T centsPerOctave = 1200)
template<class T> template<class T>
inline constexpr float normalizeCC(T ccValue) inline constexpr float normalizeCC(T ccValue)
{ {
static_assert(std::is_integral<T>::value); static_assert(std::is_integral<T>::value, "Requires an integral T");
return static_cast<float>(std::min(std::max(ccValue, static_cast<T>(0)), static_cast<T>(127))) / 127.0f; return static_cast<float>(std::min(std::max(ccValue, static_cast<T>(0)), static_cast<T>(127))) / 127.0f;
} }

View file

@ -37,7 +37,7 @@ using namespace std::literals;
sfz::Synth::Synth() sfz::Synth::Synth()
{ {
for (int i = 0; i < config::numVoices; ++i) for (int i = 0; i < config::numVoices; ++i)
voices.push_back(std::make_unique<Voice>(ccState)); voices.push_back(std::make_unique<Voice>(midiState));
voiceViewArray.reserve(config::numVoices); voiceViewArray.reserve(config::numVoices);
} }
@ -83,7 +83,7 @@ void sfz::Synth::callback(absl::string_view header, const std::vector<Opcode>& m
void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes) void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
{ {
auto lastRegion = std::make_unique<Region>(); auto lastRegion = std::make_unique<Region>(midiState);
auto parseOpcodes = [&](const auto& opcodes) { auto parseOpcodes = [&](const auto& opcodes) {
for (auto& opcode : opcodes) { for (auto& opcode : opcodes) {
@ -114,7 +114,7 @@ void sfz::Synth::clear()
numCurves = 0; numCurves = 0;
fileTicket = -1; fileTicket = -1;
defaultSwitch = absl::nullopt; defaultSwitch = absl::nullopt;
for (auto& state : ccState) for (auto& state : midiState.cc)
state = 0; state = 0;
ccNames.clear(); ccNames.clear();
globalOpcodes.clear(); globalOpcodes.clear();
@ -142,7 +142,7 @@ void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& members)
case hash("Set_cc"): [[fallthrough]]; case hash("Set_cc"): [[fallthrough]];
case hash("set_cc"): case hash("set_cc"):
if (member.parameter && Default::ccRange.containsWithEnd(*member.parameter)) if (member.parameter && Default::ccRange.containsWithEnd(*member.parameter))
setValueFromOpcode(member, ccState[*member.parameter], Default::ccRange); setValueFromOpcode(member, midiState.cc[*member.parameter], Default::ccRange);
break; break;
case hash("Label_cc"): [[fallthrough]]; case hash("Label_cc"): [[fallthrough]];
case hash("label_cc"): case hash("label_cc"):
@ -230,7 +230,7 @@ bool sfz::Synth::loadSfzFile(const std::filesystem::path& filename)
// Defaults // Defaults
for (int ccIndex = 1; ccIndex < 128; ccIndex++) for (int ccIndex = 1; ccIndex < 128; ccIndex++)
region->registerCC(region->channelRange.getStart(), ccIndex, ccState[ccIndex]); region->registerCC(region->channelRange.getStart(), ccIndex, midiState.cc[ccIndex]);
if (defaultSwitch) { if (defaultSwitch) {
region->registerNoteOn(region->channelRange.getStart(), *defaultSwitch, 127, 1.0); region->registerNoteOn(region->channelRange.getStart(), *defaultSwitch, 127, 1.0);
@ -333,7 +333,7 @@ void sfz::Synth::noteOn(int delay, int channel, int noteNumber, uint8_t velocity
ASSERT(noteNumber < 128); ASSERT(noteNumber < 128);
ASSERT(noteNumber >= 0); ASSERT(noteNumber >= 0);
sfz::noteOn(noteNumber, velocity); midiState.noteOn(noteNumber, velocity);
auto randValue = randNoteDistribution(Random::randomGenerator); auto randValue = randNoteDistribution(Random::randomGenerator);
for (auto& region : noteActivationLists[noteNumber]) { for (auto& region : noteActivationLists[noteNumber]) {
@ -361,7 +361,7 @@ void sfz::Synth::noteOff(int delay, int channel, int noteNumber, uint8_t velocit
ASSERT(noteNumber < 128); ASSERT(noteNumber < 128);
ASSERT(noteNumber >= 0); ASSERT(noteNumber >= 0);
auto replacedVelocity = velocity == 0 ? sfz::getNoteVelocity(noteNumber) : velocity; auto replacedVelocity = velocity == 0 ? midiState.getNoteVelocity(noteNumber) : velocity;
auto randValue = randNoteDistribution(Random::randomGenerator); auto randValue = randNoteDistribution(Random::randomGenerator);
for (auto& voice : voices) for (auto& voice : voices)
voice->registerNoteOff(delay, channel, noteNumber, replacedVelocity); voice->registerNoteOff(delay, channel, noteNumber, replacedVelocity);
@ -389,7 +389,7 @@ void sfz::Synth::cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexc
for (auto& voice : voices) for (auto& voice : voices)
voice->registerCC(delay, channel, ccNumber, ccValue); voice->registerCC(delay, channel, ccNumber, ccValue);
ccState[ccNumber] = ccValue; midiState.cc[ccNumber] = ccValue;
for (auto& region : ccActivationLists[ccNumber]) { for (auto& region : ccActivationLists[ccNumber]) {
if (region->registerCC(channel, ccNumber, ccValue)) { if (region->registerCC(channel, ccNumber, ccValue)) {

View file

@ -26,6 +26,7 @@
#include "Parser.h" #include "Parser.h"
#include "Region.h" #include "Region.h"
#include "LeakDetector.h" #include "LeakDetector.h"
#include "MidiState.h"
#include "AudioSpan.h" #include "AudioSpan.h"
#include "absl/types/span.h" #include "absl/types/span.h"
#include <absl/types/optional.h> #include <absl/types/optional.h>
@ -80,7 +81,7 @@ private:
std::vector<Opcode> groupOpcodes; std::vector<Opcode> groupOpcodes;
FilePool filePool; FilePool filePool;
CCValueArray ccState; MidiState midiState;
Voice* findFreeVoice() noexcept; Voice* findFreeVoice() noexcept;
std::vector<CCNamePair> ccNames; std::vector<CCNamePair> ccNames;
absl::optional<uint8_t> defaultSwitch; absl::optional<uint8_t> defaultSwitch;

View file

@ -31,8 +31,8 @@
#include "absl/algorithm/container.h" #include "absl/algorithm/container.h"
#include <memory> #include <memory>
sfz::Voice::Voice(const CCValueArray& ccState) sfz::Voice::Voice(const MidiState& midiState)
: ccState(ccState) : midiState(midiState)
{ {
} }
@ -59,39 +59,39 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number,
auto volumedB { baseVolumedB }; auto volumedB { baseVolumedB };
if (region->volumeCC) if (region->volumeCC)
volumedB += normalizeCC(ccState[region->volumeCC->first]) * region->volumeCC->second; volumedB += normalizeCC(midiState.cc[region->volumeCC->first]) * region->volumeCC->second;
volumeEnvelope.reset(db2mag(volumedB)); volumeEnvelope.reset(db2mag(volumedB));
// DBG("Base volume: " << baseVolumedB << " dB - with modifier: " << volumedB << " dB"); // DBG("Base volume: " << baseVolumedB << " dB - with modifier: " << volumedB << " dB");
baseGain = region->getBaseGain(); baseGain = region->getBaseGain();
baseGain *= region->getCrossfadeGain(ccState); baseGain *= region->getCrossfadeGain(midiState.cc);
if (triggerType != TriggerType::CC) if (triggerType != TriggerType::CC)
baseGain *= region->getNoteGain(number, value); baseGain *= region->getNoteGain(number, value);
float gain { baseGain }; float gain { baseGain };
if (region->amplitudeCC) if (region->amplitudeCC)
gain *= normalizeCC(ccState[region->amplitudeCC->first]) * normalizePercents(region->amplitudeCC->second); gain *= normalizeCC(midiState.cc[region->amplitudeCC->first]) * normalizePercents(region->amplitudeCC->second);
amplitudeEnvelope.reset(gain); amplitudeEnvelope.reset(gain);
// DBG("Base gain: " << baseGain << " - with modifier: " << gain); // DBG("Base gain: " << baseGain << " - with modifier: " << gain);
basePan = normalizeNegativePercents(region->pan); basePan = normalizeNegativePercents(region->pan);
auto pan { basePan }; auto pan { basePan };
if (region->panCC) if (region->panCC)
pan += normalizeCC(ccState[region->panCC->first]) * normalizeNegativePercents(region->panCC->second); pan += normalizeCC(midiState.cc[region->panCC->first]) * normalizeNegativePercents(region->panCC->second);
panEnvelope.reset(pan); panEnvelope.reset(pan);
// DBG("Base pan: " << basePan << " - with modifier: " << pan); // DBG("Base pan: " << basePan << " - with modifier: " << pan);
basePosition = normalizeNegativePercents(region->position); basePosition = normalizeNegativePercents(region->position);
auto position { basePosition }; auto position { basePosition };
if (region->positionCC) if (region->positionCC)
position += normalizeCC(ccState[region->positionCC->first]) * normalizeNegativePercents(region->positionCC->second); position += normalizeCC(midiState.cc[region->positionCC->first]) * normalizeNegativePercents(region->positionCC->second);
positionEnvelope.reset(position); positionEnvelope.reset(position);
// DBG("Base position: " << basePosition << " - with modifier: " << position); // DBG("Base position: " << basePosition << " - with modifier: " << position);
baseWidth = normalizeNegativePercents(region->width); baseWidth = normalizeNegativePercents(region->width);
auto width { baseWidth }; auto width { baseWidth };
if (region->widthCC) if (region->widthCC)
width += normalizeCC(ccState[region->widthCC->first]) * normalizeNegativePercents(region->widthCC->second); width += normalizeCC(midiState.cc[region->widthCC->first]) * normalizeNegativePercents(region->widthCC->second);
widthEnvelope.reset(width); widthEnvelope.reset(width);
// DBG("Base width: " << baseWidth << " - with modifier: " << width); // DBG("Base width: " << baseWidth << " - with modifier: " << width);
@ -109,13 +109,13 @@ void sfz::Voice::prepareEGEnvelope(int delay, uint8_t velocity) noexcept
}; };
egEnvelope.reset( egEnvelope.reset(
secondsToSamples(region->amplitudeEG.getAttack(ccState, velocity)), secondsToSamples(region->amplitudeEG.getAttack(midiState.cc, velocity)),
secondsToSamples(region->amplitudeEG.getRelease(ccState, velocity)), secondsToSamples(region->amplitudeEG.getRelease(midiState.cc, velocity)),
normalizePercents(region->amplitudeEG.getSustain(ccState, velocity)), normalizePercents(region->amplitudeEG.getSustain(midiState.cc, velocity)),
delay + secondsToSamples(region->amplitudeEG.getDelay(ccState, velocity)), delay + secondsToSamples(region->amplitudeEG.getDelay(midiState.cc, velocity)),
secondsToSamples(region->amplitudeEG.getDecay(ccState, velocity)), secondsToSamples(region->amplitudeEG.getDecay(midiState.cc, velocity)),
secondsToSamples(region->amplitudeEG.getHold(ccState, velocity)), secondsToSamples(region->amplitudeEG.getHold(midiState.cc, velocity)),
normalizePercents(region->amplitudeEG.getStart(ccState, velocity))); normalizePercents(region->amplitudeEG.getStart(midiState.cc, velocity)));
} }
void sfz::Voice::setFileData(std::shared_ptr<AudioBuffer<float>> file, unsigned ticket) noexcept void sfz::Voice::setFileData(std::shared_ptr<AudioBuffer<float>> file, unsigned ticket) noexcept
@ -154,7 +154,7 @@ void sfz::Voice::registerNoteOff(int delay, int channel, int noteNumber, uint8_t
if (region->loopMode == SfzLoopMode::one_shot) if (region->loopMode == SfzLoopMode::one_shot)
return; return;
if (!region->checkSustain || ccState[config::sustainCC] < config::halfCCThreshold) if (!region->checkSustain || midiState.cc[config::sustainCC] < config::halfCCThreshold)
release(delay); release(delay);
} }
} }

View file

@ -28,6 +28,7 @@
#include "HistoricalBuffer.h" #include "HistoricalBuffer.h"
#include "Region.h" #include "Region.h"
#include "AudioBuffer.h" #include "AudioBuffer.h"
#include "MidiState.h"
#include "AudioSpan.h" #include "AudioSpan.h"
#include "LeakDetector.h" #include "LeakDetector.h"
#include <absl/types/span.h> #include <absl/types/span.h>
@ -38,7 +39,7 @@ namespace sfz {
class Voice { class Voice {
public: public:
Voice() = delete; Voice() = delete;
Voice(const CCValueArray& ccState); Voice(const MidiState& midiState);
enum class TriggerType { enum class TriggerType {
NoteOn, NoteOn,
NoteOff, NoteOff,
@ -124,7 +125,7 @@ private:
int samplesPerBlock { config::defaultSamplesPerBlock }; int samplesPerBlock { config::defaultSamplesPerBlock };
float sampleRate { config::defaultSampleRate }; float sampleRate { config::defaultSampleRate };
const CCValueArray& ccState; const MidiState& midiState;
ADSREnvelope<float> egEnvelope; ADSREnvelope<float> egEnvelope;
LinearEnvelope<float> volumeEnvelope; // dB events but the envelope output is linear gain LinearEnvelope<float> volumeEnvelope; // dB events but the envelope output is linear gain
LinearEnvelope<float> amplitudeEnvelope; // linear events LinearEnvelope<float> amplitudeEnvelope; // linear events

View file

@ -27,7 +27,9 @@ using namespace Catch::literals;
TEST_CASE("Region activation", "Region tests") TEST_CASE("Region activation", "Region tests")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
SECTION("Basic state") SECTION("Basic state")
{ {

View file

@ -32,7 +32,8 @@ using namespace Catch::literals;
TEST_CASE("[Region] Crossfade in on key") TEST_CASE("[Region] Crossfade in on key")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_lokey", "1" });
region.parseOpcode({ "xfin_hikey", "3" }); region.parseOpcode({ "xfin_hikey", "3" });
@ -43,7 +44,8 @@ TEST_CASE("[Region] Crossfade in on key")
TEST_CASE("[Region] Crossfade in on key - 2") TEST_CASE("[Region] Crossfade in on key - 2")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_lokey", "1" });
region.parseOpcode({ "xfin_hikey", "5" }); region.parseOpcode({ "xfin_hikey", "5" });
@ -57,7 +59,8 @@ TEST_CASE("[Region] Crossfade in on key - 2")
TEST_CASE("[Region] Crossfade in on key - gain") TEST_CASE("[Region] Crossfade in on key - gain")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_lokey", "1" });
region.parseOpcode({ "xfin_hikey", "5" }); region.parseOpcode({ "xfin_hikey", "5" });
@ -71,7 +74,8 @@ TEST_CASE("[Region] Crossfade in on key - gain")
TEST_CASE("[Region] Crossfade out on key") TEST_CASE("[Region] Crossfade out on key")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfout_lokey", "51" }); region.parseOpcode({ "xfout_lokey", "51" });
region.parseOpcode({ "xfout_hikey", "55" }); region.parseOpcode({ "xfout_hikey", "55" });
@ -86,7 +90,8 @@ TEST_CASE("[Region] Crossfade out on key")
TEST_CASE("[Region] Crossfade out on key - gain") TEST_CASE("[Region] Crossfade out on key - gain")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfout_lokey", "51" }); region.parseOpcode({ "xfout_lokey", "51" });
region.parseOpcode({ "xfout_hikey", "55" }); region.parseOpcode({ "xfout_hikey", "55" });
@ -102,7 +107,8 @@ TEST_CASE("[Region] Crossfade out on key - gain")
TEST_CASE("[Region] Crossfade in on velocity") TEST_CASE("[Region] Crossfade in on velocity")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfin_lovel", "20" }); region.parseOpcode({ "xfin_lovel", "20" });
region.parseOpcode({ "xfin_hivel", "24" }); region.parseOpcode({ "xfin_hivel", "24" });
@ -118,7 +124,8 @@ TEST_CASE("[Region] Crossfade in on velocity")
TEST_CASE("[Region] Crossfade in on vel - gain") TEST_CASE("[Region] Crossfade in on vel - gain")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfin_lovel", "20" }); region.parseOpcode({ "xfin_lovel", "20" });
region.parseOpcode({ "xfin_hivel", "24" }); region.parseOpcode({ "xfin_hivel", "24" });
@ -135,7 +142,8 @@ TEST_CASE("[Region] Crossfade in on vel - gain")
TEST_CASE("[Region] Crossfade out on vel") TEST_CASE("[Region] Crossfade out on vel")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfout_lovel", "51" }); region.parseOpcode({ "xfout_lovel", "51" });
region.parseOpcode({ "xfout_hivel", "55" }); region.parseOpcode({ "xfout_hivel", "55" });
@ -151,7 +159,8 @@ TEST_CASE("[Region] Crossfade out on vel")
TEST_CASE("[Region] Crossfade out on vel - gain") TEST_CASE("[Region] Crossfade out on vel - gain")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfout_lovel", "51" }); region.parseOpcode({ "xfout_lovel", "51" });
region.parseOpcode({ "xfout_hivel", "55" }); region.parseOpcode({ "xfout_hivel", "55" });
@ -168,76 +177,77 @@ TEST_CASE("[Region] Crossfade out on vel - gain")
TEST_CASE("[Region] Crossfade in on CC") TEST_CASE("[Region] Crossfade in on CC")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::CCValueArray ccState; sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfin_locc24", "20" }); region.parseOpcode({ "xfin_locc24", "20" });
region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "xfin_hicc24", "24" });
region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "amp_veltrack", "0" });
ccState[24] = 19; REQUIRE( region.getCrossfadeGain(ccState) == 0.0_a ); midiState.cc[24] = 19; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
ccState[24] = 20; REQUIRE( region.getCrossfadeGain(ccState) == 0.0_a ); midiState.cc[24] = 20; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
ccState[24] = 21; REQUIRE( region.getCrossfadeGain(ccState) == 0.5_a ); midiState.cc[24] = 21; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.5_a );
ccState[24] = 22; REQUIRE( region.getCrossfadeGain(ccState) == 0.70711_a ); midiState.cc[24] = 22; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.70711_a );
ccState[24] = 23; REQUIRE( region.getCrossfadeGain(ccState) == 0.86603_a ); midiState.cc[24] = 23; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.86603_a );
ccState[24] = 24; REQUIRE( region.getCrossfadeGain(ccState) == 1.0_a ); midiState.cc[24] = 24; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
ccState[24] = 25; REQUIRE( region.getCrossfadeGain(ccState) == 1.0_a ); midiState.cc[24] = 25; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
} }
TEST_CASE("[Region] Crossfade in on CC - gain") TEST_CASE("[Region] Crossfade in on CC - gain")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::CCValueArray ccState; sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfin_locc24", "20" }); region.parseOpcode({ "xfin_locc24", "20" });
region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "xfin_hicc24", "24" });
region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "amp_veltrack", "0" });
region.parseOpcode({ "xf_cccurve", "gain" }); region.parseOpcode({ "xf_cccurve", "gain" });
ccState[24] = 19; REQUIRE( region.getCrossfadeGain(ccState) == 0.0_a ); midiState.cc[24] = 19; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
ccState[24] = 20; REQUIRE( region.getCrossfadeGain(ccState) == 0.0_a ); midiState.cc[24] = 20; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
ccState[24] = 21; REQUIRE( region.getCrossfadeGain(ccState) == 0.25_a ); midiState.cc[24] = 21; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.25_a );
ccState[24] = 22; REQUIRE( region.getCrossfadeGain(ccState) == 0.5_a ); midiState.cc[24] = 22; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.5_a );
ccState[24] = 23; REQUIRE( region.getCrossfadeGain(ccState) == 0.75_a ); midiState.cc[24] = 23; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.75_a );
ccState[24] = 24; REQUIRE( region.getCrossfadeGain(ccState) == 1.0_a ); midiState.cc[24] = 24; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
ccState[24] = 25; REQUIRE( region.getCrossfadeGain(ccState) == 1.0_a ); midiState.cc[24] = 25; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
} }
TEST_CASE("[Region] Crossfade out on CC") TEST_CASE("[Region] Crossfade out on CC")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::CCValueArray ccState; sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfout_locc24", "20" }); region.parseOpcode({ "xfout_locc24", "20" });
region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "xfout_hicc24", "24" });
region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "amp_veltrack", "0" });
ccState[24] = 19; REQUIRE( region.getCrossfadeGain(ccState) == 1.0_a ); midiState.cc[24] = 19; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
ccState[24] = 20; REQUIRE( region.getCrossfadeGain(ccState) == 1.0_a ); midiState.cc[24] = 20; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
ccState[24] = 21; REQUIRE( region.getCrossfadeGain(ccState) == 0.86603_a ); midiState.cc[24] = 21; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.86603_a );
ccState[24] = 22; REQUIRE( region.getCrossfadeGain(ccState) == 0.70711_a ); midiState.cc[24] = 22; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.70711_a );
ccState[24] = 23; REQUIRE( region.getCrossfadeGain(ccState) == 0.5_a ); midiState.cc[24] = 23; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.5_a );
ccState[24] = 24; REQUIRE( region.getCrossfadeGain(ccState) == 0.0_a ); midiState.cc[24] = 24; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
ccState[24] = 25; REQUIRE( region.getCrossfadeGain(ccState) == 0.0_a ); midiState.cc[24] = 25; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
} }
TEST_CASE("[Region] Crossfade out on CC - gain") TEST_CASE("[Region] Crossfade out on CC - gain")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::CCValueArray ccState; sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "xfout_locc24", "20" }); region.parseOpcode({ "xfout_locc24", "20" });
region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "xfout_hicc24", "24" });
region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "amp_veltrack", "0" });
region.parseOpcode({ "xf_cccurve", "gain" }); region.parseOpcode({ "xf_cccurve", "gain" });
ccState[24] = 19; REQUIRE( region.getCrossfadeGain(ccState) == 1.0_a ); midiState.cc[24] = 19; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
ccState[24] = 20; REQUIRE( region.getCrossfadeGain(ccState) == 1.0_a ); midiState.cc[24] = 20; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
ccState[24] = 21; REQUIRE( region.getCrossfadeGain(ccState) == 0.75_a ); midiState.cc[24] = 21; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.75_a );
ccState[24] = 22; REQUIRE( region.getCrossfadeGain(ccState) == 0.5_a ); midiState.cc[24] = 22; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.5_a );
ccState[24] = 23; REQUIRE( region.getCrossfadeGain(ccState) == 0.25_a ); midiState.cc[24] = 23; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.25_a );
ccState[24] = 24; REQUIRE( region.getCrossfadeGain(ccState) == 0.0_a ); midiState.cc[24] = 24; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
ccState[24] = 25; REQUIRE( region.getCrossfadeGain(ccState) == 0.0_a ); midiState.cc[24] = 25; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
} }
TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "amp_veltrack", "0" });
REQUIRE( region.getNoteGain(64, 127) == 1.0_a ); REQUIRE( region.getNoteGain(64, 127) == 1.0_a );
@ -247,7 +257,8 @@ TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0")
TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack") TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "amp_veltrack", "100" }); region.parseOpcode({ "amp_veltrack", "100" });
REQUIRE( region.getNoteGain(64, 127) == 1.0_a ); REQUIRE( region.getNoteGain(64, 127) == 1.0_a );
@ -256,7 +267,8 @@ TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack")
TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack") TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "amp_veltrack", "-100" }); region.parseOpcode({ "amp_veltrack", "-100" });
REQUIRE( region.getNoteGain(64, 127) == Approx(0.0).margin(0.0001) ); REQUIRE( region.getNoteGain(64, 127) == Approx(0.0).margin(0.0001) );
@ -265,19 +277,20 @@ TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack")
TEST_CASE("[Region] rt_decay") TEST_CASE("[Region] rt_decay")
{ {
sfz::Region region {}; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "trigger", "release" }); region.parseOpcode({ "trigger", "release" });
region.parseOpcode({ "rt_decay", "10" }); region.parseOpcode({ "rt_decay", "10" });
sfz::noteOn(64, 64); midiState.noteOn(64, 64);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) ); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) );
region.parseOpcode({ "rt_decay", "20" }); region.parseOpcode({ "rt_decay", "20" });
sfz::noteOn(64, 64); midiState.noteOn(64, 64);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) ); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) );
region.parseOpcode({ "trigger", "attack" }); region.parseOpcode({ "trigger", "attack" });
sfz::noteOn(64, 64); midiState.noteOn(64, 64);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume).margin(0.1) ); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume).margin(0.1) );
} }

View file

@ -21,13 +21,16 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "MidiState.h"
#include "Region.h" #include "Region.h"
#include "catch2/catch.hpp" #include "catch2/catch.hpp"
using namespace Catch::literals; using namespace Catch::literals;
TEST_CASE("[Region] Parsing opcodes") TEST_CASE("[Region] Parsing opcodes")
{ {
sfz::Region region; sfz::MidiState midiState;
sfz::Region region { midiState };
SECTION("sample") SECTION("sample")
{ {
REQUIRE(region.sample == ""); REQUIRE(region.sample == "");
@ -168,10 +171,10 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(!region.offBy); REQUIRE(!region.offBy);
region.parseOpcode({ "off_by", "5" }); region.parseOpcode({ "off_by", "5" });
REQUIRE(region.offBy); REQUIRE(region.offBy);
REQUIRE(region.offBy == 5); REQUIRE(*region.offBy == 5);
region.parseOpcode({ "off_by", "-1" }); region.parseOpcode({ "off_by", "-1" });
REQUIRE(region.offBy); REQUIRE(region.offBy);
REQUIRE(region.offBy == 0); REQUIRE(*region.offBy == 0);
} }
SECTION("off_mode") SECTION("off_mode")
@ -1036,7 +1039,8 @@ TEST_CASE("[Region] Parsing opcodes")
// Specific region bugs // Specific region bugs
TEST_CASE("[Region] Non-conforming floating point values in integer opcodes") TEST_CASE("[Region] Non-conforming floating point values in integer opcodes")
{ {
sfz::Region region; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "offset", "2014.5" }); region.parseOpcode({ "offset", "2014.5" });
REQUIRE(region.offset == 2014); REQUIRE(region.offset == 2014);
region.parseOpcode({ "pitch_keytrack", "-2.1" }); region.parseOpcode({ "pitch_keytrack", "-2.1" });

View file

@ -27,7 +27,9 @@ using namespace Catch::literals;
TEST_CASE("Basic triggers", "Region triggers") TEST_CASE("Basic triggers", "Region triggers")
{ {
sfz::Region region; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
SECTION("key") SECTION("key")
{ {
@ -136,7 +138,8 @@ TEST_CASE("Basic triggers", "Region triggers")
TEST_CASE("Legato triggers", "Region triggers") TEST_CASE("Legato triggers", "Region triggers")
{ {
sfz::Region region; sfz::MidiState midiState;
sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
SECTION("First note playing") SECTION("First note playing")
{ {