commit
35837c501f
12 changed files with 231 additions and 53 deletions
|
|
@ -35,7 +35,7 @@ public:
|
|||
}
|
||||
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region{midiState};
|
||||
sfz::Region region{0, midiState};
|
||||
sfz::ADSREnvelope<float> envelope;
|
||||
std::vector<float> output;
|
||||
};
|
||||
|
|
|
|||
41
src/sfizz/NumericId.h
Normal file
41
src/sfizz/NumericId.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// 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
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @brief Numeric identifier
|
||||
*
|
||||
* It is a generic numeric identifier. The template wrapper serves to enforce a
|
||||
* stronger compile-time check, such that one kind of identifier can't be
|
||||
* mistaken for another kind, or for an unrelated integer such as an index.
|
||||
*/
|
||||
template <class T>
|
||||
struct NumericId {
|
||||
constexpr NumericId() = default;
|
||||
|
||||
explicit constexpr NumericId(int number)
|
||||
: number(number)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr bool valid() const noexcept
|
||||
{
|
||||
return number != -1;
|
||||
}
|
||||
|
||||
constexpr bool operator==(NumericId other) const noexcept
|
||||
{
|
||||
return number == other.number;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(NumericId other) const noexcept
|
||||
{
|
||||
return number != other.number;
|
||||
}
|
||||
|
||||
const int number = -1;
|
||||
};
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
#include "AudioBuffer.h"
|
||||
#include "MidiState.h"
|
||||
#include "FileId.h"
|
||||
#include "NumericId.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include <bitset>
|
||||
#include <string>
|
||||
|
|
@ -35,8 +36,8 @@ namespace sfz {
|
|||
*
|
||||
*/
|
||||
struct Region {
|
||||
Region(const MidiState& midiState, absl::string_view defaultPath = "")
|
||||
: midiState(midiState), defaultPath(std::move(defaultPath))
|
||||
Region(int regionNumber, const MidiState& midiState, absl::string_view defaultPath = "")
|
||||
: id{regionNumber}, midiState(midiState), defaultPath(std::move(defaultPath))
|
||||
{
|
||||
ccSwitched.set();
|
||||
|
||||
|
|
@ -46,6 +47,14 @@ struct Region {
|
|||
Region(const Region&) = default;
|
||||
~Region() = default;
|
||||
|
||||
/**
|
||||
* @brief Get the number which identifies this region
|
||||
*/
|
||||
NumericId<Region> getId() const noexcept
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers on release?
|
||||
*
|
||||
|
|
@ -238,6 +247,8 @@ struct Region {
|
|||
*/
|
||||
float getGainToEffectBus(unsigned number) const noexcept;
|
||||
|
||||
const NumericId<Region> id;
|
||||
|
||||
// Sound source: sample playback
|
||||
FileId sampleId {}; // Sample
|
||||
int sampleQuality { Default::sampleQuality };
|
||||
|
|
|
|||
|
|
@ -44,6 +44,13 @@ sfz::Synth::~Synth()
|
|||
resources.filePool.emptyFileLoadingQueues();
|
||||
}
|
||||
|
||||
void sfz::Synth::onVoiceStateChanged(NumericId<Voice> id, Voice::State state)
|
||||
{
|
||||
(void)id;
|
||||
(void)state;
|
||||
DBG("Voice " << id.number << ": state " << static_cast<int>(state));
|
||||
}
|
||||
|
||||
void sfz::Synth::onParseFullBlock(const std::string& header, const std::vector<Opcode>& members)
|
||||
{
|
||||
switch (hash(header)) {
|
||||
|
|
@ -95,7 +102,8 @@ void sfz::Synth::onParseWarning(const SourceRange& range, const std::string& mes
|
|||
|
||||
void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
|
||||
{
|
||||
auto lastRegion = absl::make_unique<Region>(resources.midiState, defaultPath);
|
||||
int regionNumber = static_cast<int>(regions.size());
|
||||
auto lastRegion = absl::make_unique<Region>(regionNumber, resources.midiState, defaultPath);
|
||||
|
||||
auto parseOpcodes = [&](const std::vector<Opcode>& opcodes) {
|
||||
for (auto& opcode : opcodes) {
|
||||
|
|
@ -353,22 +361,20 @@ void sfz::Synth::finalizeSfzLoad()
|
|||
{
|
||||
resources.filePool.setRootDirectory(parser.originalDirectory());
|
||||
|
||||
auto currentRegion = regions.begin();
|
||||
auto lastRegion = regions.rbegin();
|
||||
auto removeCurrentRegion = [¤tRegion, &lastRegion]() {
|
||||
if (currentRegion->get() == nullptr)
|
||||
return;
|
||||
size_t currentRegionIndex = 0;
|
||||
size_t currentRegionCount = regions.size();
|
||||
|
||||
DBG("Removing the region with sample " << currentRegion->get()->sampleId);
|
||||
std::iter_swap(currentRegion, lastRegion);
|
||||
++lastRegion;
|
||||
auto removeCurrentRegion = [this, ¤tRegionIndex, ¤tRegionCount]() {
|
||||
DBG("Removing the region with sample " << regions[currentRegionIndex]->sampleId);
|
||||
regions.erase(regions.begin() + currentRegionIndex);
|
||||
--currentRegionCount;
|
||||
};
|
||||
|
||||
size_t maxFilters { 0 };
|
||||
size_t maxEQs { 0 };
|
||||
|
||||
while (currentRegion < lastRegion.base()) {
|
||||
auto region = currentRegion->get();
|
||||
while (currentRegionIndex < currentRegionCount) {
|
||||
auto region = regions[currentRegionIndex].get();
|
||||
|
||||
if (!region->oscillator && !region->isGenerator()) {
|
||||
if (!resources.filePool.checkSampleId(region->sampleId)) {
|
||||
|
|
@ -473,11 +479,10 @@ void sfz::Synth::finalizeSfzLoad()
|
|||
maxFilters = max(maxFilters, region->filters.size());
|
||||
maxEQs = max(maxEQs, region->equalizers.size());
|
||||
|
||||
++currentRegion;
|
||||
++currentRegionIndex;
|
||||
}
|
||||
const auto remainingRegions = std::distance(regions.begin(), lastRegion.base());
|
||||
DBG("Removing " << (regions.size() - remainingRegions) << " out of " << regions.size() << " regions");
|
||||
regions.resize(remainingRegions);
|
||||
DBG("Removing " << (regions.size() - currentRegionCount) << " out of " << regions.size() << " regions");
|
||||
regions.resize(currentRegionCount);
|
||||
modificationTime = checkModificationTime();
|
||||
|
||||
for (auto& voice : voices) {
|
||||
|
|
@ -1059,6 +1064,40 @@ const sfz::EffectBus* sfz::Synth::getEffectBusView(int idx) const noexcept
|
|||
return (size_t)idx < effectBuses.size() ? effectBuses[idx].get() : nullptr;
|
||||
}
|
||||
|
||||
const sfz::Region* sfz::Synth::getRegionById(NumericId<Region> id) const noexcept
|
||||
{
|
||||
const size_t size = regions.size();
|
||||
|
||||
if (size == 0 || !id.valid())
|
||||
return nullptr;
|
||||
|
||||
// search a sequence of ordered identifiers with potential gaps
|
||||
size_t index = static_cast<size_t>(id.number);
|
||||
index = std::min(index, size - 1);
|
||||
|
||||
while (index > 0 && regions[index]->getId().number > id.number)
|
||||
--index;
|
||||
|
||||
return (regions[index]->getId() == id) ? regions[index].get() : nullptr;
|
||||
}
|
||||
|
||||
const sfz::Voice* sfz::Synth::getVoiceById(NumericId<Voice> id) const noexcept
|
||||
{
|
||||
const size_t size = voices.size();
|
||||
|
||||
if (size == 0 || !id.valid())
|
||||
return nullptr;
|
||||
|
||||
// search a sequence of ordered identifiers with potential gaps
|
||||
size_t index = static_cast<size_t>(id.number);
|
||||
index = std::min(index, size - 1);
|
||||
|
||||
while (index > 0 && voices[index]->getId().number > id.number)
|
||||
--index;
|
||||
|
||||
return (voices[index]->getId() == id) ? voices[index].get() : nullptr;
|
||||
}
|
||||
|
||||
const sfz::Voice* sfz::Synth::getVoiceView(int idx) const noexcept
|
||||
{
|
||||
return (size_t)idx < voices.size() ? voices[idx].get() : nullptr;
|
||||
|
|
@ -1104,8 +1143,11 @@ void sfz::Synth::resetVoices(int numVoices)
|
|||
voices.clear();
|
||||
voices.reserve(numVoices);
|
||||
|
||||
for (int i = 0; i < numVoices; ++i)
|
||||
voices.push_back(absl::make_unique<Voice>(resources));
|
||||
for (int i = 0; i < numVoices; ++i) {
|
||||
auto voice = absl::make_unique<Voice>(i, resources);
|
||||
voice->setStateListener(this);
|
||||
voices.emplace_back(std::move(voice));
|
||||
}
|
||||
|
||||
voiceViewArray.clear();
|
||||
voiceViewArray.reserve(numVoices);
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ namespace sfz {
|
|||
* The jack_client.cpp file contains examples of the most classical usage of the
|
||||
* synth and can be used as a reference.
|
||||
*/
|
||||
class Synth final : public Parser::Listener {
|
||||
class Synth final : public Voice::StateListener, public Parser::Listener {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new Synth object with no voices. If you want sound
|
||||
|
|
@ -184,6 +184,20 @@ public:
|
|||
* @brief Export a MIDI Name document describing the loaded instrument
|
||||
*/
|
||||
std::string exportMidnam(absl::string_view model = {}) const;
|
||||
/**
|
||||
* @brief Find the region which is associated with the given identifier.
|
||||
*
|
||||
* @param id
|
||||
* @return const Region*
|
||||
*/
|
||||
const Region* getRegionById(NumericId<Region> id) const noexcept;
|
||||
/**
|
||||
* @brief Find the voice which is associated with the given identifier.
|
||||
*
|
||||
* @param id
|
||||
* @return const Voice*
|
||||
*/
|
||||
const Voice* getVoiceById(NumericId<Voice> id) const noexcept;
|
||||
/**
|
||||
* @brief Get a raw view into a specific region. This is mostly used
|
||||
* for testing.
|
||||
|
|
@ -499,6 +513,12 @@ public:
|
|||
*/
|
||||
const std::vector<CCNamePair>& getCCLabels() const noexcept { return ccLabels; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief The voice callback which is called during a change of state.
|
||||
*/
|
||||
void onVoiceStateChanged(NumericId<Voice> idNumber, Voice::State state) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief The parser callback; this is called by the parent object each time
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
#include "Interpolators.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
|
||||
sfz::Voice::Voice(sfz::Resources& resources)
|
||||
: resources(resources)
|
||||
sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources)
|
||||
: id{voiceNumber}, stateListener(nullptr), resources(resources)
|
||||
{
|
||||
filters.reserve(config::filtersPerVoice);
|
||||
equalizers.reserve(config::eqsPerVoice);
|
||||
|
|
@ -36,7 +36,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
|
|||
triggerValue = value;
|
||||
|
||||
this->region = region;
|
||||
state = State::playing;
|
||||
switchState(State::playing);
|
||||
|
||||
ASSERT(delay >= 0);
|
||||
if (delay < 0)
|
||||
|
|
@ -652,7 +652,7 @@ bool sfz::Voice::checkOffGroup(int delay, uint32_t group) noexcept
|
|||
|
||||
void sfz::Voice::reset() noexcept
|
||||
{
|
||||
state = State::idle;
|
||||
switchState(State::idle);
|
||||
region = nullptr;
|
||||
currentPromise.reset();
|
||||
sourcePosition = 0;
|
||||
|
|
@ -771,3 +771,13 @@ void sfz::Voice::updateChannelPowers(AudioSpan<float> buffer)
|
|||
channelEnvelopeFilters[i].tickLowpass(std::abs(input[s]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void sfz::Voice::switchState(State s)
|
||||
{
|
||||
if (s != state) {
|
||||
state = s;
|
||||
if (stateListener)
|
||||
stateListener->onVoiceStateChanged(id, s);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "AudioSpan.h"
|
||||
#include "LeakDetector.h"
|
||||
#include "OnePoleFilter.h"
|
||||
#include "NumericId.h"
|
||||
#include "absl/types/span.h"
|
||||
#include <memory>
|
||||
#include <random>
|
||||
|
|
@ -32,14 +33,39 @@ public:
|
|||
/**
|
||||
* @brief Construct a new voice with the midistate singleton
|
||||
*
|
||||
* @param voiceNumber
|
||||
* @param midiState
|
||||
*/
|
||||
Voice(Resources& resources);
|
||||
Voice(int voiceNumber, Resources& resources);
|
||||
enum class TriggerType {
|
||||
NoteOn,
|
||||
NoteOff,
|
||||
CC
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get the unique identifier of this voice in a synth
|
||||
*/
|
||||
NumericId<Voice> getId() const noexcept
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
enum class State {
|
||||
idle,
|
||||
playing
|
||||
};
|
||||
|
||||
class StateListener {
|
||||
public:
|
||||
virtual void onVoiceStateChanged(NumericId<Voice> /*id*/, State /*state*/) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Sets the listener which is called when the voice state changes.
|
||||
*/
|
||||
void setStateListener(StateListener *l) noexcept { stateListener = l; }
|
||||
|
||||
/**
|
||||
* @brief Change the sample rate of the voice. This is used to compute all
|
||||
* pitch related transformations so it needs to be propagated from the synth
|
||||
|
|
@ -270,12 +296,16 @@ private:
|
|||
void setupOscillatorUnison();
|
||||
void updateChannelPowers(AudioSpan<float> buffer);
|
||||
|
||||
/**
|
||||
* @brief Modify the voice state and notify any listeners.
|
||||
*/
|
||||
void switchState(State s);
|
||||
|
||||
const NumericId<Voice> id;
|
||||
StateListener* stateListener = nullptr;
|
||||
|
||||
Region* region { nullptr };
|
||||
|
||||
enum class State {
|
||||
idle,
|
||||
playing
|
||||
};
|
||||
State state { State::idle };
|
||||
bool noteIsOff { false };
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using namespace sfz::literals;
|
|||
TEST_CASE("Region activation", "Region tests")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
SECTION("Basic state")
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ using namespace sfz::literals;
|
|||
TEST_CASE("[Region] Parsing opcodes")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
|
||||
SECTION("sample")
|
||||
{
|
||||
|
|
@ -1672,7 +1672,7 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
TEST_CASE("[Region] Non-conforming floating point values in integer opcodes")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "offset", "2014.5" });
|
||||
REQUIRE(region.offset == 2014);
|
||||
region.parseOpcode({ "pitch_keytrack", "-2.1" });
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using namespace sfz::literals;
|
|||
TEST_CASE("Basic triggers", "Region triggers")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
SECTION("key")
|
||||
|
|
@ -130,7 +130,7 @@ TEST_CASE("Basic triggers", "Region triggers")
|
|||
TEST_CASE("Legato triggers", "Region triggers")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
SECTION("First note playing")
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ constexpr int numRandomTests { 64 };
|
|||
TEST_CASE("[Region] Crossfade in on key")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lokey", "1" });
|
||||
region.parseOpcode({ "xfin_hikey", "3" });
|
||||
|
|
@ -29,7 +29,7 @@ TEST_CASE("[Region] Crossfade in on key")
|
|||
TEST_CASE("[Region] Crossfade in on key - 2")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lokey", "1" });
|
||||
region.parseOpcode({ "xfin_hikey", "5" });
|
||||
|
|
@ -44,7 +44,7 @@ TEST_CASE("[Region] Crossfade in on key - 2")
|
|||
TEST_CASE("[Region] Crossfade in on key - gain")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lokey", "1" });
|
||||
region.parseOpcode({ "xfin_hikey", "5" });
|
||||
|
|
@ -59,7 +59,7 @@ TEST_CASE("[Region] Crossfade in on key - gain")
|
|||
TEST_CASE("[Region] Crossfade out on key")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_lokey", "51" });
|
||||
region.parseOpcode({ "xfout_hikey", "55" });
|
||||
|
|
@ -75,7 +75,7 @@ TEST_CASE("[Region] Crossfade out on key")
|
|||
TEST_CASE("[Region] Crossfade out on key - gain")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_lokey", "51" });
|
||||
region.parseOpcode({ "xfout_hikey", "55" });
|
||||
|
|
@ -92,7 +92,7 @@ TEST_CASE("[Region] Crossfade out on key - gain")
|
|||
TEST_CASE("[Region] Crossfade in on velocity")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lovel", "20" });
|
||||
region.parseOpcode({ "xfin_hivel", "24" });
|
||||
|
|
@ -109,7 +109,7 @@ TEST_CASE("[Region] Crossfade in on velocity")
|
|||
TEST_CASE("[Region] Crossfade in on vel - gain")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_lovel", "20" });
|
||||
region.parseOpcode({ "xfin_hivel", "24" });
|
||||
|
|
@ -127,7 +127,7 @@ TEST_CASE("[Region] Crossfade in on vel - gain")
|
|||
TEST_CASE("[Region] Crossfade out on vel")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_lovel", "51" });
|
||||
region.parseOpcode({ "xfout_hivel", "55" });
|
||||
|
|
@ -144,7 +144,7 @@ TEST_CASE("[Region] Crossfade out on vel")
|
|||
TEST_CASE("[Region] Crossfade out on vel - gain")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_lovel", "51" });
|
||||
region.parseOpcode({ "xfout_hivel", "55" });
|
||||
|
|
@ -162,7 +162,7 @@ TEST_CASE("[Region] Crossfade out on vel - gain")
|
|||
TEST_CASE("[Region] Crossfade in on CC")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_locc24", "20" });
|
||||
region.parseOpcode({ "xfin_hicc24", "24" });
|
||||
|
|
@ -186,7 +186,7 @@ TEST_CASE("[Region] Crossfade in on CC")
|
|||
TEST_CASE("[Region] Crossfade in on CC - gain")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfin_locc24", "20" });
|
||||
region.parseOpcode({ "xfin_hicc24", "24" });
|
||||
|
|
@ -210,7 +210,7 @@ TEST_CASE("[Region] Crossfade in on CC - gain")
|
|||
TEST_CASE("[Region] Crossfade out on CC")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_locc24", "20" });
|
||||
region.parseOpcode({ "xfout_hicc24", "24" });
|
||||
|
|
@ -234,7 +234,7 @@ TEST_CASE("[Region] Crossfade out on CC")
|
|||
TEST_CASE("[Region] Crossfade out on CC - gain")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "xfout_locc24", "20" });
|
||||
region.parseOpcode({ "xfout_hicc24", "24" });
|
||||
|
|
@ -259,7 +259,7 @@ TEST_CASE("[Region] Crossfade out on CC - gain")
|
|||
TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "amp_veltrack", "0" });
|
||||
REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a);
|
||||
|
|
@ -270,7 +270,7 @@ TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0")
|
|||
TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "amp_veltrack", "100" });
|
||||
REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a);
|
||||
|
|
@ -280,7 +280,7 @@ TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack")
|
|||
TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "amp_veltrack", "-100" });
|
||||
REQUIRE(region.getNoteGain(64, 127_norm) == Approx(0.0).margin(0.0001));
|
||||
|
|
@ -291,7 +291,7 @@ TEST_CASE("[Region] rt_decay")
|
|||
{
|
||||
sfz::MidiState midiState;
|
||||
midiState.setSampleRate(1000);
|
||||
sfz::Region region { midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "trigger", "release" });
|
||||
region.parseOpcode({ "rt_decay", "10" });
|
||||
|
|
@ -311,7 +311,7 @@ TEST_CASE("[Region] rt_decay")
|
|||
TEST_CASE("[Region] Base delay")
|
||||
{
|
||||
sfz::MidiState midiState;
|
||||
sfz::Region region{ midiState };
|
||||
sfz::Region region { 0, midiState };
|
||||
region.parseOpcode({ "sample", "*sine" });
|
||||
region.parseOpcode({ "delay", "10" });
|
||||
REQUIRE( region.getDelay() == 10.0f );
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "sfizz/Synth.h"
|
||||
#include "sfizz/SfzHelpers.h"
|
||||
#include "sfizz/NumericId.h"
|
||||
#include "catch2/catch.hpp"
|
||||
using namespace Catch::literals;
|
||||
using namespace sfz::literals;
|
||||
|
|
@ -462,3 +463,26 @@ TEST_CASE("[Synth] velcurve")
|
|||
REQUIRE( synth.getRegionView(1)->velocityCurve(96_norm) == Approx(0.5f).margin(1e-2) );
|
||||
REQUIRE( synth.getRegionView(1)->velocityCurve(127_norm) == 0.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Region by identifier")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString("regionByIdentifier.sfz", R"(
|
||||
<region>sample=*sine
|
||||
<region>sample=*sine
|
||||
<region>sample=doesNotExist.wav
|
||||
<region>sample=*sine
|
||||
<region>sample=doesNotExist.wav
|
||||
<region>sample=*sine
|
||||
)");
|
||||
|
||||
REQUIRE(synth.getNumRegions() == 4);
|
||||
REQUIRE(synth.getRegionView(0) == synth.getRegionById(NumericId<sfz::Region>{0}));
|
||||
REQUIRE(synth.getRegionView(1) == synth.getRegionById(NumericId<sfz::Region>{1}));
|
||||
REQUIRE(nullptr == synth.getRegionById(NumericId<sfz::Region>{2}));
|
||||
REQUIRE(synth.getRegionView(2) == synth.getRegionById(NumericId<sfz::Region>{3}));
|
||||
REQUIRE(nullptr == synth.getRegionById(NumericId<sfz::Region>{4}));
|
||||
REQUIRE(synth.getRegionView(3) == synth.getRegionById(NumericId<sfz::Region>{5}));
|
||||
REQUIRE(nullptr == synth.getRegionById(NumericId<sfz::Region>{6}));
|
||||
REQUIRE(nullptr == synth.getRegionById(NumericId<sfz::Region>{}));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue