Support the extended CCs of SFZv2 except poly aftertouch
Poly aftertouch can be added in the future in a similar way
This commit is contained in:
parent
40d18f0e23
commit
b3a73814d5
13 changed files with 196 additions and 15 deletions
|
|
@ -24,6 +24,7 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex
|
||||||
lastNotePlayed = noteNumber;
|
lastNotePlayed = noteNumber;
|
||||||
activeNotes++;
|
activeNotes++;
|
||||||
noteStates[noteNumber] = true;
|
noteStates[noteNumber] = true;
|
||||||
|
alternate = alternate == 0.0f ? 1.0f : 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -154,7 +155,6 @@ float sfz::MidiState::getChannelAftertouch() const noexcept
|
||||||
|
|
||||||
void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept
|
void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept
|
||||||
{
|
{
|
||||||
ASSERT(ccValue >= 0.0 && ccValue <= 1.0);
|
|
||||||
insertEventInVector(cc[ccNumber], delay, ccValue);
|
insertEventInVector(cc[ccNumber], delay, ccValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,13 @@ public:
|
||||||
const EventVector& getPitchEvents() const noexcept;
|
const EventVector& getPitchEvents() const noexcept;
|
||||||
const EventVector& getChannelAftertouchEvents() const noexcept;
|
const EventVector& getChannelAftertouchEvents() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the alternate state value, for extended CC 137
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
float getAlternateState() const noexcept { return alternate; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -243,6 +250,7 @@ private:
|
||||||
|
|
||||||
float sampleRate { config::defaultSampleRate };
|
float sampleRate { config::defaultSampleRate };
|
||||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||||
|
float alternate { 0.0f };
|
||||||
unsigned internalClock { 0 };
|
unsigned internalClock { 0 };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& l
|
||||||
{
|
{
|
||||||
ASSERT(events.size() > 0);
|
ASSERT(events.size() > 0);
|
||||||
ASSERT(events[0].delay == 0);
|
ASSERT(events[0].delay == 0);
|
||||||
|
|
||||||
if (envelope.size() == 0)
|
if (envelope.size() == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -103,7 +104,11 @@ void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& l
|
||||||
{
|
{
|
||||||
ASSERT(events.size() > 0);
|
ASSERT(events.size() > 0);
|
||||||
ASSERT(events[0].delay == 0);
|
ASSERT(events[0].delay == 0);
|
||||||
ASSERT(step != 0.0);
|
|
||||||
|
if (step == 0.0f) {
|
||||||
|
linearEnvelope(events, envelope, std::forward<F>(lambda));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (envelope.size() == 0)
|
if (envelope.size() == 0)
|
||||||
return;
|
return;
|
||||||
|
|
@ -155,6 +160,7 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelop
|
||||||
|
|
||||||
if (envelope.size() == 0)
|
if (envelope.size() == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||||
|
|
||||||
auto lastValue = lambda(events[0].value);
|
auto lastValue = lambda(events[0].value);
|
||||||
|
|
|
||||||
|
|
@ -1526,7 +1526,21 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec<float> spec,
|
||||||
assert(false);
|
assert(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
conn->source = ModKey(ModId::Controller, {}, p);
|
|
||||||
|
switch (p.cc) {
|
||||||
|
case ExtendedCCs::noteOnVelocity: // fallthrough
|
||||||
|
case ExtendedCCs::noteOffVelocity: // fallthrough
|
||||||
|
case ExtendedCCs::keyboardNoteNumber: // fallthrough
|
||||||
|
case ExtendedCCs::keyboardNoteGate: // fallthrough
|
||||||
|
case ExtendedCCs::unipolarRandom: // fallthrough
|
||||||
|
case ExtendedCCs::bipolarRandom: // fallthrough
|
||||||
|
case ExtendedCCs::alternate:
|
||||||
|
conn->source = ModKey(ModId::PerVoiceController, id, p);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
conn->source = ModKey(ModId::Controller, {}, p);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ Synth::Impl::Impl()
|
||||||
resetVoices(config::numVoices);
|
resetVoices(config::numVoices);
|
||||||
|
|
||||||
// modulation sources
|
// modulation sources
|
||||||
genController_.reset(new ControllerSource(resources_));
|
genController_.reset(new ControllerSource(resources_, voiceManager_));
|
||||||
genLFO_.reset(new LFOSource(voiceManager_));
|
genLFO_.reset(new LFOSource(voiceManager_));
|
||||||
genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_));
|
genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_));
|
||||||
genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, resources_.midiState));
|
genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, resources_.midiState));
|
||||||
|
|
@ -1314,6 +1314,8 @@ void Synth::pitchWheel(int delay, int pitch) noexcept
|
||||||
for (auto& voice : impl.voiceManager_) {
|
for (auto& voice : impl.voiceManager_) {
|
||||||
voice.registerPitchWheel(delay, normalizedPitch);
|
voice.registerPitchWheel(delay, normalizedPitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl.performHdcc(delay, ExtendedCCs::pitchBend, normalizedPitch, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::aftertouch(int delay, uint8_t aftertouch) noexcept
|
void Synth::aftertouch(int delay, uint8_t aftertouch) noexcept
|
||||||
|
|
@ -1704,6 +1706,7 @@ void Synth::Impl::setupModMatrix()
|
||||||
|
|
||||||
switch (sourceKey.id()) {
|
switch (sourceKey.id()) {
|
||||||
case ModId::Controller:
|
case ModId::Controller:
|
||||||
|
case ModId::PerVoiceController:
|
||||||
gen = genController_.get();
|
gen = genController_.get();
|
||||||
break;
|
break;
|
||||||
case ModId::AmpLFO:
|
case ModId::AmpLFO:
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,12 @@ struct Voice::Impl
|
||||||
*/
|
*/
|
||||||
void off(int delay, bool fast = false) noexcept;
|
void off(int delay, bool fast = false) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Setup the extended CC values for the voice
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void updateExtendedCCValues() noexcept;
|
||||||
|
|
||||||
const NumericId<Voice> id_;
|
const NumericId<Voice> id_;
|
||||||
StateListener* stateListener_ = nullptr;
|
StateListener* stateListener_ = nullptr;
|
||||||
|
|
||||||
|
|
@ -288,6 +294,10 @@ struct Voice::Impl
|
||||||
|
|
||||||
bool followPower_ { false };
|
bool followPower_ { false };
|
||||||
PowerFollower powerFollower_;
|
PowerFollower powerFollower_;
|
||||||
|
|
||||||
|
ExtendedCCValues extendedCCValues_;
|
||||||
|
fast_real_distribution<float> unipolarDist { 0.0f, 1.0f };
|
||||||
|
fast_real_distribution<float> bipolarDist { -1.0f, 1.0f };
|
||||||
};
|
};
|
||||||
|
|
||||||
Voice::Voice(int voiceNumber, Resources& resources)
|
Voice::Voice(int voiceNumber, Resources& resources)
|
||||||
|
|
@ -363,6 +373,20 @@ Voice::Impl::Impl(int voiceNumber, Resources& resources)
|
||||||
getSCurve();
|
getSCurve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ExtendedCCValues& Voice::getExtendedCCValues() const noexcept
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
return impl.extendedCCValues_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Voice::Impl::updateExtendedCCValues() noexcept
|
||||||
|
{
|
||||||
|
extendedCCValues_.unipolar = unipolarDist(Random::randomGenerator);
|
||||||
|
extendedCCValues_.bipolar = bipolarDist(Random::randomGenerator);
|
||||||
|
extendedCCValues_.alternate = resources_.midiState.getAlternateState();
|
||||||
|
extendedCCValues_.noteGate = resources_.midiState.getActiveNotes() > 0 ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexcept
|
bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexcept
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
|
@ -384,6 +408,8 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
|
||||||
|
|
||||||
impl.switchState(State::playing);
|
impl.switchState(State::playing);
|
||||||
|
|
||||||
|
impl.updateExtendedCCValues();
|
||||||
|
|
||||||
ASSERT(delay >= 0);
|
ASSERT(delay >= 0);
|
||||||
if (delay < 0)
|
if (delay < 0)
|
||||||
delay = 0;
|
delay = 0;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,14 @@ enum InterpolatorModel : int;
|
||||||
class LFO;
|
class LFO;
|
||||||
class FlexEnvelope;
|
class FlexEnvelope;
|
||||||
struct Layer;
|
struct Layer;
|
||||||
|
|
||||||
|
struct ExtendedCCValues {
|
||||||
|
float unipolar {};
|
||||||
|
float bipolar {};
|
||||||
|
float noteGate {};
|
||||||
|
float alternate {};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The SFZ voice are the polyphony holders. They get activated by the synth
|
* @brief The SFZ voice are the polyphony holders. They get activated by the synth
|
||||||
* and tasked to play a given region until the end, stopping on note-offs, off-groups
|
* and tasked to play a given region until the end, stopping on note-offs, off-groups
|
||||||
|
|
@ -384,6 +392,13 @@ public:
|
||||||
*/
|
*/
|
||||||
const TriggerEvent& getTriggerEvent();
|
const TriggerEvent& getTriggerEvent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the extended CC values
|
||||||
|
*
|
||||||
|
* @return const ExtendedCCValues&
|
||||||
|
*/
|
||||||
|
const ExtendedCCValues& getExtendedCCValues() const noexcept;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Check if the voice already belongs to a sister ring
|
* @brief Check if the voice already belongs to a sister ring
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ int ModIds::flags(ModId id) noexcept
|
||||||
return kModIsPerVoice;
|
return kModIsPerVoice;
|
||||||
case ModId::ChannelAftertouch:
|
case ModId::ChannelAftertouch:
|
||||||
return kModIsPerCycle;
|
return kModIsPerCycle;
|
||||||
|
case ModId::PerVoiceController:
|
||||||
|
return kModIsPerVoice;
|
||||||
|
|
||||||
// targets
|
// targets
|
||||||
case ModId::MasterAmplitude:
|
case ModId::MasterAmplitude:
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ enum class ModId : int {
|
||||||
PitchEG,
|
PitchEG,
|
||||||
FilEG,
|
FilEG,
|
||||||
ChannelAftertouch,
|
ChannelAftertouch,
|
||||||
|
PerVoiceController,
|
||||||
_SourcesEnd,
|
_SourcesEnd,
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,10 @@ std::string ModKey::toString() const
|
||||||
return absl::StrCat("FilterEG {", region_.number(), "}");
|
return absl::StrCat("FilterEG {", region_.number(), "}");
|
||||||
case ModId::ChannelAftertouch:
|
case ModId::ChannelAftertouch:
|
||||||
return absl::StrCat("ChannelAftertouch");
|
return absl::StrCat("ChannelAftertouch");
|
||||||
|
case ModId::PerVoiceController:
|
||||||
|
return absl::StrCat("PerVoiceController ", params_.cc,
|
||||||
|
" {curve=", params_.curve, ", smooth=", params_.smooth,
|
||||||
|
", step=", params_.step, ", region=", region_.number(), "}");
|
||||||
|
|
||||||
case ModId::MasterAmplitude:
|
case ModId::MasterAmplitude:
|
||||||
return absl::StrCat("MasterAmplitude {", region_.number(), "}");
|
return absl::StrCat("MasterAmplitude {", region_.number(), "}");
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,15 @@ struct ControllerSource::Impl {
|
||||||
float getLastTransformedValue(uint16_t cc, uint8_t curve) const noexcept;
|
float getLastTransformedValue(uint16_t cc, uint8_t curve) const noexcept;
|
||||||
double sampleRate_ = config::defaultSampleRate;
|
double sampleRate_ = config::defaultSampleRate;
|
||||||
Resources* res_ = nullptr;
|
Resources* res_ = nullptr;
|
||||||
|
VoiceManager* voiceManager_ = nullptr;
|
||||||
absl::flat_hash_map<ModKey, Smoother> smoother_;
|
absl::flat_hash_map<ModKey, Smoother> smoother_;
|
||||||
};
|
};
|
||||||
|
|
||||||
ControllerSource::ControllerSource(Resources& res)
|
ControllerSource::ControllerSource(Resources& res, VoiceManager& manager)
|
||||||
: impl_(new Impl)
|
: impl_(new Impl)
|
||||||
{
|
{
|
||||||
impl_->res_ = &res;
|
impl_->res_ = &res;
|
||||||
|
impl_->voiceManager_ = &manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
ControllerSource::~ControllerSource()
|
ControllerSource::~ControllerSource()
|
||||||
|
|
@ -85,27 +87,97 @@ void ControllerSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, u
|
||||||
|
|
||||||
void ControllerSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
void ControllerSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||||
{
|
{
|
||||||
(void)voiceId;
|
|
||||||
|
|
||||||
const ModKey::Parameters p = sourceKey.parameters();
|
const ModKey::Parameters p = sourceKey.parameters();
|
||||||
const Resources& res = *impl_->res_;
|
const Resources& res = *impl_->res_;
|
||||||
const Curve& curve = res.curves.getCurve(p.curve);
|
const Curve& curve = res.curves.getCurve(p.curve);
|
||||||
const MidiState& ms = res.midiState;
|
const MidiState& ms = res.midiState;
|
||||||
const EventVector& events = ms.getCCEvents(p.cc);
|
bool canShortcut = false;
|
||||||
|
|
||||||
auto transformValue = [p, &curve](float x) {
|
auto transformValue = [p, &curve](float x) {
|
||||||
return curve.evalNormalized(x);
|
return curve.evalNormalized(x);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (p.step > 0.0f)
|
// Ignore the eventual curve for the extended CCs
|
||||||
linearEnvelope(events, buffer, transformValue, p.step);
|
auto quantize = [p](float x) {
|
||||||
else
|
if (p.step > 0.0f)
|
||||||
linearEnvelope(events, buffer, transformValue);
|
return std::trunc(x / p.step) * p.step;
|
||||||
|
|
||||||
|
return x;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch(p.cc) {
|
||||||
|
case ExtendedCCs::noteOnVelocity: {
|
||||||
|
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
||||||
|
const float fillValue =
|
||||||
|
voice && voice->getTriggerEvent().type == TriggerEventType::NoteOn ?
|
||||||
|
voice->getTriggerEvent().value : 0.0f;
|
||||||
|
|
||||||
|
sfz::fill(buffer, quantize(fillValue));
|
||||||
|
canShortcut = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ExtendedCCs::noteOffVelocity: {
|
||||||
|
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
||||||
|
const float fillValue =
|
||||||
|
voice && voice->getTriggerEvent().type == TriggerEventType::NoteOff ?
|
||||||
|
voice->getTriggerEvent().value : 0.0f;
|
||||||
|
|
||||||
|
sfz::fill(buffer, quantize(fillValue));
|
||||||
|
canShortcut = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ExtendedCCs::keyboardNoteNumber: {
|
||||||
|
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
||||||
|
const float fillValue = voice ? normalize7Bits(voice->getTriggerEvent().number) : 0.0f;
|
||||||
|
sfz::fill(buffer, quantize(fillValue));
|
||||||
|
canShortcut = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ExtendedCCs::keyboardNoteGate: {
|
||||||
|
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
||||||
|
const float fillValue = voice ? voice->getExtendedCCValues().noteGate : 0.0f;
|
||||||
|
sfz::fill(buffer, quantize(fillValue));
|
||||||
|
canShortcut = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ExtendedCCs::unipolarRandom: {
|
||||||
|
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
||||||
|
const float fillValue = voice ? voice->getExtendedCCValues().unipolar : 0.0f;
|
||||||
|
sfz::fill(buffer, quantize(fillValue));
|
||||||
|
canShortcut = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ExtendedCCs::bipolarRandom: {
|
||||||
|
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
||||||
|
const float fillValue = voice ? voice->getExtendedCCValues().bipolar : 0.0f;
|
||||||
|
sfz::fill(buffer, quantize(fillValue));
|
||||||
|
canShortcut = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ExtendedCCs::alternate: {
|
||||||
|
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
||||||
|
const float fillValue = voice ? voice->getExtendedCCValues().alternate : 0.0f;
|
||||||
|
sfz::fill(buffer, quantize(fillValue));
|
||||||
|
canShortcut = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ExtendedCCs::pitchBend: // fallthrough
|
||||||
|
case ExtendedCCs::channelAftertouch: {
|
||||||
|
const EventVector& events = ms.getCCEvents(p.cc);
|
||||||
|
linearEnvelope(events, buffer, [](float x) { return x; }, p.step);
|
||||||
|
canShortcut = events.size() == 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
const EventVector& events = ms.getCCEvents(p.cc);
|
||||||
|
linearEnvelope(events, buffer, transformValue, p.step);
|
||||||
|
canShortcut = events.size() == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto it = impl_->smoother_.find(sourceKey);
|
auto it = impl_->smoother_.find(sourceKey);
|
||||||
if (it != impl_->smoother_.end()) {
|
if (it != impl_->smoother_.end()) {
|
||||||
Smoother& s = it->second;
|
Smoother& s = it->second;
|
||||||
bool canShortcut = events.size() == 1;
|
|
||||||
s.process(buffer, buffer, canShortcut);
|
s.process(buffer, buffer, canShortcut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "../ModGenerator.h"
|
#include "../ModGenerator.h"
|
||||||
|
#include "../../VoiceManager.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
|
@ -14,7 +15,7 @@ struct Resources;
|
||||||
|
|
||||||
class ControllerSource : public ModGenerator {
|
class ControllerSource : public ModGenerator {
|
||||||
public:
|
public:
|
||||||
explicit ControllerSource(Resources& res);
|
explicit ControllerSource(Resources& res, VoiceManager& manager);
|
||||||
~ControllerSource();
|
~ControllerSource();
|
||||||
void setSampleRate(double sampleRate) override;
|
void setSampleRate(double sampleRate) override;
|
||||||
void setSamplesPerBlock(unsigned count) override;
|
void setSamplesPerBlock(unsigned count) override;
|
||||||
|
|
|
||||||
|
|
@ -410,6 +410,36 @@ TEST_CASE("[Modulations] EG v1 CC connections")
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Modulations] LFO CC connections")
|
TEST_CASE("[Modulations] LFO CC connections")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzString("/modulation.sfz", R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
pitch_oncc128=1200
|
||||||
|
pitch_oncc129=1200
|
||||||
|
pitch_oncc131=1200
|
||||||
|
pitch_oncc132=1200
|
||||||
|
pitch_oncc133=1200
|
||||||
|
pitch_oncc134=1200
|
||||||
|
pitch_oncc135=1200
|
||||||
|
pitch_oncc136=1200
|
||||||
|
pitch_oncc137=1200
|
||||||
|
)");
|
||||||
|
|
||||||
|
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||||
|
REQUIRE(graph == createDefaultGraph({
|
||||||
|
R"("Controller 128 {curve=0, smooth=0, step=0}" -> "Pitch {0}")",
|
||||||
|
R"("Controller 129 {curve=0, smooth=0, step=0}" -> "Pitch {0}")",
|
||||||
|
R"("PerVoiceController 131 {curve=0, smooth=0, step=0, region=0}" -> "Pitch {0}")",
|
||||||
|
R"("PerVoiceController 132 {curve=0, smooth=0, step=0, region=0}" -> "Pitch {0}")",
|
||||||
|
R"("PerVoiceController 133 {curve=0, smooth=0, step=0, region=0}" -> "Pitch {0}")",
|
||||||
|
R"("PerVoiceController 134 {curve=0, smooth=0, step=0, region=0}" -> "Pitch {0}")",
|
||||||
|
R"("PerVoiceController 135 {curve=0, smooth=0, step=0, region=0}" -> "Pitch {0}")",
|
||||||
|
R"("PerVoiceController 136 {curve=0, smooth=0, step=0, region=0}" -> "Pitch {0}")",
|
||||||
|
R"("PerVoiceController 137 {curve=0, smooth=0, step=0, region=0}" -> "Pitch {0}")",
|
||||||
|
}, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Modulations] Extended CCs connections")
|
||||||
{
|
{
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzString("/modulation.sfz", R"(
|
synth.loadSfzString("/modulation.sfz", R"(
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue